Index: ps/trunk/binaries/data/mods/public/gui/session/input.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/input.js (revision 9348) +++ ps/trunk/binaries/data/mods/public/gui/session/input.js (revision 9349) @@ -1,1177 +1,1191 @@ const SDL_BUTTON_LEFT = 1; const SDL_BUTTON_MIDDLE = 2; const SDL_BUTTON_RIGHT = 3; const SDLK_LEFTBRACKET = 91; const SDLK_RIGHTBRACKET = 93; const SDLK_RSHIFT = 303; const SDLK_LSHIFT = 304; const SDLK_RCTRL = 305; const SDLK_LCTRL = 306; const SDLK_RALT = 307; const SDLK_LALT = 308; // TODO: these constants should be defined somewhere else instead, in // case any other code wants to use them too const ACTION_NONE = 0; const ACTION_GARRISON = 1; const ACTION_REPAIR = 2; var preSelectedAction = ACTION_NONE; var INPUT_NORMAL = 0; var INPUT_SELECTING = 1; var INPUT_BANDBOXING = 2; var INPUT_BUILDING_PLACEMENT = 3; var INPUT_BUILDING_CLICK = 4; var INPUT_BUILDING_DRAG = 5; var INPUT_BATCHTRAINING = 6; var INPUT_PRESELECTEDACTION = 7; var inputState = INPUT_NORMAL; var defaultPlacementAngle = Math.PI*3/4; var placementAngle = undefined; var placementPosition = undefined; var placementEntity = undefined; var mouseX = 0; var mouseY = 0; var mouseIsOverObject = false; // Number of pixels the mouse can move before the action is considered a drag var maxDragDelta = 4; // Time in milliseconds in which a double click is recognized const doubleClickTime = 500; var doubleClickTimer = 0; var doubleClicked = false; // Store the previously clicked entity - ensure a double/triple click happens on the same entity var prevClickedEntity = 0; // Same double-click behaviour for hotkey presses const doublePressTime = 500; var doublePressTimer = 0; var prevHotkey = 0; function updateCursor() { if (!mouseIsOverObject) { var action = determineAction(mouseX, mouseY); if (inputState == INPUT_NORMAL || inputState == INPUT_PRESELECTEDACTION) { if (action) { if (action.cursor) { Engine.SetCursor(action.cursor); return; } } } } Engine.SetCursor("arrow-default"); } function updateBuildingPlacementPreview() { // The preview should be recomputed every turn, so that it responds // to obstructions/fog/etc moving underneath it if (placementEntity && placementPosition) { Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle }); } } function resetPlacementEntity() { Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""}); placementEntity = undefined; placementPosition = undefined; placementAngle = undefined; } function findGatherType(gatherer, supply) { if (!gatherer || !supply) return undefined; if (gatherer[supply.type.generic+"."+supply.type.specific]) return supply.type.specific; if (gatherer[supply.type.generic]) return supply.type.generic; return undefined; } function getActionInfo(action, target) { var selection = g_Selection.toList(); // If the selection doesn't exist, no action var entState = GetEntityState(selection[0]); if (!entState) return {"possible": false}; // If the selection isn't friendly units, no action var player = Engine.GetPlayerID(); if (entState.player != player && !g_DevSettings.controlAll) return {"possible": false}; // Work out whether the selection can have rally points var haveRallyPoints = selection.every(function(ent) { var entState = GetEntityState(ent); return entState && entState.rallyPoint; }); if (!target) { if (action == "set-rallypoint" && haveRallyPoints) return {"possible": true}; else if (action == "move") return {"possible": true}; else return {"possible": false}; } if (haveRallyPoints && selection.indexOf(target) != -1 && action == "unset-rallypoint") return {"possible": true}; // Look at the first targeted entity // (TODO: maybe we eventually want to look at more, and be more context-sensitive? // e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse) var targetState = GetEntityState(target); // If we selected buildings with rally points, and then click on one of those selected // buildings, we should remove the rally point //if (haveRallyPoints && selection.indexOf(target) != -1) // return {"type": "unset-rallypoint"}; // Check if the target entity is a resource, dropsite, foundation, or enemy unit. // Check if any entities in the selection can gather the requested resource, // can return to the dropsite, can build the foundation, or can attack the enemy var simState = Engine.GuiInterfaceCall("GetSimulationState"); for each (var entityID in selection) { var entState = GetEntityState(entityID); if (!entState) continue; // Get entity owner diplomacy array var diplomacy = simState.players[entState.player].diplomacy; var playerOwned = ((targetState.player == entState.player)? true : false); var enemyOwned = ((targetState.player != entState.player && targetState.player && diplomacy[targetState.player - 1] < 0)? true : false); var gaiaOwned = ((targetState.player == 0)? true : false); // Find the resource type we're carrying, if any var carriedType = undefined; if (entState.resourceCarrying && entState.resourceCarrying.length) carriedType = entState.resourceCarrying[0].type; switch (action) { case "garrison": if (isUnit(entState) && targetState.garrisonHolder && playerOwned) { var allowedClasses = targetState.garrisonHolder.allowedClasses; for each (var unitClass in entState.identity.classes) { if (allowedClasses.indexOf(unitClass) != -1) { return {"possible": true}; } } } break; case "gather": if (targetState.resourceSupply && (playerOwned || gaiaOwned)) { var resource = findGatherType(entState.resourceGatherRates, targetState.resourceSupply); if (resource) return {"possible": true, "cursor": "action-gather-" + resource}; } break; case "returnresource": if (targetState.resourceDropsite && playerOwned && carriedType && targetState.resourceDropsite.types.indexOf(carriedType) != -1) return {"possible": true, "cursor": "action-return-" + carriedType}; break; case "build": if (targetState.foundation && entState.buildEntities && playerOwned) return {"possible": true}; break; case "repair": if (entState.buildEntities && targetState.needsRepair && playerOwned) return {"possible": true}; break; case "attack": if (entState.attack && targetState.hitpoints && (enemyOwned || gaiaOwned)) return {"possible": true}; } } if (action == "move") return {"possible": true}; else return {"possible": false}; } /** * Determine the context-sensitive action that should be performed when the mouse is at (x,y) */ function determineAction(x, y, fromMinimap) { var selection = g_Selection.toList(); // No action if there's no selection if (!selection.length) { preSelectedAction = ACTION_NONE; return undefined; } // If the selection doesn't exist, no action var entState = GetEntityState(selection[0]); if (!entState) return undefined; // If the selection isn't friendly units, no action var player = Engine.GetPlayerID(); if (entState.player != player && !g_DevSettings.controlAll) return undefined; // Work out whether the selection can have rally points var haveRallyPoints = selection.every(function(ent) { var entState = GetEntityState(ent); return entState && entState.rallyPoint; }); var targets = []; var target = undefined; var type = "none"; var cursor = ""; var targetState = undefined; if (!fromMinimap) targets = Engine.PickEntitiesAtPoint(x, y); if (targets.length) { target = targets[0]; } if (preSelectedAction != ACTION_NONE) { switch (preSelectedAction) { case ACTION_GARRISON: if (getActionInfo("garrison", target).possible) return {"type": "garrison", "cursor": "action-garrison", "target": target}; else return {"type": "none", "cursor": "action-garrison-disabled", "target": undefined}; break; case ACTION_REPAIR: if (getActionInfo("repair", target).possible) return {"type": "repair", "cursor": "action-repair", "target": target}; else return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; break; } } else if (Engine.HotkeyIsPressed("session.garrison")) { if (getActionInfo("garrison", target).possible) return {"type": "garrison", "cursor": "action-garrison", "target": target}; else return {"type": "none", "cursor": "action-garrison-disabled", "target": undefined}; } else { var actionInfo = undefined; if ((actionInfo = getActionInfo("gather", target)).possible) return {"type": "gather", "cursor": actionInfo.cursor, "target": target}; else if ((actionInfo = getActionInfo("returnresource", target)).possible) return {"type": "returnresource", "cursor": actionInfo.cursor, "target": target}; else if (getActionInfo("build", target).possible) return {"type": "build", "cursor": "action-build", "target": target}; else if (getActionInfo("repair", target).possible) return {"type": "build", "cursor": "action-repair", "target": target}; else if (getActionInfo("attack", target).possible) return {"type": "attack", "cursor": "action-attack", "target": target}; else if(getActionInfo("set-rallypoint", target).possible) return {"type": "set-rallypoint"}; else if(getActionInfo("unset-rallypoint", target).possible) return {"type": "unset-rallypoint"}; else if (getActionInfo("move", target).possible) return {"type": "move"}; } return {"type": type, "cursor": cursor, "target": target}; } var dragStart; // used for remembering mouse coordinates at start of drag operations function tryPlaceBuilding(queued) { var selection = g_Selection.toList(); // Use the preview to check it's a valid build location var ok = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle }); if (!ok) { // invalid location - don't build it return false; } // Start the construction Engine.PostNetworkCommand({ "type": "construct", "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle, "entities": selection, "autorepair": true, "autocontinue": true, "queued": queued }); Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] }); if (!queued) resetPlacementEntity(); return true; } // Limits bandboxed selections to certain types of entities based on priority function getPreferredEntities(ents) { var entStateList = []; var preferredEnts = []; // Check if there are units in the selection and get a list of entity states for each (var ent in ents) { var entState = GetEntityState(ent); if (!entState) continue; if (isUnit(entState)) preferredEnts.push(ent); entStateList.push(entState); } // If there are no units, check if there are defensive entities in the selection if (!preferredEnts.length) for (var i = 0; i < ents.length; i++) if (isDefensive(entStateList[i])) preferredEnts.push(ents[i]); return preferredEnts; } function handleInputBeforeGui(ev, hoveredObject) { // Capture mouse position so we can use it for displaying cursors, // and key states switch (ev.type) { case "mousebuttonup": case "mousebuttondown": case "mousemotion": mouseX = ev.x; mouseY = ev.y; break; } // Remember whether the mouse is over a GUI object or not mouseIsOverObject = (hoveredObject != null); // State-machine processing: // // (This is for states which should override the normal GUI processing - events will // be processed here before being passed on, and propagation will stop if this function // returns true) // // TODO: it'd probably be nice to have a better state-machine system, with guaranteed // entry/exit functions, since this is a bit broken now switch (inputState) { case INPUT_BANDBOXING: switch (ev.type) { case "mousemotion": var x0 = dragStart[0]; var y0 = dragStart[1]; var x1 = ev.x; var y1 = ev.y; if (x0 > x1) { var t = x0; x0 = x1; x1 = t; } if (y0 > y1) { var t = y0; y0 = y1; y1 = t; } var bandbox = getGUIObjectByName("bandbox"); bandbox.size = [x0, y0, x1, y1].join(" "); bandbox.hidden = false; var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID()); g_Selection.setHighlightList(ents); return false; case "mousebuttonup": if (ev.button == SDL_BUTTON_LEFT) { var x0 = dragStart[0]; var y0 = dragStart[1]; var x1 = ev.x; var y1 = ev.y; if (x0 > x1) { var t = x0; x0 = x1; x1 = t; } if (y0 > y1) { var t = y0; y0 = y1; y1 = t; } var bandbox = getGUIObjectByName("bandbox"); bandbox.hidden = true; // Get list of entities limited to preferred entities var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID()); var preferredEntities = getPreferredEntities(ents) if (preferredEntities.length) ents = preferredEntities; // Remove the bandbox hover highlighting g_Selection.setHighlightList([]); // Update the list of selected units if (Engine.HotkeyIsPressed("selection.add")) { g_Selection.addList(ents); } else if (Engine.HotkeyIsPressed("selection.remove")) { g_Selection.removeList(ents); } else { g_Selection.reset(); g_Selection.addList(ents); } inputState = INPUT_NORMAL; return true; } else if (ev.button == SDL_BUTTON_RIGHT) { // Cancel selection var bandbox = getGUIObjectByName("bandbox"); bandbox.hidden = true; g_Selection.setHighlightList([]); inputState = INPUT_NORMAL; return true; } break; } break; case INPUT_BUILDING_CLICK: switch (ev.type) { case "mousemotion": // If the mouse moved far enough from the original click location, // then switch to drag-orientatio mode var dragDeltaX = ev.x - dragStart[0]; var dragDeltaY = ev.y - dragStart[1]; var maxDragDelta = 16; if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta) { inputState = INPUT_BUILDING_DRAG; return false; } break; case "mousebuttonup": if (ev.button == SDL_BUTTON_LEFT) { // If shift is down, let the player continue placing another of the same building var queued = Engine.HotkeyIsPressed("session.queue"); if (tryPlaceBuilding(queued)) { if (queued) inputState = INPUT_BUILDING_PLACEMENT; else inputState = INPUT_NORMAL; } else { inputState = INPUT_BUILDING_PLACEMENT; } return true; } break; case "mousebuttondown": if (ev.button == SDL_BUTTON_RIGHT) { // Cancel building resetPlacementEntity(); inputState = INPUT_NORMAL; return true; } break; } break; case INPUT_BUILDING_DRAG: switch (ev.type) { case "mousemotion": var dragDeltaX = ev.x - dragStart[0]; var dragDeltaY = ev.y - dragStart[1]; var maxDragDelta = 16; if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta) { // Rotate in the direction of the mouse var target = Engine.GetTerrainAtPoint(ev.x, ev.y); placementAngle = Math.atan2(target.x - placementPosition.x, target.z - placementPosition.z); } else { // If the mouse is near the center, snap back to the default orientation placementAngle = defaultPlacementAngle; } Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle }); break; case "mousebuttonup": if (ev.button == SDL_BUTTON_LEFT) { // If shift is down, let the player continue placing another of the same building var queued = Engine.HotkeyIsPressed("session.queue"); if (tryPlaceBuilding(queued)) { if (queued) inputState = INPUT_BUILDING_PLACEMENT; else inputState = INPUT_NORMAL; } else { inputState = INPUT_BUILDING_PLACEMENT; } return true; } break; case "mousebuttondown": if (ev.button == SDL_BUTTON_RIGHT) { // Cancel building resetPlacementEntity(); inputState = INPUT_NORMAL; return true; } break; } break; case INPUT_BATCHTRAINING: switch (ev.type) { case "hotkeyup": if (ev.hotkey == "session.batchtrain") { flushTrainingQueueBatch(); inputState = INPUT_NORMAL; } break; } } return false; } function handleInputAfterGui(ev) { // Handle the time-warp testing features, restricted to single-player if (!g_IsNetworked && getGUIObjectByName("devTimeWarp").checked) { if (ev.type == "hotkeydown" && ev.hotkey == "timewarp.fastforward") Engine.SetSimRate(20.0); else if (ev.type == "hotkeyup" && ev.hotkey == "timewarp.fastforward") Engine.SetSimRate(1.0); else if (ev.type == "hotkeyup" && ev.hotkey == "timewarp.rewind") Engine.RewindTimeWarp(); } // State-machine processing: switch (inputState) { case INPUT_NORMAL: switch (ev.type) { case "mousemotion": // Highlight the first hovered entity (if any) var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y); if (ents.length) g_Selection.setHighlightList([ents[0]]); else g_Selection.setHighlightList([]); return false; case "mousebuttondown": if (ev.button == SDL_BUTTON_LEFT) { dragStart = [ ev.x, ev.y ]; inputState = INPUT_SELECTING; return true; } else if (ev.button == SDL_BUTTON_RIGHT) { var action = determineAction(ev.x, ev.y); if (!action) break; return doAction(action, ev); } break; case "hotkeydown": if (ev.hotkey.indexOf("selection.group.") == 0) { var now = new Date(); if ((now.getTime() - doublePressTimer < doublePressTime) && (ev.hotkey == prevHotkey)) { if (ev.hotkey.indexOf("selection.group.select.") == 0) { var sptr = ev.hotkey.split("."); performGroup("snap", sptr[3]); } } else { var sptr = ev.hotkey.split("."); performGroup(sptr[2], sptr[3]); doublePressTimer = now.getTime(); prevHotkey = ev.hotkey; } } break; } break; case INPUT_PRESELECTEDACTION: switch (ev.type) { case "mousebuttondown": if (ev.button == SDL_BUTTON_LEFT && preSelectedAction != ACTION_NONE) { var action = determineAction(ev.x, ev.y); if (!action) break; preSelectedAction = ACTION_NONE; inputState = INPUT_NORMAL; return doAction(action, ev); } else if (ev.button == SDL_BUTTON_RIGHT && preSelectedAction != ACTION_NONE) { preSelectedAction = ACTION_NONE; inputState = INPUT_NORMAL; break; } } break; case INPUT_SELECTING: switch (ev.type) { case "mousemotion": // If the mouse moved further than a limit, switch to bandbox mode var dragDeltaX = ev.x - dragStart[0]; var dragDeltaY = ev.y - dragStart[1]; if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta) { inputState = INPUT_BANDBOXING; return false; } var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y); g_Selection.setHighlightList(ents); return false; case "mousebuttonup": if (ev.button == SDL_BUTTON_LEFT) { var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y); if (!ents.length) { g_Selection.reset(); inputState = INPUT_NORMAL; return true; } var onScreenOnly; var selectedEntity = ents[0]; var now = new Date(); if ((now.getTime() - doubleClickTimer < doubleClickTime) && (selectedEntity == prevClickedEntity)) { // Double click or triple click has occurred // Check for double click or triple click if (!doubleClicked) { // If double click hasn't already occurred, this is a double click. // Select only similar on-screen units onScreenOnly = true; doubleClicked = true; } else { // Double click has already occurred, so this is a triple click. // Select all similar units whether they are on-screen or not onScreenOnly = false; } var templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template; ents = Engine.PickSimilarFriendlyEntities(templateToMatch, onScreenOnly); } else { // It's single click right now but it may become double or triple click doubleClicked = false; doubleClickTimer = now.getTime(); prevClickedEntity = selectedEntity; // We only want to include the first picked unit in the selection ents = [ents[0]]; } // Update the list of selected units if (Engine.HotkeyIsPressed("selection.add")) { g_Selection.addList(ents); } else if (Engine.HotkeyIsPressed("selection.remove")) { g_Selection.removeList(ents); } else { g_Selection.reset(); g_Selection.addList(ents); } inputState = INPUT_NORMAL; return true; } break; } break; case INPUT_BUILDING_PLACEMENT: switch (ev.type) { case "mousemotion": placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle }); return false; // continue processing mouse motion case "mousebuttondown": if (ev.button == SDL_BUTTON_LEFT) { placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); dragStart = [ ev.x, ev.y ]; inputState = INPUT_BUILDING_CLICK; return true; } else if (ev.button == SDL_BUTTON_RIGHT) { // Cancel building resetPlacementEntity(); inputState = INPUT_NORMAL; return true; } break; case "hotkeydown": var rotation_step = Math.PI / 12; // 24 clicks make a full rotation switch (ev.hotkey) { case "session.rotate.cw": placementAngle += rotation_step; Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle }); break; case "session.rotate.ccw": placementAngle -= rotation_step; Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { "template": placementEntity, "x": placementPosition.x, "z": placementPosition.z, "angle": placementAngle }); break; } break; } break; } return false; } function doAction(action, ev) { var selection = g_Selection.toList(); // If shift is down, add the order to the unit's order queue instead // of running it immediately var queued = Engine.HotkeyIsPressed("session.queue"); switch (action.type) { case "move": var target = Engine.GetTerrainAtPoint(ev.x, ev.y); Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); return true; case "attack": Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); return true; case "build": // (same command as repair) case "repair": Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] }); return true; case "gather": Engine.PostNetworkCommand({"type": "gather", "entities": selection, "target": action.target, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] }); return true; case "returnresource": Engine.PostNetworkCommand({"type": "returnresource", "entities": selection, "target": action.target, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] }); return true; case "garrison": Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued}); //Need to play some sound here?? return true; case "set-rallypoint": var target = Engine.GetTerrainAtPoint(ev.x, ev.y); Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z}); // Display rally point at the new coordinates, to avoid display lag Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": selection, "x": target.x, "z": target.z }); return true; case "unset-rallypoint": var target = Engine.GetTerrainAtPoint(ev.x, ev.y); Engine.PostNetworkCommand({"type": "unset-rallypoint", "entities": selection}); // Remove displayed rally point Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": [] }); return true; case "none": return true; default: error("Invalid action.type "+action.type); return false; } } function handleMinimapEvent(target) { // Partly duplicated from handleInputAfterGui(), but with the input being // world coordinates instead of screen coordinates. if (inputState == INPUT_NORMAL) { var fromMinimap = true; var action = determineAction(undefined, undefined, fromMinimap); if (!action) return false; var selection = g_Selection.toList(); var queued = Engine.HotkeyIsPressed("session.queue"); switch (action.type) { case "move": Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); return true; case "set-rallypoint": Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z}); // Display rally point at the new coordinates, to avoid display lag Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": selection, "x": target.x, "z": target.z }); return true; default: error("Invalid action.type "+action.type); } } return false; } // Called by GUI when user clicks construction button function startBuildingPlacement(buildEntType) { placementEntity = buildEntType; placementAngle = defaultPlacementAngle; inputState = INPUT_BUILDING_PLACEMENT; } // Batch training: // When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING // When the user releases shift, or clicks on a different training button, we create the batched units var batchTrainingEntity; var batchTrainingType; var batchTrainingCount; const batchIncrementSize = 5; function flushTrainingQueueBatch() { Engine.PostNetworkCommand({"type": "train", "entity": batchTrainingEntity, "template": batchTrainingType, "count": batchTrainingCount}); } // Called by GUI when user clicks training button function addToTrainingQueue(entity, trainEntType) { if (Engine.HotkeyIsPressed("session.batchtrain")) { if (inputState == INPUT_BATCHTRAINING) { // If we're already creating a batch of this unit, then just extend it if (batchTrainingEntity == entity && batchTrainingType == trainEntType) { batchTrainingCount += batchIncrementSize; return; } // Otherwise start a new one else { flushTrainingQueueBatch(); // fall through to create the new batch } } inputState = INPUT_BATCHTRAINING; batchTrainingEntity = entity; batchTrainingType = trainEntType; batchTrainingCount = batchIncrementSize; } else { // Non-batched - just create a single entity Engine.PostNetworkCommand({"type": "train", "entity": entity, "template": trainEntType, "count": 1}); } } // Returns the number of units that will be present in a batch if the user clicks // the training button with shift down function getTrainingQueueBatchStatus(entity, trainEntType) { if (inputState == INPUT_BATCHTRAINING && batchTrainingEntity == entity && batchTrainingType == trainEntType) return [batchTrainingCount, batchIncrementSize]; else return [0, batchIncrementSize]; } // Called by GUI when user clicks production queue item function removeFromTrainingQueue(entity, id) { Engine.PostNetworkCommand({"type": "stop-train", "entity": entity, "id": id}); } // Called by unit selection buttons function changePrimarySelectionGroup(templateName) { if (Engine.HotkeyIsPressed("session.deselectgroup")) g_Selection.makePrimarySelection(templateName, true); else g_Selection.makePrimarySelection(templateName, false); } // Performs the specified command (delete, town bell, repair, etc.) function performCommand(entity, commandName) { if (entity) { var entState = GetEntityState(entity); var template = GetTemplateData(entState.template); var unitName = getEntityName(template); var player = Engine.GetPlayerID(); if (entState.player == player || g_DevSettings.controlAll) { switch (commandName) { case "delete": var selection = g_Selection.toList(); if (selection.length > 0) { var message = "Are you sure you want to\ndelete the selected units?"; var deleteFunction = function () { Engine.PostNetworkCommand({"type": "delete-entities", "entities": selection}); }; g_SessionDialog.open("Delete", message, null, 340, 160, deleteFunction); } break; case "garrison": inputState = INPUT_PRESELECTEDACTION; preSelectedAction = ACTION_GARRISON; break; case "repair": inputState = INPUT_PRESELECTEDACTION; preSelectedAction = ACTION_REPAIR; break; case "unload-all": unloadAll(entity); break; default: break; } } } } // Performs the specified formation function performFormation(entity, formationName) { submitChatDirectly("FORMATIONS are not implemented yet."); if (entity) { console.write(formationName); } } // Performs the specified group function performGroup(action, groupId) { switch (action) { case "snap": case "select": var toSelect = []; g_Groups.update(); for (var ent in g_Groups.groups[groupId].ents) toSelect.push(+ent); g_Selection.reset(); g_Selection.addList(toSelect); if (action == "snap" && toSelect.length) Engine.CameraFollow(toSelect[0]); break; case "add": var selection = g_Selection.toList(); g_Groups.addEntities(groupId, selection); updateGroups(); break; case "save": var selection = g_Selection.toList(); g_Groups.groups[groupId].reset(); g_Groups.addEntities(groupId, selection); updateGroups(); break; } } // Set the camera to follow the given unit function setCameraFollow(entity) { // Follow the given entity if it's a unit if (entity) { var entState = GetEntityState(entity); if (entState && isUnit(entState)) { Engine.CameraFollow(entity); return; } } // Otherwise stop following Engine.CameraFollow(0); } var lastIdleWorker = 0; +var currIdleClass = 0; function findIdleWorker() { - lastIdleWorker = Engine.GuiInterfaceCall("FindIdleWorker", lastIdleWorker); - if (lastIdleWorker) + // Cycle through idling classes before giving up + var idleClasses = ["Worker", "Trade", "CitizenSoldier"]; + for (var i = 0; i <= idleClasses.length; ++i) { - g_Selection.reset() - g_Selection.addList([lastIdleWorker]); - Engine.CameraFollow(lastIdleWorker); - } - else - { - // TODO: display a message or play a sound to indicate no more idle units, or something + var data = { prevWorker: lastIdleWorker, idleClass: idleClasses[currIdleClass] }; + lastIdleWorker = Engine.GuiInterfaceCall("FindIdleWorker", data); + + // Check if we have valid entity + if (lastIdleWorker) + { + g_Selection.reset() + g_Selection.addList([lastIdleWorker]); + Engine.CameraFollow(lastIdleWorker); + + return; + } + + lastIdleWorker = 0; + currIdleClass = (currIdleClass + 1) % idleClasses.length; } + + // TODO: display a message or play a sound to indicate no more idle units, or something + // Reset for next cycle + currIdleClass = 0; } function unload(garrisonHolder, entity) { Engine.PostNetworkCommand({"type": "unload", "entity": entity, "garrisonHolder": garrisonHolder}); } function unloadAll(garrisonHolder) { Engine.PostNetworkCommand({"type": "unload-all", "garrisonHolder": garrisonHolder}); } Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 9348) +++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 9349) @@ -1,538 +1,534 @@ function GuiInterface() {} GuiInterface.prototype.Schema = ""; GuiInterface.prototype.Serialize = function() { // This component isn't network-synchronised so we mustn't serialise // its non-deterministic data. Instead just return an empty object. return {}; }; GuiInterface.prototype.Deserialize = function(obj) { this.Init(); }; GuiInterface.prototype.Init = function() { this.placementEntity = undefined; // = undefined or [templateName, entityID] this.rallyPoints = undefined; this.notifications = []; }; /* * All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg) * from GUI scripts, and executed here with arguments (player, arg). */ /** * Returns global information about the current game state. * This is used by the GUI and also by AI scripts. */ GuiInterface.prototype.GetSimulationState = function(player) { var ret = { "players": [] }; var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var n = cmpPlayerMan.GetNumPlayers(); for (var i = 0; i < n; ++i) { var playerEnt = cmpPlayerMan.GetPlayerByID(i); var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player); var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker); var playerData = { "name": cmpPlayer.GetName(), "civ": cmpPlayer.GetCiv(), "colour": cmpPlayer.GetColour(), "popCount": cmpPlayer.GetPopulationCount(), "popLimit": cmpPlayer.GetPopulationLimit(), "resourceCounts": cmpPlayer.GetResourceCounts(), "trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked(), "state": cmpPlayer.GetState(), "team": cmpPlayer.GetTeam(), "diplomacy": cmpPlayer.GetDiplomacy(), "phase": cmpPlayer.GetPhase() }; ret.players.push(playerData); } var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) { ret.circularMap = cmpRangeManager.GetLosCircular(); } // Add timeElapsed var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); ret.timeElapsed = cmpTimer.GetTime(); return ret; }; GuiInterface.prototype.GetExtendedSimulationState = function(player) { // Get basic simulation info var ret = this.GetSimulationState(); // Add statistics to each player var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var n = cmpPlayerMan.GetNumPlayers(); for (var i = 0; i < n; ++i) { var playerEnt = cmpPlayerMan.GetPlayerByID(i); var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker); ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics(); } return ret; }; GuiInterface.prototype.GetEntityState = function(player, ent) { var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); // All units must have a template; if not then it's a nonexistent entity id var template = cmpTempMan.GetCurrentTemplateName(ent); if (!template) return null; var ret = { "id": ent, "template": template } var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); if (cmpIdentity) { ret.identity = { "rank": cmpIdentity.GetRank(), "classes": cmpIdentity.GetClassesList() }; } var cmpPosition = Engine.QueryInterface(ent, IID_Position); if (cmpPosition && cmpPosition.IsInWorld()) { ret.position = cmpPosition.GetPosition(); } var cmpHealth = Engine.QueryInterface(ent, IID_Health); if (cmpHealth) { ret.hitpoints = cmpHealth.GetHitpoints(); ret.maxHitpoints = cmpHealth.GetMaxHitpoints(); ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()); } var cmpAttack = Engine.QueryInterface(ent, IID_Attack); if (cmpAttack) { var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? ret.attack = cmpAttack.GetAttackStrengths(type); } var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver); if (cmpArmour) { ret.armour = cmpArmour.GetArmourStrengths(); } var cmpBuilder = Engine.QueryInterface(ent, IID_Builder); if (cmpBuilder) { ret.buildEntities = cmpBuilder.GetEntitiesList(); } var cmpTrainingQueue = Engine.QueryInterface(ent, IID_TrainingQueue); if (cmpTrainingQueue) { ret.training = { "entities": cmpTrainingQueue.GetEntitiesList(), "queue": cmpTrainingQueue.GetQueue(), }; } var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); if (cmpFoundation) { ret.foundation = { "progress": cmpFoundation.GetBuildPercentage() }; } var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) { ret.player = cmpOwnership.GetOwner(); } var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply); if (cmpResourceSupply) { ret.resourceSupply = { "max": cmpResourceSupply.GetMaxAmount(), "amount": cmpResourceSupply.GetCurrentAmount(), "type": cmpResourceSupply.GetType() }; } var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer); if (cmpResourceGatherer) { ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates(); ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus(); } var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite); if (cmpResourceDropsite) { ret.resourceDropsite = { "types": cmpResourceDropsite.GetTypes() }; } var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) { ret.rallyPoint = { }; } var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); if (cmpGarrisonHolder) { ret.garrisonHolder = { "entities": cmpGarrisonHolder.GetEntities(), "allowedClasses": cmpGarrisonHolder.GetAllowedClassesList() }; } var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); ret.visibility = cmpRangeManager.GetLosVisibility(ent, player); return ret; }; GuiInterface.prototype.GetTemplateData = function(player, name) { var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); var template = cmpTempMan.GetTemplate(name); if (!template) return null; var ret = {}; if (template.Identity) { ret.selectionGroupName = template.Identity.SelectionGroupName; ret.name = { "specific": (template.Identity.SpecificName || template.Identity.GenericName), "generic": template.Identity.GenericName }; ret.icon = template.Identity.Icon; ret.tooltip = template.Identity.Tooltip; } if (template.Cost) { ret.cost = {}; if (template.Cost.Resources.food) ret.cost.food = +template.Cost.Resources.food; if (template.Cost.Resources.wood) ret.cost.wood = +template.Cost.Resources.wood; if (template.Cost.Resources.stone) ret.cost.stone = +template.Cost.Resources.stone; if (template.Cost.Resources.metal) ret.cost.metal = +template.Cost.Resources.metal; if (template.Cost.Population) ret.cost.population = +template.Cost.Population; if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus; } return ret; }; GuiInterface.prototype.PushNotification = function(notification) { this.notifications.push(notification); }; GuiInterface.prototype.GetNextNotification = function() { if (this.notifications.length) return this.notifications.pop(); else return ""; }; GuiInterface.prototype.SetSelectionHighlight = function(player, cmd) { var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var playerColours = {}; // cache of owner -> colour map for each (var ent in cmd.entities) { var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable); if (!cmpSelectable) continue; if (cmd.alpha == 0) { cmpSelectable.SetSelectionHighlight({"r":0, "g":0, "b":0, "a":0}); continue; } // Find the entity's owner's colour: var owner = -1; var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) owner = cmpOwnership.GetOwner(); var colour = playerColours[owner]; if (!colour) { colour = [1, 1, 1]; var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player); if (cmpPlayer) colour = cmpPlayer.GetColour(); playerColours[owner] = colour; } cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":cmd.alpha}); } }; GuiInterface.prototype.SetStatusBars = function(player, cmd) { for each (var ent in cmd.entities) { var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); if (cmpStatusBars) cmpStatusBars.SetEnabled(cmd.enabled); } }; /** * Displays the rally point of a building */ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) { // If there are rally points already displayed, destroy them for each (var ent in this.rallyPoints) { // Hide it first (the destruction won't be instantaneous) var cmpPosition = Engine.QueryInterface(ent, IID_Position); cmpPosition.MoveOutOfWorld(); Engine.DestroyEntity(ent); } this.rallyPoints = []; var positions = []; // DisplayRallyPoints is called passing a list of entities for which // rally points must be displayed for each (var ent in cmd.entities) { var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (!cmpRallyPoint) continue; // Verify the owner var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (!cmpOwnership || cmpOwnership.GetOwner() != player) continue; // If the command was passed an explicit position, use that and // override the real rally point position; otherwise use the real position var pos; if (cmd.x && cmd.z) pos = {"x": cmd.x, "z": cmd.z}; else pos = cmpRallyPoint.GetPosition(); if (pos) { // TODO: it'd probably be nice if we could draw some kind of line // between the building and pos, to make the marker easy to find even // if it's a long way from the building positions.push(pos); } } // Add rally point entity for each building for each (var pos in positions) { var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml"); var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position); cmpPosition.JumpTo(pos.x, pos.z); this.rallyPoints.push(rallyPoint); } }; /** * Display the building placement preview. * cmd.template is the name of the entity template, or "" to disable the preview. * cmd.x, cmd.z, cmd.angle give the location. * Returns true if the placement is okay (everything is valid and the entity is not obstructed by others). */ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd) { // See if we're changing template if (!this.placementEntity || this.placementEntity[0] != cmd.template) { // Destroy the old preview if there was one if (this.placementEntity) Engine.DestroyEntity(this.placementEntity[1]); // Load the new template if (cmd.template == "") { this.placementEntity = undefined; } else { this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)]; } } if (this.placementEntity) { var ent = this.placementEntity[1]; // Move the preview into the right location var pos = Engine.QueryInterface(ent, IID_Position); if (pos) { pos.JumpTo(cmd.x, cmd.z); pos.SetYRotation(cmd.angle); } // Check whether it's obstructed by other entities var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); var colliding = (cmpObstruction && cmpObstruction.CheckFoundationCollisions()); // Check whether it's in a visible region var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible"); var ok = (!colliding && visible); // Set it to a red shade if this is an invalid location var cmpVisual = Engine.QueryInterface(ent, IID_Visual); if (cmpVisual) { if (!ok) cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1); else cmpVisual.SetShadingColour(1, 1, 1, 1); } return ok; } return false; }; GuiInterface.prototype.PlaySound = function(player, data) { // Ignore if no entity was passed if (!data.entity) return; PlaySound(data.name, data.entity); }; -function isIdleWorker(ent) +function isIdleWorker(ent, idleClass) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - return (cmpUnitAI && cmpIdentity && cmpUnitAI.IsIdle() && cmpIdentity.HasClass("Worker")); + return (cmpUnitAI && cmpIdentity && cmpUnitAI.IsIdle() && idleClass && cmpIdentity.HasClass(idleClass)); } -GuiInterface.prototype.FindIdleWorker = function(player, prevWorker) +GuiInterface.prototype.FindIdleWorker = function(player, data) { var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var playerEntities = rangeMan.GetEntitiesByPlayer(player); - var firstUnit = 0; - // Find the first matching entity that is after the previous selection, // so that we cycle around in a predictable order for each (var ent in playerEntities) { - if (ent > prevWorker && isIdleWorker(ent)) + if (ent > data.prevWorker && isIdleWorker(ent, data.idleClass)) return ent; - else if (!firstUnit && isIdleWorker(ent)) - firstUnit = ent; } - // Nothing after the selection - just return the first entity - return firstUnit; + // No idle entities left in the class + return 0; }; GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled) { var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder); cmpPathfinder.SetDebugOverlay(enabled); }; GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled) { var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); cmpObstructionManager.SetDebugOverlay(enabled); }; GuiInterface.prototype.SetMotionDebugOverlay = function(player, data) { for each (var ent in data.entities) { var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); if (cmpUnitMotion) cmpUnitMotion.SetDebugOverlay(data.enabled); } }; GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); cmpRangeManager.SetDebugOverlay(enabled); }; // List the GuiInterface functions that can be safely called by GUI scripts. // (GUI scripts are non-deterministic and untrusted, so these functions must be // appropriately careful. They are called with a first argument "player", which is // trusted and indicates the player associated with the current client; no data should // be returned unless this player is meant to be able to see it.) var exposedFunctions = { "GetSimulationState": 1, "GetExtendedSimulationState": 1, "GetEntityState": 1, "GetTemplateData": 1, "GetNextNotification": 1, "SetSelectionHighlight": 1, "SetStatusBars": 1, "DisplayRallyPoint": 1, "SetBuildingPlacementPreview": 1, "PlaySound": 1, "FindIdleWorker": 1, "SetPathfinderDebugOverlay": 1, "SetObstructionDebugOverlay": 1, "SetMotionDebugOverlay": 1, "SetRangeDebugOverlay": 1, }; GuiInterface.prototype.ScriptCall = function(player, name, args) { if (exposedFunctions[name]) return this[name](player, args); else throw new Error("Invalid GuiInterface Call name \""+name+"\""); }; Engine.RegisterComponentType(IID_GuiInterface, "GuiInterface", GuiInterface); Index: ps/trunk/binaries/data/mods/public/simulation/components/Identity.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 9348) +++ ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 9349) @@ -1,141 +1,143 @@ function Identity() {} Identity.prototype.Schema = "Specifies various names and values associated with the unit type, typically for GUI display to users." + "" + "hele" + "Infantry Spearman" + "Hoplite" + "units/hele_infantry_spearman.png" + "" + "" + "" + "gaia" + "cart" + "celt" + "hele" + "iber" + "pers" + "rome" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "Basic" + "Advanced" + "Elite" + "" + "" + "" + "" + "" + "" + "tokens" + "" + "" + "" + "" + "Unit" + "Infantry" + "Cavalry" + "Ranged" + "Mechanical" + "Ship" + "Siege" + "Super" + "Hero" + "Support" + "Animal" + "Organic" + "Structure" + "Civic" + "CivCentre" + "Economic" + "Defensive" + "Village" + "Town" + "City" + "ConquestCritical" + "Worker" + + "CitizenSoldier" + + "Trade" + "Bow" + // TODO: what are these used for? "Javelin" + "Spear" + "Sword" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; Identity.prototype.Init = function() { }; Identity.prototype.Serialize = null; // we have no dynamic state to save Identity.prototype.GetCiv = function() { return this.template.Civ; }; Identity.prototype.GetRank = function() { if (this.template.Rank) return this.template.Rank; return ""; }; Identity.prototype.GetClassesList = function() { if (this.template.Classes) { var string = this.template.Classes._string; return string.split(/\s+/); } else { return []; } }; Identity.prototype.HasClass = function(name) { return this.GetClassesList().indexOf(name) != -1; }; Engine.RegisterComponentType(IID_Identity, "Identity", Identity); Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml (revision 9348) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml (revision 9349) @@ -1,28 +1,29 @@ Trader Trade was a very important part of ancient civilisation - effective trading and control of trade routes equaled wealth. Trade took place by many forms from foot to caravans to merchant ships. One of the most notorious examples of the power of trade was the Silk Road. Trades resources between allied Markets. + Trade 50 50 100 5.0 8.0 5.0 8.0 60 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml (revision 9348) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml (revision 9349) @@ -1,105 +1,105 @@ Infantry - Infantry Organic + Infantry CitizenSoldier Organic Basic 600 9 50 0 0 0 100 1 1 1 1 2.0 1.0 0.5 0.5 0.5 2 1 10 1 10 1 100 0.2 1000 1.0 1.0 9.0 7.0 17.5 infantry 80 1.0 structures/{civ}_house structures/{civ}_mill structures/{civ}_farmstead structures/{civ}_field structures/{civ}_corral structures/{civ}_temple structures/{civ}_market structures/{civ}_civil_centre structures/{civ}_barracks structures/{civ}_dock structures/{civ}_scout_tower structures/{civ}_wall structures/{civ}_wall_tower structures/{civ}_wall_gate structures/{civ}_fortress voice/hellenes/civ/civ_male_ack.xml voice/hellenes/civ/civ_male_attack.xml voice/hellenes/civ/civ_male_ack.xml voice/hellenes/civ/civ_male_ack.xml actor/human/movement/walk.xml actor/human/movement/run.xml attack/weapon/sword.xml actor/human/death/death.xml resource/construction/con_wood.xml resource/foraging/forage_leaves.xml resource/farming/farm.xml resource/lumbering/lumbering.xml resource/mining/pickaxe.xml resource/mining/mining.xml actor/singlesteps/steps_gravel.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 9348) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 9349) @@ -1,84 +1,84 @@ Cavalry - Cavalry Organic + Cavalry CitizenSoldier Organic Basic 900 pitch 2 13 100 200 10 4 1 1 2.0 1.0 10 30 30 30 30 130 0.2 2000 0.0 0.0 2.0 8.8 26.4 600.0 5.0 7.5 100 voice/hellenes/civ/civ_male_ack.xml voice/hellenes/civ/civ_male_attack.xml voice/hellenes/civ/civ_male_ack.xml actor/mounted/movement/walk.xml actor/mounted/movement/walk.xml attack/weapon/sword.xml actor/fauna/death/death_horse.xml interface/alarm/alarm_create_cav.xml