Android build v1.1.31
This commit is contained in:
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 111
|
versionCode 112
|
||||||
versionName "1.1.30"
|
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