Enforce hardcore retirement with level-one faction-balanced replacements
This commit is contained in:
@@ -3,6 +3,7 @@ const assert=require('node:assert/strict');
|
||||
const {execute}=require('../.test-build/service.js');
|
||||
const {seed}=require('../.test-build/seed.js');
|
||||
const {connectors}=require('../.test-build/connectors/index.js');
|
||||
const {applyAuthoritativeAleEvent}=require('../.test-build/service.js');
|
||||
|
||||
class Store {
|
||||
constructor(){this.state=seed();}
|
||||
@@ -10,6 +11,18 @@ class Store {
|
||||
async save(s,r){assert.equal(this.state.revision,r);s.revision=r+1;this.state=structuredClone(s);}
|
||||
}
|
||||
|
||||
test('game lifecycle death closes identity once and preserves Zaku',async()=>{
|
||||
const store=new Store();
|
||||
const bot=store.state.bots.find(b=>b.id==='bot-6');
|
||||
const payload={name:bot.name,isBot:true,characterGuid:99,accountId:2,deathId:'hardcore-99'};
|
||||
await applyAuthoritativeAleEvent(store,'player.permanent-death',payload);
|
||||
assert.equal(store.state.bots.find(b=>b.id===bot.id).status,'dead');
|
||||
await applyAuthoritativeAleEvent(store,'player.permanent-death',payload);
|
||||
assert.equal(store.state.events.filter(e=>e.kind==='death').length,1);
|
||||
await applyAuthoritativeAleEvent(store,'player.permanent-death',{name:'Zaku',isBot:true,characterGuid:9,accountId:1});
|
||||
assert.notEqual(store.state.bots.find(b=>b.name==='Zaku').status,'dead');
|
||||
});
|
||||
|
||||
test('Zaku is a mortal persistent hero and hardcore starts enabled',()=>{
|
||||
const state=seed();
|
||||
const zaku=state.bots.find(b=>b.id==='hero-zaku');
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Run with python3 after installing lupa; no live server or database involved."""
|
||||
from pathlib import Path
|
||||
from lupa import LuaRuntime
|
||||
|
||||
lua = LuaRuntime()
|
||||
setup='''
|
||||
rows={{10,"Dead",1,0,"dead",2},{11,"Zaku",1,1,"active",5},
|
||||
{12,"Fresh",2,0,"reserve",2},{13,"Human",3,0,"active",1},
|
||||
{14,"Wronglevel",2,0,"reserve",2}}
|
||||
players={}; writes={}; removed={}; values={}; events={}; deaths=0
|
||||
function player(guid,name,account,level,dead)
|
||||
local p={guid=guid,name=name,account=account,level=level,dead=dead}
|
||||
function p:GetGUIDLow() return self.guid end
|
||||
function p:GetName() return self.name end
|
||||
function p:GetAccountId() return self.account end
|
||||
function p:GetLevel() return self.level end
|
||||
function p:GetRace() return self.race or 2 end
|
||||
function p:IsDead() return self.dead end
|
||||
players[name]=p; return p
|
||||
end
|
||||
function AuthDBQuery(sql)
|
||||
if string.find(sql,'SELECT id FROM account',1,true) then
|
||||
if string.find(sql,'id=99 ',1,true) then return nil end
|
||||
return {GetUInt32=function() return 2 end}
|
||||
end
|
||||
local q={i=1}
|
||||
function q:GetUInt32(n) return rows[self.i][n+1] end
|
||||
function q:GetString(n) return rows[self.i][n+1] end
|
||||
function q:NextRow() self.i=self.i+1;return self.i<=#rows end
|
||||
return q
|
||||
end
|
||||
function CharDBQuery(sql) return {GetUInt32=function() return 0 end} end
|
||||
function AuthDBExecute(sql) table.insert(writes,sql) end
|
||||
mgr={}
|
||||
function mgr:Remove(p) table.insert(removed,p.guid);players[p.name]=nil end
|
||||
function mgr:SetValue(guid,key,value) values[guid..key]=value end
|
||||
function mgr:GetValue(guid,key) return values[guid..key] or 0 end
|
||||
function GetRandomPlayerbotMgr() return mgr end
|
||||
function GetPlayerByName(name) return players[name] end
|
||||
function GetPlayersInWorld() return players end
|
||||
function RegisterPlayerEvent(id,fn) events[id]=fn end
|
||||
function CreateLuaEvent(fn) tick=fn end
|
||||
function PrintError(text) end
|
||||
function PrintInfo(text) end
|
||||
function AzerothAIEmitPermanentDeath(p) deaths=deaths+1 end
|
||||
function AzerothAIEmitPlayerSnapshot(name) end
|
||||
player(10,"Dead",1,80,false)
|
||||
player(11,"Zaku",1,7,true)
|
||||
player(12,"Fresh",2,1,false)
|
||||
-- A mismatched account must never be managed, even if the GUID/name matches.
|
||||
player(13,"Human",99,20,true)
|
||||
player(14,"Wronglevel",2,55,false)
|
||||
'''
|
||||
lua.execute(setup)
|
||||
lua.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
|
||||
lua.execute('''
|
||||
assert(#removed==1 and removed[1]==10)
|
||||
assert(values['10logout']==1 and values['10add']==0)
|
||||
assert(players.Zaku and players.Human)
|
||||
assert(deaths==0)
|
||||
tick();assert(not players.Wronglevel)
|
||||
local fresh=players.Fresh
|
||||
events[8](8,nil,fresh);events[8](8,nil,fresh);events[6](6,nil,fresh)
|
||||
assert(deaths==1,'Repeated death events must be idempotent')
|
||||
tick();assert(not players.Fresh and values['12logout']==1)
|
||||
events[8](8,nil,players.Zaku);events[8](8,nil,players.Human)
|
||||
assert(deaths==1 and players.Zaku and players.Human)
|
||||
local before=#writes;tick();assert(#writes==before,'No per-second database churn')
|
||||
-- A retired identity accidentally logged in again must be evicted.
|
||||
player(10,'Dead',1,80,false);tick();assert(not players.Dead)
|
||||
player(15,'Factorynew',2,1,false);tick();assert(players.Factorynew)
|
||||
player(16,'Realhuman',99,1,true);tick();assert(players.Realhuman)
|
||||
assert(deaths==1)
|
||||
events[8](8,nil,players.Factorynew);tick();assert(not players.Factorynew)
|
||||
assert(deaths==2)
|
||||
local fast=player(17,'Fastlearner',2,1,false)
|
||||
events[3](3,fast);fast.level=2;tick();assert(players.Fastlearner)
|
||||
''')
|
||||
balanced=LuaRuntime()
|
||||
balanced.execute(setup)
|
||||
balanced.execute('''
|
||||
players={};rows={}
|
||||
for i=1,25 do
|
||||
rows[#rows+1]={100+i,'Horde'..i,2,0,'active',2}
|
||||
player(100+i,'Horde'..i,2,5,false)
|
||||
end
|
||||
rows[#rows+1]={201,'Alliancefresh',2,0,'reserve',1}
|
||||
rows[#rows+1]={202,'Hordefresh',2,0,'reserve',2}
|
||||
player(201,'Alliancefresh',2,1,false)
|
||||
player(202,'Hordefresh',2,1,false)
|
||||
''')
|
||||
balanced.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
|
||||
balanced.execute('''
|
||||
assert(players.Alliancefresh,'Underrepresented faction must fill its vacancy')
|
||||
assert(not players.Hordefresh,'Do not exceed 25 members of a faction')
|
||||
assert(values['202logout']==1)
|
||||
assert(deaths==0,'Faction deferral is not a death')
|
||||
''')
|
||||
returning=LuaRuntime()
|
||||
returning.execute(setup)
|
||||
returning.execute("""
|
||||
players={};rows={}
|
||||
for i=1,26 do
|
||||
rows[#rows+1]={100+i,'Horde'..i,2,0,'active',2}
|
||||
player(100+i,'Horde'..i,2,5,false)
|
||||
end
|
||||
rows[#rows+1]={999,'Zaku',2,1,'active',2}
|
||||
player(999,'Zaku',2,7,false)
|
||||
""")
|
||||
returning.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
|
||||
returning.execute("""
|
||||
local n=0;for _ in pairs(players) do n=n+1 end
|
||||
assert(n==25 and players.Zaku,'Returning population must obey faction cap and protect Zaku')
|
||||
assert(deaths==0,'Holding returning residents offline must not retire them')
|
||||
assert(values['125logout']==1 and values['126logout']==1)
|
||||
""")
|
||||
print('PASS: retirement, protection, level-1 entry, replay safety, and 25/25 faction balancing')
|
||||
Reference in New Issue
Block a user