Implement a basic observer mode. Any player which joins a multiplayer game but is not assigned a player slot automatically becomes an observer. Refs #69

This was SVN commit r14849.
This commit is contained in:
JoshuaJB
2014-03-16 23:29:27 +00:00
parent 94c02ec33c
commit 883f307b40
10 changed files with 116 additions and 64 deletions
@@ -1225,7 +1225,7 @@ function updatePlayerList()
hostGuidList.push(guid); hostGuidList.push(guid);
assignments[player] = hostID; assignments[player] = hostID;
if (player != 255) if (player != -1)
assignedCount++; assignedCount++;
} }
@@ -1386,7 +1386,7 @@ function swapPlayers(guid, newSlot)
// Attempt to swap the player or AI occupying the target slot, // Attempt to swap the player or AI occupying the target slot,
// if any, into the slot this player is currently in. // if any, into the slot this player is currently in.
if (playerID != 255) if (playerID != -1)
{ {
for (var i in g_PlayerAssignments) for (var i in g_PlayerAssignments)
{ {
@@ -1433,7 +1433,7 @@ function addChatMessage(msg)
// TODO: Maybe host should have distinct font/color? // TODO: Maybe host should have distinct font/color?
var color = "white"; var color = "white";
if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != 255) if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != -1)
{ // Valid player who has been assigned - get player colour { // Valid player who has been assigned - get player colour
var player = g_PlayerAssignments[msg.guid].player - 1; var player = g_PlayerAssignments[msg.guid].player - 1;
var mapName = g_GameAttributes.map; var mapName = g_GameAttributes.map;
@@ -151,7 +151,7 @@ function exitMenuButton()
var btCode = [leaveGame, resumeGame]; var btCode = [leaveGame, resumeGame];
var message = "Are you sure you want to quit? Leaving will disconnect all other players."; var message = "Are you sure you want to quit? Leaving will disconnect all other players.";
} }
else if (g_IsNetworked && !g_GameEnded) else if (g_IsNetworked && !g_GameEnded && !g_IsObserver)
{ {
var btCode = [networkReturnQuestion, resumeGame]; var btCode = [networkReturnQuestion, resumeGame];
var message = "Are you sure you want to quit?"; var message = "Are you sure you want to quit?";
@@ -270,7 +270,7 @@ function submitChatInput()
var isCheat = false; var isCheat = false;
if (text.length) if (text.length)
{ {
if (g_Players[Engine.GetPlayerID()].cheatsEnabled) if (!g_IsObserver && g_Players[Engine.GetPlayerID()].cheatsEnabled)
{ {
for each (var cheat in Object.keys(cheats)) for each (var cheat in Object.keys(cheats))
{ {
@@ -350,6 +350,9 @@ function addChatMessage(msg, playerAssignments)
if (playerAssignments[msg.guid]) if (playerAssignments[msg.guid])
{ {
var n = playerAssignments[msg.guid].player; var n = playerAssignments[msg.guid].player;
// Observers have an ID of -1 which is not a valid index.
if (n < 0)
n = 0;
playerColor = g_Players[n].color.r + " " + g_Players[n].color.g + " " + g_Players[n].color.b; playerColor = g_Players[n].color.r + " " + g_Players[n].color.g + " " + g_Players[n].color.b;
username = escapeText(playerAssignments[msg.guid].name); username = escapeText(playerAssignments[msg.guid].name);
@@ -306,13 +306,13 @@ function updateSelectionDetails()
g_Selection.update(); g_Selection.update();
var selection = g_Selection.toList(); var selection = g_Selection.toList();
if (selection.length == 0) if (selection.length == 0)
{ {
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true; Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true; Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
hideUnitCommands(); hideUnitCommands();
supplementalDetailsPanel.hidden = true; supplementalDetailsPanel.hidden = true;
detailsPanel.hidden = true; detailsPanel.hidden = true;
commandsPanel.hidden = true; commandsPanel.hidden = true;
@@ -334,11 +334,21 @@ function updateSelectionDetails()
else else
displayMultiple(selection, template); displayMultiple(selection, template);
// Show Panels // Show basic details.
supplementalDetailsPanel.hidden = false;
detailsPanel.hidden = false; detailsPanel.hidden = false;
commandsPanel.hidden = false;
// Fill out commands panel for specific unit selected (or first unit of primary group) if (g_IsObserver)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection); {
// Observers don't need these displayed.
supplementalDetailsPanel.hidden = true;
commandsPanel.hidden = true;
}
else
{
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
// Show panels
supplementalDetailsPanel.hidden = false;
commandsPanel.hidden = false;
}
} }
@@ -5,6 +5,8 @@ var g_IsNetworked = false;
var g_IsController; var g_IsController;
// Match ID for tracking // Match ID for tracking
var g_MatchID; var g_MatchID;
// Is this user an observer?
var g_IsObserver = false;
// Cache the basic player data (name, civ, color) // Cache the basic player data (name, civ, color)
var g_Players = []; var g_Players = [];
@@ -151,7 +153,29 @@ function init(initData, hotloadData)
// Cache civ data // Cache civ data
g_CivData = loadCivData(); g_CivData = loadCivData();
g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia" }; g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia"};
if (Engine.GetPlayerID() <= 0)
{
g_IsObserver = true;
// Hide stuff observers don't use.
Engine.GetGUIObjectByName("food").hidden = true;
Engine.GetGUIObjectByName("wood").hidden = true;
Engine.GetGUIObjectByName("stone").hidden = true;
Engine.GetGUIObjectByName("metal").hidden = true;
Engine.GetGUIObjectByName("population").hidden = true;
Engine.GetGUIObjectByName("diplomacyButton1").hidden = true;
Engine.GetGUIObjectByName("tradeButton1").hidden = true;
Engine.GetGUIObjectByName("menuResignButton").enabled = false;
Engine.GetGUIObjectByName("pauseButton").enabled = false;
Engine.GetGUIObjectByName("observerText").hidden = false;
}
else
{
// TODO: Get a civ icon for gaia/observers.
Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem;
Engine.GetGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name;
}
g_GameSpeeds = initGameSpeeds(); g_GameSpeeds = initGameSpeeds();
g_CurrentSpeed = Engine.GetSimRate(); g_CurrentSpeed = Engine.GetSimRate();
@@ -161,9 +185,6 @@ function init(initData, hotloadData)
var idx = g_GameSpeeds.speeds.indexOf(g_CurrentSpeed); var idx = g_GameSpeeds.speeds.indexOf(g_CurrentSpeed);
gameSpeed.selected = idx != -1 ? idx : g_GameSpeeds["default"]; gameSpeed.selected = idx != -1 ? idx : g_GameSpeeds["default"];
gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); } gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); }
Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem;
Engine.GetGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name;
initMenuPosition(); // set initial position initMenuPosition(); // set initial position
// Populate player selection dropdown // Populate player selection dropdown
@@ -191,9 +212,11 @@ function init(initData, hotloadData)
else else
{ {
// Starting for the first time: // Starting for the first time:
var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music;
initMusic(); initMusic();
global.music.storeTracks(civMusic); if (!g_IsObserver){
var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music;
global.music.storeTracks(civMusic);
}
global.music.setState(global.music.states.PEACE); global.music.setState(global.music.states.PEACE);
playRandomAmbient("temperate"); playRandomAmbient("temperate");
} }
@@ -214,7 +237,7 @@ function init(initData, hotloadData)
function selectViewPlayer(playerID) function selectViewPlayer(playerID)
{ {
Engine.SetPlayerID(playerID); Engine.SetPlayerID(playerID);
if (playerID != 0) { if (playerID > 0) {
Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem; Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem;
Engine.GetGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name; Engine.GetGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name;
} }
@@ -266,31 +289,40 @@ function resignGame(leaveGameAfterResign)
function leaveGame(willRejoin) function leaveGame(willRejoin)
{ {
var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
var playerState = extendedSimState.players[Engine.GetPlayerID()];
var mapSettings = Engine.GetMapSettings(); var mapSettings = Engine.GetMapSettings();
var gameResult; var gameResult;
if (g_Disconnected) if (g_IsObserver)
{ {
gameResult = "You have been disconnected." // Observers don't win/lose.
gameResult = "You have left the game.";
global.music.setState(global.music.states.VICTORY);
} }
else if (playerState.state == "won") else
{ {
gameResult = "You have won the battle!"; var playerState = extendedSimState.players[Engine.GetPlayerID()];
} if (g_Disconnected)
else if (playerState.state == "defeated")
{
gameResult = "You have been defeated...";
}
else // "active"
{
global.music.setState(global.music.states.DEFEAT);
if (willRejoin)
gameResult = "You have left the game.";
else
{ {
gameResult = "You have abandoned the game."; gameResult = "You have been disconnected."
resignGame(true); }
else if (playerState.state == "won")
{
gameResult = "You have won the battle!";
}
else if (playerState.state == "defeated")
{
gameResult = "You have been defeated...";
}
else // "active"
{
global.music.setState(global.music.states.DEFEAT);
if (willRejoin)
gameResult = "You have left the game.";
else
{
gameResult = "You have abandoned the game.";
resignGame(true);
}
} }
} }
@@ -372,7 +404,8 @@ function onTick()
onSimulationUpdate(); onSimulationUpdate();
// Display rally points for selected buildings // Display rally points for selected buildings
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); if (!g_IsObserver)
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
} }
// Run timers // Run timers
@@ -401,7 +434,7 @@ function onTick()
function checkPlayerState() function checkPlayerState()
{ {
// Once the game ends, we're done here. // Once the game ends, we're done here.
if (g_GameEnded) if (g_GameEnded || g_IsObserver)
return; return;
// Send a game report for each player in this game. // Send a game report for each player in this game.
@@ -499,16 +532,18 @@ function onSimulationUpdate()
updateDebug(); updateDebug();
updatePlayerDisplay(); updatePlayerDisplay();
updateSelectionDetails(); updateSelectionDetails();
updateResearchDisplay();
updateBuildingPlacementPreview(); updateBuildingPlacementPreview();
updateTimeElapsedCounter(); updateTimeElapsedCounter();
updateTimeNotifications(); updateTimeNotifications();
// Update music state on basis of battle state. if (!g_IsObserver)
var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID()); {
if (battleState) updateResearchDisplay();
global.music.setState(global.music.states[battleState]); // Update music state on basis of battle state.
var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID());
if (battleState)
global.music.setState(global.music.states[battleState]);
}
} }
/** /**
@@ -625,31 +625,31 @@
size="10 0 45% 100%" size="10 0 45% 100%"
> >
<!-- Food --> <!-- Food -->
<object size="0 0 90 100%" type="image" style="resourceCounter" tooltip="Food" tooltip_style="sessionToolTipBold"> <object name="food" size="0 0 90 100%" type="image" style="resourceCounter" tooltip="Food" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/> <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceFood"/> <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceFood"/>
</object> </object>
<!-- Wood --> <!-- Wood -->
<object size="90 0 180 100%" type="image" style="resourceCounter" tooltip="Wood" tooltip_style="sessionToolTipBold"> <object name="wood" size="90 0 180 100%" type="image" style="resourceCounter" tooltip="Wood" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/> <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceWood"/> <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceWood"/>
</object> </object>
<!-- Stone --> <!-- Stone -->
<object size="180 0 270 100%" type="image" style="resourceCounter" tooltip="Stone" tooltip_style="sessionToolTipBold"> <object name="stone" size="180 0 270 100%" type="image" style="resourceCounter" tooltip="Stone" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/> <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceStone"/> <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceStone"/>
</object> </object>
<!-- Metal --> <!-- Metal -->
<object size="270 0 360 100%" type="image" style="resourceCounter" tooltip="Metal" tooltip_style="sessionToolTipBold"> <object name="metal" size="270 0 360 100%" type="image" style="resourceCounter" tooltip="Metal" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/> <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceMetal"/> <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceMetal"/>
</object> </object>
<!-- Population --> <!-- Population -->
<object size="360 0 450 100%" type="image" style="resourceCounter" tooltip="Population (current / limit)" tooltip_style="sessionToolTipBold"> <object name="population" size="360 0 450 100%" type="image" style="resourceCounter" tooltip="Population (current / limit)" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/> <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/> <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
</object> </object>
@@ -665,10 +665,16 @@
<action on="SelectionChange">selectViewPlayer(this.selected);</action> <action on="SelectionChange">selectViewPlayer(this.selected);</action>
</object> </object>
<!-- ================================ ================================ --> <!-- ================================ ================================ -->
<!-- Phase --> <!-- Phase -->
<!-- ================================ ================================ --> <!-- ================================ ================================ -->
<!--<object size="50%+50 4 50%+300 100%-2" name="PhaseTitleBar" type="text" font="serif-bold-stroke-14" textcolor="white"> Death Match :: Village Phase</object>--> <!--<object size="50%+50 4 50%+300 100%-2" name="PhaseTitleBar" type="text" font="serif-bold-stroke-14" textcolor="white"> Death Match :: Village Phase</object>-->
<!-- ================================ ================================ -->
<!-- Observer Mode Warning -->
<!-- ================================ ================================ -->
<object size="50 4 50% 100%-2" name="observerText" type="text" style="ModernLabelText" text_align="left" hidden="true">Observer Mode (experimental)</object>
<!-- ================================ ================================ --> <!-- ================================ ================================ -->
@@ -1323,7 +1329,7 @@
<!-- ================================ ================================ --> <!-- ================================ ================================ -->
<!-- Network status --> <!-- Network status -->
<!-- ================================ ================================ --> <!-- ================================ ================================ -->
<object name="netStatus" type="text" style="netStatus" z="100" hidden="true"> <object name="netStatus" type="text" style="netStatus" z="200" hidden="true">
<object type="button" <object type="button"
name="disconnectedExitButton" name="disconnectedExitButton"
style="StoneButton" style="StoneButton"
@@ -25,6 +25,10 @@ PlayerManager.prototype.GetPlayerByID = function(id)
if (id in this.playerEntities) if (id in this.playerEntities)
return this.playerEntities[id]; return this.playerEntities[id];
// All players at or below ID 0 get gaia-level data. (Observers for example)
if (id <= 0)
return this.playerEntities[0];
var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line
warn("GetPlayerByID: no player defined for id '"+id+"'\n"+stack); warn("GetPlayerByID: no player defined for id '"+id+"'\n"+stack);
@@ -80,10 +80,6 @@ ResourceSupply.prototype.GetMaxGatherers = function()
ResourceSupply.prototype.GetGatherers = function() ResourceSupply.prototype.GetGatherers = function()
{ {
//if (player === undefined)
// return this.gatherers;
//
//
var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
var total = []; var total = [];
for (var playerid = 0; playerid <= numPlayers; playerid++) for (var playerid = 0; playerid <= numPlayers; playerid++)
+1 -1
View File
@@ -122,7 +122,7 @@ START_NMT_CLASS_(PlayerAssignment, NMT_PLAYER_ASSIGNMENT)
NMT_START_ARRAY(m_Hosts) NMT_START_ARRAY(m_Hosts)
NMT_FIELD(CStr8, m_GUID) NMT_FIELD(CStr8, m_GUID)
NMT_FIELD(CStrW, m_Name) NMT_FIELD(CStrW, m_Name)
NMT_FIELD_INT(m_PlayerID, u8, 1) NMT_FIELD_INT(m_PlayerID, i8, 1)
NMT_END_ARRAY() NMT_END_ARRAY()
END_NMT_CLASS() END_NMT_CLASS()
@@ -1398,10 +1398,8 @@ public:
virtual bool GetLosRevealAll(player_id_t player) virtual bool GetLosRevealAll(player_id_t player)
{ {
// Special player value can force reveal-all for every player // Special player value can force reveal-all for every player
if(m_LosRevealAll[MAX_LOS_PLAYER_ID+1]) if (m_LosRevealAll[MAX_LOS_PLAYER_ID+1] || player == -1)
return true; return true;
if (player == -1)
return false;
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID+1); ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID+1);
// Otherwise check the player-specific flag // Otherwise check the player-specific flag
if (m_LosRevealAll[player]) if (m_LosRevealAll[player])