Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6cca2810c | ||
|
|
f48e6130b8 | ||
|
|
61de17d63e |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.warren.iwanttoheal"
|
applicationId "com.warren.iwanttoheal"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 109
|
versionCode 112
|
||||||
versionName "1.1.29"
|
versionName "1.1.31"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
+59
-15
@@ -212,6 +212,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.iwt2-arena-layout {
|
.iwt2-arena-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 172px minmax(0, 1fr);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -221,6 +223,7 @@
|
|||||||
.iwt2-arena-stage {
|
.iwt2-arena-stage {
|
||||||
background: #090c10;
|
background: #090c10;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
grid-column: 2;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -317,6 +320,22 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iwt2-bar em {
|
||||||
|
color: #fff7df;
|
||||||
|
font-size: 0.56rem;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
left: 0;
|
||||||
|
line-height: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 1px 0 #050608;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
.iwt2-bar.boss span {
|
.iwt2-bar.boss span {
|
||||||
background: #dc5162;
|
background: #dc5162;
|
||||||
}
|
}
|
||||||
@@ -337,17 +356,18 @@
|
|||||||
.iwt2-party-list {
|
.iwt2-party-list {
|
||||||
background: rgba(15, 19, 26, 0.88);
|
background: rgba(15, 19, 26, 0.88);
|
||||||
border: 0;
|
border: 0;
|
||||||
left: 28px;
|
bottom: 0;
|
||||||
max-height: calc(100% - 112px);
|
left: 0;
|
||||||
|
max-height: none;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 28px;
|
top: 0;
|
||||||
width: 172px;
|
width: 172px;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-content: start;
|
align-content: center;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1099,7 +1119,8 @@
|
|||||||
.iwt2-gear-class,
|
.iwt2-gear-class,
|
||||||
.iwt2-gear-slot,
|
.iwt2-gear-slot,
|
||||||
.iwt2-infusion-row,
|
.iwt2-infusion-row,
|
||||||
.iwt2-gear-upgrade-button {
|
.iwt2-gear-upgrade-button,
|
||||||
|
.iwt2-gear-back {
|
||||||
background: #151922;
|
background: #151922;
|
||||||
border: 2px solid #08090d;
|
border: 2px solid #08090d;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
@@ -1177,7 +1198,8 @@
|
|||||||
.iwt2-gear-class.game-selected,
|
.iwt2-gear-class.game-selected,
|
||||||
.iwt2-gear-slot.game-selected,
|
.iwt2-gear-slot.game-selected,
|
||||||
.iwt2-infusion-row.game-selected,
|
.iwt2-infusion-row.game-selected,
|
||||||
.iwt2-gear-upgrade-button.game-selected {
|
.iwt2-gear-upgrade-button.game-selected,
|
||||||
|
.iwt2-gear-back.game-selected {
|
||||||
outline-color: #e5b95f;
|
outline-color: #e5b95f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1187,6 +1209,12 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iwt2-gear-back {
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.iwt2-gear-upgrade-button:disabled,
|
.iwt2-gear-upgrade-button:disabled,
|
||||||
.iwt2-infusion-row:disabled {
|
.iwt2-infusion-row:disabled {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -1401,6 +1429,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1000px) and (max-height: 620px) {
|
@media (max-width: 1000px) and (max-height: 620px) {
|
||||||
|
.iwt2-arena-layout {
|
||||||
|
grid-template-columns: 154px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.game-shell.iwt2-shell {
|
.game-shell.iwt2-shell {
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
width: min(100%, calc(100% - 20px));
|
width: min(100%, calc(100% - 20px));
|
||||||
@@ -1640,6 +1672,11 @@
|
|||||||
padding: 7px 9px;
|
padding: 7px 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iwt2-gear-back {
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.iwt2-gear-detail {
|
.iwt2-gear-detail {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
@@ -1735,20 +1772,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.iwt2-party-list {
|
.iwt2-party-list {
|
||||||
gap: 5px;
|
align-content: center;
|
||||||
left: 10px;
|
gap: 7px;
|
||||||
max-height: calc(100% - 84px);
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
max-height: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
top: 10px;
|
top: 0;
|
||||||
width: 154px;
|
width: 154px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iwt2-party-row {
|
.iwt2-party-row {
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
grid-template-columns: 22px minmax(0, 1fr);
|
grid-template-columns: 22px minmax(0, 1fr);
|
||||||
height: 62px;
|
height: 70px;
|
||||||
min-height: 62px;
|
min-height: 70px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1782,8 +1821,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.iwt2-party-row .iwt2-bar {
|
.iwt2-party-row .iwt2-bar {
|
||||||
height: 7px;
|
height: 12px;
|
||||||
margin-top: 2px;
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iwt2-party-row .iwt2-bar.mana {
|
||||||
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iwt2-party-effects {
|
.iwt2-party-effects {
|
||||||
@@ -1803,6 +1846,7 @@
|
|||||||
|
|
||||||
.iwt2-ability-bar {
|
.iwt2-ability-bar {
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
|
display: none;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
grid-template-columns: repeat(6, 36px);
|
grid-template-columns: repeat(6, 36px);
|
||||||
max-width: 256px;
|
max-width: 256px;
|
||||||
|
|||||||
+18
-2
@@ -435,6 +435,10 @@ function hasDedicatedGameNavigation() {
|
|||||||
).some(isVisible)
|
).some(isVisible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDedicatedNavigationAction(action: InputAction) {
|
||||||
|
return action.startsWith('navigate') || action === 'confirm' || action === 'back'
|
||||||
|
}
|
||||||
|
|
||||||
function dispatchGameAction(action: InputAction, device: InputDevice) {
|
function dispatchGameAction(action: InputAction, device: InputDevice) {
|
||||||
window.dispatchEvent(new CustomEvent(GAME_ACTION_EVENT, {
|
window.dispatchEvent(new CustomEvent(GAME_ACTION_EVENT, {
|
||||||
detail: { action, device },
|
detail: { action, device },
|
||||||
@@ -621,7 +625,12 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
setLastDevice(device)
|
setLastDevice(device)
|
||||||
document.documentElement.dataset.inputDevice = device
|
document.documentElement.dataset.inputDevice = device
|
||||||
|
|
||||||
if (controllerUiInput && dedicatedNavAction && hasDedicatedGameNavigation()) {
|
if (
|
||||||
|
device === 'controller'
|
||||||
|
&& dedicatedNavAction
|
||||||
|
&& !keyboardInputRef.current
|
||||||
|
&& hasDedicatedGameNavigation()
|
||||||
|
) {
|
||||||
if (document.activeElement instanceof HTMLElement) document.activeElement.blur()
|
if (document.activeElement instanceof HTMLElement) document.activeElement.blur()
|
||||||
dispatchGameAction(action, device)
|
dispatchGameAction(action, device)
|
||||||
return
|
return
|
||||||
@@ -709,6 +718,7 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
'navigateRight',
|
'navigateRight',
|
||||||
'confirm',
|
'confirm',
|
||||||
'back',
|
'back',
|
||||||
|
'pause',
|
||||||
] satisfies InputAction[]
|
] satisfies InputAction[]
|
||||||
const directTargetActions = [
|
const directTargetActions = [
|
||||||
'targetParty1',
|
'targetParty1',
|
||||||
@@ -738,7 +748,13 @@ export function InputProvider({ children }: { children: ReactNode }) {
|
|||||||
'navigateLeft',
|
'navigateLeft',
|
||||||
'navigateRight',
|
'navigateRight',
|
||||||
] satisfies InputAction[]
|
] satisfies InputAction[]
|
||||||
const action = DPAD_NAV_ACTIONS[token] && (!combatActive || uiOverlay)
|
const dedicatedNavigationActive = hasDedicatedGameNavigation()
|
||||||
|
const action = dedicatedNavigationActive
|
||||||
|
? uiPriority.find((candidate) => bindingsRef.current.controller[candidate] === token)
|
||||||
|
?? (DPAD_NAV_ACTIONS[token] && isDedicatedNavigationAction(DPAD_NAV_ACTIONS[token])
|
||||||
|
? DPAD_NAV_ACTIONS[token]
|
||||||
|
: undefined)
|
||||||
|
: DPAD_NAV_ACTIONS[token] && (!combatActive || uiOverlay)
|
||||||
? DPAD_NAV_ACTIONS[token]
|
? DPAD_NAV_ACTIONS[token]
|
||||||
: uiOverlay
|
: uiOverlay
|
||||||
? uiPriority.find((candidate) => bindingsRef.current.controller[candidate] === token)
|
? uiPriority.find((candidate) => bindingsRef.current.controller[candidate] === token)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
export function ArenaBar({
|
export function ArenaBar({
|
||||||
className = '',
|
className = '',
|
||||||
current,
|
current,
|
||||||
|
label,
|
||||||
max,
|
max,
|
||||||
shield = 0,
|
shield = 0,
|
||||||
}: {
|
}: {
|
||||||
className?: string
|
className?: string
|
||||||
current: number
|
current: number
|
||||||
|
label?: string
|
||||||
max: number
|
max: number
|
||||||
shield?: number
|
shield?: number
|
||||||
}) {
|
}) {
|
||||||
@@ -15,6 +17,7 @@ export function ArenaBar({
|
|||||||
<div className={`iwt2-bar ${className}`}>
|
<div className={`iwt2-bar ${className}`}>
|
||||||
<span style={{ width: `${percent}%` }} />
|
<span style={{ width: `${percent}%` }} />
|
||||||
{shieldPercent > 0 && <i style={{ width: `${shieldPercent}%` }} />}
|
{shieldPercent > 0 && <i style={{ width: `${shieldPercent}%` }} />}
|
||||||
|
{label && <em>{label}</em>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ export function PartyFrames({
|
|||||||
<div>
|
<div>
|
||||||
<div className="iwt2-party-row-title">
|
<div className="iwt2-party-row-title">
|
||||||
<strong>{meta.name}</strong>
|
<strong>{meta.name}</strong>
|
||||||
<small>{Math.ceil(member.health)}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<ArenaBar className="hp" current={member.health} max={member.maxHealth} shield={member.shield} />
|
<ArenaBar className="hp" current={member.health} label={`${Math.ceil(member.maxHealth)}`} max={member.maxHealth} shield={member.shield} />
|
||||||
{member.maxMana > 0 && (
|
{member.maxMana > 0 && (
|
||||||
<ArenaBar className="mana" current={member.mana} max={member.maxMana} />
|
<ArenaBar className="mana" current={member.mana} max={member.maxMana} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
tickIwt2Arena,
|
tickIwt2Arena,
|
||||||
type Iwt2ArenaState,
|
type Iwt2ArenaState,
|
||||||
} from '../sim/arenaState'
|
} from '../sim/arenaState'
|
||||||
import type { Iwt2EntityId } from '../sim'
|
import type { Iwt2ArenaBounds, Iwt2EntityId } from '../sim'
|
||||||
import { castIwt2HealerAbility } from '../sim'
|
import { castIwt2HealerAbility } from '../sim'
|
||||||
import { PhaserArena } from '../render/PhaserArena'
|
import { PhaserArena } from '../render/PhaserArena'
|
||||||
import {
|
import {
|
||||||
@@ -64,6 +64,10 @@ const PVP_RESULT_OVERLAY_NAV_ENTRIES: OverlayNavEntry[] = [
|
|||||||
]
|
]
|
||||||
const EMPTY_ROGUELIKE_BUFFS: Iwt2RoguelikeSelfBuffId[] = []
|
const EMPTY_ROGUELIKE_BUFFS: Iwt2RoguelikeSelfBuffId[] = []
|
||||||
const IWT2_PVP_BOSS_HEALTH_MULTIPLIER = 0.7
|
const IWT2_PVP_BOSS_HEALTH_MULTIPLIER = 0.7
|
||||||
|
const IWT2_TOP_PARTY_RAIL_WIDTH = 172
|
||||||
|
const IWT2_THOR_TOP_PARTY_RAIL_WIDTH = 154
|
||||||
|
const IWT2_THOR_TOP_BREAKPOINT_WIDTH = 1000
|
||||||
|
const IWT2_THOR_TOP_BREAKPOINT_HEIGHT = 620
|
||||||
|
|
||||||
type BossArenaScreenProps = {
|
type BossArenaScreenProps = {
|
||||||
bossId: Iwt2BossId
|
bossId: Iwt2BossId
|
||||||
@@ -103,6 +107,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
const pvpBossHealthScale = pvpRoguelike ? IWT2_PVP_BOSS_HEALTH_MULTIPLIER : 1
|
const pvpBossHealthScale = pvpRoguelike ? IWT2_PVP_BOSS_HEALTH_MULTIPLIER : 1
|
||||||
const combinedBossHealthScale = bossHealthScale * difficultyHealthScale * pvpBossHealthScale
|
const combinedBossHealthScale = bossHealthScale * difficultyHealthScale * pvpBossHealthScale
|
||||||
const combinedDamageScale = difficultyDamageScale * roguelikeDamageScale
|
const combinedDamageScale = difficultyDamageScale * roguelikeDamageScale
|
||||||
|
const arenaBounds = useMemo(() => createTopScreenArenaBounds(), [])
|
||||||
const [arenaState, setArenaState] = useState<Iwt2ArenaState>(() => createArenaState(
|
const [arenaState, setArenaState] = useState<Iwt2ArenaState>(() => createArenaState(
|
||||||
bossId,
|
bossId,
|
||||||
bossIds,
|
bossIds,
|
||||||
@@ -111,6 +116,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
roguelikeBuffs,
|
roguelikeBuffs,
|
||||||
createPressureState(roguelikeStage, roguelikeContentType),
|
createPressureState(roguelikeStage, roguelikeContentType),
|
||||||
pveGearActive ? save.gearProgress : undefined,
|
pveGearActive ? save.gearProgress : undefined,
|
||||||
|
arenaBounds,
|
||||||
))
|
))
|
||||||
const [opponentArenaState, setOpponentArenaState] = useState<Iwt2ArenaState | null>(() => (
|
const [opponentArenaState, setOpponentArenaState] = useState<Iwt2ArenaState | null>(() => (
|
||||||
pvpRoguelike
|
pvpRoguelike
|
||||||
@@ -120,6 +126,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
combinedBossHealthScale,
|
combinedBossHealthScale,
|
||||||
combinedDamageScale,
|
combinedDamageScale,
|
||||||
createPressureState(roguelikeStage, roguelikeContentType),
|
createPressureState(roguelikeStage, roguelikeContentType),
|
||||||
|
arenaBounds,
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
))
|
))
|
||||||
@@ -183,6 +190,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
roguelikeBuffs,
|
roguelikeBuffs,
|
||||||
pressureState,
|
pressureState,
|
||||||
pveGearActive ? save.gearProgress : undefined,
|
pveGearActive ? save.gearProgress : undefined,
|
||||||
|
arenaBounds,
|
||||||
)
|
)
|
||||||
const nextOpponentState = pvpRoguelike
|
const nextOpponentState = pvpRoguelike
|
||||||
? createInitialIwt2ArenaState(
|
? createInitialIwt2ArenaState(
|
||||||
@@ -191,6 +199,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
combinedBossHealthScale,
|
combinedBossHealthScale,
|
||||||
combinedDamageScale,
|
combinedDamageScale,
|
||||||
pressureState,
|
pressureState,
|
||||||
|
arenaBounds,
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
recordedKillIdsRef.current = new Set()
|
recordedKillIdsRef.current = new Set()
|
||||||
@@ -205,7 +214,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
setPetAwards([])
|
setPetAwards([])
|
||||||
setSelectedOverlayAction('primary')
|
setSelectedOverlayAction('primary')
|
||||||
setStatus('playing')
|
setStatus('playing')
|
||||||
}, [bossId, bossIds, combinedBossHealthScale, combinedDamageScale, pveGearActive, pvpRoguelike, roguelikeBuffs, roguelikeContentType, roguelikeStage, save.gearProgress])
|
}, [arenaBounds, bossId, bossIds, combinedBossHealthScale, combinedDamageScale, pveGearActive, pvpRoguelike, roguelikeBuffs, roguelikeContentType, roguelikeStage, save.gearProgress])
|
||||||
|
|
||||||
const showOverlay = useCallback((nextStatus: Exclude<ArenaStatus, 'playing'>) => {
|
const showOverlay = useCallback((nextStatus: Exclude<ArenaStatus, 'playing'>) => {
|
||||||
setSelectedOverlayAction('primary')
|
setSelectedOverlayAction('primary')
|
||||||
@@ -337,6 +346,7 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (action.startsWith('targetParty')) {
|
if (action.startsWith('targetParty')) {
|
||||||
|
if (statusRef.current !== 'playing') return
|
||||||
const index = Number(action.replace('targetParty', '')) - 1
|
const index = Number(action.replace('targetParty', '')) - 1
|
||||||
const target = stateRef.current.party[index]
|
const target = stateRef.current.party[index]
|
||||||
if (target) setSelectedPartyId(target.id)
|
if (target) setSelectedPartyId(target.id)
|
||||||
@@ -701,6 +711,7 @@ function createArenaState(
|
|||||||
buffs: Iwt2RoguelikeSelfBuffId[],
|
buffs: Iwt2RoguelikeSelfBuffId[],
|
||||||
roguelikePressure: ReturnType<typeof createPressureState>,
|
roguelikePressure: ReturnType<typeof createPressureState>,
|
||||||
gearProgress?: Iwt2Save['gearProgress'],
|
gearProgress?: Iwt2Save['gearProgress'],
|
||||||
|
bounds?: Iwt2ArenaBounds,
|
||||||
): Iwt2ArenaState {
|
): Iwt2ArenaState {
|
||||||
const baseState = createInitialIwt2ArenaState(
|
const baseState = createInitialIwt2ArenaState(
|
||||||
bossId,
|
bossId,
|
||||||
@@ -708,6 +719,7 @@ function createArenaState(
|
|||||||
bossHealthScale,
|
bossHealthScale,
|
||||||
partyDamageTakenScale,
|
partyDamageTakenScale,
|
||||||
roguelikePressure,
|
roguelikePressure,
|
||||||
|
bounds,
|
||||||
)
|
)
|
||||||
const state = gearProgress ? applyIwt2PveGearStats(baseState, gearProgress) : baseState
|
const state = gearProgress ? applyIwt2PveGearStats(baseState, gearProgress) : baseState
|
||||||
const shieldedDamageTakenMultiplier = shieldedDamageTakenMultiplierForBuffs(buffs)
|
const shieldedDamageTakenMultiplier = shieldedDamageTakenMultiplierForBuffs(buffs)
|
||||||
@@ -723,6 +735,17 @@ function createArenaState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createTopScreenArenaBounds(): Iwt2ArenaBounds {
|
||||||
|
if (typeof window === 'undefined') return { width: 960, height: 540 }
|
||||||
|
const thorTopLayout = window.innerWidth <= IWT2_THOR_TOP_BREAKPOINT_WIDTH
|
||||||
|
&& window.innerHeight <= IWT2_THOR_TOP_BREAKPOINT_HEIGHT
|
||||||
|
const railWidth = thorTopLayout ? IWT2_THOR_TOP_PARTY_RAIL_WIDTH : IWT2_TOP_PARTY_RAIL_WIDTH
|
||||||
|
return {
|
||||||
|
width: Math.max(320, Math.round(window.innerWidth - railWidth)),
|
||||||
|
height: Math.max(240, Math.round(window.innerHeight)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createPressureState(
|
function createPressureState(
|
||||||
stage: number | undefined,
|
stage: number | undefined,
|
||||||
contentType: Iwt2RoguelikeContentType | undefined,
|
contentType: Iwt2RoguelikeContentType | undefined,
|
||||||
|
|||||||
@@ -1256,7 +1256,7 @@ export function Iwt2RoguelikeUpgradeScreen({
|
|||||||
setSelectedIndex((current) => moveUpgradeSelection(entries, current, action))
|
setSelectedIndex((current) => moveUpgradeSelection(entries, current, action))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (action === 'back' || action === 'pause') return
|
if (action === 'back') onBack()
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1547,33 +1547,10 @@ export function Iwt2HunterProfileScreen({
|
|||||||
|
|
||||||
function moveSelection(action: InputAction) {
|
function moveSelection(action: InputAction) {
|
||||||
if (!action.startsWith('navigate') || navEntries.length === 0) return
|
if (!action.startsWith('navigate') || navEntries.length === 0) return
|
||||||
setSelectedIndex((current) => {
|
const nextIndex = moveHunterProfileSelection(navEntries, selectedIndex, action)
|
||||||
const bounded = Math.min(current, navEntries.length - 1)
|
const nextEntry = navEntries[nextIndex]
|
||||||
const active = navEntries[bounded]
|
setSelectedIndex(nextIndex)
|
||||||
if (!active) return 0
|
if (nextEntry?.kind === 'item') setFocusedItemKey(nextEntry.itemKey)
|
||||||
const candidates = navEntries
|
|
||||||
.map((entry, index) => ({ entry, index }))
|
|
||||||
.filter(({ index }) => index !== bounded)
|
|
||||||
.filter(({ entry }) => {
|
|
||||||
if (action === 'navigateLeft') return entry.column < active.column
|
|
||||||
if (action === 'navigateRight') return entry.column > active.column
|
|
||||||
if (action === 'navigateUp') return entry.row < active.row
|
|
||||||
return entry.row > active.row
|
|
||||||
})
|
|
||||||
if (candidates.length === 0) return bounded
|
|
||||||
candidates.sort((a, b) => {
|
|
||||||
const aPrimary = Math.abs(a.entry.row - active.row) + Math.abs(a.entry.column - active.column)
|
|
||||||
const bPrimary = Math.abs(b.entry.row - active.row) + Math.abs(b.entry.column - active.column)
|
|
||||||
const aSecondary = action === 'navigateLeft' || action === 'navigateRight'
|
|
||||||
? Math.abs(a.entry.row - active.row)
|
|
||||||
: Math.abs(a.entry.column - active.column)
|
|
||||||
const bSecondary = action === 'navigateLeft' || action === 'navigateRight'
|
|
||||||
? Math.abs(b.entry.row - active.row)
|
|
||||||
: Math.abs(b.entry.column - active.column)
|
|
||||||
return aPrimary - bPrimary || aSecondary - bSecondary || a.index - b.index
|
|
||||||
})
|
|
||||||
return candidates[0]?.index ?? bounded
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEntry(entry: Iwt2HunterProfileNavEntry | undefined) {
|
function openEntry(entry: Iwt2HunterProfileNavEntry | undefined) {
|
||||||
@@ -1863,6 +1840,7 @@ function Iwt2ProfileNameEditor({
|
|||||||
<label>
|
<label>
|
||||||
<span>Profile Name</span>
|
<span>Profile Name</span>
|
||||||
<input
|
<input
|
||||||
|
data-controller-nav="skip"
|
||||||
maxLength={IWT2_NAME_MAX_LENGTH}
|
maxLength={IWT2_NAME_MAX_LENGTH}
|
||||||
onChange={(event) => setDraft(event.target.value.slice(0, IWT2_NAME_MAX_LENGTH))}
|
onChange={(event) => setDraft(event.target.value.slice(0, IWT2_NAME_MAX_LENGTH))}
|
||||||
value={draft}
|
value={draft}
|
||||||
@@ -1901,38 +1879,42 @@ export function Iwt2CloudSaveScreen({
|
|||||||
const [onlineSave, setOnlineSave] = useState<Iwt2Save | null>(null)
|
const [onlineSave, setOnlineSave] = useState<Iwt2Save | null>(null)
|
||||||
const [syncingOnlineSave, setSyncingOnlineSave] = useState(false)
|
const [syncingOnlineSave, setSyncingOnlineSave] = useState(false)
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
|
const statusMessage = message || (!onlineBackupsAvailable ? 'Online save unavailable in offline mode.' : '')
|
||||||
|
|
||||||
|
const checkOnlineSave = useCallback(async (isCancelled: () => boolean) => {
|
||||||
|
setSyncingOnlineSave(true)
|
||||||
|
setMessage('Checking online save...')
|
||||||
|
try {
|
||||||
|
const result = await loadIwt2OnlineSave()
|
||||||
|
if (isCancelled()) return
|
||||||
|
setOnlineSave(result.save)
|
||||||
|
setMessage(result.save ? 'Online save loaded.' : 'No online save yet.')
|
||||||
|
} catch (reason) {
|
||||||
|
if (isCancelled()) return
|
||||||
|
setOnlineSave(null)
|
||||||
|
setMessage(reason instanceof Error ? reason.message : 'Unable to check online save.')
|
||||||
|
} finally {
|
||||||
|
if (!isCancelled()) setSyncingOnlineSave(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
if (!onlineBackupsAvailable) {
|
if (!onlineBackupsAvailable) {
|
||||||
setOnlineSave(null)
|
|
||||||
setMessage('Online save unavailable in offline mode.')
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSyncingOnlineSave(true)
|
const timer = window.setTimeout(() => {
|
||||||
setMessage('Checking online save...')
|
void checkOnlineSave(() => cancelled)
|
||||||
loadIwt2OnlineSave()
|
}, 0)
|
||||||
.then((result) => {
|
|
||||||
if (cancelled) return
|
|
||||||
setOnlineSave(result.save)
|
|
||||||
setMessage(result.save ? 'Online save loaded.' : 'No online save yet.')
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
if (cancelled) return
|
|
||||||
setOnlineSave(null)
|
|
||||||
setMessage(reason instanceof Error ? reason.message : 'Unable to check online save.')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (!cancelled) setSyncingOnlineSave(false)
|
|
||||||
})
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
|
window.clearTimeout(timer)
|
||||||
}
|
}
|
||||||
}, [onlineBackupsAvailable])
|
}, [checkOnlineSave, onlineBackupsAvailable])
|
||||||
|
|
||||||
const useLocalSave = useCallback(async () => {
|
const handleUseLocalSave = useCallback(async () => {
|
||||||
if (!onlineBackupsAvailable) {
|
if (!onlineBackupsAvailable) {
|
||||||
setMessage('Using local save. Sign in online to update the server copy.')
|
setMessage('Using local save. Sign in online to update the server copy.')
|
||||||
return
|
return
|
||||||
@@ -1950,13 +1932,13 @@ export function Iwt2CloudSaveScreen({
|
|||||||
}
|
}
|
||||||
}, [onlineBackupsAvailable, save])
|
}, [onlineBackupsAvailable, save])
|
||||||
|
|
||||||
const useOnlineSave = useCallback(() => {
|
const handleUseOnlineSave = useCallback(() => {
|
||||||
if (!onlineSave) return
|
if (!onlineBackupsAvailable || !onlineSave) return
|
||||||
onSaveUpdated(onlineSave)
|
onSaveUpdated(onlineSave)
|
||||||
setMessage('Local save now uses online progress.')
|
setMessage('Local save now uses online progress.')
|
||||||
}, [onlineSave, onSaveUpdated])
|
}, [onlineBackupsAvailable, onlineSave, onSaveUpdated])
|
||||||
|
|
||||||
const useNewSave = useCallback(() => {
|
const handleUseNewSave = useCallback(() => {
|
||||||
onSaveUpdated(createDefaultIwt2Save())
|
onSaveUpdated(createDefaultIwt2Save())
|
||||||
setMessage('Started a new local IWT2 save.')
|
setMessage('Started a new local IWT2 save.')
|
||||||
}, [onSaveUpdated])
|
}, [onSaveUpdated])
|
||||||
@@ -1969,7 +1951,7 @@ export function Iwt2CloudSaveScreen({
|
|||||||
value: onlineBackupsAvailable ? 'Upload' : 'Current',
|
value: onlineBackupsAvailable ? 'Upload' : 'Current',
|
||||||
disabled: syncingOnlineSave,
|
disabled: syncingOnlineSave,
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
void useLocalSave()
|
void handleUseLocalSave()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1977,30 +1959,30 @@ export function Iwt2CloudSaveScreen({
|
|||||||
label: 'Use Online Save',
|
label: 'Use Online Save',
|
||||||
detail: syncingOnlineSave ? 'Checking online save...' : formatIwt2SaveSummary(onlineSave),
|
detail: syncingOnlineSave ? 'Checking online save...' : formatIwt2SaveSummary(onlineSave),
|
||||||
value: onlineSave ? 'Download' : 'Empty',
|
value: onlineSave ? 'Download' : 'Empty',
|
||||||
disabled: syncingOnlineSave || !onlineSave,
|
disabled: syncingOnlineSave || !onlineBackupsAvailable || !onlineSave,
|
||||||
onConfirm: useOnlineSave,
|
onConfirm: handleUseOnlineSave,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'new-save',
|
key: 'new-save',
|
||||||
label: 'Use New Save File',
|
label: 'Use New Save File',
|
||||||
detail: 'Start over with a fresh IWT2 character. Online save is not overwritten until you choose local save.',
|
detail: 'Start over with a fresh IWT2 character. Online save is not overwritten until you choose local save.',
|
||||||
value: 'New',
|
value: 'New',
|
||||||
onConfirm: useNewSave,
|
onConfirm: handleUseNewSave,
|
||||||
},
|
},
|
||||||
], onBack), [
|
], onBack), [
|
||||||
|
handleUseLocalSave,
|
||||||
|
handleUseNewSave,
|
||||||
|
handleUseOnlineSave,
|
||||||
onlineBackupsAvailable,
|
onlineBackupsAvailable,
|
||||||
onlineSave,
|
onlineSave,
|
||||||
onBack,
|
onBack,
|
||||||
save,
|
save,
|
||||||
syncingOnlineSave,
|
syncingOnlineSave,
|
||||||
useLocalSave,
|
|
||||||
useNewSave,
|
|
||||||
useOnlineSave,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Iwt2ScreenShell title="Backup Slot" onBack={onBack}>
|
<Iwt2ScreenShell title="Backup Slot" onBack={onBack}>
|
||||||
{message && <p className="iwt2-screen-note">{message}</p>}
|
{statusMessage && <p className="iwt2-screen-note">{statusMessage}</p>}
|
||||||
<Iwt2ActionList actions={actions} />
|
<Iwt2ActionList actions={actions} />
|
||||||
</Iwt2ScreenShell>
|
</Iwt2ScreenShell>
|
||||||
)
|
)
|
||||||
@@ -2114,6 +2096,16 @@ export function Iwt2GearUpgradeScreen({
|
|||||||
<section className="content-screen iwt2-screen-shell iwt2-gear-screen" data-game-nav-active="true">
|
<section className="content-screen iwt2-screen-shell iwt2-gear-screen" data-game-nav-active="true">
|
||||||
<div className="iwt2-gear-layout">
|
<div className="iwt2-gear-layout">
|
||||||
<section className="iwt2-gear-column">
|
<section className="iwt2-gear-column">
|
||||||
|
<button
|
||||||
|
className={`back-button iwt2-gear-back ${activeEntry?.kind === 'back' ? 'game-selected' : ''}`}
|
||||||
|
data-controller-nav="skip"
|
||||||
|
data-game-selected={activeEntry?.kind === 'back' ? 'true' : undefined}
|
||||||
|
onClick={onBack}
|
||||||
|
onPointerDown={() => selectGearEntry(navEntries, setSelectedIndex, 'back')}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
<div className="iwt2-gear-class-list">
|
<div className="iwt2-gear-class-list">
|
||||||
{IWT2_PARTY_ORDER.map((classId) => {
|
{IWT2_PARTY_ORDER.map((classId) => {
|
||||||
const metadata = IWT2_CLASS_METADATA[classId]
|
const metadata = IWT2_CLASS_METADATA[classId]
|
||||||
@@ -2372,6 +2364,39 @@ function activeUpgradeEntry(entries: Iwt2UpgradeNavEntry[], index: number) {
|
|||||||
return entries[Math.min(index, entries.length - 1)]
|
return entries[Math.min(index, entries.length - 1)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveHunterProfileSelection(
|
||||||
|
entries: Iwt2HunterProfileNavEntry[],
|
||||||
|
current: number,
|
||||||
|
action: InputAction,
|
||||||
|
) {
|
||||||
|
if (!action.startsWith('navigate') || entries.length === 0) return current
|
||||||
|
const bounded = Math.min(current, entries.length - 1)
|
||||||
|
const active = entries[bounded]
|
||||||
|
if (!active) return 0
|
||||||
|
const candidates = entries
|
||||||
|
.map((entry, index) => ({ entry, index }))
|
||||||
|
.filter(({ index }) => index !== bounded)
|
||||||
|
.filter(({ entry }) => {
|
||||||
|
if (action === 'navigateLeft') return entry.column < active.column
|
||||||
|
if (action === 'navigateRight') return entry.column > active.column
|
||||||
|
if (action === 'navigateUp') return entry.row < active.row
|
||||||
|
return entry.row > active.row
|
||||||
|
})
|
||||||
|
if (candidates.length === 0) return bounded
|
||||||
|
candidates.sort((a, b) => {
|
||||||
|
const aPrimary = Math.abs(a.entry.row - active.row) + Math.abs(a.entry.column - active.column)
|
||||||
|
const bPrimary = Math.abs(b.entry.row - active.row) + Math.abs(b.entry.column - active.column)
|
||||||
|
const aSecondary = action === 'navigateLeft' || action === 'navigateRight'
|
||||||
|
? Math.abs(a.entry.row - active.row)
|
||||||
|
: Math.abs(a.entry.column - active.column)
|
||||||
|
const bSecondary = action === 'navigateLeft' || action === 'navigateRight'
|
||||||
|
? Math.abs(b.entry.row - active.row)
|
||||||
|
: Math.abs(b.entry.column - active.column)
|
||||||
|
return aPrimary - bPrimary || aSecondary - bSecondary || a.index - b.index
|
||||||
|
})
|
||||||
|
return candidates[0]?.index ?? bounded
|
||||||
|
}
|
||||||
|
|
||||||
function upgradeEntryDisabled(entry: Iwt2UpgradeNavEntry) {
|
function upgradeEntryDisabled(entry: Iwt2UpgradeNavEntry) {
|
||||||
return entry.kind === 'upgradeContinue' && Boolean(entry.disabled)
|
return entry.kind === 'upgradeContinue' && Boolean(entry.disabled)
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-10
@@ -9,6 +9,7 @@ import type {
|
|||||||
Iwt2EntityId,
|
Iwt2EntityId,
|
||||||
Iwt2GroundHazardState,
|
Iwt2GroundHazardState,
|
||||||
Iwt2HostileAddState,
|
Iwt2HostileAddState,
|
||||||
|
Iwt2ArenaBounds,
|
||||||
Iwt2PartyAiRole,
|
Iwt2PartyAiRole,
|
||||||
Iwt2PartyEntityId,
|
Iwt2PartyEntityId,
|
||||||
Iwt2PartyEntityState,
|
Iwt2PartyEntityState,
|
||||||
@@ -66,14 +67,15 @@ export function createInitialIwt2ArenaState(
|
|||||||
bossHealthScale = 1,
|
bossHealthScale = 1,
|
||||||
partyDamageTakenScale = 1,
|
partyDamageTakenScale = 1,
|
||||||
roguelikePressure?: Iwt2RoguelikePressureState,
|
roguelikePressure?: Iwt2RoguelikePressureState,
|
||||||
|
bounds: Iwt2ArenaBounds = { width: DEFAULT_ARENA_WIDTH, height: DEFAULT_ARENA_HEIGHT },
|
||||||
): Iwt2ArenaState {
|
): Iwt2ArenaState {
|
||||||
const initialBossIds = bossIds?.length ? bossIds.slice(0, 2) : chooseInitialBossIds(bossId)
|
const initialBossIds = bossIds?.length ? bossIds.slice(0, 2) : chooseInitialBossIds(bossId)
|
||||||
const bosses = initialBossIds.map((id, index) => createBossEntity(id, index, bossHealthScale))
|
const bosses = initialBossIds.map((id, index) => createBossEntity(id, index, bossHealthScale, bounds))
|
||||||
return {
|
return {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
time: 0,
|
time: 0,
|
||||||
bounds: { width: DEFAULT_ARENA_WIDTH, height: DEFAULT_ARENA_HEIGHT },
|
bounds,
|
||||||
party: INITIAL_PARTY.map((member) => createPartyMember(member, partyDamageTakenScale)),
|
party: INITIAL_PARTY.map((member) => createPartyMember(member, partyDamageTakenScale, bounds)),
|
||||||
projectiles: [],
|
projectiles: [],
|
||||||
hostileAdds: [],
|
hostileAdds: [],
|
||||||
hazards: [],
|
hazards: [],
|
||||||
@@ -95,9 +97,9 @@ function chooseInitialBossIds(primaryBossId: Iwt2BossId): Iwt2BossId[] {
|
|||||||
return [primaryBossId, random]
|
return [primaryBossId, random]
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBossEntity(bossId: Iwt2BossId, index: number, healthScale: number): Iwt2BossEntityState {
|
function createBossEntity(bossId: Iwt2BossId, index: number, healthScale: number, bounds: Iwt2ArenaBounds): Iwt2BossEntityState {
|
||||||
const bossMetadata = IWT2_BOSS_METADATA[bossId]
|
const bossMetadata = IWT2_BOSS_METADATA[bossId]
|
||||||
const position = initialBossPosition(index)
|
const position = scaleArenaPoint(initialBossPosition(index), bounds)
|
||||||
const maxHealth = Math.max(1, Math.round(bossMetadata.maxHealth * Math.max(0.01, healthScale)))
|
const maxHealth = Math.max(1, Math.round(bossMetadata.maxHealth * Math.max(0.01, healthScale)))
|
||||||
return {
|
return {
|
||||||
id: bossId,
|
id: bossId,
|
||||||
@@ -123,9 +125,9 @@ function createBossEntity(bossId: Iwt2BossId, index: number, healthScale: number
|
|||||||
chargeHitEntityIds: [],
|
chargeHitEntityIds: [],
|
||||||
slamApplied: false,
|
slamApplied: false,
|
||||||
wallContactSeconds: 0,
|
wallContactSeconds: 0,
|
||||||
relocateTarget: { x: DEFAULT_ARENA_WIDTH * 0.58, y: DEFAULT_ARENA_HEIGHT * 0.5 + (index === 0 ? -64 : 64) },
|
relocateTarget: scaleArenaPoint({ x: DEFAULT_ARENA_WIDTH * 0.58, y: DEFAULT_ARENA_HEIGHT * 0.5 + (index === 0 ? -64 : 64) }, bounds),
|
||||||
fireballCooldownRemaining: initialBossSecondaryCooldown(bossId) + index * 0.7,
|
fireballCooldownRemaining: initialBossSecondaryCooldown(bossId) + index * 0.7,
|
||||||
fireballTarget: { x: 320, y: 250 },
|
fireballTarget: scaleArenaPoint({ x: 320, y: 250 }, bounds),
|
||||||
birdWaveThresholdsTriggered: [],
|
birdWaveThresholdsTriggered: [],
|
||||||
mechanicLanes: [],
|
mechanicLanes: [],
|
||||||
mechanicCircles: [],
|
mechanicCircles: [],
|
||||||
@@ -141,6 +143,13 @@ function initialBossPosition(index: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scaleArenaPoint(point: Iwt2Vec2, bounds: Iwt2ArenaBounds): Iwt2Vec2 {
|
||||||
|
return {
|
||||||
|
x: point.x * bounds.width / DEFAULT_ARENA_WIDTH,
|
||||||
|
y: point.y * bounds.height / DEFAULT_ARENA_HEIGHT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initialBossSpecialCooldown(bossId: Iwt2BossId): number {
|
function initialBossSpecialCooldown(bossId: Iwt2BossId): number {
|
||||||
if (bossId === 'bulldrome') return 2
|
if (bossId === 'bulldrome') return 2
|
||||||
if (bossId === 'great-jaggi') return 2.4
|
if (bossId === 'great-jaggi') return 2.4
|
||||||
@@ -324,7 +333,7 @@ function regeneratePartyMana(party: Iwt2PartyEntityState[], dt: number): Iwt2Par
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPartyMember(initial: InitialPartyMember, damageTakenScale: number): Iwt2PartyEntityState {
|
function createPartyMember(initial: InitialPartyMember, damageTakenScale: number, bounds: Iwt2ArenaBounds): Iwt2PartyEntityState {
|
||||||
const metadata = IWT2_CLASS_METADATA[initial.classId]
|
const metadata = IWT2_CLASS_METADATA[initial.classId]
|
||||||
const safeDamageTakenScale = Number.isFinite(damageTakenScale) ? Math.max(0.01, damageTakenScale) : 1
|
const safeDamageTakenScale = Number.isFinite(damageTakenScale) ? Math.max(0.01, damageTakenScale) : 1
|
||||||
return {
|
return {
|
||||||
@@ -332,7 +341,7 @@ function createPartyMember(initial: InitialPartyMember, damageTakenScale: number
|
|||||||
kind: 'party',
|
kind: 'party',
|
||||||
classId: initial.classId,
|
classId: initial.classId,
|
||||||
aiRole: initial.aiRole,
|
aiRole: initial.aiRole,
|
||||||
position: { x: initial.x, y: initial.y },
|
position: scaleArenaPoint({ x: initial.x, y: initial.y }, bounds),
|
||||||
velocity: { x: 0, y: 0 },
|
velocity: { x: 0, y: 0 },
|
||||||
facing: { x: 1, y: 0 },
|
facing: { x: 1, y: 0 },
|
||||||
radius: metadata.radius,
|
radius: metadata.radius,
|
||||||
@@ -355,7 +364,7 @@ function createPartyMember(initial: InitialPartyMember, damageTakenScale: number
|
|||||||
attackReady: false,
|
attackReady: false,
|
||||||
status: createEmptyStatus(),
|
status: createEmptyStatus(),
|
||||||
hotEffects: [],
|
hotEffects: [],
|
||||||
preferredOffset: { ...initial.preferredOffset },
|
preferredOffset: scaleArenaPoint(initial.preferredOffset, bounds),
|
||||||
decisionSecondsRemaining: initial.decisionOffset,
|
decisionSecondsRemaining: initial.decisionOffset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
Iwt2ArenaIndicator,
|
Iwt2ArenaIndicator,
|
||||||
Iwt2ArenaInput,
|
Iwt2ArenaInput,
|
||||||
|
Iwt2ArenaBounds,
|
||||||
Iwt2ArenaState as Iwt2CoreArenaState,
|
Iwt2ArenaState as Iwt2CoreArenaState,
|
||||||
Iwt2BossEntityState,
|
Iwt2BossEntityState,
|
||||||
Iwt2HostileAddState,
|
Iwt2HostileAddState,
|
||||||
@@ -61,8 +62,9 @@ export function createInitialIwt2ArenaState(
|
|||||||
bossHealthScale?: number,
|
bossHealthScale?: number,
|
||||||
partyDamageTakenScale?: number,
|
partyDamageTakenScale?: number,
|
||||||
roguelikePressure?: Iwt2RoguelikePressureState,
|
roguelikePressure?: Iwt2RoguelikePressureState,
|
||||||
|
bounds?: Iwt2ArenaBounds,
|
||||||
): Iwt2ArenaState {
|
): Iwt2ArenaState {
|
||||||
return decorateArenaState(createCoreIwt2ArenaState(bossId, bossIds, bossHealthScale, partyDamageTakenScale, roguelikePressure))
|
return decorateArenaState(createCoreIwt2ArenaState(bossId, bossIds, bossHealthScale, partyDamageTakenScale, roguelikePressure, bounds))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tickIwt2Arena(state: Iwt2ArenaState, input: Iwt2ArenaInput, dt: number): Iwt2ArenaState {
|
export function tickIwt2Arena(state: Iwt2ArenaState, input: Iwt2ArenaInput, dt: number): Iwt2ArenaState {
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ const ROGUELIKE_PRESSURE_INITIAL_STAGE_DAMAGE = 2
|
|||||||
const ROGUELIKE_PRESSURE_SOURCE_ID = 'roguelike-pressure'
|
const ROGUELIKE_PRESSURE_SOURCE_ID = 'roguelike-pressure'
|
||||||
|
|
||||||
export function roguelikeIncomingDamageScale(
|
export function roguelikeIncomingDamageScale(
|
||||||
_stage: number,
|
stage: number,
|
||||||
_contentType: Iwt2RoguelikePressureContentType,
|
contentType: Iwt2RoguelikePressureContentType,
|
||||||
): number {
|
): number {
|
||||||
|
void stage
|
||||||
|
void contentType
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user