Add private admin console and disconnect handling
This commit is contained in:
@@ -42,7 +42,26 @@ export class Game {
|
||||
const player=this.state.players[twitchUserId]; if(!player) return false
|
||||
player.extensionBound=true; player.connectionState=connected?'connected':'disconnected'; return true
|
||||
}
|
||||
disconnect(twitchUserId: string): void { const p=this.state.players[twitchUserId]; if(p) p.connectionState='disconnected' }
|
||||
disconnect(twitchUserId: string): boolean {
|
||||
const player=this.state.players[twitchUserId]
|
||||
if(!player || player.connectionState==='disconnected') return false
|
||||
player.connectionState='disconnected'
|
||||
if(this.state.phase.kind==='player' && player.lifeState==='alive' && player.ap>0) this.autoGuard(player)
|
||||
this.finishPlayerPhaseIfReady()
|
||||
return true
|
||||
}
|
||||
removePlayer(twitchUserId:string):boolean {
|
||||
const player=this.state.players[twitchUserId]
|
||||
if(!player)return false
|
||||
delete this.state.players[twitchUserId]
|
||||
if(this.state.goblin.targetPlayerId===twitchUserId){this.state.goblin.mode='returning';this.state.goblin.targetPlayerId=null}
|
||||
this.log('player-removed',twitchUserId,null,`${player.displayName} was removed by an administrator.`)
|
||||
if(Object.keys(this.state.players).length===0)this.state.phase={kind:'dormant'}
|
||||
else this.finishPlayerPhaseIfReady()
|
||||
return true
|
||||
}
|
||||
forceEndPlayerPhase():boolean {if(this.state.phase.kind!=='player')return false;this.endPlayerPhase();return true}
|
||||
resetRunByAdmin():void {this.resetRun('admin-reset','An administrator reset the run.')}
|
||||
|
||||
spawn(message: SpawnMessage): SpawnResult {
|
||||
if(this.externalEvents.has(message.externalEventId)) return {accepted:false,reason:'DUPLICATE',message:'That spawn request was already handled.'}
|
||||
@@ -127,9 +146,12 @@ export class Game {
|
||||
private startPlayerPhase(): void {
|
||||
const living=Object.values(this.state.players).filter(p=>p.lifeState==='alive')
|
||||
if(living.length===0){this.state.phase={kind:'dormant'};return}
|
||||
for(const p of Object.values(this.state.players)){p.guard=0;p.eligibleThisPhase=p.lifeState==='alive';p.ap=p.lifeState==='alive'?2:0}
|
||||
const now=this.deps.clock.now(), duration=Math.min(living.length*25_000,120_000)
|
||||
this.state.phase={kind:'player',phaseId:this.deps.ids.next('phase'),startedAt:now,deadlineAt:now+duration,initialEligiblePlayerIds:living.map(p=>p.twitchUserId)}
|
||||
for(const p of Object.values(this.state.players)){
|
||||
p.guard=0;p.eligibleThisPhase=p.lifeState==='alive';p.ap=p.lifeState==='alive'?2:0
|
||||
if(p.lifeState==='alive' && p.extensionBound && p.connectionState==='disconnected') this.autoGuard(p)
|
||||
}
|
||||
this.log('player-phase-started',null,null,`Player Phase begins: ${duration/1000} seconds.`)
|
||||
}
|
||||
private finishPlayerPhaseIfReady(): void {
|
||||
@@ -139,10 +161,14 @@ export class Game {
|
||||
}
|
||||
private endPlayerPhase(): void {
|
||||
if(this.state.phase.kind!=='player') return
|
||||
for(const p of Object.values(this.state.players)){if(p.lifeState==='alive')p.guard=p.ap;p.ap=0;p.eligibleThisPhase=false}
|
||||
for(const p of Object.values(this.state.players)){if(p.lifeState==='alive')p.guard+=p.ap;p.ap=0;p.eligibleThisPhase=false}
|
||||
this.state.phase={kind:'enemy',phaseId:this.deps.ids.next('phase')};this.log('enemy-phase-started',null,null,'Enemy Phase begins.')
|
||||
this.runGoblin(); if(this.state.phase.kind==='enemy') this.startPlayerPhase()
|
||||
}
|
||||
private autoGuard(player:PlayerState):void {
|
||||
player.guard+=player.ap;player.ap=0;player.eligibleThisPhase=false
|
||||
this.log('player-auto-guarded',player.twitchUserId,null,`${player.displayName} is disconnected and converts unused AP to Guard.`)
|
||||
}
|
||||
private runGoblin(): void {
|
||||
if(this.state.goblin.mode==='dead' || this.state.goblin.mode==='guarding') return
|
||||
let ap=2
|
||||
@@ -176,10 +202,10 @@ export class Game {
|
||||
const floor=this.deps.generateFloor(`${this.state.runId}-floor-${this.state.floorNumber}`);this.state.floor=floor;this.state.goblin=this.newGoblin(floor)
|
||||
this.restorePlayers();this.log('floor-advanced',null,null,`The party reaches Floor ${this.state.floorNumber}!`);this.startPlayerPhase()
|
||||
}
|
||||
private resetRun(): void {
|
||||
private resetRun(logType='party-wipe',message='The party has fallen. A new run begins on Floor 1.'): void {
|
||||
this.state.phase={kind:'transition',reason:'party-wipe'};this.state.runId=this.deps.ids.next('run');this.state.floorNumber=1
|
||||
const floor=this.deps.generateFloor(`${this.state.runId}-floor-1`);this.state.floor=floor;this.state.goblin=this.newGoblin(floor)
|
||||
this.restorePlayers();this.log('party-wipe',null,null,'The party has fallen. A new run begins on Floor 1.');this.startPlayerPhase()
|
||||
this.restorePlayers();this.log(logType,null,null,message);this.startPlayerPhase()
|
||||
}
|
||||
private restorePlayers(): void { for(const p of Object.values(this.state.players)){p.hp=3;p.lifeState='alive';p.ap=0;p.guard=0;p.healAvailable=true;p.diedOnFloor=null;p.eligibleThisPhase=false;p.participatingFloor=this.state.floorNumber;p.position=copy(this.state.floor.spawnTiles[0]!)} }
|
||||
private nextPathStep(start:TilePosition,goal:TilePosition):TilePosition|null {
|
||||
|
||||
Reference in New Issue
Block a user