Ticket #786: 786_walls_16apr12.patch
File 786_walls_16apr12.patch, 179.1 KB (added by , 12 years ago) |
---|
-
binaries/data/mods/public/gui/session/input.js
diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 23ed23a..8034091 100644
a b const ACTION_GARRISON = 1; 17 17 const ACTION_REPAIR = 2; 18 18 var preSelectedAction = ACTION_NONE; 19 19 20 var INPUT_NORMAL = 0; 21 var INPUT_SELECTING = 1; 22 var INPUT_BANDBOXING = 2; 23 var INPUT_BUILDING_PLACEMENT = 3; 24 var INPUT_BUILDING_CLICK = 4; 25 var INPUT_BUILDING_DRAG = 5; 26 var INPUT_BATCHTRAINING = 6; 27 var INPUT_PRESELECTEDACTION = 7; 20 const INPUT_NORMAL = 0; 21 const INPUT_SELECTING = 1; 22 const INPUT_BANDBOXING = 2; 23 const INPUT_BUILDING_PLACEMENT = 3; 24 const INPUT_BUILDING_CLICK = 4; 25 const INPUT_BUILDING_DRAG = 5; 26 const INPUT_BATCHTRAINING = 6; 27 const INPUT_PRESELECTEDACTION = 7; 28 const INPUT_BUILDING_WALL_CLICK = 8; 29 const INPUT_BUILDING_WALL_PATHING = 9; 28 30 29 31 var inputState = INPUT_NORMAL; 30 32 31 var defaultPlacementAngle = Math.PI*3/4; 32 var placementAngle = undefined; 33 var placementPosition = undefined; 34 var placementEntity = undefined; 33 const defaultPlacementAngle = Math.PI*3/4; 34 var placementSupport = new PlacementSupport(); 35 35 36 36 var mouseX = 0; 37 37 var mouseY = 0; … … function updateCursorAndTooltip() 88 88 function updateBuildingPlacementPreview() 89 89 { 90 90 // The preview should be recomputed every turn, so that it responds 91 // to obstructions/fog/etc moving underneath it 91 // to obstructions/fog/etc moving underneath it, or in the case of 92 // the wall previews, in response to new tower foundations getting 93 // constructed for it to snap to. 92 94 93 if (placement Entity && placementPosition)95 if (placementSupport.mode === "building") 94 96 { 95 return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { 96 "template": placementEntity, 97 "x": placementPosition.x, 98 "z": placementPosition.z, 99 "angle": placementAngle 100 }); 97 if (placementSupport.template && placementSupport.position) 98 { 99 return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { 100 "template": placementSupport.template, 101 "x": placementSupport.position.x, 102 "z": placementSupport.position.z, 103 "angle": placementSupport.angle, 104 }); 105 } 106 } 107 else if (placementSupport.mode === "wall") 108 { 109 if (placementSupport.wallSet && placementSupport.position) 110 { 111 // Fetch an updated list of snapping candidate entities 112 placementSupport.wallSnapEntities = Engine.PickSimilarFriendlyEntities( 113 placementSupport.wallSet.tower, 114 placementSupport.wallSnapEntitiesIncludeOffscreen, 115 true, // require exact template match 116 true // include foundations 117 ); 118 119 return Engine.GuiInterfaceCall("SetWallPlacementPreview", { 120 "wallSet": placementSupport.wallSet, 121 "start": placementSupport.position, 122 "end": placementSupport.wallEndPosition, 123 "snapEntities": placementSupport.wallSnapEntities, // snapping entities (towers) for starting a wall segment 124 }); 125 } 101 126 } 102 127 103 128 return false; 104 129 } 105 130 106 function resetPlacementEntity()107 {108 Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});109 placementEntity = undefined;110 placementPosition = undefined;111 placementAngle = undefined;112 }113 114 131 function findGatherType(gatherer, supply) 115 132 { 116 133 if (!gatherer || !supply) … … var dragStart; // used for remembering mouse coordinates at start of drag operat 434 451 435 452 function tryPlaceBuilding(queued) 436 453 { 454 if (placementSupport.mode !== "building") 455 { 456 error("[tryPlaceBuilding] Called while in '"+placementSupport.mode+"' placement mode instead of 'building'"); 457 return false; 458 } 459 437 460 var selection = g_Selection.toList(); 438 461 439 462 // Use the preview to check it's a valid build location … … function tryPlaceBuilding(queued) 447 470 // Start the construction 448 471 Engine.PostNetworkCommand({ 449 472 "type": "construct", 450 "template": placement Entity,451 "x": placement Position.x,452 "z": placement Position.z,453 "angle": placement Angle,473 "template": placementSupport.template, 474 "x": placementSupport.position.x, 475 "z": placementSupport.position.z, 476 "angle": placementSupport.angle, 454 477 "entities": selection, 455 478 "autorepair": true, 456 479 "autocontinue": true, … … function tryPlaceBuilding(queued) 459 482 Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] }); 460 483 461 484 if (!queued) 462 resetPlacementEntity(); 485 placementSupport.Reset(); 486 487 return true; 488 } 489 490 function tryPlaceWall() 491 { 492 if (placementSupport.mode !== "wall") 493 { 494 error("[tryPlaceWall] Called while in '" + placementSupport.mode + "' placement mode; expected 'wall' mode"); 495 return false; 496 } 497 498 var wallPlacementInfo = updateBuildingPlacementPreview(); // entities making up the wall (wall segments, towers, ...) 499 if (!(wallPlacementInfo === false || typeof(wallPlacementInfo) === "object")) 500 { 501 error("[tryPlaceWall] Unexpected return value from updateBuildingPlacementPreview: '" + uneval(placementInfo) + "'; expected either 'false' or 'object'"); 502 return false; 503 } 504 505 if (!wallPlacementInfo) 506 return false; 507 508 var selection = g_Selection.toList(); 509 var cmd = { 510 "type": "construct-wall", 511 "autorepair": true, 512 "autocontinue": true, 513 "queued": true, 514 "entities": selection, 515 "wallSet": placementSupport.wallSet, 516 "pieces": wallPlacementInfo.pieces, 517 "startSnappedEntity": wallPlacementInfo.startSnappedEnt, 518 "endSnappedEntity": wallPlacementInfo.endSnappedEnt, 519 }; 520 521 //warn("-------------"); 522 //for (var k in cmd) 523 // warn(" " + k + ": " + uneval(cmd[k])); 524 //warn("-------------"); 525 526 /*for each (var entInfo in wallPlacementInfo.pieces) 527 { 528 cmd.pieces.push({ 529 "template": entInfo.template, 530 "x": entInfo.x, 531 "z": entInfo.z, 532 "angle": entInfo.angle, 533 }); 534 }*/ 535 536 Engine.PostNetworkCommand(cmd); 537 Engine.GuiInterfaceCall("PlaySound", {"name": "order_repair", "entity": selection[0] }); 463 538 464 539 return true; 465 540 } … … function handleInputBeforeGui(ev, hoveredObject) 659 734 if (ev.button == SDL_BUTTON_RIGHT) 660 735 { 661 736 // Cancel building 662 resetPlacementEntity();737 placementSupport.Reset(); 663 738 inputState = INPUT_NORMAL; 664 739 return true; 665 740 } … … function handleInputBeforeGui(ev, hoveredObject) 667 742 } 668 743 break; 669 744 745 case INPUT_BUILDING_WALL_CLICK: 746 // User is mid-click in choosing a starting point for building a wall. The build process can still be cancelled at this point 747 // by right-clicking; releasing the left mouse button will 'register' the starting point and commence endpoint choosing mode. 748 switch (ev.type) 749 { 750 case "mousebuttonup": 751 if (ev.button === SDL_BUTTON_LEFT) 752 { 753 inputState = INPUT_BUILDING_WALL_PATHING; warn("-> INPUT_BUILDING_WALL_PATHING"); 754 return true; 755 } 756 break; 757 758 case "mousebuttondown": 759 if (ev.button == SDL_BUTTON_RIGHT) 760 { 761 // Cancel building 762 placementSupport.Reset(); 763 updateBuildingPlacementPreview(); 764 765 inputState = INPUT_NORMAL; warn("-> INPUT_NORMAL"); 766 return true; 767 } 768 break; 769 } 770 break; 771 772 case INPUT_BUILDING_WALL_PATHING: 773 // User has chosen a starting point for constructing the wall, and is now looking to set the endpoint. 774 // Right-clicking cancels wall building mode, left-clicking sets the endpoint and builds the wall and returns to 775 // normal input mode. Optionally, shift + left-clicking does not return to normal input, and instead allows the 776 // user to continue building walls. 777 switch (ev.type) 778 { 779 case "mousemotion": 780 placementSupport.wallEndPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); 781 782 // Update the building placement preview, and by extension, the list of snapping candidate entities for both (!) 783 // the ending point and the starting point to snap to. 784 // 785 // TODO: Note that here, we need to fetch all similar entities, including any offscreen ones, to support the case 786 // where the snap entity for the starting point has moved offscreen, or has been deleted/destroyed, or was a 787 // foundation and has been replaced with a completed entity since the user first chose it. Fetching all towers on 788 // the entire map instead of only the current screen might get expensive fast since walls all have a ton of towers 789 // in them. Might be useful to query only for entities within a certain range around the starting point and ending 790 // points. 791 792 placementSupport.wallSnapEntitiesIncludeOffscreen = true; 793 updateBuildingPlacementPreview(); // includes an update of the snap entity candidates 794 break; 795 796 case "mousebuttondown": 797 if (ev.button == SDL_BUTTON_LEFT) 798 { 799 if (tryPlaceWall()) 800 { 801 if (Engine.HotkeyIsPressed("session.queue")) 802 { 803 // continue building, just set a new starting position where we left off 804 placementSupport.position = placementSupport.wallEndPosition; 805 placementSupport.wallEndPosition = undefined; 806 807 inputState = INPUT_BUILDING_WALL_CLICK; warn("-> INPUT_BUILDING_WALL_CLICK"); 808 } 809 else 810 { 811 placementSupport.Reset(); 812 inputState = INPUT_NORMAL; warn("-> INPUT_NORMAL"); 813 } 814 } 815 else 816 { 817 // TODO: display some useful error message to the player 818 error("Cannot build wall here!"); 819 } 820 821 updateBuildingPlacementPreview(); 822 return true; 823 } 824 else if (ev.button == SDL_BUTTON_RIGHT) 825 { 826 // reset to normal input mode 827 placementSupport.Reset(); 828 updateBuildingPlacementPreview(); 829 830 inputState = INPUT_NORMAL; warn("-> INPUT_NORMAL"); 831 return true; 832 } 833 break; 834 } 835 break; 836 670 837 case INPUT_BUILDING_DRAG: 671 838 switch (ev.type) 672 839 { … … function handleInputBeforeGui(ev, hoveredObject) 678 845 { 679 846 // Rotate in the direction of the mouse 680 847 var target = Engine.GetTerrainAtPoint(ev.x, ev.y); 681 placement Angle = Math.atan2(target.x - placementPosition.x, target.z - placementPosition.z);848 placementSupport.angle = Math.atan2(target.x - placementSupport.position.x, target.z - placementSupport.position.z); 682 849 } 683 850 else 684 851 { 685 852 // If the mouse is near the center, snap back to the default orientation 686 placement Angle = defaultPlacementAngle;853 placementSupport.SetDefaultAngle(); 687 854 } 688 855 689 856 var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { 690 "template": placement Entity,691 "x": placement Position.x,692 "z": placement Position.z857 "template": placementSupport.template, 858 "x": placementSupport.position.x, 859 "z": placementSupport.position.z 693 860 }); 694 861 if (snapData) 695 862 { 696 placement Angle = snapData.angle;697 placement Position.x = snapData.x;698 placement Position.z = snapData.z;863 placementSupport.angle = snapData.angle; 864 placementSupport.position.x = snapData.x; 865 placementSupport.position.z = snapData.z; 699 866 } 700 867 701 868 updateBuildingPlacementPreview(); … … function handleInputBeforeGui(ev, hoveredObject) 725 892 if (ev.button == SDL_BUTTON_RIGHT) 726 893 { 727 894 // Cancel building 728 resetPlacementEntity();895 placementSupport.Reset(); 729 896 inputState = INPUT_NORMAL; 730 897 return true; 731 898 } … … function handleInputAfterGui(ev) 819 986 break; 820 987 } 821 988 break; 989 822 990 case INPUT_PRESELECTEDACTION: 823 991 switch (ev.type) 824 992 { … … function handleInputAfterGui(ev) 849 1017 } 850 1018 } 851 1019 break; 1020 852 1021 case INPUT_SELECTING: 853 1022 switch (ev.type) 854 1023 { … … function handleInputAfterGui(ev) 922 1091 } 923 1092 924 1093 // TODO: Should we handle "control all units" here as well? 925 ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank );1094 ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false); 926 1095 } 927 1096 else 928 1097 { … … function handleInputAfterGui(ev) 961 1130 switch (ev.type) 962 1131 { 963 1132 case "mousemotion": 964 placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); 965 var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { 966 "template": placementEntity, 967 "x": placementPosition.x, 968 "z": placementPosition.z 969 }); 970 if (snapData) 1133 1134 placementSupport.position = Engine.GetTerrainAtPoint(ev.x, ev.y); 1135 1136 if (placementSupport.mode === "wall") 971 1137 { 972 placementAngle = snapData.angle; 973 placementPosition.x = snapData.x; 974 placementPosition.z = snapData.z; 1138 // Including only the on-screen towers in the next snap candidate list is sufficient here, since the user is 1139 // still selecting a starting point (which must necessarily be on-screen). (The update itself happens in the 1140 // call to updateBuildingPlacementPreview below). 1141 placementSupport.wallSnapEntitiesIncludeOffscreen = false; 1142 } 1143 else 1144 { 1145 var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { 1146 "template": placementSupport.template, 1147 "x": placementSupport.position.x, 1148 "z": placementSupport.position.z, 1149 }); 1150 if (snapData) 1151 { 1152 placementSupport.angle = snapData.angle; 1153 placementSupport.position.x = snapData.x; 1154 placementSupport.position.z = snapData.z; 1155 } 975 1156 } 976 1157 977 updateBuildingPlacementPreview(); 978 1158 updateBuildingPlacementPreview(); // includes an update of the snap entity candidates 979 1159 return false; // continue processing mouse motion 980 1160 981 1161 case "mousebuttondown": 982 1162 if (ev.button == SDL_BUTTON_LEFT) 983 1163 { 984 placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); 985 dragStart = [ ev.x, ev.y ]; 986 inputState = INPUT_BUILDING_CLICK; 1164 if (placementSupport.mode === "wall") 1165 { 1166 var validPlacement = updateBuildingPlacementPreview(); 1167 if (validPlacement !== false) 1168 { 1169 inputState = INPUT_BUILDING_WALL_CLICK; 1170 } 1171 else 1172 { 1173 // TODO: add a user-friendly notification 1174 error("Not a valid starting location!"); 1175 } 1176 } 1177 else 1178 { 1179 placementSupport.position = Engine.GetTerrainAtPoint(ev.x, ev.y); 1180 dragStart = [ ev.x, ev.y ]; 1181 inputState = INPUT_BUILDING_CLICK; 1182 } 987 1183 return true; 988 1184 } 989 1185 else if (ev.button == SDL_BUTTON_RIGHT) 990 1186 { 991 1187 // Cancel building 992 resetPlacementEntity();1188 placementSupport.Reset(); 993 1189 inputState = INPUT_NORMAL; 994 1190 return true; 995 1191 } … … function handleInputAfterGui(ev) 1002 1198 switch (ev.hotkey) 1003 1199 { 1004 1200 case "session.rotate.cw": 1005 placement Angle += rotation_step;1201 placementSupport.angle += rotation_step; 1006 1202 updateBuildingPlacementPreview(); 1007 1203 break; 1008 1204 case "session.rotate.ccw": 1009 placement Angle -= rotation_step;1205 placementSupport.angle -= rotation_step; 1010 1206 updateBuildingPlacementPreview(); 1011 1207 break; 1012 1208 } … … function handleMinimapEvent(target) 1144 1340 } 1145 1341 1146 1342 // Called by GUI when user clicks construction button 1147 function startBuildingPlacement(buildEntType) 1343 // @param buildTemplate Template name of the entity the user wants to build 1344 function startBuildingPlacement(buildTemplate) 1148 1345 { 1149 placementEntity = buildEntType; 1150 placementAngle = defaultPlacementAngle; 1151 inputState = INPUT_BUILDING_PLACEMENT; 1346 // TODO: we should clear any highlight selection rings here. If the mouse was over an entity before going onto the GUI 1347 // to start building a structure, then the highlight selection rings are kept during the construction of the building. 1348 // Gives the impression that somehow the hovered-over entity has something to do with the building you're constructing. 1349 1350 placementSupport.SetDefaultAngle(); 1351 1352 // find out if we're building a wall, and change the entity appropriately if so 1353 var templateData = GetTemplateData(buildTemplate); 1354 if (templateData.wallSet) 1355 { 1356 placementSupport.mode = "wall"; 1357 placementSupport.wallSet = templateData.wallSet; 1358 inputState = INPUT_BUILDING_PLACEMENT; 1359 } 1360 else 1361 { 1362 placementSupport.mode = "building"; 1363 placementSupport.template = buildTemplate; 1364 inputState = INPUT_BUILDING_PLACEMENT; 1365 } 1152 1366 } 1153 1367 1154 1368 // Called by GUI when user changes preferred trading goods -
new file inaries/data/mods/public/gui/session/placement.js
diff --git a/binaries/data/mods/public/gui/session/placement.js b/binaries/data/mods/public/gui/session/placement.js new file mode 100644 index 0000000..29d5d04
- + 1 function PlacementSupport() 2 { 3 this.Reset(); 4 } 5 6 PlacementSupport.DEFAULT_ANGLE = Math.PI*3/4; 7 8 /** 9 * Resets the building placement support state. Use this to cancel construction of an entity. 10 */ 11 PlacementSupport.prototype.Reset = function() 12 { 13 this.mode = null; 14 this.position = null; 15 this.template = null; 16 this.wallSet = null; // maps types of wall pieces ("tower", "long", "short", ...) to template names 17 this.wallSnapEntities = null; // list of candidate entities to snap the starting and (!) ending positions to when building walls 18 this.wallEndPosition = null; 19 this.wallSnapEntitiesIncludeOffscreen = false; // should the next update of the snap candidate list include offscreen towers? 20 21 this.SetDefaultAngle(); 22 23 warn("PlacementSupport::Reset"); 24 Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""}); 25 Engine.GuiInterfaceCall("SetWallPlacementPreview", {"wallSet": null}); 26 }; 27 28 PlacementSupport.prototype.SetDefaultAngle = function() 29 { 30 this.angle = PlacementSupport.DEFAULT_ANGLE; 31 }; 32 No newline at end of file -
binaries/data/mods/public/gui/session/session.js
diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index 8ecf776..c981c18 100644
a b function getSavedGameData() 169 169 } 170 170 171 171 var lastTickTime = new Date; 172 173 /** 174 * Called every frame. 175 */ 172 176 function onTick() 173 177 { 174 178 var now = new Date; … … function onTick() 187 191 188 192 updateCursorAndTooltip(); 189 193 190 // If the selection changed, we need to regenerate the sim display 194 // If the selection changed, we need to regenerate the sim display (the display depends on both the 195 // simulation state and the current selection). 191 196 if (g_Selection.dirty) 192 197 { 193 198 onSimulationUpdate(); … … function checkPlayerState() 252 257 } 253 258 } 254 259 260 /** 261 * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation 262 * update (see session.xml), or from onTick when the selection has changed. 263 */ 255 264 function onSimulationUpdate() 256 265 { 257 266 g_Selection.dirty = false; -
binaries/data/mods/public/gui/session/session.xml
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index 5de7e23..f5a37f3 100644
a b 9 9 <script file="gui/common/timer.js"/> 10 10 <script file="gui/session/session.js"/> 11 11 <script file="gui/session/selection.js"/> 12 <script file="gui/session/placement.js"/> 12 13 <script file="gui/session/input.js"/> 13 14 <script file="gui/session/menu.js"/> 14 15 <script file="gui/session/selection_details.js"/> -
binaries/data/mods/public/gui/session/unit_commands.js
diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js index 8e8e2da..0d84dde 100644
a b function layoutButtonRow(rowNumber, guiName, buttonSideLength, buttonSpacer, sta 120 120 } 121 121 } 122 122 123 // Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay) 123 /** 124 * Helper function for updateUnitCommands; sets up "unit panels" (i.e. panels with rows of icons) for the currently selected 125 * unit. 126 * 127 * @param guiName Short identifier string of this panel; see constants defined at the top of this file. 128 * @param usedPanels Output object; usedPanels[guiName] will be set to 1 to indicate that this panel was used during this 129 * run of updateUnitCommands and should not be hidden. TODO: why is this done this way instead of having 130 * updateUnitCommands keep track of this? 131 * @param unitEntState Entity state of the (first) selected unit. 132 * @param items Panel-specific data to construct the icons with. 133 * @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument. 134 */ 124 135 function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 125 136 { 126 137 usedPanels[guiName] = 1; … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 183 194 for (i = 0; i < numberOfItems; i++) 184 195 { 185 196 var item = items[i]; 186 var entType = ((guiName == "Queue") ? item.template : item);197 var entType = ((guiName == "Queue") ? item.template : item); 187 198 var template; 188 199 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance") 189 200 { … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 236 247 var [batchSize, batchIncrement] = getTrainingQueueBatchStatus(unitEntState.id, entType); 237 248 var trainNum = batchSize ? batchSize+batchIncrement : batchIncrement; 238 249 239 tooltip += "\n" + getEntityCost (template);250 tooltip += "\n" + getEntityCostTooltip(template); 240 251 241 252 if (template.health) 242 253 tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + template.health; … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 256 267 if (template.tooltip) 257 268 tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]"; 258 269 259 tooltip += "\n" + getEntityCost(template); 270 tooltip += "\n" + getEntityCostTooltip(template); // see utility_functions.js 271 tooltip += getPopulationBonusTooltip(template); // see utility_functions.js 260 272 261 tooltip += getPopulationBonus(template);262 273 if (template.health) 263 274 tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + template.health; 264 275 … … function setupUnitBarterPanel(unitEntState) 483 494 } 484 495 } 485 496 486 // Updates right Unit Commands Panel - runs in the main session loop via updateSelectionDetails() 497 /** 498 * Updates the right hand side "Unit Commands" panel. Runs in the main session loop via updateSelectionDetails(). 499 * Delegates to setupUnitPanel to set up individual subpanels, appropriately activated depending on the selected 500 * unit's state. 501 * 502 * @param entState Entity state of the (first) selected unit. 503 * @param supplementalDetailsPanel Reference to the "supplementalSelectionDetails" GUI Object 504 * @param selection Array of currently selected entity IDs. 505 */ 487 506 function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection) 488 507 { 489 508 // Panels that are active -
binaries/data/mods/public/gui/session/utility_functions.js
diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js index 0d95a70..bbc81f7 100644
a b function getEntityCommandsList(entState) 193 193 return commands; 194 194 } 195 195 196 function getEntityCost(template) 196 /** 197 * Helper function for getEntityCostTooltip. 198 */ 199 function getEntityCostComponentsTooltipString(template) 197 200 { 198 var cost = ""; 199 if (template.cost) 201 var costs = []; 202 if (template.cost.food) costs.push(template.cost.food + " [font=\"serif-12\"]Food[/font]"); 203 if (template.cost.wood) costs.push(template.cost.wood + " [font=\"serif-12\"]Wood[/font]"); 204 if (template.cost.metal) costs.push(template.cost.metal + " [font=\"serif-12\"]Metal[/font]"); 205 if (template.cost.stone) costs.push(template.cost.stone + " [font=\"serif-12\"]Stone[/font]"); 206 if (template.cost.population) costs.push(template.cost.population + " [font=\"serif-12\"]Population[/font]"); 207 return costs; 208 } 209 210 /** 211 * Returns the cost information to display in the specified entity's construction button tooltip. 212 */ 213 function getEntityCostTooltip(template) 214 { 215 var cost = "[font=\"serif-bold-13\"]Costs:[/font] "; 216 217 // Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of 218 // their own; the individual wall pieces within it do. 219 if (template.wallSet) 220 { 221 var templateLong = GetTemplateData(template.wallSet.long); 222 var templateMedium = GetTemplateData(template.wallSet.medium); 223 var templateShort = GetTemplateData(template.wallSet.short); 224 var templateTower = GetTemplateData(template.wallSet.tower); 225 226 // TODO: the costs of the wall segments should be the same, and for now we will assume they are (ideally we 227 // should take the average here or something). 228 var wallCosts = getEntityCostComponentsTooltipString(templateLong); 229 var towerCosts = getEntityCostComponentsTooltipString(templateTower); 230 231 cost += "\n"; 232 cost += " Walls: " + wallCosts.join(", ") + "\n"; 233 cost += " Towers: " + towerCosts.join(", "); 234 } 235 else if (template.cost) 200 236 { 201 var costs = []; 202 if (template.cost.food) costs.push(template.cost.food + " [font=\"serif-12\"]Food[/font]"); 203 if (template.cost.wood) costs.push(template.cost.wood + " [font=\"serif-12\"]Wood[/font]"); 204 if (template.cost.metal) costs.push(template.cost.metal + " [font=\"serif-12\"]Metal[/font]"); 205 if (template.cost.stone) costs.push(template.cost.stone + " [font=\"serif-12\"]Stone[/font]"); 206 if (template.cost.population) costs.push(template.cost.population + " [font=\"serif-12\"]Population[/font]"); 207 208 cost += "[font=\"serif-bold-13\"]Costs:[/font] " + costs.join(", "); 237 var costs = getEntityCostComponentsTooltipString(template); 238 cost += costs.join(", "); 209 239 } 240 else 241 { 242 cost = ""; // cleaner than duplicating the serif-bold-13 stuff 243 } 244 210 245 return cost; 211 246 } 212 247 213 function getPopulationBonus(template) 248 /** 249 * Returns the population bonus information to display in the specified entity's construction button tooltip. 250 */ 251 function getPopulationBonusTooltip(template) 214 252 { 215 253 var popBonus = ""; 216 if (template.cost .populationBonus)254 if (template.cost && template.cost.populationBonus) 217 255 popBonus = "\n[font=\"serif-bold-13\"]Population Bonus:[/font] " + template.cost.populationBonus; 218 256 return popBonus; 219 257 } -
new file inaries/data/mods/public/maps/scenarios/WallTest.xml
diff --git a/binaries/data/mods/public/maps/scenarios/WallTest.pmp b/binaries/data/mods/public/maps/scenarios/WallTest.pmp new file mode 100644 index 0000000..bf23296 Binary files /dev/null and b/binaries/data/mods/public/maps/scenarios/WallTest.pmp differ diff --git a/binaries/data/mods/public/maps/scenarios/WallTest.xml b/binaries/data/mods/public/maps/scenarios/WallTest.xml new file mode 100644 index 0000000..44428a8
- + 1 <?xml version="1.0" encoding="UTF-8"?> 2 3 <Scenario version="5"> 4 <Environment> 5 <LightingModel>standard</LightingModel> 6 <SkySet>default</SkySet> 7 <SunColour r="0.74902" g="0.74902" b="0.74902"/> 8 <SunElevation angle="0.785398"/> 9 <SunRotation angle="-0.785396"/> 10 <TerrainAmbientColour r="0.501961" g="0.501961" b="0.501961"/> 11 <UnitsAmbientColour r="0.501961" g="0.501961" b="0.501961"/> 12 <Water> 13 <WaterBody> 14 <Type>default</Type> 15 <Colour r="0.294118" g="0.34902" b="0.694118"/> 16 <Height>5</Height> 17 <Shininess>150</Shininess> 18 <Waviness>8</Waviness> 19 <Murkiness>0.45</Murkiness> 20 <Tint r="0.27451" g="0.294118" b="0.584314"/> 21 <ReflectionTint r="0.27451" g="0.294118" b="0.584314"/> 22 <ReflectionTintStrength>0</ReflectionTintStrength> 23 </WaterBody> 24 </Water> 25 </Environment> 26 <Camera> 27 <Position x="292.862" y="79.7402" z="170.777"/> 28 <Rotation angle="0"/> 29 <Declination angle="0.610865"/> 30 </Camera> 31 <ScriptSettings><![CDATA[ 32 { 33 "CircularMap": true, 34 "Description": "Give an interesting description of your map.", 35 "GameType": "conquest", 36 "Keywords": [], 37 "LockTeams": false, 38 "Name": "WallTest", 39 "PlayerData": [ 40 { 41 "AI": "", 42 "Civ": "hele", 43 "Colour": { 44 "b": 200, 45 "g": 46, 46 "r": 46 47 }, 48 "Name": "Player 1", 49 "PopulationLimit": 100000, 50 "Resources": { 51 "food": 100000, 52 "metal": 100000, 53 "stone": 100000, 54 "wood": 100000 55 }, 56 "StartingCamera": { 57 "Position": { 58 "x": 292.862, 59 "y": 22.3825, 60 "z": 252.692 61 }, 62 "Rotation": { 63 "x": 35, 64 "y": 0, 65 "z": 0 66 } 67 }, 68 "Team": -1 69 }, 70 { 71 "AI": "qbot", 72 "Civ": "hele", 73 "Colour": { 74 "b": 20, 75 "g": 20, 76 "r": 150 77 }, 78 "Name": "Player 2", 79 "PopulationLimit": 2, 80 "Resources": { 81 "food": 5000, 82 "metal": 5000, 83 "stone": 5000, 84 "wood": 5000 85 }, 86 "Team": -1 87 } 88 ], 89 "RevealMap": false 90 } 91 ]]></ScriptSettings> 92 <Entities> 93 <Entity uid="11"> 94 <Template>structures/celt_civil_centre</Template> 95 <Player>1</Player> 96 <Position x="286.75593" z="268.53187"/> 97 <Orientation y="2.35621"/> 98 </Entity> 99 <Entity uid="12"> 100 <Template>structures/pers_civil_centre</Template> 101 <Player>2</Player> 102 <Position x="751.73603" z="789.12446"/> 103 <Orientation y="2.35621"/> 104 </Entity> 105 <Entity uid="13"> 106 <Template>other/palisades_rocks_tower</Template> 107 <Player>1</Player> 108 <Position x="280.52155" z="152.9048"/> 109 <Orientation y="2.35621"/> 110 </Entity> 111 <Entity uid="14"> 112 <Template>other/palisades_rocks_tower</Template> 113 <Player>1</Player> 114 <Position x="405.53681" z="237.12357"/> 115 <Orientation y="2.35621"/> 116 </Entity> 117 <Entity uid="15"> 118 <Template>other/palisades_rocks_tower</Template> 119 <Player>1</Player> 120 <Position x="273.05485" z="368.58619"/> 121 <Orientation y="2.35621"/> 122 </Entity> 123 <Entity uid="16"> 124 <Template>other/palisades_rocks_tower</Template> 125 <Player>1</Player> 126 <Position x="162.9825" z="248.80494"/> 127 <Orientation y="2.35621"/> 128 </Entity> 129 <Entity uid="56"> 130 <Template>units/celt_fanatic</Template> 131 <Player>1</Player> 132 <Position x="297.80228" z="238.4332"/> 133 <Orientation y="2.35621"/> 134 </Entity> 135 <Entity uid="57"> 136 <Template>units/celt_fanatic</Template> 137 <Player>1</Player> 138 <Position x="300.73261" z="240.50628"/> 139 <Orientation y="2.35621"/> 140 </Entity> 141 <Entity uid="58"> 142 <Template>units/celt_fanatic</Template> 143 <Player>1</Player> 144 <Position x="304.17972" z="243.5565"/> 145 <Orientation y="2.35621"/> 146 </Entity> 147 <Entity uid="59"> 148 <Template>units/celt_fanatic</Template> 149 <Player>1</Player> 150 <Position x="308.13715" z="246.98673"/> 151 <Orientation y="2.35621"/> 152 </Entity> 153 <Entity uid="60"> 154 <Template>units/celt_fanatic</Template> 155 <Player>1</Player> 156 <Position x="312.3144" z="251.31986"/> 157 <Orientation y="2.35621"/> 158 </Entity> 159 <Entity uid="61"> 160 <Template>units/celt_fanatic</Template> 161 <Player>1</Player> 162 <Position x="317.41407" z="256.17164"/> 163 <Orientation y="2.35621"/> 164 </Entity> 165 </Entities> 166 <Paths/> 167 </Scenario> 168 No newline at end of file -
binaries/data/mods/public/simulation/components/BuildRestrictions.js
diff --git a/binaries/data/mods/public/simulation/components/BuildRestrictions.js b/binaries/data/mods/public/simulation/components/BuildRestrictions.js index b9d1e87..b50999c 100644
a b BuildRestrictions.prototype.Init = function() 71 71 this.territories = this.template.Territory.split(/\s+/); 72 72 }; 73 73 74 /** 75 * Returns true iff this entity can be built at its current position. 76 */ 74 77 BuildRestrictions.prototype.CheckPlacement = function(player) 75 78 { 76 79 // TODO: Return error code for invalid placement, which can be handled by the UI -
binaries/data/mods/public/simulation/components/Foundation.js
diff --git a/binaries/data/mods/public/simulation/components/Foundation.js b/binaries/data/mods/public/simulation/components/Foundation.js index 14029da..f63a1e3 100644
a b Foundation.prototype.Build = function(builderEnt, work) 187 187 cmpBuildingPosition.SetXZRotation(rot.x, rot.z); 188 188 // TODO: should add a ICmpPosition::CopyFrom() instead of all this 189 189 190 // ---------------------------------------------------------------------- 191 190 192 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 191 193 var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership); 192 194 cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner()); 193 195 196 // ---------------------------------------------------------------------- 197 198 // Copy over the obstruction control group IDs from the foundation entities. This is needed to ensure that when a foundation 199 // is completed and replaced by a new entity, it remains in the same control group(s) as any other foundation entities that 200 // may surround it. This is the mechanism that is used to e.g. enable wall pieces to be built closely together, ignoring their 201 // mutual obstruction shapes (since they would otherwise be prevented from being built so closely together). If the control 202 // groups are not copied over, the new entity will default to a new control group containing only itself, and will hence block 203 // construction of any surrounding foundations that it was previously in the same control group with. 204 205 // Note that this will result in the completed building entities having control group IDs that equal entity IDs of old (and soon 206 // to be deleted) foundation entities. This should not have any consequences, however, since the control group IDs are only meant 207 // to be unique identifiers, which is still true when reusing the old ones. 208 209 var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); 210 var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction); 211 cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup()); 212 cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2()); 213 214 // ---------------------------------------------------------------------- 215 194 216 var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); 195 217 cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(); 196 218 197 219 var cmpIdentity = Engine.QueryInterface(building, IID_Identity); 198 if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1) cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter(); 220 if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1) 221 cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter(); 199 222 200 223 var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 201 224 var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health); … … Foundation.prototype.Build = function(builderEnt, work) 203 226 204 227 PlaySound("constructed", building); 205 228 206 Engine.PostMessage(this.entity, MT_ConstructionFinished, 207 { "entity": this.entity, "newentity": building }); 208 229 Engine.PostMessage(this.entity, MT_ConstructionFinished, { "entity": this.entity, "newentity": building }); 209 230 Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: building }); 210 231 211 232 Engine.DestroyEntity(this.entity); -
binaries/data/mods/public/simulation/components/GuiInterface.js
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 2c4a82f..47058e7 100644
a b GuiInterface.prototype.Deserialize = function(obj) 18 18 GuiInterface.prototype.Init = function() 19 19 { 20 20 this.placementEntity = undefined; // = undefined or [templateName, entityID] 21 this.placementWallEntities = undefined; 22 this.placementWallLastAngle = 0; 21 23 this.rallyPoints = undefined; 22 24 this.notifications = []; 23 25 this.renamedEntities = []; … … GuiInterface.prototype.GetEntityState = function(player, ent) 131 133 var ret = { 132 134 "id": ent, 133 135 "template": template 134 } 136 }; 135 137 136 138 var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); 137 139 if (cmpIdentity) … … GuiInterface.prototype.GetEntityState = function(player, ent) 202 204 }; 203 205 } 204 206 207 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); 208 if (cmpObstruction) 209 { 210 ret.obstruction = { 211 "controlGroup": cmpObstruction.GetControlGroup(), 212 "controlGroup2": cmpObstruction.GetControlGroup2(), 213 }; 214 } 215 205 216 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 206 217 if (cmpOwnership) 207 218 { … … GuiInterface.prototype.GetTemplateData = function(player, name) 311 322 } 312 323 } 313 324 325 if (template.BuildRestrictions) 326 { 327 // required properties 328 ret.buildRestrictions = { 329 "placementType": template.BuildRestrictions.PlacementType, 330 "territory": template.BuildRestrictions.Territory, 331 "category": template.BuildRestrictions.Category, 332 }; 333 334 // optional properties 335 if (template.BuildRestrictions.Distance) 336 { 337 ret.buildRestrictions.distance = { 338 "fromCategory": template.BuildRestrictions.Distance.FromCategory, 339 }; 340 if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance; 341 if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance; 342 } 343 } 344 314 345 if (template.Cost) 315 346 { 316 347 ret.cost = {}; … … GuiInterface.prototype.GetTemplateData = function(player, name) 322 353 if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus; 323 354 } 324 355 356 if (template.Footprint) 357 { 358 ret.footprint = {"height": template.Footprint.Height}; 359 360 if (template.Footprint.Square) 361 ret.footprint.square = {"width": +template.Footprint.Square["@width"], "depth": +template.Footprint.Square["@depth"]}; 362 else if (template.Footprint.Circle) 363 ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]}; 364 else 365 warn("[GetTemplateData] Unrecognized Footprint type"); 366 } 367 368 if (template.Obstruction) 369 { 370 ret.obstruction = { 371 "active": ("" + template.Obstruction.Active == "true"), 372 "blockMovement": ("" + template.Obstruction.BlockMovement == "true"), 373 "blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"), 374 "blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"), 375 "blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"), 376 "disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"), 377 "disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"), 378 "shape": {} 379 }; 380 381 if (template.Obstruction.Static) 382 { 383 ret.obstruction.shape.type = "static"; 384 ret.obstruction.shape.width = +template.Obstruction.Static["@width"]; 385 ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"]; 386 } 387 else 388 { 389 ret.obstruction.shape.type = "unit"; 390 ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"]; 391 } 392 } 393 325 394 if (template.Health) 326 395 { 327 396 ret.health = +template.Health.Max; … … GuiInterface.prototype.GetTemplateData = function(player, name) 346 415 if (template.UnitMotion.Run) ret.speed.run = +template.UnitMotion.Run.Speed; 347 416 } 348 417 418 if (template.WallSet) 419 { 420 ret.wallSet = { 421 "long": template.WallSet.WallLong, 422 "medium": template.WallSet.WallMedium, 423 "short": template.WallSet.WallShort, 424 "tower": template.WallSet.Tower, 425 "gate": template.WallSet.Gate, 426 }; 427 } 428 429 if (template.WallPiece) 430 { 431 ret.wallPiece = {"length": +template.WallPiece.Length}; 432 } 433 349 434 return ret; 350 435 }; 351 436 … … GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) 517 602 * Display the building placement preview. 518 603 * cmd.template is the name of the entity template, or "" to disable the preview. 519 604 * cmd.x, cmd.z, cmd.angle give the location. 605 * 520 606 * Returns true if the placement is okay (everything is valid and the entity is not obstructed by others). 521 607 */ 522 608 GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd) … … GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd) 585 671 return false; 586 672 }; 587 673 674 /** 675 * Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not 676 * specified. Returns an object with information about the list of entities that need to be newly constructed to complete 677 * at least a part of the wall, or false if there are entities required to build at least part of the wall but none of 678 * them can be validly constructed. 679 * 680 * It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one 681 * another depending on things like snapping and whether some of the entities inside them can be validly positioned. 682 * We have: 683 * - The list of entities that previews the wall. This list is usually equal to the entities required to construct the 684 * entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities 685 * to preview the completed tower on top of its foundation. 686 * 687 * - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether 688 * any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing 689 * towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we 690 * snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly 691 * constructed. 692 * 693 * - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same 694 * as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens 695 * e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly 696 * constructed but come after said first invalid entity are also truncated away. 697 * 698 * With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there 699 * were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in 700 * case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset 701 * argument (see below). Otherwise, it will return an object with the following information: 702 * 703 * result: { 704 * 'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers. 705 * 'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this 706 * can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side 707 * but the wall construction was truncated before we could reach it, it won't be set here. Currently only 708 * supports towers. 709 * 'pieces': Array with the following data for each of the entities in the third list: 710 * [{ 711 * 'template': Template name of the entity. 712 * 'x': X coordinate of the entity's position. 713 * 'z': Z coordinate of the entity's position. 714 * 'angle': Rotation around the Y axis of the entity (in radians). 715 * }, 716 * ...] 717 * } 718 * 719 * @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview. 720 * @param cmd.start Starting point of the wall segment being created. 721 * @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only 722 * the starting point of the wall is available at this time (e.g. while the player is still in the process 723 * of picking a starting point), and that therefore only the first entity in the wall (a tower) should be 724 * previewed. 725 * @param cmd.snapEntities List of candidate entities to snap the start and ending positions to. 726 */ 727 GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) 728 { 729 var wallSet = cmd.wallSet; 730 731 var start = { 732 "pos": cmd.start, 733 "angle": 0, 734 "snapped": false, // did the start position snap to anything? 735 "snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID 736 }; 737 738 var end = { 739 "pos": cmd.end, 740 "angle": 0, 741 "snapped": false, // did the start position snap to anything? 742 "snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID 743 }; 744 745 // -------------------------------------------------------------------------------- 746 // do some entity cache management and check for snapping 747 748 if (!this.placementWallEntities) 749 this.placementWallEntities = {}; 750 751 if (!wallSet) 752 { 753 // we're clearing the preview, clear the entity cache and bail 754 var numCleared = 0; 755 for (var tpl in this.placementWallEntities) 756 { 757 for each (var ent in this.placementWallEntities[tpl].entities) 758 Engine.DestroyEntity(ent); 759 760 this.placementWallEntities[tpl].numUsed = 0; 761 this.placementWallEntities[tpl].entities = []; 762 // keep template data around 763 } 764 765 return false; 766 } 767 else 768 { 769 // Move all existing cached entities outside of the world and reset their use count 770 for (var tpl in this.placementWallEntities) 771 { 772 for each (var ent in this.placementWallEntities[tpl].entities) 773 { 774 var pos = Engine.QueryInterface(ent, IID_Position); 775 if (pos) 776 pos.MoveOutOfWorld(); 777 } 778 779 this.placementWallEntities[tpl].numUsed = 0; 780 } 781 782 // Create cache entries for templates we haven't seen before 783 for each (var tpl in wallSet) 784 { 785 if (!(tpl in this.placementWallEntities)) 786 { 787 //warn("[SetWallPlacementPreview] Initializing wall data for '" + tpl + "'"); 788 this.placementWallEntities[tpl] = { 789 "numUsed": 0, 790 "entities": [], 791 "templateData": this.GetTemplateData(player, tpl), 792 }; 793 794 // ensure that the loaded template data contains a wallPiece component 795 if (!this.placementWallEntities[tpl].templateData.wallPiece) 796 { 797 error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'"); 798 return false; 799 } 800 } 801 } 802 } 803 804 805 // prevent division by zero errors further on if the start and end positions are the same 806 if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z)) 807 end.pos = undefined; 808 809 // See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list 810 // of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping 811 // data (if any) to determine whether it snapped to an entity, and to which one (see GetFoundationSnapData). 812 if (cmd.snapEntities) 813 { 814 var snapRadius = this.placementWallEntities[wallSet.tower].templateData.wallPiece.length * 0.5; // determined through trial and error 815 var startSnapData = this.GetFoundationSnapData(player, { 816 "x": start.pos.x, 817 "z": start.pos.z, 818 "template": wallSet.tower, 819 "snapEntities": cmd.snapEntities, 820 "snapRadius": snapRadius, 821 }); 822 823 if (startSnapData) 824 { 825 start.pos.x = startSnapData.x; 826 start.pos.z = startSnapData.z; 827 start.angle = startSnapData.angle; 828 start.snapped = true; 829 830 if (startSnapData.ent) 831 start.snappedEnt = startSnapData.ent; 832 } 833 834 if (end.pos) 835 { 836 var endSnapData = this.GetFoundationSnapData(player, { 837 "x": end.pos.x, 838 "z": end.pos.z, 839 "template": wallSet.tower, 840 "snapEntities": cmd.snapEntities, 841 "snapRadius": snapRadius, 842 }); 843 844 if (endSnapData) 845 { 846 end.pos.x = endSnapData.x; 847 end.pos.z = endSnapData.z; 848 end.angle = endSnapData.angle; 849 end.snapped = true; 850 851 if (endSnapData.ent) 852 end.snappedEnt = endSnapData.ent; 853 } 854 } 855 } 856 857 // clear the single-building preview entity (we'll be rolling our own) 858 this.SetBuildingPlacementPreview(player, {"template": ""}); 859 860 // -------------------------------------------------------------------------------- 861 // calculate wall placement and position preview entities 862 863 var result = {"pieces": []}; 864 865 var previewEntities = []; 866 if (end.pos) 867 previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js 868 869 // For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would 870 // otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of 871 // an issue, because all preview entities have their obstruction components deactivated, meaning that their 872 // obstruction shapes do not register in the simulation and hence cannot affect it. This means that the preview 873 // entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces. 874 875 // Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION 876 // flag set), which is what we want. The only exception to this is when snapping to existing towers (or 877 // foundations thereof); the wall segments that connect up to these will be found to be obstructed by the 878 // existing tower/foundation and be shaded red to indicate that they cannot be placed there. To prevent this, 879 // we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so 880 // that they are free from mutual obstruction (per definition of obstruction control groups). This is done by 881 // assigning them an extra "controlGroup" field, which we'll then set during the placement loop below. 882 883 // Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully 884 // constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed 885 // by the foundation it snaps to. 886 887 if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY) 888 { 889 var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction); 890 if (previewEntities.length > 0 && startEntObstruction) 891 previewEntities[0].controlGroup = startEntObstruction.GetControlGroup(); 892 893 // if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group 894 var startEntState = this.GetEntityState(player, start.snappedEnt); 895 if (startEntState.foundation) 896 { 897 var cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position); 898 if (cmpPosition) 899 { 900 previewEntities.unshift({ 901 "template": wallSet.tower, 902 "pos": start.pos, 903 "angle": cmpPosition.GetRotation().y, 904 "controlGroup": (startEntObstruction ? startEntObstruction.GetControlGroup() : undefined), 905 "excludeFromResult": true, 906 }); 907 } 908 } 909 } 910 else 911 { 912 // Didn't snap to an existing entity, add the starting tower manually. Reuse the placement angle that was last 913 // seen on a validly positioned wall piece, for better visual consistency and to prevent odd-looking jumps in 914 // rotation when shift-clicking to build a wall. 915 916 // (Consider what happens if we used a constant of, say, 0 instead. Issuing the build command for a wall is 917 // asynchronous, so when the preview updates after shift-clicking, the foundation is typically not there yet in 918 // the simulation. This means they cannot possibly be picked in the list of candidate entities for snapping, and 919 // so we hit this case and rotate the preview to 0 radians. Then, after one or two simulation updates or so, the 920 // foundation registers and onSimulationUpdate in session.js updates the preview again. It first grabs a new list 921 // of snapping candidates, which this time does contain the new foundations; so we snap to the entity, and rotate 922 // the preview back to the foundation's angle. The result is a noticeable rotation to 0 and back, which is 923 // undesirable. So, for a split second there until the simulation updates, we fake it by reusing the last angle, 924 // and hope the player doesn't notice). 925 previewEntities.unshift({ 926 "template": wallSet.tower, 927 "pos": start.pos, 928 "angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle) 929 }); 930 } 931 932 if (end.pos) 933 { 934 // Analogous to the starting side case above 935 if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY) 936 { 937 var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction); 938 939 // TODO: technically it's possible (but highly unlikely) for the last entity in previewEntities to be the same as the first, 940 // i.e. the same wall piece snapping to both a starting and an ending tower. For this to happen, the wall piece would have to 941 // be exactly as long as the distance between start.pos and end.pos, but it's possible, so we should probably turn 942 // '.controlGroup' into '.controlGroups' and append to it, and assign them to the primary and secondary control groups if there 943 // are two (there can only be 0, 1 or 2). 944 if (previewEntities.length > 0 && endEntObstruction) 945 previewEntities[previewEntities.length-1].controlGroup = endEntObstruction.GetControlGroup(); 946 947 // if we're snapping to a foundation, add an extra preview tower and also set it to the same control group 948 var endEntState = this.GetEntityState(player, end.snappedEnt); 949 if (endEntState.foundation) 950 { 951 var cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position); 952 if (cmpPosition) 953 { 954 previewEntities.push({ 955 "template": wallSet.tower, 956 "pos": end.pos, 957 "angle": cmpPosition.GetRotation().y, 958 "controlGroup": (endEntObstruction ? endEntObstruction.GetControlGroup() : undefined), 959 "excludeFromResult": true 960 }); 961 } 962 } 963 } 964 else 965 { 966 previewEntities.push({ 967 "template": wallSet.tower, 968 "pos": end.pos, 969 "angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle) 970 }); 971 } 972 } 973 974 // Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed to build at 975 // least a part of the wall (meaning that the subset is truncated after the first entity that needs to be, but cannot validly be, 976 // constructed). 977 978 var allPiecesValid = true; 979 var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity 980 981 for (var i = 0; i < previewEntities.length; ++i) 982 { 983 var entInfo = previewEntities[i]; 984 985 var ent = null; 986 var tpl = entInfo.template; 987 var entPool = this.placementWallEntities[tpl]; 988 989 if (entPool.numUsed >= entPool.entities.length) 990 { 991 // allocate new entity 992 //warn("Allocating new entity of template '" + tpl + "'"); 993 ent = Engine.AddLocalEntity("preview|" + tpl); 994 entPool.entities.push(ent); 995 } 996 else 997 { 998 // reuse an existing one 999 ent = entPool.entities[entPool.numUsed]; 1000 } 1001 1002 if (!ent) 1003 { 1004 error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'"); 1005 continue; 1006 } 1007 1008 // move piece to right location 1009 // TODO: reuse SetBuildingPlacementReview for this, multiplexed to be able to deal with multiple entities? 1010 1011 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 1012 if (cmpPosition) 1013 { 1014 cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z); 1015 cmpPosition.SetYRotation(entInfo.angle); 1016 } 1017 1018 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); 1019 if (!cmpObstruction) 1020 { 1021 error("[SetWallPlacementPreview] Preview entity of template " + tpl + " does not have an obstruction component!"); 1022 continue; 1023 } 1024 1025 if (entInfo.controlGroup) 1026 { 1027 cmpObstruction.SetControlGroup(entInfo.controlGroup); 1028 } 1029 else 1030 { 1031 // Reset the control group (remember, we're reusing entities; without this, an ending wall segment that was 1032 // at one point snapped to an existing tower, and is subsequently reused to be the same ending segment (only 1033 // this time not snapped), would no longer be capable of being obstructed by the same tower) 1034 cmpObstruction.SetControlGroup(ent); 1035 } 1036 1037 // check whether this wall piece can be validly positioned here 1038 var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); 1039 if (!cmpBuildRestrictions) 1040 { 1041 error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for template '" + tpl + "'"); 1042 continue; 1043 } 1044 1045 var validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement(player)); 1046 allPiecesValid = allPiecesValid && validPlacement; 1047 1048 var cmpVisual = Engine.QueryInterface(ent, IID_Visual); 1049 if (cmpVisual) 1050 { 1051 if (!validPlacement) 1052 cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1); 1053 else 1054 cmpVisual.SetShadingColour(1, 1, 1, 1); 1055 } 1056 1057 // The requirement that all pieces so far have to have valid positions, rather than only this single one, 1058 // ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible 1059 // for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall 1060 // through and past an existing building). 1061 1062 // Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed 1063 // on top of foundations of incompleted towers that we snapped to; they must not be part of the result. 1064 1065 if (!entInfo.excludeFromResult) 1066 numRequiredPieces++; 1067 1068 if (allPiecesValid && !entInfo.excludeFromResult) 1069 { 1070 result.pieces.push({ 1071 "template": tpl, 1072 "x": entInfo.pos.x, 1073 "z": entInfo.pos.z, 1074 "angle": entInfo.angle, 1075 }); 1076 this.placementWallLastAngle = entInfo.angle; 1077 } 1078 1079 entPool.numUsed++; 1080 } 1081 1082 // If any were entities required to build the wall, but none of them could be validly positioned, return failure 1083 // (see method documentation). 1084 if (numRequiredPieces > 0 && result.pieces.length <= 0) 1085 return false; 1086 1087 if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY) 1088 result.startSnappedEnt = start.snappedEnt; 1089 1090 // We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed, 1091 // i.e. are included in result.pieces (see docs for the result object). 1092 if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid) 1093 result.endSnappedEnt = end.snappedEnt; 1094 1095 return result; 1096 }; 1097 1098 /** 1099 * Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap 1100 * it to (if necessary/useful). 1101 * 1102 * @param data.x The X position of the foundation to snap. 1103 * @param data.z The Z position of the foundation to snap. 1104 * @param data.template The template to get the foundation snapping data for. 1105 * @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius 1106 * around the entity. Only takes effect when used in conjunction with data.snapRadius. 1107 * When this option is used and the foundation is found to snap to one of the entities passed in this list 1108 * (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent", 1109 * holding the ID of the entity that was snapped to. 1110 * @param data.snapRadius Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that 1111 * {data.x, data.z} must be located within to have it snap to that entity. 1112 */ 588 1113 GuiInterface.prototype.GetFoundationSnapData = function(player, data) 589 1114 { 590 1115 var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 591 1116 var template = cmpTemplateMgr.GetTemplate(data.template); 592 1117 1118 if (!template) 1119 { 1120 warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'"); 1121 return false; 1122 } 1123 1124 if (data.snapEntities && data.snapRadius && data.snapRadius > 0) 1125 { 1126 // see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest 1127 // (TODO: break unlikely ties by choosing the lowest entity ID) 1128 1129 var minDist2 = -1; 1130 var minDistEntitySnapData = null; 1131 var radius2 = data.snapRadius * data.snapRadius; 1132 1133 for each (ent in data.snapEntities) 1134 { 1135 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 1136 if (!cmpPosition || !cmpPosition.IsInWorld()) 1137 continue; 1138 1139 var pos = cmpPosition.GetPosition(); 1140 var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z); 1141 if (dist2 > radius2) 1142 continue; 1143 1144 if (minDist2 < 0 || dist2 < minDist2) 1145 { 1146 minDist2 = dist2; 1147 minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent}; 1148 } 1149 } 1150 1151 if (minDistEntitySnapData != null) 1152 return minDistEntitySnapData; 1153 } 1154 593 1155 if (template.BuildRestrictions.Category == "Dock") 594 1156 { 595 1157 var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain); … … var exposedFunctions = { 813 1375 "SetStatusBars": 1, 814 1376 "DisplayRallyPoint": 1, 815 1377 "SetBuildingPlacementPreview": 1, 1378 "SetWallPlacementPreview": 1, 816 1379 "GetFoundationSnapData": 1, 817 1380 "PlaySound": 1, 818 1381 "FindIdleUnit": 1, -
new file inaries/data/mods/public/simulation/components/WallPiece.js
diff --git a/binaries/data/mods/public/simulation/components/WallPiece.js b/binaries/data/mods/public/simulation/components/WallPiece.js new file mode 100644 index 0000000..f55f4d0
- + 1 function WallPiece() {} 2 3 WallPiece.prototype.Schema = 4 "<a:help></a:help>" + 5 "<a:example>" + 6 "</a:example>" + 7 "<element name='Length'>" + 8 "<ref name='nonNegativeDecimal'/>" + 9 "</element>"; 10 11 12 WallPiece.prototype.Init = function() 13 { 14 }; 15 16 Engine.RegisterComponentType(IID_WallPiece, "WallPiece", WallPiece); -
new file inaries/data/mods/public/simulation/components/WallSet.js
diff --git a/binaries/data/mods/public/simulation/components/WallSet.js b/binaries/data/mods/public/simulation/components/WallSet.js new file mode 100644 index 0000000..79b673b
- + 1 function WallSet() {} 2 3 WallSet.prototype.Schema = 4 "<a:help></a:help>" + 5 "<a:example>" + 6 "</a:example>" + 7 "<interleave>" + 8 "<element name='WallLong'>" + 9 "<text/>" + 10 "</element>" + 11 "<element name='WallMedium'>" + 12 "<text/>" + 13 "</element>" + 14 "<element name='WallShort'>" + 15 "<text/>" + 16 "</element>" + 17 "<element name='Tower'>" + 18 "<text/>" + 19 "</element>" + 20 "<element name='Gate'>" + 21 "<text/>" + 22 "</element>" + 23 "</interleave>"; 24 25 26 WallSet.prototype.Init = function() 27 { 28 }; 29 30 WallSet.prototype.Serialize = null; 31 32 Engine.RegisterComponentType(IID_WallSet, "WallSet", WallSet); -
new file inaries/data/mods/public/simulation/components/interfaces/WallPiece.js
diff --git a/binaries/data/mods/public/simulation/components/interfaces/WallPiece.js b/binaries/data/mods/public/simulation/components/interfaces/WallPiece.js new file mode 100644 index 0000000..ad4ae36
- + 1 Engine.RegisterInterface("WallPiece"); 2 No newline at end of file -
new file inaries/data/mods/public/simulation/components/interfaces/WallSet.js
diff --git a/binaries/data/mods/public/simulation/components/interfaces/WallSet.js b/binaries/data/mods/public/simulation/components/interfaces/WallSet.js new file mode 100644 index 0000000..10e0cfc
- + 1 Engine.RegisterInterface("WallSet"); 2 No newline at end of file -
binaries/data/mods/public/simulation/helpers/Commands.js
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 4d07c2e..0724af0 100644
a b function ProcessCommand(player, cmd) 8 8 var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 9 9 if (!cmpPlayerMan || player < 0) 10 10 return; 11 11 12 var playerEnt = cmpPlayerMan.GetPlayerByID(player); 12 13 if (playerEnt == INVALID_ENTITY) 13 14 return; 15 14 16 var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player); 15 17 if (!cmpPlayer) 16 18 return; 19 17 20 var controlAllUnits = cmpPlayer.CanControlAllUnits(); 18 21 19 22 // Note: checks of UnitAI targets are not robust enough here, as ownership … … function ProcessCommand(player, cmd) 146 149 break; 147 150 148 151 case "construct": 149 // Message structure: 150 // { 151 // "type": "construct", 152 // "entities": [...], 153 // "template": "...", 154 // "x": ..., 155 // "z": ..., 156 // "angle": ..., 157 // "autorepair": true, // whether to automatically start constructing/repairing the new foundation 158 // "autocontinue": true, // whether to automatically gather/build/etc after finishing this 159 // "queued": true, 160 // } 161 162 /* 163 * Construction process: 164 * . Take resources away immediately. 165 * . Create a foundation entity with 1hp, 0% build progress. 166 * . Increase hp and build progress up to 100% when people work on it. 167 * . If it's destroyed, an appropriate fraction of the resource cost is refunded. 168 * . If it's completed, it gets replaced with the real building. 169 */ 170 171 // Check that we can control these units 172 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 173 if (!entities.length) 174 break; 175 176 // Tentatively create the foundation (we might find later that it's a invalid build command) 177 var ent = Engine.AddEntity("foundation|" + cmd.template); 178 if (ent == INVALID_ENTITY) 179 { 180 // Error (e.g. invalid template names) 181 error("Error creating foundation entity for '" + cmd.template + "'"); 182 break; 183 } 184 185 // Move the foundation to the right place 186 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 187 cmpPosition.JumpTo(cmd.x, cmd.z); 188 cmpPosition.SetYRotation(cmd.angle); 189 190 // Check whether it's obstructed by other entities or invalid terrain 191 var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); 192 if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player)) 193 { 194 if (g_DebugCommands) 195 { 196 warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd)); 197 } 198 199 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 200 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" }); 201 202 // Remove the foundation because the construction was aborted 203 Engine.DestroyEntity(ent); 204 break; 205 } 206 207 // Check build limits 208 var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits); 209 if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) 210 { 211 if (g_DebugCommands) 212 { 213 warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd)); 214 } 215 216 // TODO: The UI should tell the user they can't build this (but we still need this check) 217 218 // Remove the foundation because the construction was aborted 219 Engine.DestroyEntity(ent); 220 break; 221 } 222 223 // TODO: AI has no visibility info 224 if (!cmpPlayer.IsAI()) 225 { 226 // Check whether it's in a visible or fogged region 227 // tell GetLosVisibility to force RetainInFog because preview entities set this to false, 228 // which would show them as hidden instead of fogged 229 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 230 var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden"); 231 if (!visible) 232 { 233 if (g_DebugCommands) 234 { 235 warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd)); 236 } 237 238 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 239 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" }); 240 241 Engine.DestroyEntity(ent); 242 break; 243 } 244 } 245 246 var cmpCost = Engine.QueryInterface(ent, IID_Cost); 247 if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts())) 248 { 249 if (g_DebugCommands) 250 { 251 warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd)); 252 } 253 254 Engine.DestroyEntity(ent); 255 break; 256 } 257 258 // Make it owned by the current player 259 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 260 cmpOwnership.SetOwner(player); 261 262 // Initialise the foundation 263 var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); 264 cmpFoundation.InitialiseConstruction(player, cmd.template); 265 266 // Tell the units to start building this new entity 267 if (cmd.autorepair) 268 { 269 ProcessCommand(player, { 270 "type": "repair", 271 "entities": entities, 272 "target": ent, 273 "autocontinue": cmd.autocontinue, 274 "queued": cmd.queued 275 }); 276 } 152 TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd); 153 break; 277 154 155 case "construct-wall": 156 TryConstructWall(player, cmpPlayer, controlAllUnits, cmd); 278 157 break; 279 158 280 159 case "delete-entities": … … function ExtractFormations(ents) 467 346 } 468 347 469 348 /** 349 * Attempts to construct a building using the specified parameters. 350 * Returns true on success, false on failure. 351 */ 352 function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) 353 { 354 // Message structure: 355 // { 356 // "type": "construct", 357 // "entities": [...], // entities that will be ordered to construct the building (if applicable) 358 // "template": "...", // template name of the entity being constructed 359 // "x": ..., 360 // "z": ..., 361 // "angle": ..., 362 // "autorepair": true, // whether to automatically start constructing/repairing the new foundation 363 // "autocontinue": true, // whether to automatically gather/build/etc after finishing this 364 // "queued": true, // whether to add the construction/repairing of this foundation to entities' queue (if applicable) 365 // "obstructionControlGroup": ..., // Optional; the obstruction control group ID that should be set for this building prior to obstruction 366 // // testing to determine placement validity. If specified, must be a valid control group ID (> 0). 367 // "obstructionControlGroup2": ..., // Optional; secondary obstruction control group ID that should be set for this building prior to obstruction 368 // // testing to determine placement validity. May be INVALID_ENTITY. 369 // } 370 371 /* 372 * Construction process: 373 * . Take resources away immediately. 374 * . Create a foundation entity with 1hp, 0% build progress. 375 * . Increase hp and build progress up to 100% when people work on it. 376 * . If it's destroyed, an appropriate fraction of the resource cost is refunded. 377 * . If it's completed, it gets replaced with the real building. 378 */ 379 380 // Check whether we can control these units 381 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 382 if (!entities.length) 383 return false; 384 385 // Tentatively create the foundation (we might find later that it's a invalid build command) 386 var ent = Engine.AddEntity("foundation|" + cmd.template); 387 if (ent == INVALID_ENTITY) 388 { 389 // Error (e.g. invalid template names) 390 error("Error creating foundation entity for '" + cmd.template + "'"); 391 return false; 392 } 393 394 // Move the foundation to the right place 395 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 396 cmpPosition.JumpTo(cmd.x, cmd.z); 397 cmpPosition.SetYRotation(cmd.angle); 398 399 // Set the obstruction control group if needed 400 if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2) 401 { 402 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); 403 404 // primary control group must always be valid 405 if (cmd.obstructionControlGroup) 406 { 407 if (cmd.obstructionControlGroup <= 0) 408 warn("[TryConstructBuilding] Invalid primary obstruction control group " + cmd.obstructionControlGroup + " received; must be > 0"); 409 410 cmpObstruction.SetControlGroup(cmd.obstructionControlGroup); 411 } 412 413 if (cmd.obstructionControlGroup2) 414 cmpObstruction.SetControlGroup2(cmd.obstructionControlGroup2); 415 } 416 417 // Check whether it's obstructed by other entities or invalid terrain 418 var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); 419 if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player)) 420 { 421 if (g_DebugCommands) 422 { 423 warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd)); 424 } 425 426 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 427 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" }); 428 429 // Remove the foundation because the construction was aborted 430 Engine.DestroyEntity(ent); 431 return false; 432 } 433 434 // Check build limits 435 var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits); 436 if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) 437 { 438 if (g_DebugCommands) 439 { 440 warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd)); 441 } 442 443 // TODO: The UI should tell the user they can't build this (but we still need this check) 444 445 // Remove the foundation because the construction was aborted 446 Engine.DestroyEntity(ent); 447 return false; 448 } 449 450 // TODO: AI has no visibility info 451 if (!cmpPlayer.IsAI()) 452 { 453 // Check whether it's in a visible or fogged region 454 // tell GetLosVisibility to force RetainInFog because preview entities set this to false, 455 // which would show them as hidden instead of fogged 456 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 457 var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden"); 458 if (!visible) 459 { 460 if (g_DebugCommands) 461 { 462 warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd)); 463 } 464 465 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 466 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" }); 467 468 Engine.DestroyEntity(ent); 469 return false; 470 } 471 } 472 473 var cmpCost = Engine.QueryInterface(ent, IID_Cost); 474 if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts())) 475 { 476 if (g_DebugCommands) 477 { 478 warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd)); 479 } 480 481 Engine.DestroyEntity(ent); 482 return false; 483 } 484 485 // Make it owned by the current player 486 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 487 cmpOwnership.SetOwner(player); 488 489 // Initialise the foundation 490 var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); 491 cmpFoundation.InitialiseConstruction(player, cmd.template); 492 493 // Tell the units to start building this new entity 494 if (cmd.autorepair) 495 { 496 ProcessCommand(player, { 497 "type": "repair", 498 "entities": entities, 499 "target": ent, 500 "autocontinue": cmd.autocontinue, 501 "queued": cmd.queued 502 }); 503 } 504 505 return ent; 506 } 507 508 function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) 509 { 510 // Message structure: 511 // { 512 // "type": "construct-wall", 513 // "entities": [...], // entities that will be ordered to construct the wall (if applicable) 514 // "pieces": [ 515 // { 516 // "template": "...", // template name of the wall piece entity being constructed (e.g. towers, long/short/med segments, ...) 517 // "x": ..., 518 // "z": ..., 519 // "angle": ..., 520 // }, 521 // ... 522 // ], 523 // "wallSet": { 524 // "tower": [tower template name], 525 // "long": [long wall segment template name], 526 // ... 527 // (TODO: we only really need the tower template name here) 528 // }, 529 // "startSnappedEntity": // optional; entity ID of tower being snapped to at the starting side of the wall 530 // "endSnappedEntity": // optional; entity ID of tower being snapped to at the ending side of the wall 531 // "autorepair": true, // whether to automatically start constructing/repairing the new foundation 532 // "autocontinue": true, // whether to automatically gather/build/etc after finishing this 533 // "queued": true, // whether to add the construction/repairing of this wall's pieces to entities' queue (if applicable) 534 // } 535 536 if (cmd.pieces.length <= 0) 537 return; 538 539 if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.tower) 540 { 541 error("[TryConstructWall] Starting wall piece cannot be a tower (" + cmd.wallSet.tower + ") when snapping at the starting side"); 542 return; 543 } 544 545 if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.tower) 546 { 547 error("[TryConstructWall] Ending wall piece cannot be a tower (" + cmd.wallSet.tower + ") when snapping at the ending side"); 548 return; 549 } 550 551 // Assign obstruction control groups to allow the wall pieces to mutually overlap during foundation placement 552 // and during construction. The scheme here is that whatever wall pieces are inbetween two towers inherit the control 553 // groups of both of the towers they are connected to (either newly constructed ones as part of the wall, or existing 554 // towers in the case of snapping). The towers themselves all keep their default unique control groups. 555 556 // To support this, every non-tower piece registers the entity ID of the tower foundations that neighbour it on either side. 557 // We can't build the whole wall at once by linearly stepping through the wall pieces and build them, because the 558 // wall segments may/will need the entity IDs of towers that come afterwards. 559 560 // So, build it in two passes: 561 // - In the first pass, go left to right (i.e. from start to end position), and construct wall piece foundations as 562 // far as we can without running into a piece that cannot be built (e.g. because it is obstructed). At each step, 563 // set the left-neighbouring tower ID as the primary control group, thus allowing it to be built overlapping it. 564 // If we encounter a new tower along the way (which will gain its own control group), first build it using temporarily 565 // the same control group of the previous (non-tower) piece, then set the previous piece's secondary control group 566 // to the tower's entity ID, and then restore the primary control group of the constructed tower back its original 567 // (unique) value. (If we hadn't first temporarily given it the previous piece's control group, it wouldn't've been 568 // able to be placed while overlapping the previous piece). 569 // If we hit a piece that can't be built, backtrack to the last tower that was successfully built, deleting any 570 // non-tower pieces that were built after it (we want walls to always end in a tower so we can always extend it 571 // by snapping onto it). 572 // 573 // - In the second pass, go right to left from said last successfully placed wall piece (which might be a tower we 574 // backtracked to), this time registering the right neighbouring tower in each non-tower piece. 575 576 // first pass; L -> R 577 578 var lastTowerIndex = -1; // index of the last tower we've encountered in cmd.pieces 579 var lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces 580 581 if (cmd.startSnappedEntity) 582 { 583 var cmpSnappedStartObstruction = Engine.QueryInterface(cmd.startSnappedEntity, IID_Obstruction); 584 if (!cmpSnappedStartObstruction) 585 { 586 error("[TryConstructWall] Snapped entity on starting side does not have an obstruction component"); 587 return; 588 } 589 590 lastTowerControlGroup = cmpSnappedStartObstruction.GetControlGroup(); 591 //warn("setting lastTowerControlGroup to control group of start snapped entity " + cmd.startSnappedEntity + ": " + lastTowerControlGroup); 592 } 593 594 var i = 0; 595 for (; i < cmd.pieces.length; ++i) 596 { 597 var piece = cmd.pieces[i]; 598 599 //warn("---[>>]--- iteration " + i); 600 601 // except for if we don't do start position snapping and the first entity we build is therefore a tower, 602 // 'lastTowerControlGroup' must always be defined and valid here 603 if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY) 604 { 605 if (!(i == 0 && piece.template == cmd.wallSet.tower && !cmd.startSnappedEntity)) 606 { 607 error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass)"); 608 break; 609 } 610 } 611 612 var constructPieceCmd = { 613 "type": "construct", 614 "entities": cmd.entities, 615 "template": piece.template, 616 "x": piece.x, 617 "z": piece.z, 618 "angle": piece.angle, 619 "autorepair": cmd.autorepair, 620 "autocontinue": cmd.autocontinue, 621 "queued": cmd.queued, 622 // regardless of whether we're building a tower or an intermediate wall piece, it is always (first) constructed 623 // using the control group of the last tower (see algorithm outline above) 624 "obstructionControlGroup": lastTowerControlGroup, // allowed to be null; should be the case when the first entity is a tower 625 }; 626 627 // if we're building the last piece and we're attaching to a snapped entity, we need to add in the snapped entity's 628 // control group directly at construction time (instead of setting it in the second pass) to allow it to be built with 629 // overlap to the snapped entity 630 if (cmd.endSnappedEntity && i == cmd.pieces.length - 1) 631 { 632 var cmpEndSnappedObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction); 633 // TODO: ensure cmpEndSnappedObstruction exists 634 constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup(); 635 } 636 637 //for (var k in constructPieceCmd) 638 // warn(" " + k + ": " + uneval(constructPieceCmd[k])); 639 //warn(" "); 640 641 var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd); 642 if (pieceEntityId) 643 { 644 //warn(" successfully built wall piece; ID = " + pieceEntityId); 645 646 // wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later 647 piece.ent = pieceEntityId; 648 649 // if we built a tower, do the control group dance (see algorithm outline above) and update lastTowerControlGroup 650 // and lastTowerIndex 651 if (piece.template == cmd.wallSet.tower) 652 { 653 var cmpTowerObstruction = Engine.QueryInterface(pieceEntityId, IID_Obstruction); 654 var newTowerControlGroup = pieceEntityId; 655 656 if (i > 0) 657 { 658 //warn(" updating previous wall piece's secondary control group to " + newTowerControlGroup); 659 var cmpPreviousObstruction = Engine.QueryInterface(cmd.pieces[i-1].ent, IID_Obstruction); 660 // TODO: ensure that cmpPreviousObstruction exists 661 // TODO: ensure that the previous obstruction does not yet have a secondary control group set 662 cmpPreviousObstruction.SetControlGroup2(newTowerControlGroup); 663 } 664 665 // TODO: ensure that cmpTowerObstruction exists 666 cmpTowerObstruction.SetControlGroup(newTowerControlGroup); // give the tower its own unique control group 667 //warn(" updating tower control group to " + newTowerControlGroup); 668 669 lastTowerIndex = i;// warn(" updated lastTowerIndex to " + lastTowerIndex); 670 lastTowerControlGroup = newTowerControlGroup;// warn(" updated lastTowerControlGroup to " + newTowerControlGroup); 671 } 672 } 673 else 674 { 675 //error(" (!!) failed to build wall piece, backtracking to last tower index " + lastTowerIndex); 676 677 // wall piece foundation failed to build, backtrack and delete entities until the last tower that was succesfully built 678 var j = i - 1; 679 for (; (j >= 0 && cmd.pieces[j].template !== cmd.wallSet.tower); --j) 680 { 681 // the previous pieces should all have their entity ID registered; if not, something's wrong 682 if (!cmd.pieces[j].ent) 683 { 684 error("[TryConstructWall] Cannot backtrack to delete dangling wall pieces after placement failure; no registered entity ID"); 685 continue; 686 } 687 688 //warn(" destroying backtracked entity " + cmd.pieces[j].ent + " of template " + cmd.pieces[j].template); 689 Engine.DestroyEntity(cmd.pieces[j].ent); 690 } 691 692 i = j + 1; // compensate for the -1 subtracted by lastBuiltPieceIndex below 693 break; 694 } 695 } 696 697 var lastBuiltPieceIndex = i - 1; 698 var wallComplete = (lastBuiltPieceIndex == cmd.pieces.length - 1); 699 700 //warn("lastBuiltPieceIndex = " + lastBuiltPieceIndex); 701 //warn("wall complete? " + (wallComplete ? "yes" : "no")); 702 703 // At this point, 'i' is the index of the last wall piece that was successfully constructed (taking into account backtracking 704 // to the last tower upon foundation placement failure). Now do the second pass going right-to-left, registering the control 705 // groups of the towers to the right of each piece as their secondary control groups. 706 707 lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces 708 709 // only start off with the ending side's snapped tower's control group if we were able to build the entire wall 710 if (cmd.endSnappedEntity && wallComplete) 711 { 712 var cmpSnappedEndObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction); 713 if (!cmpSnappedEndObstruction) 714 { 715 error("[TryConstructWall] Snapped entity on ending side does not have an obstruction component"); 716 return; 717 } 718 719 lastTowerControlGroup = cmpSnappedEndObstruction.GetControlGroup(); 720 } 721 722 for (var j = lastBuiltPieceIndex; j >= 0; --j) 723 { 724 var piece = cmd.pieces[j]; 725 726 //warn("---[<<]--- iteration " + j); 727 728 // In general, lastTowerControlGroup must always be defined and valid here, except when the last successfully built 729 // piece is a tower that we didn't snap to. This can happen: 730 // - If we didn't do any end-side snapping and the last successfully built piece is therefore a tower. Note 731 // that in this case it doesn't matter if we did or did not complete the entire wall. 732 // - If we did do end-side snapping, but we didn't complete the entire wall and had to end at an intermediate 733 // tower. 734 735 if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY) 736 { 737 var ok = (j == lastBuiltPieceIndex && ((!cmd.endSnappedEntity && piece.template == cmd.wallSet.tower) || 738 ( cmd.endSnappedEntity && piece.template == cmd.wallSet.tower && !wallComplete))); 739 if (!ok) 740 { 741 error("[TryConstructWall] Expected last tower control group to be available, none found (2nd pass)"); 742 break; 743 } 744 } 745 746 var cmpPieceObstruction = Engine.QueryInterface(piece.ent, IID_Obstruction); 747 // TODO: make sure piece.ent/cmpPieceObstruction exists 748 749 if (piece.template == cmd.wallSet.tower) 750 { 751 // encountered a tower entity, update the last tower control group 752 lastTowerControlGroup = cmpPieceObstruction.GetControlGroup(); 753 //warn(" updated last tower control group to " + lastTowerControlGroup); 754 } 755 else 756 { 757 // Encountered a non-tower entity, update its secondary control group. 758 // Note that the wall piece may already have its secondary control group set to the tower's entity ID from the 759 // first pass, in which case we should validate it against lastTowerControlGroup. 760 var existingSecondaryControlGroup = cmpPieceObstruction.GetControlGroup2(); 761 if (existingSecondaryControlGroup == INVALID_ENTITY) 762 { 763 cmpPieceObstruction.SetControlGroup2(lastTowerControlGroup); 764 //warn(" set wall piece secondary control group to " + lastTowerControlGroup); 765 } 766 else if (existingSecondaryControlGroup != lastTowerControlGroup) 767 { 768 error("[TryConstructWall] Existing secondary control group of non-tower entity does not match expected value (2nd pass)"); 769 break; 770 } 771 } 772 } 773 774 //warn("--- Done ---"); 775 warn("Built " + (lastBuiltPieceIndex + 1) + "/" + cmd.pieces.length + " pieces"); 776 //warn("------------"); 777 } 778 779 /** 470 780 * Remove the given list of entities from their current formations. 471 781 */ 472 782 function RemoveFromFormation(ents) -
new file inaries/data/mods/public/simulation/helpers/Walls.js
diff --git a/binaries/data/mods/public/simulation/helpers/Walls.js b/binaries/data/mods/public/simulation/helpers/Walls.js new file mode 100644 index 0000000..3dd66ea
- + 1 /** 2 * Returns the wall piece entities needed to construct a wall between start.pos and end.pos. Assumes start.pos != end.pos. 3 * The result is an array of objects, each one containing the following information about a single wall piece entity: 4 * - 'template': the template name of the entity 5 * - 'pos': position of the entity, as an object with keys 'x' and 'z' 6 * - 'angle': orientation of the entity, as an angle in radians 7 * 8 * All the pieces in the resulting array are ordered left-to-right (or right-to-left) as they appear in the physical wall. 9 * 10 * @param placementData Object that associates the wall piece template names with information about those kinds of pieces. 11 * Expects placementData[templateName].templateData to contain the parsed template information about 12 * the template whose filename is <i>templateName</i>. 13 * @param wallSet Object that associates template names with the kinds of wall pieces that supported. Expected to contain 14 * template names for keys "long" (long wall segment), "medium" (medium wall segment), "short" (short wall 15 * segment), "tower" (intermediate tower between wall segments), "gate" (replacement for long walls). 16 * @param start Object holding the starting position of the wall. Must contain keys 'x' and 'z'. 17 * @param end Object holding the ending position of the wall. Must contains keys 'x' and 'z'. 18 */ 19 function GetWallPlacement(placementData, wallSet, start, end) 20 { 21 var result = []; 22 23 var candidateSegments = [ 24 {"template": wallSet.long, "len": placementData[wallSet.long].templateData.wallPiece.length}, 25 {"template": wallSet.medium, "len": placementData[wallSet.medium].templateData.wallPiece.length}, 26 {"template": wallSet.short, "len": placementData[wallSet.short].templateData.wallPiece.length}, 27 ]; 28 29 var towerWidth = placementData[wallSet.tower].templateData.wallPiece.length; 30 31 var dir = {"x": end.pos.x - start.pos.x, "z": end.pos.z - start.pos.z}; 32 var len = Math.sqrt(dir.x * dir.x + dir.z * dir.z); 33 34 if (len <= towerWidth) 35 // we'll need room for at least our starting and ending towers to fit next to eachother 36 return result; 37 38 var placement = GetWallSegmentsRec(len, candidateSegments, 0.05, 0.8, towerWidth, 0, []); 39 40 // TODO: make sure intermediate towers are spaced out far enough for their obstructions to not overlap, implying that 41 // tower's wallpiece lengths should be > their obstruction width, which is undesirable because it prevents towers with 42 // wide bases 43 if (placement) 44 { 45 var placedEntities = placement.segments; // list of chosen candidate segments 46 var r = placement.r; // remaining distance to target without towers (must be <= (N-1) * towerWidth) 47 var s = r / (2 * placedEntities.length); // spacing 48 49 var dirNormalized = {"x": dir.x / len, "z": dir.z / len}; 50 var angle = -Math.atan2(dir.z, dir.x); // angle of this wall segment (relative to world-space X/Z axes) 51 52 var progress = 0; 53 for (var i = 0; i < placedEntities.length; i++) 54 { 55 var placedEntity = placedEntities[i]; 56 var targetX = start.pos.x + (progress + s + placedEntity.len/2) * dirNormalized.x; 57 var targetZ = start.pos.z + (progress + s + placedEntity.len/2) * dirNormalized.z; 58 59 result.push({ 60 "template": placedEntity.template, 61 "pos": {"x": targetX, "z": targetZ}, 62 "angle": angle, 63 }); 64 65 if (i < placedEntities.length - 1) 66 { 67 var towerX = start.pos.x + (progress + placedEntity.len + 2*s) * dirNormalized.x; 68 var towerZ = start.pos.z + (progress + placedEntity.len + 2*s) * dirNormalized.z; 69 70 result.push({ 71 "template": wallSet.tower, 72 "pos": {"x": towerX, "z": towerZ}, 73 "angle": angle, 74 }); 75 } 76 77 progress += placedEntity.len + 2*s; 78 } 79 } 80 else 81 { 82 error("No placement possible"); 83 } 84 85 return result; 86 } 87 88 /** 89 * Helper function for GetWallPlacement. Finds a list of wall segments and the corresponding remaining spacing/overlap 90 * distance "r" that will suffice to construct a wall of the given distance. It is understood that two extra towers will 91 * be placed centered at the starting and ending points of the wall. 92 * 93 * @param d Total distance between starting and ending points (constant throughout calls). 94 * @param candidateSegments List of candidate segments (constant throughout calls). Should be ordered longer-to-shorter 95 * for better execution speed. 96 * @param minOverlap Minimum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are 97 * not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely). 98 * Must be <= maxOverlap. 99 * @param maxOverlap Maximum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are 100 * not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely). 101 * Must be >= minOverlap. 102 * @param t Length of a single tower (constant throughout calls). Acts as buffer space for wall segments (see comments). 103 * @param distSoFar Sum of all the wall segments' lengths in 'segments'. 104 * @param segments Current list of wall segments placed. 105 */ 106 function GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, distSoFar, segments) 107 { 108 // The idea is to find a number N of wall segments (excluding towers) so that the sum of their lengths adds up to a 109 // value that is within certain bounds of the distance 'd' between the starting and ending points of the wall. This 110 // creates either a positive or negative 'buffer' of space, that can be compensated for by spacing the wall segments 111 // out away from each other, or inwards, overlapping each other. The spaces or overlaps can then be covered up by 112 // placing towers on top of them. In this way, the same set of wall segments can be used to span a wider range of 113 // target distances. 114 // 115 // In this function, it is understood that two extra towers will be placed centered at the starting and ending points. 116 // They are allowed to contribute to the buffer space. 117 // 118 // The buffer space equals the difference between d and the sum of the lengths of all the wall segments, and is denoted 119 // 'r' for 'remaining space'. Positive values of r mean that the walls will need to be spaced out, negative values of r 120 // mean that they will need to overlap. Clearly, there are limits to how far wall segments can be spaced out or 121 // overlapped, depending on how much 'buffer space' each tower provides, and how far 'into' towers the wall segments are 122 // allowed to overlap. 123 // 124 // Let 't' signify the width of a tower. When there are N wall segments, then the maximum distance that can be covered 125 // using only these walls (plus the towers covering up any gaps) is achieved when the walls and towers touch outer-border- 126 // to-outer-border. Therefore, the maximum value of r is then given by: 127 // 128 // rMax = t/2 + (N-1)*t + t/2 129 // = N*t 130 // 131 // where the two half-tower widths are buffer space contributed by the implied towers on the starting and ending points. 132 // Similarly, a value rMin = -N*t can be derived for the minimal value of r. Note that a value of r = 0 means that the 133 // wall segment lengths add up to exactly d, meaning that each one starts and ends right in the center of a tower. 134 // 135 // Thus, we establish: 136 // -Nt <= r <= Nt 137 // 138 // We can further generalize this by adding in parameters to control the depth to within which wall segments are allowed to 139 // overlap with a tower. The bounds above assume that a wall segment is allowed to overlap across the entire range of 0 140 // (not overlapping at all, as in the upper boundary) to 1 (overlapping maximally, as in the lower boundary). 141 // 142 // By requiring that walls overlap towers to a degree of at least 0 < minOverlap <= 1, it is clear that this lowers the 143 // distance that can be maximally reached by the same set of wall segments, compared to the value of minOverlap = 0 that 144 // we assumed to initially find Nt. 145 // 146 // Consider a value of minOverlap = 0.5, meaning that any wall segment must protrude at least halfway into towers; in this 147 // situation, wall segments must at least touch boundaries or overlap mutually, implying that the sum of their lengths 148 // must equal or exceed 'd', establishing an upper bound of 0 for r. 149 // Similarly, consider a value of minOverlap = 1, meaning that any wall segment must overlap towers maximally; this situation 150 // is equivalent to the one for finding the lower bound -Nt on r. 151 // 152 // With the implicit value minOverlap = 0 that yielded the upper bound Nt above, simple interpolation and a similar exercise 153 // for maxOverlap, we find: 154 // (1-2*maxOverlap) * Nt <= r <= (1-2*minOverlap) * Nt 155 // 156 // To find N segments that satisfy this requirement, we try placing L, M and S wall segments in turn and continue recursively 157 // as long as the value of r is not within the bounds. If continuing recursively returns an impossible configuration, we 158 // backtrack and try a wall segment of the next length instead. Note that we should prefer to use the long segments first since 159 // they can be replaced by gates. 160 161 for each (var candSegment in candidateSegments) 162 { 163 segments.push(candSegment); 164 165 var newDistSoFar = distSoFar + candSegment.len; 166 var r = d - newDistSoFar; 167 168 // TODO: these don't have to be recalculated every iteration 169 var rLowerBound = (1 - 2 * maxOverlap) * segments.length * t; 170 var rUpperBound = (1 - 2 * minOverlap) * segments.length * t; 171 172 if (r < rLowerBound) 173 { 174 // we've allocated too much wall length, pop the last segment and try the next 175 //warn("Distance so far exceeds target, trying next level"); 176 segments.pop(); 177 continue; 178 } 179 else if (r > rUpperBound) 180 { 181 var recursiveResult = GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, newDistSoFar, segments); 182 if (!recursiveResult) 183 { 184 // recursive search with this piece yielded no results, pop it and try the next one 185 segments.pop(); 186 continue; 187 } 188 else 189 return recursiveResult; 190 } 191 else 192 { 193 // found a placement 194 return {"segments": segments, "r": r}; 195 } 196 } 197 198 // no placement possible :( 199 return false; 200 } 201 202 Engine.RegisterGlobal("GetWallPlacement", GetWallPlacement); -
binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml index 5a882ef..0040043 100644
a b 21 21 <VisualActor> 22 22 <Actor>props/special/palisade_rocks_gate.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>11.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml index 8af8b59..fe04d11 100644
a b 22 22 <VisualActor> 23 23 <Actor>props/special/palisade_rocks_long.xml</Actor> 24 24 </VisualActor> 25 <WallPiece> 26 <Length>11.0</Length> 27 </WallPiece> 25 28 </Entity> -
binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml index db4a799..dca7de4 100644
a b 22 22 <VisualActor> 23 23 <Actor>props/special/palisade_rocks_medium.xml</Actor> 24 24 </VisualActor> 25 <WallPiece> 26 <Length>8.0</Length> 27 </WallPiece> 25 28 </Entity> -
binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml index f7a6bcf..bfd3bb3 100644
a b 22 22 <VisualActor> 23 23 <Actor>props/special/palisade_rocks_short.xml</Actor> 24 24 </VisualActor> 25 <WallPiece> 26 <Length>4.0</Length> 27 </WallPiece> 25 28 </Entity> 26 -
binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml index 8a56c70..f2b70fa 100644
a b 22 22 <VisualActor> 23 23 <Actor>props/special/palisade_rocks_tower.xml</Actor> 24 24 </VisualActor> 25 <WallPiece> 26 <Length>4.0</Length> 27 </WallPiece> 25 28 </Entity> -
binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml index 0675ccc..63fa2e6 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_gate.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>22.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml index 41d1fdc..a9a4451 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_long.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>22.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml index 4b51497..851af02 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/celts/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>22.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml index 1f17003..66523a9 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/celts/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>22.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml index d67c982..8ebcd99 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_tower.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>6.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml index 5726f89..1c1ed7b 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_gate.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>35.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml index 280419e..a254cd2 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_long.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>34.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml index 13550bc..cfd2034 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>21.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml index 2db9bd1..1f227a8 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>21.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml index e3be746..c7ecf13 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>10.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml index c5e81aa..0a19abf 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_tower.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>5.0</Length> 26 </WallPiece> 24 27 </Entity> 28 No newline at end of file -
binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml index 1030027..d005300 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/iberians/wall_gate.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>32.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml index 8fd77e4..e339f42 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/iberians/wall_long.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>34.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml index 9bbdf5d..3435685 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/iberians/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>23.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml index fa32ee1..2436a59 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/iberians/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>10.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml index 264de9e..8f368de 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/iberians/wall_tower.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>8.5</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml index 7218abe..d423d91 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_gate.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>34.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml index 48cad21..c2e7dd1 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_long.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>34.0</Length> 26 </WallPiece> 24 27 </Entity> 28 No newline at end of file -
binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml index e983e18..8a9ef69 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>21.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml index d92c6b6..c5a096c 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>10.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml index 75cadce..ec80dcd 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_tower.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>5.5</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml index ad4b54e..20fc90a 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/romans/wall_gate.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>34.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml index ae4a5ef..b0003b8 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/romans/wall_long.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>34.0</Length> 20 </WallPiece> 18 21 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml index 169ccbe..bc67e85 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/romans/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>22.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml index 8a79992..58ab55b 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/romans/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>10.0</Length> 26 </WallPiece> 24 27 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml index 6f68a9c..252eec4 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/romans/wall_tower.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>6.5</Length> 20 </WallPiece> 18 21 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/wallsets/celt_palisade.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_palisade.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_palisade.xml new file mode 100644 index 0000000..ff082ce
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement. 3 Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 4 gates, wall segments of various length, etc.) --> 5 <Entity> 6 <Identity> 7 <Icon>gaia/special_palisade.png</Icon> 8 <Civ>celt</Civ> 9 <SpecificName>WALLSET</SpecificName> 10 <History>WALLSET</History> 11 <!-- TODO: Undesirable copy/pasta from the wall templates :( --> 12 <Classes datatype="tokens">Town Wall</Classes> 13 <GenericName>Palisade Wallset</GenericName> 14 <Tooltip>WALLSET</Tooltip> 15 </Identity> 16 <WallSet> 17 <Tower>other/palisades_rocks_tower</Tower> 18 <Gate>other/palisades_rocks_gate</Gate> 19 <WallLong>other/palisades_rocks_long</WallLong> 20 <WallMedium>other/palisades_rocks_medium</WallMedium> 21 <WallShort>other/palisades_rocks_short</WallShort> 22 </WallSet> 23 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/wallsets/celt_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_stone.xml new file mode 100644 index 0000000..c3baf09
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement. 3 Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 4 gates, wall segments of various length, etc.) --> 5 <Entity> 6 <Identity> 7 <Icon>structures/wall.png</Icon> 8 <Civ>iber</Civ> 9 <SpecificName>Celt Wallset</SpecificName> 10 <!-- TODO: Undesirable copy/pasta from the wall templates :( --> 11 <Classes datatype="tokens">Town Wall</Classes> 12 <GenericName>City Wall</GenericName> 13 <Tooltip>(Celt WallSet)</Tooltip> 14 </Identity> 15 <WallSet> 16 <Tower>structures/celt_wall_tower</Tower> 17 <Gate>structures/celt_wall_gate</Gate> 18 <WallLong>structures/celt_wall_long</WallLong> 19 <WallMedium>structures/celt_wall_medium</WallMedium> 20 <WallShort>structures/celt_wall_short</WallShort> 21 </WallSet> 22 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/wallsets/hele_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/hele_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/hele_stone.xml new file mode 100644 index 0000000..71a6e26
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement. 3 Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 4 gates, wall segments of various length, etc.) --> 5 <Entity> 6 <Identity> 7 <Icon>structures/wall.png</Icon> 8 <Civ>hele</Civ> 9 <SpecificName>HELE WALLSET</SpecificName> 10 <History>All Hellenic cities were surrounded by stone walls for protection against enemy raids. Some of these fortifications, like the Athenian Long Walls, for example, were massive structures.</History> 11 <!-- TODO: Undesirable copy/pasta from the wall templates :( --> 12 <Classes datatype="tokens">Town Wall</Classes> 13 <GenericName>City Wall</GenericName> 14 <Tooltip>HELE WALLSET</Tooltip> 15 </Identity> 16 <WallSet> 17 <Tower>structures/hele_wall_tower</Tower> 18 <Gate>structures/hele_wall_gate</Gate> 19 <WallLong>structures/hele_wall_long</WallLong> 20 <WallMedium>structures/hele_wall_med</WallMedium> 21 <WallShort>structures/hele_wall_short</WallShort> 22 </WallSet> 23 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/wallsets/iber_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/iber_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/iber_stone.xml new file mode 100644 index 0000000..669298e
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement. 3 Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 4 gates, wall segments of various length, etc.) --> 5 <Entity> 6 <Identity> 7 <Icon>structures/wall.png</Icon> 8 <Civ>iber</Civ> 9 <SpecificName>Iber Wallset</SpecificName> 10 <!-- TODO: Undesirable copy/pasta from the wall templates :( --> 11 <Classes datatype="tokens">Town Wall</Classes> 12 <GenericName>City Wall</GenericName> 13 <Tooltip>(Iber WallSet)</Tooltip> 14 </Identity> 15 <WallSet> 16 <Tower>structures/iber_wall_tower</Tower> 17 <Gate>structures/iber_wall_gate</Gate> 18 <WallLong>structures/iber_wall_long</WallLong> 19 <WallMedium>structures/iber_wall_medium</WallMedium> 20 <WallShort>structures/iber_wall_short</WallShort> 21 </WallSet> 22 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/wallsets/pers_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/pers_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/pers_stone.xml new file mode 100644 index 0000000..4ec9533
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement. 3 Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 4 gates, wall segments of various length, etc.) --> 5 <Entity> 6 <Identity> 7 <Icon>structures/wall.png</Icon> 8 <Civ>iber</Civ> 9 <SpecificName>Persian WallSet</SpecificName> 10 <!-- TODO: Undesirable copy/pasta from the wall templates :( --> 11 <Classes datatype="tokens">Town Wall</Classes> 12 <GenericName>City Wall</GenericName> 13 <Tooltip>Persian WallSet</Tooltip> 14 </Identity> 15 <WallSet> 16 <Tower>structures/pers_wall_tower</Tower> 17 <Gate>structures/pers_wall_gate</Gate> 18 <WallLong>structures/pers_wall_long</WallLong> 19 <WallMedium>structures/pers_wall_medium</WallMedium> 20 <WallShort>structures/pers_wall_short</WallShort> 21 </WallSet> 22 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/wallsets/rome_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/rome_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/rome_stone.xml new file mode 100644 index 0000000..d5ebab1
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement. 3 Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 4 gates, wall segments of various length, etc.) --> 5 <Entity> 6 <Identity> 7 <Icon>structures/wall.png</Icon> 8 <Civ>iber</Civ> 9 <SpecificName>Roman WallSet</SpecificName> 10 <!-- TODO: Undesirable copy/pasta from the wall templates :( --> 11 <Classes datatype="tokens">Town Wall</Classes> 12 <GenericName>City Wall</GenericName> 13 <Tooltip>Roman WallSet</Tooltip> 14 </Identity> 15 <WallSet> 16 <Tower>structures/rome_wall_tower</Tower> 17 <Gate>structures/rome_wall_gate</Gate> 18 <WallLong>structures/rome_wall_long</WallLong> 19 <WallMedium>structures/rome_wall_medium</WallMedium> 20 <WallShort>structures/rome_wall_short</WallShort> 21 </WallSet> 22 </Entity> -
binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml index 4d51082..3ef5b3a 100644
a b 20 20 structures/{civ}_dock 21 21 structures/{civ}_outpost 22 22 structures/{civ}_defense_tower 23 structures/{civ}_wall 24 structures/{civ}_wall_tower 25 structures/{civ}_wall_gate 23 <!-- TODO: add stone wallset here? do all civs have stone wallsets? --> 26 24 structures/{civ}_fortress 27 25 </Entities> 28 26 </Builder> -
binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml b/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml index c90ad5f..8db4942 100644
a b 19 19 structures/{civ}_barracks 20 20 structures/{civ}_dock 21 21 structures/{civ}_scout_tower 22 structures/{civ}_wall 23 structures/{civ}_wall_tower 24 structures/{civ}_wall_gate 22 special/wallsets/{civ}_land 25 23 structures/{civ}_fortress 26 24 </Entities> 27 25 </Builder> -
binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml
diff --git a/binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml b/binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml index 096de32..ffd1fca 100644
a b 12 12 <Hack>60.0</Hack> 13 13 </Charge> 14 14 </Attack> 15 <!-- TODO: remove me; temporarily here for ease of playing with the wall system --> 16 <Builder> 17 <Rate>50.0</Rate> 18 <Entities datatype="tokens"> 19 structures/wallsets/{civ}_palisade 20 structures/wallsets/iber_stone 21 structures/wallsets/pers_stone 22 structures/wallsets/hele_stone 23 structures/wallsets/rome_stone 24 </Entities> 25 </Builder> 15 26 <Cost> 16 27 <Resources> 17 28 <food>0</food> -
source/graphics/Overlay.h
diff --git a/binaries/system/ActorEditor.exe b/binaries/system/ActorEditor.exe deleted file mode 100644 index 15cdfc5..0000000 Binary files a/binaries/system/ActorEditor.exe and /dev/null differ diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h index ce6f7c4..6d0dfe7 100644
a b 21 21 #include "graphics/RenderableObject.h" 22 22 #include "graphics/Texture.h" 23 23 #include "maths/Vector3D.h" 24 #include "maths/FixedVector3D.h" 24 25 #include "ps/Overlay.h" // CColor (TODO: that file has nothing to do with overlays, it should be renamed) 25 26 26 27 class CTerrain; … … struct SOverlayLine 37 38 std::vector<float> m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed 38 39 u8 m_Thickness; // pixels 39 40 40 /// Utility function; pushes three vertex coordinates at once onto the coordinates array41 41 void PushCoords(const float x, const float y, const float z) { m_Coords.push_back(x); m_Coords.push_back(y); m_Coords.push_back(z); } 42 /// Utility function; pushes a vertex location onto the coordinates array43 42 void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); } 43 void PushCoords(const CFixedVector3D& v) { PushCoords(v.X.ToFloat(), v.Y.ToFloat(), v.Z.ToFloat()); } 44 44 }; 45 45 46 46 /** -
source/gui/scripting/ScriptFunctions.cpp
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 4fad858..a097b17 100644
a b std::vector<entity_id_t> PickFriendlyEntitiesOnScreen(void* cbdata, int player) 141 141 return PickFriendlyEntitiesInRect(cbdata, 0, 0, g_xres, g_yres, player); 142 142 } 143 143 144 std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank )144 std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations) 145 145 { 146 return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false );146 return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations); 147 147 } 148 148 149 149 CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y) … … void GuiScriptingInit(ScriptInterface& scriptInterface) 574 574 scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint"); 575 575 scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect"); 576 576 scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen"); 577 scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");577 scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities"); 578 578 scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtPoint>("GetTerrainAtPoint"); 579 579 580 580 // Network / game setup functions -
source/lib/self_test.h
diff --git a/source/lib/self_test.h b/source/lib/self_test.h index 889616a..27e3870 100644
a b std::vector<T> ts_make_vector(T* start, size_t size_bytes) 279 279 return std::vector<T>(start, start+(size_bytes/sizeof(T))); 280 280 } 281 281 #define TS_ASSERT_VECTOR_EQUALS_ARRAY(vec1, array) TS_ASSERT_EQUALS(vec1, ts_make_vector((array), sizeof(array))) 282 #define TS_ASSERT_VECTOR_CONTAINS(vec1, element) TS_ASSERT(std::find((vec1).begin(), (vec1).end(), element) != (vec1).end()); 282 283 283 284 class ScriptInterface; 284 285 // Script-based testing setup (defined in test_setup.cpp). Defines TS_* functions. -
source/simulation2/components/CCmpObstruction.cpp
diff --git a/source/simulation2/components/CCmpObstruction.cpp b/source/simulation2/components/CCmpObstruction.cpp index 92325ba..cd23aab 100644
a b 20 20 #include "simulation2/system/Component.h" 21 21 #include "ICmpObstruction.h" 22 22 23 #include "ps/CLogger.h" 24 #include "simulation2/MessageTypes.h" 23 25 #include "simulation2/components/ICmpObstructionManager.h" 24 26 #include "simulation2/components/ICmpPosition.h" 25 27 26 #include "simulation2/MessageTypes.h"27 28 28 /** 29 29 * Obstruction implementation. This keeps the ICmpPathfinder's model of the world updated when the 30 30 * entities move and die, with shapes derived from ICmpFootprint. … … public: 49 49 STATIC, 50 50 UNIT 51 51 } m_Type; 52 52 53 entity_pos_t m_Size0; // radius or width 53 54 entity_pos_t m_Size1; // radius or depth 54 55 flags_t m_TemplateFlags; 55 56 56 57 // Dynamic state: 57 58 58 bool m_Active; // whether the obstruction is obstructing or just an inactive placeholder 59 /// Whether the obstruction is actively obstructing or just an inactive placeholder 60 bool m_Active; 59 61 bool m_Moving; 62 63 /** 64 * Unique identifier for grouping obstruction shapes, typically to have member shapes ignore 65 * each other during obstruction tests. Defaults to the entity ID. 66 * 67 * TODO: if needed, perhaps add a mask to specify with respect to which flags members of the 68 * group should ignore each other. 69 */ 60 70 entity_id_t m_ControlGroup; 71 entity_id_t m_ControlGroup2; 72 73 /// Identifier of this entity's obstruction shape. Contains structure, but should be treated 74 /// as opaque here. 61 75 tag_t m_Tag; 76 /// Set of flags affecting the behaviour of this entity's obstruction shape. 62 77 flags_t m_Flags; 63 78 64 79 static std::string GetSchema() … … public: 139 154 m_Tag = tag_t(); 140 155 m_Moving = false; 141 156 m_ControlGroup = GetEntityId(); 157 m_ControlGroup2 = INVALID_ENTITY; 142 158 } 143 159 144 160 virtual void Deinit() … … public: 194 210 // Need to create a new pathfinder shape: 195 211 if (m_Type == STATIC) 196 212 m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), 197 data.x, data.z, data.a, m_Size0, m_Size1, m_Flags );213 data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); 198 214 else 199 215 m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), 200 216 data.x, data.z, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); … … public: 241 257 if (!cmpPosition->IsInWorld()) 242 258 return; // don't need an obstruction 243 259 260 // TODO: code duplication from message handlers 244 261 CFixedVector2D pos = cmpPosition->GetPosition2D(); 245 262 if (m_Type == STATIC) 246 263 m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), 247 pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags );264 pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); 248 265 else 249 266 m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), 250 267 pos.X, pos.Y, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); … … public: 255 272 256 273 // Delete the obstruction shape 257 274 275 // TODO: code duplication from message handlers 258 276 if (m_Tag.valid()) 259 277 { 260 278 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); … … public: 346 364 if (!cmpPathfinder) 347 365 return false; // error 348 366 367 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter 368 if (m_ControlGroup == INVALID_ENTITY) 369 { 370 LOGERROR(L"[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid"); 371 return false; 372 } 373 349 374 // Get passability class 350 375 ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className); 351 376 352 // Ignore collisions with self, or with non-foundation-blocking obstructions 353 SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); 377 // Ignore collisions within the same control group, or with other non-foundation-blocking shapes. 378 // Note that, since the control group for each entity defaults to the entity's ID, this is typically 379 // equivalent to only ignoring the entity's own shape and other non-foundation-blocking shapes. 380 SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, 381 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); 354 382 355 383 if (m_Type == STATIC) 356 384 return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass); … … public: 375 403 if (!cmpObstructionManager) 376 404 return ret; // error 377 405 378 // Ignore collisions with self, or with non-construction-blocking obstructions 379 SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); 406 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter 407 if (m_ControlGroup == INVALID_ENTITY) 408 { 409 LOGERROR(L"[CmpObstruction] Cannot test for construction obstructions; primary control group must be valid"); 410 return ret; 411 } 412 413 // Ignore collisions within the same control group, or with other non-construction-blocking shapes. 414 // Note that, since the control group for each entity defaults to the entity's ID, this is typically 415 // equivalent to only ignoring the entity's own shape and other non-construction-blocking shapes. 416 SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, 417 ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); 380 418 381 419 if (m_Type == STATIC) 382 420 cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); … … public: 401 439 virtual void SetControlGroup(entity_id_t group) 402 440 { 403 441 m_ControlGroup = group; 442 UpdateControlGroups(); 443 } 404 444 405 if (m_Tag.valid() && m_Type == UNIT) 445 virtual void SetControlGroup2(entity_id_t group2) 446 { 447 m_ControlGroup2 = group2; 448 UpdateControlGroups(); 449 } 450 451 virtual entity_id_t GetControlGroup() 452 { 453 return m_ControlGroup; 454 } 455 456 virtual entity_id_t GetControlGroup2() 457 { 458 return m_ControlGroup2; 459 } 460 461 void UpdateControlGroups() 462 { 463 if (m_Tag.valid()) 406 464 { 407 465 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 408 466 if (cmpObstructionManager) 409 cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); 467 { 468 if (m_Type == UNIT) 469 { 470 cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); 471 } 472 else if (m_Type == STATIC) 473 { 474 cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); 475 } 476 } 410 477 } 411 478 } 412 479 -
source/simulation2/components/CCmpObstructionManager.cpp
diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index e1a8d6a..4836eab 100644
a b 1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 32 32 #include "ps/Overlay.h" 33 33 #include "ps/Profile.h" 34 34 #include "renderer/Scene.h" 35 #include "ps/CLogger.h" 35 36 36 37 // Externally, tags are opaque non-zero positive integers. 37 38 // Internally, they are tagged (by shape) indexes into shape lists. … … struct StaticShape 65 66 CFixedVector2D u, v; // orthogonal unit vectors - axes of local coordinate space 66 67 entity_pos_t hw, hh; // half width/height in local coordinate space 67 68 ICmpObstructionManager::flags_t flags; 69 entity_id_t group; 70 entity_id_t group2; 68 71 }; 69 72 70 73 /** … … struct SerializeStaticShape 102 105 serialize.NumberFixed_Unbounded("hw", value.hw); 103 106 serialize.NumberFixed_Unbounded("hh", value.hh); 104 107 serialize.NumberU8_Unbounded("flags", value.flags); 108 serialize.NumberU32_Unbounded("group", value.group); 105 109 } 106 110 }; 107 111 … … public: 257 261 return UNIT_INDEX_TO_TAG(id); 258 262 } 259 263 260 virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags )264 virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 /* = INVALID_ENTITY */) 261 265 { 262 266 fixed s, c; 263 267 sincos_approx(a, s, c); 264 268 CFixedVector2D u(c, -s); 265 269 CFixedVector2D v(s, c); 266 270 267 StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags };271 StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags, group, group2 }; 268 272 u32 id = m_StaticShapeNext++; 269 273 m_StaticShapes[id] = shape; 270 274 MakeDirtyStatic(flags); … … public: 367 371 } 368 372 } 369 373 374 virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) 375 { 376 ENSURE(TAG_IS_VALID(tag) && TAG_IS_STATIC(tag)); 377 378 if (TAG_IS_STATIC(tag)) 379 { 380 StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)]; 381 shape.group = group; 382 shape.group2 = group2; 383 } 384 } 385 370 386 virtual void RemoveShape(tag_t tag) 371 387 { 372 388 ENSURE(TAG_IS_VALID(tag)); … … bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti 533 549 std::map<u32, UnitShape>::iterator it = m_UnitShapes.find(unitShapes[i]); 534 550 ENSURE(it != m_UnitShapes.end()); 535 551 536 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))552 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 537 553 continue; 538 554 539 555 CFixedVector2D center(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti 548 564 std::map<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]); 549 565 ENSURE(it != m_StaticShapes.end()); 550 566 551 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))567 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 552 568 continue; 553 569 554 570 CFixedVector2D center(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestStaticShape(const IObstructionTestFilter& filte 592 608 593 609 for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it) 594 610 { 595 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))611 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 596 612 continue; 597 613 598 614 CFixedVector2D center1(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestStaticShape(const IObstructionTestFilter& filte 608 624 609 625 for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it) 610 626 { 611 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))627 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 612 628 continue; 613 629 614 630 CFixedVector2D center1(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, 649 665 650 666 for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it) 651 667 { 652 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))668 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 653 669 continue; 654 670 655 671 entity_pos_t r1 = it->second.r; … … bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, 665 681 666 682 for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it) 667 683 { 668 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))684 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 669 685 continue; 670 686 671 687 CFixedVector2D center1(it->second.x, it->second.z); … … void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter 869 885 std::map<u32, UnitShape>::iterator it = m_UnitShapes.find(unitShapes[i]); 870 886 ENSURE(it != m_UnitShapes.end()); 871 887 872 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))888 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 873 889 continue; 874 890 875 891 entity_pos_t r = it->second.r; … … void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter 890 906 std::map<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]); 891 907 ENSURE(it != m_StaticShapes.end()); 892 908 893 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))909 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 894 910 continue; 895 911 896 912 entity_pos_t r = it->second.hw + it->second.hh; // overestimate the max dist of an edge from the center -
source/simulation2/components/CCmpRallyPointRenderer.cpp
diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp index dc16628..5e43033 100644
a b void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>& 870 870 // process from there on until the entire line is checked. The output is the array of base nodes. 871 871 872 872 std::vector<CVector2D> newCoords; 873 StationaryO bstructionFilter obstructionFilter;873 StationaryOnlyObstructionFilter obstructionFilter; 874 874 entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness); 875 875 ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass); 876 876 -
source/simulation2/components/ICmpObstruction.cpp
diff --git a/source/simulation2/components/ICmpObstruction.cpp b/source/simulation2/components/ICmpObstruction.cpp index ab8324e..15509e9 100644
a b 1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool) 29 29 DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool) 30 30 DEFINE_INTERFACE_METHOD_0("GetBlockMovementFlag", bool, ICmpObstruction, GetBlockMovementFlag) 31 31 DEFINE_INTERFACE_METHOD_1("SetControlGroup", void, ICmpObstruction, SetControlGroup, entity_id_t) 32 DEFINE_INTERFACE_METHOD_0("GetControlGroup", entity_id_t, ICmpObstruction, GetControlGroup) 33 DEFINE_INTERFACE_METHOD_1("SetControlGroup2", void, ICmpObstruction, SetControlGroup2, entity_id_t) 34 DEFINE_INTERFACE_METHOD_0("GetControlGroup2", entity_id_t, ICmpObstruction, GetControlGroup2) 32 35 END_INTERFACE_WRAPPER(Obstruction) -
source/simulation2/components/ICmpObstruction.h
diff --git a/source/simulation2/components/ICmpObstruction.h b/source/simulation2/components/ICmpObstruction.h index f8693e7..4964b65 100644
a b 1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … public: 44 44 /** 45 45 * Test whether this entity is colliding with any obstruction that are set to 46 46 * block the creation of foundations. 47 * @param ignoredEntities List of entities to ignore during the test. 47 48 * @return true if foundation is valid (not obstructed) 48 49 */ 49 50 virtual bool CheckFoundation(std::string className) = 0; … … public: 70 71 */ 71 72 virtual void SetControlGroup(entity_id_t group) = 0; 72 73 74 /// See SetControlGroup. 75 virtual entity_id_t GetControlGroup() = 0; 76 77 virtual void SetControlGroup2(entity_id_t group2) = 0; 78 virtual entity_id_t GetControlGroup2() = 0; 79 73 80 DECLARE_INTERFACE_TYPE(Obstruction) 74 81 }; 75 82 -
source/simulation2/components/ICmpObstructionManager.h
diff --git a/source/simulation2/components/ICmpObstructionManager.h b/source/simulation2/components/ICmpObstructionManager.h index b81a6d1..9a6507c 100644
a b 1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … public: 93 93 94 94 /** 95 95 * Register a static shape. 96 * 96 97 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none) 97 98 * @param x,z coordinates of center, in world space 98 99 * @param a angle of rotation (clockwise from +Z direction) 99 100 * @param w width (size along X axis) 100 101 * @param h height (size along Z axis) 101 102 * @param flags a set of EFlags values 103 * @param group primary control group of the shape. Must be a valid control group ID. 104 * @param group2 Optional; secondary control group of the shape. Defaults to INVALID_ENTITY. 102 105 * @return a valid tag for manipulating the shape 103 106 * @see StaticShape 104 107 */ 105 virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags) = 0; 108 virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, 109 entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 = INVALID_ENTITY) = 0; 106 110 107 111 /** 108 112 * Register a unit shape. 113 * 109 114 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none) 110 115 * @param x,z coordinates of center, in world space 111 116 * @param r radius of circle or half the unit's width/height … … public: 115 120 * @return a valid tag for manipulating the shape 116 121 * @see UnitShape 117 122 */ 118 virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t r, flags_t flags, entity_id_t group) = 0; 123 virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t r, flags_t flags, 124 entity_id_t group) = 0; 119 125 120 126 /** 121 127 * Adjust the position and angle of an existing shape. … … public: 141 147 virtual void SetUnitControlGroup(tag_t tag, entity_id_t group) = 0; 142 148 143 149 /** 150 * Sets the control group of a static shape. 151 * @param tag Tag of the shape to set the control group for. Must be a valid and static shape tag. 152 * @param group Control group entity ID. 153 */ 154 virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) = 0; 155 156 /** 144 157 * Remove an existing shape. The tag will be made invalid and must not be used after this. 145 158 * @param tag tag of shape (must be valid) 146 159 */ … … public: 162 175 163 176 /** 164 177 * Collision test a static square shape against the current set of shapes. 165 * @param filter filter to restrict the shapes that are counted178 * @param filter filter to restrict the shapes that are being tested against 166 179 * @param x X coordinate of center 167 180 * @param z Z coordinate of center 168 181 * @param a angle of rotation (clockwise from +Z direction) … … public: 176 189 std::vector<entity_id_t>* out) = 0; 177 190 178 191 /** 179 * Collision test a unit shape against the current set of shapes. 180 * @param filter filter to restrict the shapes that are counted 181 * @param x X coordinate of center 182 * @param z Z coordinate of center 183 * @param r radius (half the unit's width/height) 192 * Collision test a unit shape against the current set of registered shapes, and optionally writes a list of the colliding 193 * shapes' entities to an output list. 194 * 195 * @param filter filter to restrict the shapes that are being tested against 196 * @param x X coordinate of shape's center 197 * @param z Z coordinate of shape's center 198 * @param r radius of the shape (half the unit's width/height) 184 199 * @param out if non-NULL, all colliding shapes' entities will be added to this list 200 * 185 201 * @return true if there is a collision 186 202 */ 187 203 virtual bool TestUnitShape(const IObstructionTestFilter& filter, … … public: 274 290 virtual ~IObstructionTestFilter() {} 275 291 276 292 /** 277 * Return true if the shape should be counted for collisions.293 * Return true if the shape with the specified parameters should be tested for collisions. 278 294 * This is called for all shapes that would collide, and also for some that wouldn't. 295 * 279 296 * @param tag tag of shape being tested 280 297 * @param flags set of EFlags for the shape 281 * @param group the control group (typically the shape's unit, or the unit's formation controller, or 0) 298 * @param group the control group of the shape (typically the shape's unit, or the unit's formation controller, or 0) 299 * @param group2 an optional secondary control group of the shape, or INVALID_ENTITY if none specified. Currently 300 * exists only for static shapes. 282 301 */ 283 virtual bool Allowed(tag_t tag, flags_t flags, entity_id_t group) const = 0;302 virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const = 0; 284 303 }; 285 304 286 305 /** 287 * Obstruction test filter that acceptsall shapes.306 * Obstruction test filter that will test against all shapes. 288 307 */ 289 308 class NullObstructionFilter : public IObstructionTestFilter 290 309 { 291 310 public: 292 virtual bool Allowed(tag_t UNUSED(tag), flags_t UNUSED(flags), entity_id_t UNUSED(group)) const311 virtual bool TestShape(tag_t UNUSED(tag), flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const 293 312 { 294 313 return true; 295 314 } 296 315 }; 297 316 298 317 /** 299 * Obstruction test filter that accepts all non-movingshapes.318 * Obstruction test filter that will test only against stationary (i.e. non-moving) shapes. 300 319 */ 301 class StationaryO bstructionFilter : public IObstructionTestFilter320 class StationaryOnlyObstructionFilter : public IObstructionTestFilter 302 321 { 303 322 public: 304 virtual bool Allowed(tag_t UNUSED(tag), flags_t flags, entity_id_t UNUSED(group)) const323 virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const 305 324 { 306 325 return !(flags & ICmpObstructionManager::FLAG_MOVING); 307 326 } … … public: 309 328 310 329 /** 311 330 * Obstruction test filter that reject shapes in a given control group, 312 * and optionally rejects moving shapes, 313 * and rejects shapes that don't block unit movement. 331 * and rejects shapes that don't block unit movement, and optionally rejects moving shapes. 314 332 */ 315 333 class ControlGroupMovementObstructionFilter : public IObstructionTestFilter 316 334 { 317 335 bool m_AvoidMoving; 318 336 entity_id_t m_Group; 337 319 338 public: 320 339 ControlGroupMovementObstructionFilter(bool avoidMoving, entity_id_t group) : 321 340 m_AvoidMoving(avoidMoving), m_Group(group) 322 { 323 } 341 {} 324 342 325 virtual bool Allowed(tag_t UNUSED(tag), flags_t flags, entity_id_t group) const343 virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const 326 344 { 327 if (group == m_Group )345 if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group)) 328 346 return false; 329 347 if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT)) 330 348 return false; … … public: 335 353 }; 336 354 337 355 /** 338 * Obstruction test filter that rejects a specific shape. 356 * Obstruction test filter that will test only against shapes that: 357 * - are part of neither one of the specified control groups 358 * - AND have at least one of the specified flags set. 359 * 360 * The first (primary) control group to reject shapes from must be specified and valid. Set the 361 * secondary control group to INVALID_ENTITY to use only the first. 362 * 363 * This filter is useful to e.g. allow foundations within the same control group to be placed and 364 * constructed arbitrarily close together (e.g. for wall pieces that need to link up tightly). 365 */ 366 class SkipControlGroupsRequireFlagObstructionFilter : public IObstructionTestFilter 367 { 368 entity_id_t m_Group; 369 entity_id_t m_Group2; 370 flags_t m_Mask; 371 372 public: 373 SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1, entity_id_t group2, flags_t mask) : 374 m_Group(group1), m_Group2(group2), m_Mask(mask) 375 { 376 // the primary control group to filter out must be valid 377 ENSURE(m_Group != INVALID_ENTITY); 378 379 // for simplicity, if m_Group2 is INVALID_ENTITY (i.e. not used), then set it equal to m_Group 380 // so that we have fewer special cases to consider in TestShape(). 381 if (m_Group2 == INVALID_ENTITY) 382 m_Group2 = m_Group; 383 } 384 385 virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const 386 { 387 // to be included in the testing, a shape must have at least one of the flags in m_Mask set, and its 388 // primary control group must be valid and must equal neither our primary nor secondary control group. 389 bool includeInTesting = ((flags & m_Mask) != 0 && group != m_Group && group != m_Group2); 390 391 // if the shape being tested has a valid secondary control group, exclude it from testing if it 392 // matches either our primary or secondary control group. 393 if (group2 != INVALID_ENTITY) 394 includeInTesting = (includeInTesting && group2 != m_Group && group2 != m_Group2); 395 396 return includeInTesting; 397 } 398 }; 399 400 /** 401 * Obstruction test filter that will test only against shapes that do not have the specified tag set. 339 402 */ 340 403 class SkipTagObstructionFilter : public IObstructionTestFilter 341 404 { … … public: 345 408 { 346 409 } 347 410 348 virtual bool Allowed(tag_t tag, flags_t UNUSED(flags), entity_id_t UNUSED(group)) const411 virtual bool TestShape(tag_t tag, flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const 349 412 { 350 413 return tag.n != m_Tag.n; 351 414 } 352 415 }; 353 416 354 417 /** 355 * Obstruction test filter that rejects a specific shape, and requires the given flags. 418 * Obstruction test filter that will test only against shapes that: 419 * - do not have the specified tag 420 * - AND have at least one of the specified flags set. 356 421 */ 357 class SkipTag FlagsObstructionFilter : public IObstructionTestFilter422 class SkipTagRequireFlagsObstructionFilter : public IObstructionTestFilter 358 423 { 359 424 tag_t m_Tag; 360 425 flags_t m_Mask; 361 426 public: 362 SkipTag FlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask)427 SkipTagRequireFlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask) 363 428 { 364 429 } 365 430 366 virtual bool Allowed(tag_t tag, flags_t flags, entity_id_t UNUSED(group)) const431 virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const 367 432 { 368 433 return (tag.n != m_Tag.n && (flags & m_Mask) != 0); 369 434 } -
new file source/simulation2/components/tests/test_ObstructionManager.h
diff --git a/source/simulation2/components/tests/test_ObstructionManager.h b/source/simulation2/components/tests/test_ObstructionManager.h new file mode 100644 index 0000000..f327e1a
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "simulation2/system/ComponentTest.h" 19 20 #include "simulation2/components/ICmpObstructionManager.h" 21 22 class TestCmpObstructionManager : public CxxTest::TestSuite 23 { 24 typedef ICmpObstructionManager::tag_t tag_t; 25 typedef ICmpObstructionManager::ObstructionSquare ObstructionSquare; 26 27 // some variables for setting up a scene with 3 shapes 28 entity_id_t ent1, ent2, ent3; // entity IDs 29 entity_angle_t ent1a, ent2r, ent3r; // angles/radiuses 30 entity_pos_t ent1x, ent1z, ent1w, ent1h, // positions/dimensions 31 ent2x, ent2z, 32 ent3x, ent3z; 33 entity_id_t ent1g1, ent1g2, ent2g, ent3g; // control groups 34 35 tag_t shape1, shape2, shape3; 36 37 ICmpObstructionManager* cmp; 38 ComponentTestHelper* testHelper; 39 40 public: 41 void setUp() 42 { 43 CXeromyces::Startup(); 44 CxxTest::setAbortTestOnFail(true); 45 46 // set up a simple scene with some predefined obstruction shapes 47 // (we can't position shapes on the origin because the world bounds must range 48 // from 0 to X, so instead we'll offset things by, say, 10). 49 50 ent1 = 1; 51 ent1a = fixed::Zero(); 52 ent1w = fixed::FromFloat(4); 53 ent1h = fixed::FromFloat(2); 54 ent1x = fixed::FromInt(10); 55 ent1z = fixed::FromInt(10); 56 ent1g1 = ent1; 57 ent1g2 = INVALID_ENTITY; 58 59 ent2 = 2; 60 ent2r = fixed::FromFloat(1); 61 ent2x = ent1x; 62 ent2z = ent1z; 63 ent2g = ent1g1; 64 65 ent3 = 3; 66 ent3r = fixed::FromFloat(3); 67 ent3x = ent2x; 68 ent3z = ent2z + ent2r + ent3r; // ensure it just touches the border of ent2 69 ent3g = ent3; 70 71 testHelper = new ComponentTestHelper; 72 cmp = testHelper->Add<ICmpObstructionManager>(CID_ObstructionManager, ""); 73 cmp->SetBounds(fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(100), fixed::FromInt(100)); 74 75 shape1 = cmp->AddStaticShape(ent1, ent1x, ent1z, ent1a, ent1w, ent1h, 76 ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION | 77 ICmpObstructionManager::FLAG_BLOCK_MOVEMENT | 78 ICmpObstructionManager::FLAG_MOVING, ent1g1, ent1g2); 79 80 shape2 = cmp->AddUnitShape(ent2, ent2x, ent2z, ent2r, 81 ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION | 82 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent2g); 83 84 shape3 = cmp->AddUnitShape(ent3, ent3x, ent3z, ent3r, 85 ICmpObstructionManager::FLAG_BLOCK_MOVEMENT | 86 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent3g); 87 } 88 89 void tearDown() 90 { 91 delete testHelper; 92 cmp = NULL; // not our responsibility to deallocate 93 94 CXeromyces::Terminate(); 95 } 96 97 /** 98 * Verifies the collision testing procedure. Collision-tests some simple shapes against the shapes registered in 99 * the scene, and verifies the result of the test against the expected value. 100 */ 101 void test_collision_simple() 102 { 103 std::vector<entity_id_t> out; 104 NullObstructionFilter nullFilter; 105 106 // Collision-test a simple shape nested inside shape3 against all shapes in the scene. Since the tested shape 107 // overlaps only with shape 3, we should find only shape 3 in the result. 108 109 cmp->TestUnitShape(nullFilter, ent3x, ent3z, fixed::FromInt(1), &out); 110 TS_ASSERT_EQUALS(1, out.size()); 111 TS_ASSERT_EQUALS(ent3, out[0]); 112 out.clear(); 113 114 cmp->TestStaticShape(nullFilter, ent3x, ent3z, fixed::Zero(), fixed::FromInt(1), fixed::FromInt(1), &out); 115 TS_ASSERT_EQUALS(1, out.size()); 116 TS_ASSERT_EQUALS(ent3, out[0]); 117 out.clear(); 118 119 // Similarly, collision-test a simple shape nested inside both shape1 and shape2. Since the tested shape overlaps 120 // only with shapes 1 and 2, those are the only ones we should find in the result. 121 122 cmp->TestUnitShape(nullFilter, ent2x, ent2z, ent2r/2, &out); 123 TS_ASSERT_EQUALS(2, out.size()); 124 TS_ASSERT_VECTOR_CONTAINS(out, ent1); 125 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 126 out.clear(); 127 128 cmp->TestStaticShape(nullFilter, ent2x, ent2z, fixed::Zero(), ent2r, ent2r, &out); 129 TS_ASSERT_EQUALS(2, out.size()); 130 TS_ASSERT_VECTOR_CONTAINS(out, ent1); 131 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 132 out.clear(); 133 } 134 135 /** 136 * Verifies the behaviour of the null obstruction filter. Tests with this filter will be performed against all 137 * registered shapes. 138 */ 139 void test_collision_filter_null() 140 { 141 std::vector<entity_id_t> out; 142 143 // Collision test a scene-covering shape against all shapes in the scene. We should find all registered shapes 144 // in the result. 145 146 NullObstructionFilter nullFilter; 147 148 cmp->TestUnitShape(nullFilter, ent1x, ent1z, fixed::FromInt(10), &out); 149 TS_ASSERT_EQUALS(3, out.size()); 150 TS_ASSERT_VECTOR_CONTAINS(out, ent1); 151 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 152 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 153 out.clear(); 154 155 cmp->TestStaticShape(nullFilter, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 156 TS_ASSERT_EQUALS(3, out.size()); 157 TS_ASSERT_VECTOR_CONTAINS(out, ent1); 158 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 159 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 160 out.clear(); 161 } 162 163 /** 164 * Verifies the behaviour of the StationaryOnlyObstructionFilter. Tests with this filter will be performed only 165 * against non-moving (stationary) shapes. 166 */ 167 void test_collision_filter_stationary_only() 168 { 169 std::vector<entity_id_t> out; 170 171 // Collision test a scene-covering shape against all shapes in the scene, but skipping shapes that are moving, 172 // i.e. shapes that have the MOVING flag. Since only shape 1 is flagged as moving, we should find 173 // shapes 2 and 3 in each case. 174 175 StationaryOnlyObstructionFilter ignoreMoving; 176 177 cmp->TestUnitShape(ignoreMoving, ent1x, ent1z, fixed::FromInt(10), &out); 178 TS_ASSERT_EQUALS(2, out.size()); 179 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 180 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 181 out.clear(); 182 183 cmp->TestStaticShape(ignoreMoving, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 184 TS_ASSERT_EQUALS(2, out.size()); 185 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 186 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 187 out.clear(); 188 } 189 190 /** 191 * Verifies the behaviour of the SkipTagObstructionFilter. Tests with this filter will be performed against 192 * all registered shapes that do not have the specified tag set. 193 */ 194 void test_collision_filter_skip_tag() 195 { 196 std::vector<entity_id_t> out; 197 198 // Collision-test shape 2's obstruction shape against all shapes in the scene, but skipping tests against 199 // shape 2. Since shape 2 overlaps only with shape 1, we should find only shape 1's entity ID in the result. 200 201 SkipTagObstructionFilter ignoreShape2(shape2); 202 203 cmp->TestUnitShape(ignoreShape2, ent2x, ent2z, ent2r/2, &out); 204 TS_ASSERT_EQUALS(1, out.size()); 205 TS_ASSERT_EQUALS(ent1, out[0]); 206 out.clear(); 207 208 cmp->TestStaticShape(ignoreShape2, ent2x, ent2z, fixed::Zero(), ent2r, ent2r, &out); 209 TS_ASSERT_EQUALS(1, out.size()); 210 TS_ASSERT_EQUALS(ent1, out[0]); 211 out.clear(); 212 } 213 214 /** 215 * Verifies the behaviour of the SkipTagFlagsObstructionFilter. Tests with this filter will be performed against 216 * all registered shapes that do not have the specified tag set, and that have at least one of required flags set. 217 */ 218 void test_collision_filter_skip_tag_require_flag() 219 { 220 std::vector<entity_id_t> out; 221 222 // Collision-test a scene-covering shape against all shapes in the scene, but skipping tests against shape 1 223 // and requiring the BLOCK_MOVEMENT flag. Since shape 1 is being ignored and shape 2 does not have the required 224 // flag, we should find only shape 3 in the results. 225 226 SkipTagRequireFlagsObstructionFilter skipShape1RequireBlockMovement(shape1, ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); 227 228 cmp->TestUnitShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::FromInt(10), &out); 229 TS_ASSERT_EQUALS(1, out.size()); 230 TS_ASSERT_EQUALS(ent3, out[0]); 231 out.clear(); 232 233 cmp->TestStaticShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 234 TS_ASSERT_EQUALS(1, out.size()); 235 TS_ASSERT_EQUALS(ent3, out[0]); 236 out.clear(); 237 238 // If we now do the same test, but require at least one of the entire set of available filters, we should find 239 // all shapes that are not shape 1 and that have at least one flag set. Since all shapes in our testing scene 240 // have at least one flag set, we should find shape 2 and shape 3 in the results. 241 242 SkipTagRequireFlagsObstructionFilter skipShape1RequireAnyFlag(shape1, (ICmpObstructionManager::flags_t) -1); 243 244 cmp->TestUnitShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out); 245 TS_ASSERT_EQUALS(2, out.size()); 246 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 247 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 248 out.clear(); 249 250 cmp->TestStaticShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 251 TS_ASSERT_EQUALS(2, out.size()); 252 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 253 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 254 out.clear(); 255 256 // And if we now do the same test yet again, but specify an empty set of flags, then it becomes impossible for 257 // any shape to have at least one of the required flags, and we should hence find no shapes in the result. 258 259 SkipTagRequireFlagsObstructionFilter skipShape1RejectAll(shape1, 0U); 260 261 cmp->TestUnitShape(skipShape1RejectAll, ent1x, ent1z, fixed::FromInt(10), &out); 262 TS_ASSERT_EQUALS(0, out.size()); 263 out.clear(); 264 265 cmp->TestStaticShape(skipShape1RejectAll, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 266 TS_ASSERT_EQUALS(0, out.size()); 267 out.clear(); 268 } 269 270 /** 271 * Verifies the behaviour of SkipControlGroupsRequireFlagObstructionFilter. Tests with this filter will be performed 272 * against all registered shapes that are members of neither specified control groups, and that have at least one of 273 * the specified flags set. 274 */ 275 void test_collision_filter_skip_controlgroups_require_flag() 276 { 277 std::vector<entity_id_t> out; 278 279 // Collision-test a shape that overlaps the entire scene, but ignoring shapes from shape1's control group 280 // (which also includes shape 2), and requiring that either the BLOCK_FOUNDATION or the 281 // BLOCK_CONSTRUCTION flag is set, or both. Since shape 1 and shape 2 both belong to shape 1's control 282 // group, and shape 3 has the BLOCK_FOUNDATION flag (but not BLOCK_CONSTRUCTION), we should find only 283 // shape 3 in the result. 284 285 SkipControlGroupsRequireFlagObstructionFilter skipGroup1ReqFoundConstr(ent1g1, INVALID_ENTITY, 286 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); 287 288 cmp->TestUnitShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out); 289 TS_ASSERT_EQUALS(1, out.size()); 290 TS_ASSERT_EQUALS(ent3, out[0]); 291 out.clear(); 292 293 cmp->TestStaticShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 294 TS_ASSERT_EQUALS(1, out.size()); 295 TS_ASSERT_EQUALS(ent3, out[0]); 296 out.clear(); 297 298 // Perform the same test, but now also exclude shape 3's control group (in addition to shape 1's control 299 // group). Despite shape 3 having at least one of the required flags set, it should now also be ignored, 300 // yielding an empty result set. 301 302 SkipControlGroupsRequireFlagObstructionFilter skipGroup1And3ReqFoundConstr(ent1g1, ent3g, 303 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); 304 305 cmp->TestUnitShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out); 306 TS_ASSERT_EQUALS(0, out.size()); 307 out.clear(); 308 309 cmp->TestStaticShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 310 TS_ASSERT_EQUALS(0, out.size()); 311 out.clear(); 312 313 // Same test, but this time excluding only shape 3's control group, and requiring any of the available flags 314 // to be set. Since both shape 1 and shape 2 have at least one flag set and are both in a different control 315 // group, we should find them in the result. 316 317 SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireAnyFlag(ent3g, INVALID_ENTITY, 318 (ICmpObstructionManager::flags_t) -1); 319 320 cmp->TestUnitShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out); 321 TS_ASSERT_EQUALS(2, out.size()); 322 TS_ASSERT_VECTOR_CONTAINS(out, ent1); 323 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 324 out.clear(); 325 326 cmp->TestStaticShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 327 TS_ASSERT_EQUALS(2, out.size()); 328 TS_ASSERT_VECTOR_CONTAINS(out, ent1); 329 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 330 out.clear(); 331 332 // Finally, the same test as the one directly above, now with an empty set of required flags. Since it now becomes 333 // impossible for shape 1 and shape 2 to have at least one of the required flags set, and shape 3 is excluded by 334 // virtue of the control group filtering, we should find an empty result. 335 336 SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireNoFlags(ent3g, INVALID_ENTITY, 0U); 337 338 cmp->TestUnitShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::FromInt(10), &out); 339 TS_ASSERT_EQUALS(0, out.size()); 340 out.clear(); 341 342 cmp->TestStaticShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 343 TS_ASSERT_EQUALS(0, out.size()); 344 out.clear(); 345 346 // ------------------------------------------------------------------------------------ 347 348 // In the tests up until this point, the shapes have all been filtered out based on their primary control group. 349 // Now, to verify that shapes are also filtered out based on their secondary control groups, add a fourth shape 350 // with arbitrarily-chosen dual control groups, and also change shape 1's secondary control group to another 351 // arbitrarily-chosen control group. Then, do a scene-covering collision test while filtering out a combination 352 // of shape 1's secondary control group, and one of shape 4's control groups. We should find neither ent1 nor ent4 353 // in the result. 354 355 entity_id_t ent4 = 4, 356 ent4g1 = 17, 357 ent4g2 = 19, 358 ent1g2_new = 18; // new secondary control group for entity 1 359 entity_pos_t ent4x = fixed::FromInt(4), 360 ent4z = fixed::Zero(), 361 ent4w = fixed::FromInt(1), 362 ent4h = fixed::FromInt(1); 363 entity_angle_t ent4a = fixed::FromDouble(M_PI/3); 364 365 cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, ent4g1, ent4g2); 366 cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2_new); 367 368 // Exclude shape 1's and shape 4's secondary control groups from testing, and require any available flag to be set. 369 // Since neither shape 2 nor shape 3 are part of those control groups and both have at least one available flag set, 370 // the results should only those two shapes' entities. 371 372 SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4SecRequireAny(ent1g2_new, ent4g2, 373 (ICmpObstructionManager::flags_t) -1); 374 375 cmp->TestUnitShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::FromInt(10), &out); 376 TS_ASSERT_EQUALS(2, out.size()); 377 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 378 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 379 out.clear(); 380 381 cmp->TestStaticShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 382 TS_ASSERT_EQUALS(2, out.size()); 383 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 384 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 385 out.clear(); 386 387 // Same as the above, but now exclude shape 1's secondary and shape 4's primary control group, while still requiring 388 // any available flag to be set. (Note that the test above used shape 4's secondary control group). Results should 389 // remain the same. 390 391 SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4PrimRequireAny(ent1g2_new, ent4g1, 392 (ICmpObstructionManager::flags_t) -1); 393 394 cmp->TestUnitShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::FromInt(10), &out); 395 TS_ASSERT_EQUALS(2, out.size()); 396 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 397 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 398 out.clear(); 399 400 cmp->TestStaticShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out); 401 TS_ASSERT_EQUALS(2, out.size()); 402 TS_ASSERT_VECTOR_CONTAINS(out, ent2); 403 TS_ASSERT_VECTOR_CONTAINS(out, ent3); 404 out.clear(); 405 406 cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2); // restore shape 1's original secondary control group 407 } 408 409 void test_adjacent_shapes() 410 { 411 std::vector<entity_id_t> out; 412 NullObstructionFilter nullFilter; 413 SkipTagObstructionFilter ignoreShape1(shape1); 414 SkipTagObstructionFilter ignoreShape2(shape2); 415 SkipTagObstructionFilter ignoreShape3(shape3); 416 417 // Collision-test a shape that is perfectly adjacent to shape3. This should be counted as a hit according to 418 // the code at the time of writing. 419 420 entity_angle_t ent4a = fixed::FromDouble(M_PI); // rotated 180 degrees, should not affect collision test 421 entity_pos_t ent4w = fixed::FromInt(2), 422 ent4h = fixed::FromInt(1), 423 ent4x = ent3x + ent3r + ent4w/2, // make ent4 adjacent to ent3 424 ent4z = ent3z; 425 426 cmp->TestStaticShape(nullFilter, ent4x, ent4z, ent4a, ent4w, ent4h, &out); 427 TS_ASSERT_EQUALS(1, out.size()); 428 TS_ASSERT_EQUALS(ent3, out[0]); 429 out.clear(); 430 431 cmp->TestUnitShape(nullFilter, ent4x, ent4z, ent4w/2, &out); 432 TS_ASSERT_EQUALS(1, out.size()); 433 TS_ASSERT_EQUALS(ent3, out[0]); 434 out.clear(); 435 436 // now do the same tests, but move the shape a little bit to the right so that it doesn't touch anymore 437 438 cmp->TestStaticShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4a, ent4w, ent4h, &out); 439 TS_ASSERT_EQUALS(0, out.size()); 440 out.clear(); 441 442 cmp->TestUnitShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4w/2, &out); 443 TS_ASSERT_EQUALS(0, out.size()); 444 out.clear(); 445 } 446 447 /** 448 * Verifies that fetching the registered shapes from the obstruction manager yields the correct results. 449 */ 450 void test_get_obstruction() 451 { 452 ObstructionSquare obSquare1 = cmp->GetObstruction(shape1); 453 ObstructionSquare obSquare2 = cmp->GetObstruction(shape2); 454 ObstructionSquare obSquare3 = cmp->GetObstruction(shape3); 455 456 TS_ASSERT_EQUALS(obSquare1.hh, ent1h/2); 457 TS_ASSERT_EQUALS(obSquare1.hw, ent1w/2); 458 TS_ASSERT_EQUALS(obSquare1.x, ent1x); 459 TS_ASSERT_EQUALS(obSquare1.z, ent1z); 460 TS_ASSERT_EQUALS(obSquare1.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0))); 461 TS_ASSERT_EQUALS(obSquare1.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1))); 462 463 TS_ASSERT_EQUALS(obSquare2.hh, ent2r); 464 TS_ASSERT_EQUALS(obSquare2.hw, ent2r); 465 TS_ASSERT_EQUALS(obSquare2.x, ent2x); 466 TS_ASSERT_EQUALS(obSquare2.z, ent2z); 467 TS_ASSERT_EQUALS(obSquare2.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0))); 468 TS_ASSERT_EQUALS(obSquare2.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1))); 469 470 TS_ASSERT_EQUALS(obSquare3.hh, ent3r); 471 TS_ASSERT_EQUALS(obSquare3.hw, ent3r); 472 TS_ASSERT_EQUALS(obSquare3.x, ent3x); 473 TS_ASSERT_EQUALS(obSquare3.z, ent3z); 474 TS_ASSERT_EQUALS(obSquare3.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0))); 475 TS_ASSERT_EQUALS(obSquare3.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1))); 476 } 477 }; 478 No newline at end of file -
source/simulation2/helpers/Selection.cpp
diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp index b33ab0d..7ab3259 100644
a b 27 27 #include "simulation2/components/ICmpTemplateManager.h" 28 28 #include "simulation2/components/ICmpSelectable.h" 29 29 #include "simulation2/components/ICmpVisual.h" 30 #include "ps/CLogger.h" 30 31 31 32 std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables) 32 33 { … … std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simul 161 162 return hitEnts; 162 163 } 163 164 164 std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables) 165 std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, 166 const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, 167 bool allowEditorSelectables, bool allowFoundations) 165 168 { 166 169 CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY); 167 170 CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY); … … std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu 179 182 180 183 if (matchRank) 181 184 { 182 // Exact template name matching 183 if (cmpTemplateManager->GetCurrentTemplateName(ent) != templateName) 185 // Exact template name matching, optionally also allowing foundations 186 std::string curTemplateName = cmpTemplateManager->GetCurrentTemplateName(ent); 187 bool matches = (curTemplateName == templateName || 188 (allowFoundations && curTemplateName.substr(0, 11) == "foundation|" && curTemplateName.substr(11) == templateName)); 189 if (!matches) 184 190 continue; 185 191 } 186 192 -
source/simulation2/helpers/Selection.h
diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h index d3a6a8e..484ee08 100644
a b std::vector<entity_id_t> PickEntitiesInRect(CSimulation2& simulation, const CCam 76 76 * @param matchRank if true, only entities that exactly match templateName will be selected, 77 77 * else entities with matching SelectionGroupName will be selected. 78 78 * @param allowEditorSelectables if true, all entities with the ICmpSelectable interface 79 * will be selected (including decorative actors), else only those selectable ingame. 79 * will be selected (including decorative actors), else only those selectable in-game. 80 * @param allowFoundations if true, foundations are also included in the results. Only takes 81 * effect when matchRank = true. 80 82 * 81 83 * @return unordered list of selected entities. 82 84 * @see ICmpIdentity 83 85 */ 84 std::vector<entity_id_t> PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables); 86 std::vector<entity_id_t> PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, 87 player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables, bool allowFoundations); 85 88 86 89 } // namespace 87 90 -
source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
diff --git a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp index 4567564..d36b2c9 100644
a b QUERYHANDLER(PickSimilarObjects) 488 488 if (cmpOwnership) 489 489 owner = cmpOwnership->GetOwner(); 490 490 491 msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, owner, false, true, true );491 msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, owner, false, true, true, false); 492 492 } 493 493 494 494