Allow level-55 Death Knights within race-based faction limits
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
-- Persistent hardcore retirement. The native random-bot manager fills vacancies
|
-- 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.
|
-- Install only after deploy/playable/install-lifecycle.py has seeded the registry.
|
||||||
local residents = {}
|
local residents = {}
|
||||||
local ready = false
|
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)"
|
return "CONVERT(UNHEX('"..(string.gsub(value,".",function(c) return string.format("%02X",string.byte(c)) end)).."') USING utf8mb4)"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function startingLevel(player)
|
||||||
|
return player:GetClass()==6 and 55 or 1
|
||||||
|
end
|
||||||
|
|
||||||
local function enroll(player)
|
local function enroll(player)
|
||||||
local guid=player:GetGUIDLow()
|
local guid=player:GetGUIDLow()
|
||||||
if unmanaged[guid] then return nil end
|
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
|
if not owned then unmanaged[guid]=true;return nil end
|
||||||
local character=CharDBQuery("SELECT totaltime FROM characters WHERE guid="..guid.." AND account="..account)
|
local character=CharDBQuery("SELECT totaltime FROM characters WHERE guid="..guid.." AND account="..account)
|
||||||
if not character then return nil end
|
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()}
|
local row={name=name,account=account,protected=false,state=state,race=player:GetRace()}
|
||||||
residents[guid]=row
|
residents[guid]=row
|
||||||
AuthDBExecute("INSERT IGNORE INTO azerothai_lifecycle(guid,name,account_id,protected,state,race) VALUES ("..
|
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
|
if player:IsDead() then
|
||||||
recordDeath(player)
|
recordDeath(player)
|
||||||
elseif row.state=="reserve" then
|
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"
|
row.state="blocked"
|
||||||
AuthDBExecute("UPDATE azerothai_lifecycle SET state='blocked' WHERE guid="..guid.." AND state='reserve'")
|
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
|
elseif faction(row) and counts[faction(row)]<TARGET_PER_FACTION then
|
||||||
counts[faction(row)]=counts[faction(row)]+1
|
counts[faction(row)]=counts[faction(row)]+1
|
||||||
row.state="active"
|
row.state="active"
|
||||||
online[guid]=true
|
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
|
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
|
else
|
||||||
-- A login already in flight must not push its faction over the target.
|
-- A login already in flight must not push its faction over the target.
|
||||||
mgr:Remove(player)
|
mgr:Remove(player)
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ for guid, name, account, level, played, cls in characters:
|
|||||||
status = 'dead'
|
status = 'dead'
|
||||||
elif bot and bot['status'] != 'retired':
|
elif bot and bot['status'] != 'retired':
|
||||||
status = 'active'
|
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'
|
status = 'reserve'
|
||||||
else:
|
else:
|
||||||
status = 'blocked'
|
status = 'blocked'
|
||||||
counts[status] = counts.get(status, 0) + 1
|
counts[status] = counts.get(status, 0) + 1
|
||||||
rows.append((guid, name, account, int(protected), status))
|
rows.append((guid, name, account, int(protected), status))
|
||||||
assert any(name.lower() == 'zaku' and protected for _, name, _, protected, _ in rows)
|
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)
|
print('Lifecycle plan:', json.dumps(counts), flush=True)
|
||||||
if not args.apply:
|
if not args.apply:
|
||||||
raise SystemExit(0)
|
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')
|
shutil.copy2(repo / 'ale/azerothai_lifecycle.lua', core / 'azerothai-lua/azerothai_lifecycle.lua')
|
||||||
settings = {'AC_AI_PLAYERBOT_DISABLE_RANDOM_LEVELS': '1',
|
settings = {'AC_AI_PLAYERBOT_DISABLE_RANDOM_LEVELS': '1',
|
||||||
'AC_AI_PLAYERBOT_RANDOMBOT_STARTING_LEVEL': '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()
|
content = override.read_text()
|
||||||
for key, value in settings.items():
|
for key, value in settings.items():
|
||||||
pattern = r'(?m)^(\s*)' + key + r':.*$'
|
pattern = r'(?m)^(\s*)' + key + r':.*$'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ installation registry; human accounts are never included.
|
|||||||
|
|
||||||
The installer promotes unused manual-summoning reserve accounts into the native
|
The installer promotes unused manual-summoning reserve accounts into the native
|
||||||
autonomous pool. The native random-bot manager maintains its configured population target using
|
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
|
own names and AzerothAI recipes/memories assigned when observed. This does not
|
||||||
reset or resurrect a dead character. Duplicate death events are harmless.
|
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
|
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
|
when it is exhausted. The population will fall below target if no eligible
|
||||||
characters remain. Check `azerothai_lifecycle` state counts before extending it.
|
characters remain. Check `azerothai_lifecycle` state counts before extending it.
|
||||||
Death Knights are excluded from new random logins because they do not start at
|
Death Knights are eligible and count toward their race’s faction, within the same
|
||||||
level 1. Normal gameplay can still level a replacement after its initial entry.
|
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
|
The guard allocates new arrivals against actual living faction counts, targeting
|
||||||
25 Alliance and 25 Horde, including protected residents. It holds excess-faction
|
25 Alliance and 25 Horde, including protected residents. It holds excess-faction
|
||||||
reserves and excess returning residents offline without retiring them. Returning
|
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
|
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
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function player(guid,name,account,level,dead)
|
|||||||
function p:GetName() return self.name end
|
function p:GetName() return self.name end
|
||||||
function p:GetAccountId() return self.account end
|
function p:GetAccountId() return self.account end
|
||||||
function p:GetLevel() return self.level 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:GetRace() return self.race or 2 end
|
||||||
function p:IsDead() return self.dead end
|
function p:IsDead() return self.dead end
|
||||||
players[name]=p; return p
|
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(deaths==0,'Holding returning residents offline must not retire them')
|
||||||
assert(values['125logout']==1 and values['126logout']==1)
|
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')
|
print('PASS: retirement, protection, level-1 entry, replay safety, and 25/25 faction balancing')
|
||||||
|
|||||||
Reference in New Issue
Block a user