mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Allow removing player entities when starting a match.
This commit is contained in:
parent
e4e80a2504
commit
b36782388b
8 changed files with 180 additions and 28 deletions
|
|
@ -18,6 +18,7 @@ PlayerSettingControls.PlayerAssignment = class PlayerAssignment extends GameSett
|
|||
this.clientItemFactory = new PlayerAssignmentItem.Client();
|
||||
this.aiItemFactory = new PlayerAssignmentItem.AI();
|
||||
this.unassignedItem = new PlayerAssignmentItem.Unassigned().createItem();
|
||||
this.removedItem = new PlayerAssignmentItem.Removed().createItem();
|
||||
|
||||
this.aiItems =
|
||||
g_Settings.AIDescriptions.filter(ai => !ai.data.hidden).map(
|
||||
|
|
@ -31,12 +32,18 @@ PlayerSettingControls.PlayerAssignment = class PlayerAssignment extends GameSett
|
|||
this.rebuildList();
|
||||
|
||||
const savedAI = this.isSavedGame && g_GameSettings.playerAI.get(this.playerIndex);
|
||||
const savedRemoved = this.isSavedGame && g_GameSettings.playerRemoved.get(this.playerIndex);
|
||||
|
||||
if (savedAI)
|
||||
{
|
||||
this.setSelectedValue(savedAI.bot);
|
||||
this.setEnabled(false);
|
||||
}
|
||||
else if (savedRemoved)
|
||||
{
|
||||
this.setSelectedValue(this.removedItem.Value);
|
||||
this.setEnabled(false);
|
||||
}
|
||||
else
|
||||
this.rebuildList();
|
||||
|
||||
|
|
@ -54,7 +61,8 @@ PlayerSettingControls.PlayerAssignment = class PlayerAssignment extends GameSett
|
|||
{
|
||||
const isPlayerSlot = Object.values(g_PlayerAssignments).some(x => x.player === this.playerIndex + 1);
|
||||
if (!isPlayerSlot && !g_GameSettings.playerAI.get(this.playerIndex) &&
|
||||
this.playerIndex >= oldNb && this.playerIndex < g_GameSettings.playerCount.nbPlayers)
|
||||
!g_GameSettings.playerRemoved.get(this.playerIndex) &&
|
||||
this.playerIndex >= oldNb && this.playerIndex < g_GameSettings.playerCount.nbPlayers)
|
||||
{
|
||||
// Add AIs to unused slots by default.
|
||||
// TODO: we could save the settings in case the player lowers, then re-raises the # of players.
|
||||
|
|
@ -104,6 +112,14 @@ PlayerSettingControls.PlayerAssignment = class PlayerAssignment extends GameSett
|
|||
this.setSelectedValue(ai.bot);
|
||||
return;
|
||||
}
|
||||
|
||||
const isRemoved = g_GameSettings.playerRemoved.get(this.playerIndex);
|
||||
if (isRemoved)
|
||||
{
|
||||
this.rebuildList();
|
||||
this.setSelectedValue(this.removedItem.Value)
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSelectedValue(undefined);
|
||||
}
|
||||
|
|
@ -118,10 +134,12 @@ PlayerSettingControls.PlayerAssignment = class PlayerAssignment extends GameSett
|
|||
// If loading a saved game clients and unassigned players can't be replaced by a AI. Don't show
|
||||
// the AIs in the dropdown.
|
||||
const disableAI = this.isSavedGame && !g_GameSettings.playerAI.get(this.playerIndex);
|
||||
const disabledPlayer = this.isSavedGame && !g_GameSettings.playerRemoved.get(this.playerIndex);
|
||||
this.values = prepareForDropdown([
|
||||
...this.playerItems,
|
||||
...disableAI ? [] : this.aiItems,
|
||||
this.unassignedItem
|
||||
...disableAI || disabledPlayer ? [] : this.aiItems,
|
||||
this.unassignedItem,
|
||||
this.removedItem
|
||||
]);
|
||||
|
||||
const selected = this.dropdown.list_data?.[this.dropdown.selected];
|
||||
|
|
@ -173,6 +191,7 @@ PlayerSettingControls.PlayerAssignment.prototype.AutocompleteOrder = 100;
|
|||
guidToAssign, isSavedGame)
|
||||
{
|
||||
const sourcePlayer = g_PlayerAssignments[guidToAssign].player - 1;
|
||||
g_GameSettings.playerRemoved.set(playerIndex, false);
|
||||
if (sourcePlayer >= 0)
|
||||
{
|
||||
const ai = g_GameSettings.playerAI.get(playerIndex);
|
||||
|
|
@ -186,15 +205,9 @@ PlayerSettingControls.PlayerAssignment.prototype.AutocompleteOrder = 100;
|
|||
g_GameSettings.playerColor.swap(sourcePlayer, playerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
playerAssignmentsController.assignPlayer(guidToAssign, playerIndex);
|
||||
gameSettingsController.setNetworkInitAttributes();
|
||||
}
|
||||
|
||||
isSelected(pData, guid, value)
|
||||
{
|
||||
return guid !== undefined && guid == value;
|
||||
}
|
||||
};
|
||||
|
||||
PlayerAssignmentItem.Client.prototype.PlayerTags =
|
||||
|
|
@ -227,14 +240,9 @@ PlayerSettingControls.PlayerAssignment.prototype.AutocompleteOrder = 100;
|
|||
"difficulty": +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty"),
|
||||
"behavior": Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior"),
|
||||
});
|
||||
|
||||
g_GameSettings.playerRemoved.set(playerIndex, false);
|
||||
gameSettingsController.setNetworkInitAttributes();
|
||||
}
|
||||
|
||||
isSelected(pData, guid, value)
|
||||
{
|
||||
return !guid && pData.AI && pData.AI == value;
|
||||
}
|
||||
};
|
||||
|
||||
PlayerAssignmentItem.AI.prototype.Label =
|
||||
|
|
@ -262,14 +270,10 @@ PlayerSettingControls.PlayerAssignment.prototype.AutocompleteOrder = 100;
|
|||
playerAssignmentsController.unassignClient(playerIndex + 1);
|
||||
|
||||
g_GameSettings.playerAI.setAI(playerIndex, undefined);
|
||||
g_GameSettings.playerRemoved.set(playerIndex, false);
|
||||
|
||||
gameSettingsController.setNetworkInitAttributes();
|
||||
}
|
||||
|
||||
isSelected(pData, guid, value)
|
||||
{
|
||||
return !guid && !pData.AI;
|
||||
}
|
||||
};
|
||||
|
||||
PlayerAssignmentItem.Unassigned.prototype.Label =
|
||||
|
|
@ -278,3 +282,34 @@ PlayerSettingControls.PlayerAssignment.prototype.AutocompleteOrder = 100;
|
|||
PlayerAssignmentItem.Unassigned.prototype.Tags =
|
||||
{ "color": "140 140 140" };
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
PlayerAssignmentItem.Removed = class
|
||||
{
|
||||
createItem()
|
||||
{
|
||||
return {
|
||||
"Handler": this,
|
||||
"Value": "removed",
|
||||
"Autocomplete": this.Label,
|
||||
"Caption": setStringTags(this.Label, this.Tags)
|
||||
};
|
||||
}
|
||||
|
||||
onSelectionChange(gameSettingsController, playerAssignmentsController, playerIndex)
|
||||
{
|
||||
g_GameSettings.playerRemoved.set(playerIndex, true);
|
||||
playerAssignmentsController.unassignClient(playerIndex + 1);
|
||||
g_GameSettings.playerAI.setAI(playerIndex, undefined);
|
||||
|
||||
gameSettingsController.setNetworkInitAttributes();
|
||||
}
|
||||
};
|
||||
|
||||
PlayerAssignmentItem.Removed.prototype.Label =
|
||||
translate("Removed");
|
||||
|
||||
PlayerAssignmentItem.Removed.prototype.Tags =
|
||||
{ "color": "255 140 140" };
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
GameSettings.prototype.Attributes.PlayerRemoved = class PlayerRemoved extends GameSetting
|
||||
{
|
||||
init()
|
||||
{
|
||||
// NB: watchers aren't auto-triggered when modifying array elements.
|
||||
this.values = [];
|
||||
this.settings.playerCount.watch(() => this.maybeUpdate(), ["nbPlayers"]);
|
||||
}
|
||||
|
||||
toInitAttributes(attribs)
|
||||
{
|
||||
if (!attribs.settings.PlayerData)
|
||||
attribs.settings.PlayerData = [];
|
||||
while (attribs.settings.PlayerData.length < this.values.length)
|
||||
attribs.settings.PlayerData.push({});
|
||||
for (let i = 0; i < this.values.length; ++i)
|
||||
attribs.settings.PlayerData[i].Removed = this.values[i] ?? false;
|
||||
}
|
||||
|
||||
fromInitAttributes(attribs)
|
||||
{
|
||||
if (!this.getLegacySetting(attribs, "PlayerData"))
|
||||
return;
|
||||
const pData = this.getLegacySetting(attribs, "PlayerData");
|
||||
for (let i = 0; i < this.values.length; ++i)
|
||||
{
|
||||
if (!pData[i])
|
||||
{
|
||||
this.set(+i, false);
|
||||
continue;
|
||||
}
|
||||
this.set(+i, pData[i].Removed);
|
||||
}
|
||||
}
|
||||
|
||||
_resize(nb)
|
||||
{
|
||||
while (this.values.length > nb)
|
||||
this.values.pop();
|
||||
while (this.values.length < nb)
|
||||
this.values.push(false);
|
||||
}
|
||||
|
||||
maybeUpdate()
|
||||
{
|
||||
if (this.values.length === this.settings.playerCount.nbPlayers)
|
||||
return;
|
||||
this._resize(this.settings.playerCount.nbPlayers);
|
||||
this.trigger("values");
|
||||
}
|
||||
|
||||
swap(sourceIndex, targetIndex)
|
||||
{
|
||||
[this.values[sourceIndex], this.values[targetIndex]] = [this.values[targetIndex], this.values[sourceIndex]];
|
||||
this.trigger("values");
|
||||
}
|
||||
|
||||
set(playerIndex, removed)
|
||||
{
|
||||
this.values[playerIndex] = removed;
|
||||
this.trigger("values");
|
||||
}
|
||||
|
||||
get(playerIndex)
|
||||
{
|
||||
return this.values[playerIndex] ?? false;
|
||||
}
|
||||
};
|
||||
|
|
@ -65,6 +65,7 @@ Player.prototype.Init = function()
|
|||
this.startCam = undefined;
|
||||
this.controlAllUnits = false;
|
||||
this.isAI = false;
|
||||
this.isRemoved = false;
|
||||
this.cheatsEnabled = false;
|
||||
this.panelEntities = [];
|
||||
this.resourceNames = {};
|
||||
|
|
@ -576,6 +577,16 @@ Player.prototype.IsAI = function()
|
|||
return this.isAI;
|
||||
};
|
||||
|
||||
Player.prototype.SetRemoved = function(flag)
|
||||
{
|
||||
this.isRemoved = flag;
|
||||
};
|
||||
|
||||
Player.prototype.IsRemoved = function()
|
||||
{
|
||||
return this.isRemoved;
|
||||
};
|
||||
|
||||
/**
|
||||
* Do some map dependant initializations
|
||||
*/
|
||||
|
|
@ -760,6 +771,10 @@ Player.prototype.OnGlobalPlayerDefeated = function(msg)
|
|||
if (!cmpSound)
|
||||
return;
|
||||
|
||||
// Don't play defeat/win sounds for removed players.
|
||||
if (this.playerID === msg.playerId && this.IsRemoved() || QueryPlayerIDInterface(msg.playerId)?.IsRemoved())
|
||||
return;
|
||||
|
||||
const soundGroup = cmpSound.GetSoundGroup(this.playerID === msg.playerId ? "defeated" : Engine.QueryInterface(this.entity, IID_Diplomacy).IsAlly(msg.playerId) ? "defeated_ally" : this.HasWon() ? "won" : "defeated_enemy");
|
||||
if (soundGroup)
|
||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager).PlaySoundGroupForPlayer(soundGroup, this.playerID);
|
||||
|
|
|
|||
|
|
@ -47,10 +47,18 @@ function InitGame(settings)
|
|||
const cmpPlayer = QueryPlayerIDInterface(i);
|
||||
cmpPlayer.SetCheatsEnabled(!!settings.CheatsEnabled);
|
||||
|
||||
if (settings.PlayerData[i] && !!settings.PlayerData[i].AI)
|
||||
if (settings.PlayerData[i])
|
||||
{
|
||||
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, +settings.PlayerData[i].AIDiff, settings.PlayerData[i].AIBehavior || "random");
|
||||
cmpPlayer.SetAI(true);
|
||||
if(!!settings.PlayerData[i].Removed)
|
||||
{
|
||||
cmpPlayer.Defeat(undefined);
|
||||
continue;
|
||||
}
|
||||
else if (!!settings.PlayerData[i].AI)
|
||||
{
|
||||
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, +settings.PlayerData[i].AIDiff, settings.PlayerData[i].AIBehavior || "random");
|
||||
cmpPlayer.SetAI(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.AllyView)
|
||||
|
|
|
|||
|
|
@ -66,9 +66,11 @@ function LoadPlayerSettings(settings, newPlayers)
|
|||
cmpPlayer.SetColor(color.r, color.g, color.b);
|
||||
|
||||
// Special case for gaia
|
||||
if (i == 0)
|
||||
if (i === 0)
|
||||
continue;
|
||||
|
||||
cmpPlayer.SetRemoved(getPlayerSetting(i, "Removed"));
|
||||
|
||||
// StartingResources
|
||||
if (settings.PlayerData[i].Resources !== undefined)
|
||||
cmpPlayer.SetResourceCounts(settings.PlayerData[i].Resources);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2024 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -1108,10 +1108,21 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
|
|||
debug_warn(L"Invalid map XML data");
|
||||
}
|
||||
|
||||
entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
|
||||
entity_id_t player = cmpPlayerManager->GetPlayerByID(PlayerID);
|
||||
CmpPtr<ICmpPlayer> cmpPlayer(sim, player);
|
||||
|
||||
// Don't add entities for removed players.
|
||||
if (cmpPlayer && cmpPlayer->IsRemoved())
|
||||
{
|
||||
completed_jobs++;
|
||||
LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
|
||||
continue;
|
||||
}
|
||||
|
||||
entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
|
||||
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
|
||||
{ // Don't add entities with invalid player IDs
|
||||
{
|
||||
// Don't add entities with invalid player IDs
|
||||
LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(TemplateName));
|
||||
}
|
||||
else
|
||||
|
|
@ -1497,9 +1508,16 @@ int CMapReader::ParseEntities()
|
|||
{
|
||||
// Get current entity struct
|
||||
currEnt = entities[entity_idx];
|
||||
entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
|
||||
CmpPtr<ICmpPlayer> cmpPlayer(sim, player);
|
||||
// Don't add entities for removed players.
|
||||
if (cmpPlayer && cmpPlayer->IsRemoved())
|
||||
{
|
||||
entity_idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
|
||||
entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
|
||||
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
|
||||
{ // Don't add entities with invalid player IDs
|
||||
LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(currEnt.templateName));
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ public:
|
|||
return m_Script.Call<std::string>("GetState");
|
||||
}
|
||||
|
||||
bool IsRemoved() override
|
||||
{
|
||||
return m_Script.Call<bool>("IsRemoved");
|
||||
}
|
||||
|
||||
bool IsActive() final
|
||||
{
|
||||
return m_IsActive;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public:
|
|||
virtual CFixedVector3D GetStartingCameraRot() = 0;
|
||||
|
||||
virtual bool HasStartingCamera() = 0;
|
||||
virtual bool IsRemoved() = 0;
|
||||
virtual std::string GetState() = 0;
|
||||
|
||||
// See the cpp file for why this is implemented in C++.
|
||||
|
|
|
|||
Loading…
Reference in a new issue