Allow level-55 Death Knights within race-based faction limits

This commit is contained in:
Codex
2026-09-22 16:20:47 -04:00
parent 77f6b56c2f
commit 360f164905
4 changed files with 34 additions and 14 deletions
+10 -6
View File
@@ -1,5 +1,5 @@
-- Persistent hardcore retirement. The native random-bot manager fills vacancies
-- from its factory-created, unused level-1 reserve; identities are never reset.
-- from its factory-created, unused starting-level reserve; identities are never reset.
-- Install only after deploy/playable/install-lifecycle.py has seeded the registry.
local residents = {}
local ready = false
@@ -21,6 +21,10 @@ local function sqltext(value)
return "CONVERT(UNHEX('"..(string.gsub(value,".",function(c) return string.format("%02X",string.byte(c)) end)).."') USING utf8mb4)"
end
local function startingLevel(player)
return player:GetClass()==6 and 55 or 1
end
local function enroll(player)
local guid=player:GetGUIDLow()
if unmanaged[guid] then return nil end
@@ -33,7 +37,7 @@ local function enroll(player)
if not owned then unmanaged[guid]=true;return nil end
local character=CharDBQuery("SELECT totaltime FROM characters WHERE guid="..guid.." AND account="..account)
if not character then return nil end
local state=(player:GetLevel()==1 and character:GetUInt32(0)<=60) and "reserve" or "blocked"
local state=(player:GetLevel()==startingLevel(player) and character:GetUInt32(0)<=60) and "reserve" or "blocked"
local row={name=name,account=account,protected=false,state=state,race=player:GetRace()}
residents[guid]=row
AuthDBExecute("INSERT IGNORE INTO azerothai_lifecycle(guid,name,account_id,protected,state,race) VALUES ("..
@@ -130,17 +134,17 @@ local function enforce()
if player:IsDead() then
recordDeath(player)
elseif row.state=="reserve" then
if (arrivals[guid] or player:GetLevel())~=1 then
if (arrivals[guid] or player:GetLevel())~=startingLevel(player) then
row.state="blocked"
AuthDBExecute("UPDATE azerothai_lifecycle SET state='blocked' WHERE guid="..guid.." AND state='reserve'")
PrintError("[AzerothAI] Replacement did not start at level 1: "..row.name)
PrintError("[AzerothAI] Replacement did not start at its class starting level: "..row.name)
elseif faction(row) and counts[faction(row)]<TARGET_PER_FACTION then
counts[faction(row)]=counts[faction(row)]+1
row.state="active"
online[guid]=true
AuthDBExecute("UPDATE azerothai_lifecycle SET state='active',entry_level=1,activated_at=COALESCE(activated_at,NOW()) WHERE guid="..guid.." AND state='reserve'")
AuthDBExecute("UPDATE azerothai_lifecycle SET state='active',entry_level="..startingLevel(player)..",activated_at=COALESCE(activated_at,NOW()) WHERE guid="..guid.." AND state='reserve'")
if AzerothAIEmitPlayerSnapshot then AzerothAIEmitPlayerSnapshot(row.name) end
PrintInfo("[AzerothAI] New level-1 resident entered the world: "..row.name)
PrintInfo("[AzerothAI] New resident entered at class starting level: "..row.name)
else
-- A login already in flight must not push its faction over the target.
mgr:Remove(player)
+3 -3
View File
@@ -55,14 +55,14 @@ for guid, name, account, level, played, cls in characters:
status = 'dead'
elif bot and bot['status'] != 'retired':
status = 'active'
elif int(level) == 1 and int(played) == 0 and int(cls) != 6:
elif int(level) == (55 if int(cls) == 6 else 1) and int(played) == 0:
status = 'reserve'
else:
status = 'blocked'
counts[status] = counts.get(status, 0) + 1
rows.append((guid, name, account, int(protected), status))
assert any(name.lower() == 'zaku' and protected for _, name, _, protected, _ in rows)
assert counts.get('reserve', 0) >= counts.get('dead', 0), 'Insufficient unused level-1 reserve'
assert counts.get('reserve', 0) >= counts.get('dead', 0), 'Insufficient unused starting-level reserve'
print('Lifecycle plan:', json.dumps(counts), flush=True)
if not args.apply:
raise SystemExit(0)
@@ -119,7 +119,7 @@ for key in ['POLL_MS', 'PROTECTED', 'KNOWN_BOT_ACCOUNTS']:
shutil.copy2(repo / 'ale/azerothai_lifecycle.lua', core / 'azerothai-lua/azerothai_lifecycle.lua')
settings = {'AC_AI_PLAYERBOT_DISABLE_RANDOM_LEVELS': '1',
'AC_AI_PLAYERBOT_RANDOMBOT_STARTING_LEVEL': '1',
'AC_AI_PLAYERBOT_DISABLE_DEATH_KNIGHT_LOGIN': '1'}
'AC_AI_PLAYERBOT_DISABLE_DEATH_KNIGHT_LOGIN': '0'}
content = override.read_text()
for key, value in settings.items():
pattern = r'(?m)^(\s*)' + key + r':.*$'
+5 -5
View File
@@ -8,7 +8,7 @@ installation registry; human accounts are never included.
The installer promotes unused manual-summoning reserve accounts into the native
autonomous pool. The native random-bot manager maintains its configured population target using
unused factory-created characters. Replacements start at level 1, with their
unused factory-created characters. Replacements start at level 1 (Death Knights at level 55), with their
own names and AzerothAI recipes/memories assigned when observed. This does not
reset or resurrect a dead character. Duplicate death events are harmless.
@@ -16,15 +16,15 @@ The reserve is finite: this deployment began with 450 unused level-1 characters.
This release does not create additional accounts or extend the factory reserve
when it is exhausted. The population will fall below target if no eligible
characters remain. Check `azerothai_lifecycle` state counts before extending it.
Death Knights are excluded from new random logins because they do not start at
level 1. Normal gameplay can still level a replacement after its initial entry.
Death Knights are eligible and count toward their race’s faction, within the same
25-per-faction cap. Their approved starting level is 55. Normal gameplay can still level a replacement after its initial entry.
The guard allocates new arrivals against actual living faction counts, targeting
25 Alliance and 25 Horde, including protected residents. It holds excess-faction
reserves and excess returning residents offline without retiring them. Returning
residents retain their progress and get priority over unused reserves. New activations record `entry_level=1`; subsequent leveling is allowed.
residents retain their progress and get priority over unused reserves. New activations record `entry_level=1` or `entry_level=55` for Death Knights; subsequent leveling is allowed.
Native factory-created newcomers are enrolled only from verified RNDBOT accounts
and only when fresh and level 1.
and only when fresh and at their class starting level.
## Installation
+16
View File
@@ -14,6 +14,7 @@ function player(guid,name,account,level,dead)
function p:GetName() return self.name end
function p:GetAccountId() return self.account end
function p:GetLevel() return self.level end
function p:GetClass() return self.class or 1 end
function p:GetRace() return self.race or 2 end
function p:IsDead() return self.dead end
players[name]=p; return p
@@ -114,4 +115,19 @@ assert(n==25 and players.Zaku,'Returning population must obey faction cap and pr
assert(deaths==0,'Holding returning residents offline must not retire them')
assert(values['125logout']==1 and values['126logout']==1)
""")
dk=LuaRuntime()
dk.execute(setup)
dk.execute("""
players={};rows={{201,'AllianceDK',2,0,'reserve',1},{202,'HordeDK',2,0,'reserve',2}}
local a=player(201,'AllianceDK',2,55,false);a.class=6;a.race=1
local h=player(202,'HordeDK',2,55,false);h.class=6
""")
dk.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
dk.execute("""
assert(players.AllianceDK and players.HordeDK)
local entries=0
for _,q in pairs(writes) do if string.find(q,'entry_level=55',1,true) then entries=entries+1 end end
assert(entries==2,'Death Knights must be audited at level 55')
events[8](8,nil,players.HordeDK);tick();assert(not players.HordeDK and deaths==1)
""")
print('PASS: retirement, protection, level-1 entry, replay safety, and 25/25 faction balancing')