Ticket #786: 786_walls_04may12.patch
File 786_walls_04may12.patch, 223.2 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 cf50970..c0d71a2 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() 83 83 Engine.SetCursor("arrow-default"); 84 84 if (!tooltipSet) 85 85 informationTooltip.hidden = true; 86 87 var wallDragTooltip = getGUIObjectByName("wallDragTooltip"); 88 if (placementSupport.wallDragTooltip) 89 { 90 wallDragTooltip.caption = placementSupport.wallDragTooltip; 91 wallDragTooltip.hidden = false; 92 } 93 else 94 { 95 wallDragTooltip.caption = ""; 96 wallDragTooltip.hidden = true; 97 } 86 98 } 87 99 88 100 function updateBuildingPlacementPreview() 89 101 { 90 // The preview should be recomputed every turn, so that it responds 91 // to obstructions/fog/etc moving underneath it 102 // The preview should be recomputed every turn, so that it responds to obstructions/fog/etc moving underneath it, or 103 // in the case of the wall previews, in response to new tower foundations getting constructed for it to snap to. 104 // See onSimulationUpdate in session.js. 92 105 93 if (placement Entity && placementPosition)106 if (placementSupport.mode === "building") 94 107 { 95 return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { 96 "template": placementEntity, 97 "x": placementPosition.x, 98 "z": placementPosition.z, 99 "angle": placementAngle 100 }); 108 if (placementSupport.template && placementSupport.position) 109 { 110 return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { 111 "template": placementSupport.template, 112 "x": placementSupport.position.x, 113 "z": placementSupport.position.z, 114 "angle": placementSupport.angle, 115 }); 116 } 117 } 118 else if (placementSupport.mode === "wall") 119 { 120 if (placementSupport.wallSet && placementSupport.position) 121 { 122 // Fetch an updated list of snapping candidate entities 123 placementSupport.wallSnapEntities = Engine.PickSimilarFriendlyEntities( 124 placementSupport.wallSet.templates.tower, 125 placementSupport.wallSnapEntitiesIncludeOffscreen, 126 true, // require exact template match 127 true // include foundations 128 ); 129 130 return Engine.GuiInterfaceCall("SetWallPlacementPreview", { 131 "wallSet": placementSupport.wallSet, 132 "start": placementSupport.position, 133 "end": placementSupport.wallEndPosition, 134 "snapEntities": placementSupport.wallSnapEntities, // snapping entities (towers) for starting a wall segment 135 }); 136 } 101 137 } 102 138 103 139 return false; 104 140 } 105 141 106 function resetPlacementEntity()107 {108 Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});109 placementEntity = undefined;110 placementPosition = undefined;111 placementAngle = undefined;112 }113 114 142 function findGatherType(gatherer, supply) 115 143 { 116 144 if (!gatherer || !supply) … … var dragStart; // used for remembering mouse coordinates at start of drag operat 459 487 460 488 function tryPlaceBuilding(queued) 461 489 { 490 if (placementSupport.mode !== "building") 491 { 492 error("[tryPlaceBuilding] Called while in '"+placementSupport.mode+"' placement mode instead of 'building'"); 493 return false; 494 } 495 462 496 var selection = g_Selection.toList(); 463 497 464 498 // Use the preview to check it's a valid build location … … function tryPlaceBuilding(queued) 472 506 // Start the construction 473 507 Engine.PostNetworkCommand({ 474 508 "type": "construct", 475 "template": placement Entity,476 "x": placement Position.x,477 "z": placement Position.z,478 "angle": placement Angle,509 "template": placementSupport.template, 510 "x": placementSupport.position.x, 511 "z": placementSupport.position.z, 512 "angle": placementSupport.angle, 479 513 "entities": selection, 480 514 "autorepair": true, 481 515 "autocontinue": true, … … function tryPlaceBuilding(queued) 484 518 Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] }); 485 519 486 520 if (!queued) 487 resetPlacementEntity(); 521 placementSupport.Reset(); 522 523 return true; 524 } 525 526 function tryPlaceWall() 527 { 528 if (placementSupport.mode !== "wall") 529 { 530 error("[tryPlaceWall] Called while in '" + placementSupport.mode + "' placement mode; expected 'wall' mode"); 531 return false; 532 } 533 534 var wallPlacementInfo = updateBuildingPlacementPreview(); // entities making up the wall (wall segments, towers, ...) 535 if (!(wallPlacementInfo === false || typeof(wallPlacementInfo) === "object")) 536 { 537 error("[tryPlaceWall] Unexpected return value from updateBuildingPlacementPreview: '" + uneval(placementInfo) + "'; expected either 'false' or 'object'"); 538 return false; 539 } 540 541 if (!wallPlacementInfo) 542 return false; 543 544 var selection = g_Selection.toList(); 545 var cmd = { 546 "type": "construct-wall", 547 "autorepair": true, 548 "autocontinue": true, 549 "queued": true, 550 "entities": selection, 551 "wallSet": placementSupport.wallSet, 552 "pieces": wallPlacementInfo.pieces, 553 "startSnappedEntity": wallPlacementInfo.startSnappedEnt, 554 "endSnappedEntity": wallPlacementInfo.endSnappedEnt, 555 }; 556 557 //warn("-------------"); 558 //for (var k in cmd) 559 // warn(" " + k + ": " + uneval(cmd[k])); 560 //warn("-------------"); 561 562 // make sure that there's at least one non-tower entity getting built, to prevent silly edge cases where the start and end 563 // point are too close together for the algorithm to place a wall segment inbetween, and only the towers are being previewed 564 // (this is somewhat non-ideal and hardcode-ish) 565 var hasWallSegment = false; 566 for (var k in cmd.pieces) 567 { 568 if (cmd.pieces[k].template != cmd.wallSet.templates.tower) // TODO: hardcode-ish :( 569 hasWallSegment = true; 570 } 571 572 if (hasWallSegment) 573 { 574 Engine.PostNetworkCommand(cmd); 575 Engine.GuiInterfaceCall("PlaySound", {"name": "order_repair", "entity": selection[0] }); 576 } 488 577 489 578 return true; 490 579 } … … function handleInputBeforeGui(ev, hoveredObject) 684 773 if (ev.button == SDL_BUTTON_RIGHT) 685 774 { 686 775 // Cancel building 687 resetPlacementEntity(); 776 placementSupport.Reset(); 777 inputState = INPUT_NORMAL; 778 return true; 779 } 780 break; 781 } 782 break; 783 784 case INPUT_BUILDING_WALL_CLICK: 785 // User is mid-click in choosing a starting point for building a wall. The build process can still be cancelled at this point 786 // by right-clicking; releasing the left mouse button will 'register' the starting point and commence endpoint choosing mode. 787 switch (ev.type) 788 { 789 case "mousebuttonup": 790 if (ev.button === SDL_BUTTON_LEFT) 791 { 792 inputState = INPUT_BUILDING_WALL_PATHING; 793 return true; 794 } 795 break; 796 797 case "mousebuttondown": 798 if (ev.button == SDL_BUTTON_RIGHT) 799 { 800 // Cancel building 801 placementSupport.Reset(); 802 updateBuildingPlacementPreview(); 803 688 804 inputState = INPUT_NORMAL; 689 805 return true; 690 806 } … … function handleInputBeforeGui(ev, hoveredObject) 692 808 } 693 809 break; 694 810 811 case INPUT_BUILDING_WALL_PATHING: 812 // User has chosen a starting point for constructing the wall, and is now looking to set the endpoint. 813 // Right-clicking cancels wall building mode, left-clicking sets the endpoint and builds the wall and returns to 814 // normal input mode. Optionally, shift + left-clicking does not return to normal input, and instead allows the 815 // user to continue building walls. 816 switch (ev.type) 817 { 818 case "mousemotion": 819 placementSupport.wallEndPosition = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 820 821 // Update the building placement preview, and by extension, the list of snapping candidate entities for both (!) 822 // the ending point and the starting point to snap to. 823 // 824 // TODO: Note that here, we need to fetch all similar entities, including any offscreen ones, to support the case 825 // where the snap entity for the starting point has moved offscreen, or has been deleted/destroyed, or was a 826 // foundation and has been replaced with a completed entity since the user first chose it. Fetching all towers on 827 // the entire map instead of only the current screen might get expensive fast since walls all have a ton of towers 828 // in them. Might be useful to query only for entities within a certain range around the starting point and ending 829 // points. 830 831 placementSupport.wallSnapEntitiesIncludeOffscreen = true; 832 var result = updateBuildingPlacementPreview(); // includes an update of the snap entity candidates 833 834 if (result && result.cost) 835 { 836 placementSupport.wallDragTooltip = ""; 837 for (var resource in result.cost) 838 { 839 if (result.cost[resource] > 0) 840 placementSupport.wallDragTooltip += getCostComponentDisplayName(resource) + ": " + result.cost[resource] + "\n"; 841 } 842 } 843 844 break; 845 846 case "mousebuttondown": 847 if (ev.button == SDL_BUTTON_LEFT) 848 { 849 if (tryPlaceWall()) 850 { 851 if (Engine.HotkeyIsPressed("session.queue")) 852 { 853 // continue building, just set a new starting position where we left off 854 placementSupport.position = placementSupport.wallEndPosition; 855 placementSupport.wallEndPosition = undefined; 856 857 inputState = INPUT_BUILDING_WALL_CLICK; 858 } 859 else 860 { 861 placementSupport.Reset(); 862 inputState = INPUT_NORMAL; 863 } 864 } 865 else 866 { 867 placementSupport.wallDragTooltip = "Cannot build wall here!"; 868 } 869 870 updateBuildingPlacementPreview(); 871 return true; 872 } 873 else if (ev.button == SDL_BUTTON_RIGHT) 874 { 875 // reset to normal input mode 876 placementSupport.Reset(); 877 updateBuildingPlacementPreview(); 878 879 inputState = INPUT_NORMAL; 880 return true; 881 } 882 break; 883 } 884 break; 885 695 886 case INPUT_BUILDING_DRAG: 696 887 switch (ev.type) 697 888 { … … function handleInputBeforeGui(ev, hoveredObject) 702 893 if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta) 703 894 { 704 895 // Rotate in the direction of the mouse 705 var target = Engine.GetTerrainAt Point(ev.x, ev.y);706 placement Angle = Math.atan2(target.x - placementPosition.x, target.z - placementPosition.z);896 var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 897 placementSupport.angle = Math.atan2(target.x - placementSupport.position.x, target.z - placementSupport.position.z); 707 898 } 708 899 else 709 900 { 710 901 // If the mouse is near the center, snap back to the default orientation 711 placement Angle = defaultPlacementAngle;902 placementSupport.SetDefaultAngle(); 712 903 } 713 904 714 905 var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { 715 "template": placement Entity,716 "x": placement Position.x,717 "z": placement Position.z906 "template": placementSupport.template, 907 "x": placementSupport.position.x, 908 "z": placementSupport.position.z 718 909 }); 719 910 if (snapData) 720 911 { 721 placement Angle = snapData.angle;722 placement Position.x = snapData.x;723 placement Position.z = snapData.z;912 placementSupport.angle = snapData.angle; 913 placementSupport.position.x = snapData.x; 914 placementSupport.position.z = snapData.z; 724 915 } 725 916 726 917 updateBuildingPlacementPreview(); … … function handleInputBeforeGui(ev, hoveredObject) 750 941 if (ev.button == SDL_BUTTON_RIGHT) 751 942 { 752 943 // Cancel building 753 resetPlacementEntity();944 placementSupport.Reset(); 754 945 inputState = INPUT_NORMAL; 755 946 return true; 756 947 } … … function handleInputAfterGui(ev) 844 1035 break; 845 1036 } 846 1037 break; 1038 847 1039 case INPUT_PRESELECTEDACTION: 848 1040 switch (ev.type) 849 1041 { … … function handleInputAfterGui(ev) 874 1066 } 875 1067 } 876 1068 break; 1069 877 1070 case INPUT_SELECTING: 878 1071 switch (ev.type) 879 1072 { … … function handleInputAfterGui(ev) 947 1140 } 948 1141 949 1142 // TODO: Should we handle "control all units" here as well? 950 ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank );1143 ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false); 951 1144 } 952 1145 else 953 1146 { … … function handleInputAfterGui(ev) 986 1179 switch (ev.type) 987 1180 { 988 1181 case "mousemotion": 989 placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); 990 var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { 991 "template": placementEntity, 992 "x": placementPosition.x, 993 "z": placementPosition.z 994 }); 995 if (snapData) 1182 1183 placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 1184 1185 if (placementSupport.mode === "wall") 996 1186 { 997 placementAngle = snapData.angle; 998 placementPosition.x = snapData.x; 999 placementPosition.z = snapData.z; 1187 // Including only the on-screen towers in the next snap candidate list is sufficient here, since the user is 1188 // still selecting a starting point (which must necessarily be on-screen). (The update itself happens in the 1189 // call to updateBuildingPlacementPreview below). 1190 placementSupport.wallSnapEntitiesIncludeOffscreen = false; 1191 } 1192 else 1193 { 1194 var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { 1195 "template": placementSupport.template, 1196 "x": placementSupport.position.x, 1197 "z": placementSupport.position.z, 1198 }); 1199 if (snapData) 1200 { 1201 placementSupport.angle = snapData.angle; 1202 placementSupport.position.x = snapData.x; 1203 placementSupport.position.z = snapData.z; 1204 } 1000 1205 } 1001 1206 1002 updateBuildingPlacementPreview(); 1003 1207 updateBuildingPlacementPreview(); // includes an update of the snap entity candidates 1004 1208 return false; // continue processing mouse motion 1005 1209 1006 1210 case "mousebuttondown": 1007 1211 if (ev.button == SDL_BUTTON_LEFT) 1008 1212 { 1009 placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); 1010 dragStart = [ ev.x, ev.y ]; 1011 inputState = INPUT_BUILDING_CLICK; 1213 if (placementSupport.mode === "wall") 1214 { 1215 var validPlacement = updateBuildingPlacementPreview(); 1216 if (validPlacement !== false) 1217 { 1218 inputState = INPUT_BUILDING_WALL_CLICK; 1219 } 1220 } 1221 else 1222 { 1223 placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 1224 dragStart = [ ev.x, ev.y ]; 1225 inputState = INPUT_BUILDING_CLICK; 1226 } 1012 1227 return true; 1013 1228 } 1014 1229 else if (ev.button == SDL_BUTTON_RIGHT) 1015 1230 { 1016 1231 // Cancel building 1017 resetPlacementEntity();1232 placementSupport.Reset(); 1018 1233 inputState = INPUT_NORMAL; 1019 1234 return true; 1020 1235 } … … function handleInputAfterGui(ev) 1027 1242 switch (ev.hotkey) 1028 1243 { 1029 1244 case "session.rotate.cw": 1030 placement Angle += rotation_step;1245 placementSupport.angle += rotation_step; 1031 1246 updateBuildingPlacementPreview(); 1032 1247 break; 1033 1248 case "session.rotate.ccw": 1034 placement Angle -= rotation_step;1249 placementSupport.angle -= rotation_step; 1035 1250 updateBuildingPlacementPreview(); 1036 1251 break; 1037 1252 } … … function doAction(action, ev) 1054 1269 switch (action.type) 1055 1270 { 1056 1271 case "move": 1057 var target = Engine.GetTerrainAt Point(ev.x, ev.y);1272 var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 1058 1273 Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued}); 1059 1274 Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); 1060 1275 return true; … … function doAction(action, ev) 1105 1320 } 1106 1321 else 1107 1322 { 1108 pos = Engine.GetTerrainAt Point(ev.x, ev.y);1323 pos = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 1109 1324 } 1110 1325 Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": pos.x, "z": pos.z, "data": action.data}); 1111 1326 // Display rally point at the new coordinates, to avoid display lag … … function doAction(action, ev) 1117 1332 return true; 1118 1333 1119 1334 case "unset-rallypoint": 1120 var target = Engine.GetTerrainAt Point(ev.x, ev.y);1335 var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); 1121 1336 Engine.PostNetworkCommand({"type": "unset-rallypoint", "entities": selection}); 1122 1337 // Remove displayed rally point 1123 1338 Engine.GuiInterfaceCall("DisplayRallyPoint", { … … function handleMinimapEvent(target) 1175 1390 } 1176 1391 1177 1392 // Called by GUI when user clicks construction button 1178 function startBuildingPlacement(buildEntType) 1393 // @param buildTemplate Template name of the entity the user wants to build 1394 function startBuildingPlacement(buildTemplate) 1179 1395 { 1180 placementEntity = buildEntType; 1181 placementAngle = defaultPlacementAngle; 1182 inputState = INPUT_BUILDING_PLACEMENT; 1396 // TODO: we should clear any highlight selection rings here. If the mouse was over an entity before going onto the GUI 1397 // to start building a structure, then the highlight selection rings are kept during the construction of the building. 1398 // Gives the impression that somehow the hovered-over entity has something to do with the building you're constructing. 1399 1400 placementSupport.SetDefaultAngle(); 1401 1402 // find out if we're building a wall, and change the entity appropriately if so 1403 var templateData = GetTemplateData(buildTemplate); 1404 if (templateData.wallSet) 1405 { 1406 placementSupport.mode = "wall"; 1407 placementSupport.wallSet = templateData.wallSet; 1408 inputState = INPUT_BUILDING_PLACEMENT; 1409 } 1410 else 1411 { 1412 placementSupport.mode = "building"; 1413 placementSupport.template = buildTemplate; 1414 inputState = INPUT_BUILDING_PLACEMENT; 1415 } 1183 1416 } 1184 1417 1185 1418 // 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..eb31161
- + 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 this.wallDragTooltip = null; // tooltip text while the user is draggin the wall. Used to indicate the current cost to build the wall. 21 22 this.SetDefaultAngle(); 23 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 4155773..e334319 100644
a b function getSavedGameData() 187 187 } 188 188 189 189 var lastTickTime = new Date; 190 191 /** 192 * Called every frame. 193 */ 190 194 function onTick() 191 195 { 192 196 var now = new Date; … … function onTick() 205 209 206 210 updateCursorAndTooltip(); 207 211 208 // If the selection changed, we need to regenerate the sim display 212 // If the selection changed, we need to regenerate the sim display (the display depends on both the 213 // simulation state and the current selection). 209 214 if (g_Selection.dirty) 210 215 { 211 216 onSimulationUpdate(); … … function checkPlayerState() 292 297 } 293 298 } 294 299 300 /** 301 * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation 302 * update (see session.xml), or from onTick when the selection has changed. 303 */ 295 304 function onSimulationUpdate() 296 305 { 297 306 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 a623c86..cd8b22d 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"/> … … 465 466 </object> 466 467 467 468 <!-- ================================ ================================ --> 468 <!-- Information tooltip --> 469 <!-- Information tooltip 470 Follows the mouse around if 'independent' is set to 'true'. --> 469 471 <!-- ================================ ================================ --> 470 472 <object name="informationTooltip" type="tooltip" independent="true" style="informationTooltip"/> 471 473 472 474 <!-- ================================ ================================ --> 475 <!-- Wall-dragging tooltip. Shows the total cost of building a wall while the player is dragging it. --> 476 <!-- ================================ ================================ --> 477 <object name="wallDragTooltip" type="tooltip" independent="true" style="informationTooltip"/> 478 479 <!-- ================================ ================================ --> 473 480 <!-- START of BOTTOM PANEL --> 474 481 <!-- ================================ ================================ --> 475 482 -
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 efb7151..b7e2cd0 100644
a b function setOverlay(object, value) 133 133 object.hidden = !value; 134 134 } 135 135 136 // Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay) 136 /** 137 * Helper function for updateUnitCommands; sets up "unit panels" (i.e. panels with rows of icons) for the currently selected 138 * unit. 139 * 140 * @param guiName Short identifier string of this panel; see constants defined at the top of this file. 141 * @param usedPanels Output object; usedPanels[guiName] will be set to 1 to indicate that this panel was used during this 142 * run of updateUnitCommands and should not be hidden. TODO: why is this done this way instead of having 143 * updateUnitCommands keep track of this? 144 * @param unitEntState Entity state of the (first) selected unit. 145 * @param items Panel-specific data to construct the icons with. 146 * @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument. 147 */ 137 148 function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 138 149 { 139 150 usedPanels[guiName] = 1; … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 320 331 var [batchSize, batchIncrement] = getTrainingBatchStatus(unitEntState.id, entType); 321 332 var trainNum = batchSize ? batchSize+batchIncrement : batchIncrement; 322 333 323 tooltip += "\n" + getEntityCost (template);334 tooltip += "\n" + getEntityCostTooltip(template); 324 335 325 336 if (template.health) 326 337 tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + template.health; … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 340 351 if (template.tooltip) 341 352 tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]"; 342 353 343 tooltip += "\n" + getEntityCost (template);354 tooltip += "\n" + getEntityCostTooltip(template); 344 355 345 356 if (item.pair) 346 357 { … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 348 359 if (template1.tooltip) 349 360 tooltip1 += "\n[font=\"serif-13\"]" + template1.tooltip + "[/font]"; 350 361 351 tooltip1 += "\n" + getEntityCost (template1);362 tooltip1 += "\n" + getEntityCostTooltip(template1); 352 363 } 353 364 break; 354 365 … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 357 368 if (template.tooltip) 358 369 tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]"; 359 370 360 tooltip += "\n" + getEntityCost(template); 371 tooltip += "\n" + getEntityCostTooltip(template); // see utility_functions.js 372 tooltip += getPopulationBonusTooltip(template); // see utility_functions.js 361 373 362 tooltip += getPopulationBonus(template);363 374 if (template.health) 364 375 tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + template.health; 365 376 … … function setupUnitBarterPanel(unitEntState) 685 696 } 686 697 } 687 698 688 // Updates right Unit Commands Panel - runs in the main session loop via updateSelectionDetails() 699 /** 700 * Updates the right hand side "Unit Commands" panel. Runs in the main session loop via updateSelectionDetails(). 701 * Delegates to setupUnitPanel to set up individual subpanels, appropriately activated depending on the selected 702 * unit's state. 703 * 704 * @param entState Entity state of the (first) selected unit. 705 * @param supplementalDetailsPanel Reference to the "supplementalSelectionDetails" GUI Object 706 * @param selection Array of currently selected entity IDs. 707 */ 689 708 function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection) 690 709 { 691 710 // 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..f6400d1 100644
a b const FLORA = "flora"; 3 3 const FAUNA = "fauna"; 4 4 const SPECIAL = "special"; 5 5 6 const COST_DISPLAY_NAMES = { 7 "food": "Food", 8 "wood": "Wood", 9 "stone": "Stone", 10 "metal": "Metal", 11 "population": "Population", 12 }; 13 6 14 //-------------------------------- -------------------------------- -------------------------------- 7 15 // Utility functions 8 16 //-------------------------------- -------------------------------- -------------------------------- … … function getEntityCommandsList(entState) 193 201 return commands; 194 202 } 195 203 196 function getEntityCost(template) 204 /** 205 * Translates a cost component identifier as they are used internally (e.g. "population", "food", etc.) to proper 206 * display names. 207 */ 208 function getCostComponentDisplayName(costComponentName) 209 { 210 return COST_DISPLAY_NAMES[costComponentName]; 211 } 212 213 /** 214 * Helper function for getEntityCostTooltip. 215 */ 216 function getEntityCostComponentsTooltipString(template) 217 { 218 var costs = []; 219 if (template.cost.food) costs.push(template.cost.food + " [font=\"serif-12\"]" + getCostComponentDisplayName("food") + "[/font]"); 220 if (template.cost.wood) costs.push(template.cost.wood + " [font=\"serif-12\"]" + getCostComponentDisplayName("wood") + "[/font]"); 221 if (template.cost.metal) costs.push(template.cost.metal + " [font=\"serif-12\"]" + getCostComponentDisplayName("metal") + "[/font]"); 222 if (template.cost.stone) costs.push(template.cost.stone + " [font=\"serif-12\"]" + getCostComponentDisplayName("stone") + "[/font]"); 223 if (template.cost.population) costs.push(template.cost.population + " [font=\"serif-12\"]" + getCostComponentDisplayName("population") + "[/font]"); 224 return costs; 225 } 226 227 /** 228 * Returns the cost information to display in the specified entity's construction button tooltip. 229 */ 230 function getEntityCostTooltip(template) 197 231 { 198 var cost = ""; 199 if (template.cost) 232 var cost = "[font=\"serif-bold-13\"]Costs:[/font] "; 233 234 // Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of 235 // their own; the individual wall pieces within it do. 236 if (template.wallSet) 237 { 238 var templateLong = GetTemplateData(template.wallSet.templates.long); 239 var templateMedium = GetTemplateData(template.wallSet.templates.medium); 240 var templateShort = GetTemplateData(template.wallSet.templates.short); 241 var templateTower = GetTemplateData(template.wallSet.templates.tower); 242 243 // TODO: the costs of the wall segments should be the same, and for now we will assume they are (ideally we 244 // should take the average here or something). 245 var wallCosts = getEntityCostComponentsTooltipString(templateLong); 246 var towerCosts = getEntityCostComponentsTooltipString(templateTower); 247 248 cost += "\n"; 249 cost += " Walls: " + wallCosts.join(", ") + "\n"; 250 cost += " Towers: " + towerCosts.join(", "); 251 } 252 else if (template.cost) 200 253 { 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(", "); 254 var costs = getEntityCostComponentsTooltipString(template); 255 cost += costs.join(", "); 209 256 } 257 else 258 { 259 cost = ""; // cleaner than duplicating the serif-bold-13 stuff 260 } 261 210 262 return cost; 211 263 } 212 264 213 function getPopulationBonus(template) 265 /** 266 * Returns the population bonus information to display in the specified entity's construction button tooltip. 267 */ 268 function getPopulationBonusTooltip(template) 214 269 { 215 270 var popBonus = ""; 216 if (template.cost .populationBonus)271 if (template.cost && template.cost.populationBonus) 217 272 popBonus = "\n[font=\"serif-bold-13\"]Population Bonus:[/font] " + template.cost.populationBonus; 218 273 return popBonus; 219 274 } -
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..6ee915f 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..976a455
- + 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="270.873" y="91.2117" z="198.563"/> 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="64"> 130 <Template>units/cart_infantry_swordsman_a</Template> 131 <Player>1</Player> 132 <Position x="297.3943" z="245.43734"/> 133 <Orientation y="2.35621"/> 134 </Entity> 135 <Entity uid="65"> 136 <Template>units/celt_infantry_spearman_a</Template> 137 <Player>1</Player> 138 <Position x="300.65796" z="247.85009"/> 139 <Orientation y="2.35621"/> 140 </Entity> 141 <Entity uid="66"> 142 <Template>units/hele_infantry_spearman_a</Template> 143 <Player>1</Player> 144 <Position x="304.05463" z="250.32634"/> 145 <Orientation y="2.35621"/> 146 </Entity> 147 <Entity uid="67"> 148 <Template>units/iber_infantry_swordsman_a</Template> 149 <Player>1</Player> 150 <Position x="307.60114" z="254.54291"/> 151 <Orientation y="2.35621"/> 152 </Entity> 153 <Entity uid="68"> 154 <Template>units/pers_infantry_spearman_a</Template> 155 <Player>1</Player> 156 <Position x="311.45011" z="258.70716"/> 157 <Orientation y="2.35621"/> 158 </Entity> 159 <Entity uid="69"> 160 <Template>units/rome_infantry_swordsman_a</Template> 161 <Player>1</Player> 162 <Position x="315.29044" z="262.83661"/> 163 <Orientation y="2.35621"/> 164 </Entity> 165 <Entity uid="70"> 166 <Template>structures/cart_house</Template> 167 <Player>1</Player> 168 <Position x="252.36847" z="276.04014"/> 169 <Orientation y="2.35621"/> 170 </Entity> 171 <Entity uid="71"> 172 <Template>structures/cart_farmstead</Template> 173 <Player>1</Player> 174 <Position x="266.76517" z="291.61655"/> 175 <Orientation y="2.35621"/> 176 </Entity> 177 <Entity uid="72"> 178 <Template>structures/cart_mill</Template> 179 <Player>1</Player> 180 <Position x="285.44394" z="308.2942"/> 181 <Orientation y="2.35621"/> 182 </Entity> 183 </Entities> 184 <Paths/> 185 </Scenario> 186 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 c29ded5..b2a2fd3 100644
a b Foundation.prototype.Build = function(builderEnt, work) 189 189 cmpBuildingPosition.SetXZRotation(rot.x, rot.z); 190 190 // TODO: should add a ICmpPosition::CopyFrom() instead of all this 191 191 192 // ---------------------------------------------------------------------- 193 192 194 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 193 195 var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership); 194 196 cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner()); 195 197 198 // ---------------------------------------------------------------------- 199 200 // Copy over the obstruction control group IDs from the foundation entities. This is needed to ensure that when a foundation 201 // is completed and replaced by a new entity, it remains in the same control group(s) as any other foundation entities that 202 // may surround it. This is the mechanism that is used to e.g. enable wall pieces to be built closely together, ignoring their 203 // mutual obstruction shapes (since they would otherwise be prevented from being built so closely together). If the control 204 // groups are not copied over, the new entity will default to a new control group containing only itself, and will hence block 205 // construction of any surrounding foundations that it was previously in the same control group with. 206 207 // Note that this will result in the completed building entities having control group IDs that equal entity IDs of old (and soon 208 // to be deleted) foundation entities. This should not have any consequences, however, since the control group IDs are only meant 209 // to be unique identifiers, which is still true when reusing the old ones. 210 211 var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); 212 var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction); 213 cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup()); 214 cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2()); 215 216 // ---------------------------------------------------------------------- 217 196 218 var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); 197 219 cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(); 198 220 199 221 var cmpIdentity = Engine.QueryInterface(building, IID_Identity); 200 if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1) cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter(); 222 if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1) 223 cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter(); 201 224 202 225 var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 203 226 var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health); … … Foundation.prototype.Build = function(builderEnt, work) 207 230 208 231 Engine.PostMessage(this.entity, MT_ConstructionFinished, 209 232 { "entity": this.entity, "newentity": building }); 210 211 233 Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: building }); 212 234 213 235 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 2292e94..f1b9bea 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) 141 143 var ret = { 142 144 "id": ent, 143 145 "template": template 144 } 146 }; 145 147 146 148 var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); 147 149 if (cmpIdentity) … … GuiInterface.prototype.GetEntityState = function(player, ent) 214 216 }; 215 217 } 216 218 219 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); 220 if (cmpObstruction) 221 { 222 ret.obstruction = { 223 "controlGroup": cmpObstruction.GetControlGroup(), 224 "controlGroup2": cmpObstruction.GetControlGroup2(), 225 }; 226 } 227 217 228 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 218 229 if (cmpOwnership) 219 230 { … … GuiInterface.prototype.GetTemplateData = function(player, name) 331 342 } 332 343 } 333 344 345 if (template.BuildRestrictions) 346 { 347 // required properties 348 ret.buildRestrictions = { 349 "placementType": template.BuildRestrictions.PlacementType, 350 "territory": template.BuildRestrictions.Territory, 351 "category": template.BuildRestrictions.Category, 352 }; 353 354 // optional properties 355 if (template.BuildRestrictions.Distance) 356 { 357 ret.buildRestrictions.distance = { 358 "fromCategory": template.BuildRestrictions.Distance.FromCategory, 359 }; 360 if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance; 361 if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance; 362 } 363 } 364 334 365 if (template.Cost) 335 366 { 336 367 ret.cost = {}; … … GuiInterface.prototype.GetTemplateData = function(player, name) 342 373 if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus; 343 374 } 344 375 376 if (template.Footprint) 377 { 378 ret.footprint = {"height": template.Footprint.Height}; 379 380 if (template.Footprint.Square) 381 ret.footprint.square = {"width": +template.Footprint.Square["@width"], "depth": +template.Footprint.Square["@depth"]}; 382 else if (template.Footprint.Circle) 383 ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]}; 384 else 385 warn("[GetTemplateData] Unrecognized Footprint type"); 386 } 387 388 if (template.Obstruction) 389 { 390 ret.obstruction = { 391 "active": ("" + template.Obstruction.Active == "true"), 392 "blockMovement": ("" + template.Obstruction.BlockMovement == "true"), 393 "blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"), 394 "blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"), 395 "blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"), 396 "disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"), 397 "disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"), 398 "shape": {} 399 }; 400 401 if (template.Obstruction.Static) 402 { 403 ret.obstruction.shape.type = "static"; 404 ret.obstruction.shape.width = +template.Obstruction.Static["@width"]; 405 ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"]; 406 } 407 else 408 { 409 ret.obstruction.shape.type = "unit"; 410 ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"]; 411 } 412 } 413 345 414 if (template.Health) 346 415 { 347 416 ret.health = +template.Health.Max; … … GuiInterface.prototype.GetTemplateData = function(player, name) 367 436 if (template.UnitMotion.Run) ret.speed.run = +template.UnitMotion.Run.Speed; 368 437 } 369 438 439 if (template.WallSet) 440 { 441 ret.wallSet = { 442 "templates": { 443 "tower": template.WallSet.Templates.Tower, 444 "gate": template.WallSet.Templates.Gate, 445 "long": template.WallSet.Templates.WallLong, 446 "medium": template.WallSet.Templates.WallMedium, 447 "short": template.WallSet.Templates.WallShort, 448 }, 449 "maxTowerOverlap": +template.WallSet.MaxTowerOverlap, 450 "minTowerOverlap": +template.WallSet.MinTowerOverlap, 451 }; 452 } 453 454 if (template.WallPiece) 455 { 456 ret.wallPiece = {"length": +template.WallPiece.Length}; 457 } 458 370 459 return ret; 371 460 }; 372 461 … … GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) 598 687 * Display the building placement preview. 599 688 * cmd.template is the name of the entity template, or "" to disable the preview. 600 689 * cmd.x, cmd.z, cmd.angle give the location. 690 * 601 691 * Returns true if the placement is okay (everything is valid and the entity is not obstructed by others). 602 692 */ 603 693 GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd) … … GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd) 666 756 return false; 667 757 }; 668 758 759 /** 760 * 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 761 * specified. Returns an object with information about the list of entities that need to be newly constructed to complete 762 * 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 763 * them can be validly constructed. 764 * 765 * It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one 766 * another depending on things like snapping and whether some of the entities inside them can be validly positioned. 767 * We have: 768 * - The list of entities that previews the wall. This list is usually equal to the entities required to construct the 769 * entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities 770 * to preview the completed tower on top of its foundation. 771 * 772 * - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether 773 * any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing 774 * towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we 775 * snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly 776 * constructed. 777 * 778 * - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same 779 * as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens 780 * e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly 781 * constructed but come after said first invalid entity are also truncated away. 782 * 783 * 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 784 * were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in 785 * case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset 786 * argument (see below). Otherwise, it will return an object with the following information: 787 * 788 * result: { 789 * 'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers. 790 * 'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this 791 * can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side 792 * but the wall construction was truncated before we could reach it, it won't be set here. Currently only 793 * supports towers. 794 * 'pieces': Array with the following data for each of the entities in the third list: 795 * [{ 796 * 'template': Template name of the entity. 797 * 'x': X coordinate of the entity's position. 798 * 'z': Z coordinate of the entity's position. 799 * 'angle': Rotation around the Y axis of the entity (in radians). 800 * }, 801 * ...] 802 * 'cost': { The total cost required for constructing all the pieces as listed above. 803 * 'food': ..., 804 * 'wood': ..., 805 * 'stone': ..., 806 * 'metal': ..., 807 * 'population': ..., 808 * 'populationBonus': ..., 809 * } 810 * } 811 * 812 * @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview. 813 * @param cmd.start Starting point of the wall segment being created. 814 * @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only 815 * the starting point of the wall is available at this time (e.g. while the player is still in the process 816 * of picking a starting point), and that therefore only the first entity in the wall (a tower) should be 817 * previewed. 818 * @param cmd.snapEntities List of candidate entities to snap the start and ending positions to. 819 */ 820 GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) 821 { 822 var wallSet = cmd.wallSet; 823 824 var start = { 825 "pos": cmd.start, 826 "angle": 0, 827 "snapped": false, // did the start position snap to anything? 828 "snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID 829 }; 830 831 var end = { 832 "pos": cmd.end, 833 "angle": 0, 834 "snapped": false, // did the start position snap to anything? 835 "snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID 836 }; 837 838 // -------------------------------------------------------------------------------- 839 // do some entity cache management and check for snapping 840 841 if (!this.placementWallEntities) 842 this.placementWallEntities = {}; 843 844 if (!wallSet) 845 { 846 // we're clearing the preview, clear the entity cache and bail 847 var numCleared = 0; 848 for (var tpl in this.placementWallEntities) 849 { 850 for each (var ent in this.placementWallEntities[tpl].entities) 851 Engine.DestroyEntity(ent); 852 853 this.placementWallEntities[tpl].numUsed = 0; 854 this.placementWallEntities[tpl].entities = []; 855 // keep template data around 856 } 857 858 return false; 859 } 860 else 861 { 862 // Move all existing cached entities outside of the world and reset their use count 863 for (var tpl in this.placementWallEntities) 864 { 865 for each (var ent in this.placementWallEntities[tpl].entities) 866 { 867 var pos = Engine.QueryInterface(ent, IID_Position); 868 if (pos) 869 pos.MoveOutOfWorld(); 870 } 871 872 this.placementWallEntities[tpl].numUsed = 0; 873 } 874 875 // Create cache entries for templates we haven't seen before 876 for each (var tpl in wallSet.templates) 877 { 878 if (!(tpl in this.placementWallEntities)) 879 { 880 this.placementWallEntities[tpl] = { 881 "numUsed": 0, 882 "entities": [], 883 "templateData": this.GetTemplateData(player, tpl), 884 }; 885 886 // ensure that the loaded template data contains a wallPiece component 887 if (!this.placementWallEntities[tpl].templateData.wallPiece) 888 { 889 error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'"); 890 return false; 891 } 892 } 893 } 894 } 895 896 // prevent division by zero errors further on if the start and end positions are the same 897 if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z)) 898 end.pos = undefined; 899 900 // 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 901 // of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping 902 // data to determine whether it snapped to an entity (if any), and to which one (see GetFoundationSnapData). 903 if (cmd.snapEntities) 904 { 905 var snapRadius = this.placementWallEntities[wallSet.templates.tower].templateData.wallPiece.length * 0.5; // determined through trial and error 906 var startSnapData = this.GetFoundationSnapData(player, { 907 "x": start.pos.x, 908 "z": start.pos.z, 909 "template": wallSet.templates.tower, 910 "snapEntities": cmd.snapEntities, 911 "snapRadius": snapRadius, 912 }); 913 914 if (startSnapData) 915 { 916 start.pos.x = startSnapData.x; 917 start.pos.z = startSnapData.z; 918 start.angle = startSnapData.angle; 919 start.snapped = true; 920 921 if (startSnapData.ent) 922 start.snappedEnt = startSnapData.ent; 923 } 924 925 if (end.pos) 926 { 927 var endSnapData = this.GetFoundationSnapData(player, { 928 "x": end.pos.x, 929 "z": end.pos.z, 930 "template": wallSet.templates.tower, 931 "snapEntities": cmd.snapEntities, 932 "snapRadius": snapRadius, 933 }); 934 935 if (endSnapData) 936 { 937 end.pos.x = endSnapData.x; 938 end.pos.z = endSnapData.z; 939 end.angle = endSnapData.angle; 940 end.snapped = true; 941 942 if (endSnapData.ent) 943 end.snappedEnt = endSnapData.ent; 944 } 945 } 946 } 947 948 // clear the single-building preview entity (we'll be rolling our own) 949 this.SetBuildingPlacementPreview(player, {"template": ""}); 950 951 // -------------------------------------------------------------------------------- 952 // calculate wall placement and position preview entities 953 954 var result = { 955 "pieces": [], 956 "cost": {"food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0}, 957 }; 958 959 var previewEntities = []; 960 if (end.pos) 961 previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js 962 963 // For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would 964 // otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of 965 // an issue, because all preview entities have their obstruction components deactivated, meaning that their 966 // obstruction shapes do not register in the simulation and hence cannot affect it. This implies that the preview 967 // entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces. 968 969 // Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION 970 // flag set), which is what we want. The only exception to this is when snapping to existing towers (or 971 // foundations thereof); the wall segments that connect up to these will be found to be obstructed by the 972 // existing tower/foundation, and be shaded red to indicate that they cannot be placed there. To prevent this, 973 // we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so 974 // that they are free from mutual obstruction (per definition of obstruction control groups). This is done by 975 // assigning them an extra "controlGroup" field, which we'll then set during the placement loop below. 976 977 // Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully 978 // constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed 979 // by the foundation it snaps to. 980 981 if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY) 982 { 983 var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction); 984 if (previewEntities.length > 0 && startEntObstruction) 985 previewEntities[0].controlGroups = [startEntObstruction.GetControlGroup()]; 986 987 // if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group 988 var startEntState = this.GetEntityState(player, start.snappedEnt); 989 if (startEntState.foundation) 990 { 991 var cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position); 992 if (cmpPosition) 993 { 994 previewEntities.unshift({ 995 "template": wallSet.templates.tower, 996 "pos": start.pos, 997 "angle": cmpPosition.GetRotation().y, 998 "controlGroups": [(startEntObstruction ? startEntObstruction.GetControlGroup() : undefined)], 999 "excludeFromResult": true, // preview only, must not appear in the result 1000 }); 1001 } 1002 } 1003 } 1004 else 1005 { 1006 // Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps 1007 // when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned 1008 // wall piece. 1009 1010 // To illustrate the last point, consider what happens if we used some constant instead, say, 0. Issuing the 1011 // build command for a wall is asynchronous, so when the preview updates after shift-clicking, the wall piece 1012 // foundations are not registered yet in the simulation. This means they cannot possibly be picked in the list 1013 // of candidate entities for snapping. In the next preview update, we therefore hit this case, and would rotate 1014 // the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and 1015 // onSimulationUpdate in session.js updates the preview again. It first grabs a new list of snapping candidates, 1016 // which this time does include the new foundations; so we snap to the entity, and rotate the preview back to 1017 // the foundation's angle. 1018 1019 // The result is a noticeable rotation to 0 and back, which is undesirable. So, for a split second there until 1020 // the simulation updates, we fake it by reusing the last angle and hope the player doesn't notice. 1021 previewEntities.unshift({ 1022 "template": wallSet.templates.tower, 1023 "pos": start.pos, 1024 "angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle) 1025 }); 1026 } 1027 1028 if (end.pos) 1029 { 1030 // Analogous to the starting side case above 1031 if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY) 1032 { 1033 var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction); 1034 1035 // Note that it's possible for the last entity in previewEntities to be the same as the first, i.e. the 1036 // same wall piece snapping to both a starting and an ending tower. And it might be more common than you would 1037 // expect; the allowed overlap between wall segments and towers facilitates this to some degree. To deal with 1038 // the possibility of dual initial control groups, we use a '.controlGroups' array rather than a single 1039 // '.controlGroup' property. Note that this array can only ever have 0, 1 or 2 elements (checked at a later time). 1040 if (previewEntities.length > 0 && endEntObstruction) 1041 { 1042 previewEntities[previewEntities.length-1].controlGroups = (previewEntities[previewEntities.length-1].controlGroups || []); 1043 previewEntities[previewEntities.length-1].controlGroups.push(endEntObstruction.GetControlGroup()); 1044 } 1045 1046 // if we're snapping to a foundation, add an extra preview tower and also set it to the same control group 1047 var endEntState = this.GetEntityState(player, end.snappedEnt); 1048 if (endEntState.foundation) 1049 { 1050 var cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position); 1051 if (cmpPosition) 1052 { 1053 previewEntities.push({ 1054 "template": wallSet.templates.tower, 1055 "pos": end.pos, 1056 "angle": cmpPosition.GetRotation().y, 1057 "controlGroups": [(endEntObstruction ? endEntObstruction.GetControlGroup() : undefined)], 1058 "excludeFromResult": true 1059 }); 1060 } 1061 } 1062 } 1063 else 1064 { 1065 previewEntities.push({ 1066 "template": wallSet.templates.tower, 1067 "pos": end.pos, 1068 "angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle) 1069 }); 1070 } 1071 } 1072 1073 var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain); 1074 if (!cmpTerrain) 1075 { 1076 error("[SetWallPlacementPreview] System Terrain component not found"); 1077 return false; 1078 } 1079 1080 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1081 if (!cmpRangeManager) 1082 { 1083 error("[SetWallPlacementPreview] System RangeManager component not found"); 1084 return false; 1085 } 1086 1087 // Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed 1088 // to build at least a part of the wall (meaning that the subset is truncated after the first entity that needs to be, 1089 // but cannot validly be, constructed). See method-level documentation for more details. 1090 1091 var allPiecesValid = true; 1092 var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity 1093 1094 for (var i = 0; i < previewEntities.length; ++i) 1095 { 1096 var entInfo = previewEntities[i]; 1097 1098 var ent = null; 1099 var tpl = entInfo.template; 1100 var tplData = this.placementWallEntities[tpl].templateData; 1101 var entPool = this.placementWallEntities[tpl]; 1102 1103 if (entPool.numUsed >= entPool.entities.length) 1104 { 1105 // allocate new entity 1106 ent = Engine.AddLocalEntity("preview|" + tpl); 1107 entPool.entities.push(ent); 1108 } 1109 else 1110 { 1111 // reuse an existing one 1112 ent = entPool.entities[entPool.numUsed]; 1113 } 1114 1115 if (!ent) 1116 { 1117 error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'"); 1118 continue; 1119 } 1120 1121 // move piece to right location 1122 // TODO: consider reusing SetBuildingPlacementReview for this, enhanced to be able to deal with multiple entities 1123 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 1124 if (cmpPosition) 1125 { 1126 cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z); 1127 cmpPosition.SetYRotation(entInfo.angle); 1128 1129 // if this piece is a tower, then it should have a Y position that is at least as high as its surrounding pieces 1130 if (tpl === wallSet.templates.tower) 1131 { 1132 var terrainGroundPrev = null; 1133 var terrainGroundNext = null; 1134 1135 if (i > 0) 1136 terrainGroundPrev = cmpTerrain.GetGroundLevel(previewEntities[i-1].pos.x, previewEntities[i-1].pos.z); 1137 if (i < previewEntities.length - 1) 1138 terrainGroundNext = cmpTerrain.GetGroundLevel(previewEntities[i+1].pos.x, previewEntities[i+1].pos.z); 1139 1140 if (terrainGroundPrev != null || terrainGroundNext != null) 1141 { 1142 var targetY = Math.max(terrainGroundPrev, terrainGroundNext); 1143 cmpPosition.SetHeightFixed(targetY); 1144 } 1145 } 1146 } 1147 1148 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); 1149 if (!cmpObstruction) 1150 { 1151 error("[SetWallPlacementPreview] Preview entity of template '" + tpl + "' does not have an Obstruction component"); 1152 continue; 1153 } 1154 1155 // Assign any predefined control groups. Note that there can only be 0, 1 or 2 predefined control groups; if there are 1156 // more, we've made a programming error. The control groups are assigned from the entInfo.controlGroups array on a 1157 // first-come first-served basis; the first value in the array is always assigned as the primary control group, and 1158 // any second value as the secondary control group. 1159 1160 // By default, we reset the control groups to their standard values. Remember that we're reusing entities; if we don't 1161 // reset them, then an ending wall segment that was e.g. at one point snapped to an existing tower, and is subsequently 1162 // reused as a non-snapped ending wall segment, would no longer be capable of being obstructed by the same tower it was 1163 // once snapped to. 1164 1165 var primaryControlGroup = ent; 1166 var secondaryControlGroup = INVALID_ENTITY; 1167 1168 if (entInfo.controlGroups && entInfo.controlGroups.length > 0) 1169 { 1170 if (entInfo.controlGroups.length > 2) 1171 { 1172 error("[SetWallPlacementPreview] Encountered preview entity of template '" + tpl + "' with more than 2 initial control groups"); 1173 break; 1174 } 1175 1176 primaryControlGroup = entInfo.controlGroups[0]; 1177 if (entInfo.controlGroups.length > 1) 1178 secondaryControlGroup = entInfo.controlGroups[1]; 1179 } 1180 1181 cmpObstruction.SetControlGroup(primaryControlGroup); 1182 cmpObstruction.SetControlGroup2(secondaryControlGroup); 1183 1184 // check whether this wall piece can be validly positioned here 1185 var validPlacement = false; 1186 1187 // Check whether it's in a visible or fogged region 1188 // tell GetLosVisibility to force RetainInFog because preview entities set this to false, 1189 // which would show them as hidden instead of fogged 1190 // TODO: should definitely reuse SetBuildingPlacementPreview, this is just straight up copy/pasta 1191 var visible = (cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden"); 1192 if (visible) 1193 { 1194 var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); 1195 if (!cmpBuildRestrictions) 1196 { 1197 error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'"); 1198 continue; 1199 } 1200 1201 validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement(player)); 1202 } 1203 1204 allPiecesValid = allPiecesValid && validPlacement; 1205 1206 var cmpVisual = Engine.QueryInterface(ent, IID_Visual); 1207 if (cmpVisual) 1208 { 1209 if (!allPiecesValid) 1210 cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1); 1211 else 1212 cmpVisual.SetShadingColour(1, 1, 1, 1); 1213 } 1214 1215 // The requirement below that all pieces so far have to have valid positions, rather than only this single one, 1216 // ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible 1217 // for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall 1218 // through and past an existing building). 1219 1220 // Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed 1221 // on top of foundations of incompleted towers that we snapped to; they must not be part of the result. 1222 1223 if (!entInfo.excludeFromResult) 1224 numRequiredPieces++; 1225 1226 if (allPiecesValid && !entInfo.excludeFromResult) 1227 { 1228 result.pieces.push({ 1229 "template": tpl, 1230 "x": entInfo.pos.x, 1231 "z": entInfo.pos.z, 1232 "angle": entInfo.angle, 1233 }); 1234 this.placementWallLastAngle = entInfo.angle; 1235 1236 // grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components 1237 // copied over, so we need to fetch it from the template instead). 1238 // TODO: we should really use a Cost object or at least some utility functions for this, this is mindless 1239 // boilerplate that's probably duplicated in tons of places. 1240 result.cost.food += tplData.cost.food; 1241 result.cost.wood += tplData.cost.wood; 1242 result.cost.stone += tplData.cost.stone; 1243 result.cost.metal += tplData.cost.metal; 1244 result.cost.population += tplData.cost.population; 1245 result.cost.populationBonus += tplData.cost.populationBonus; 1246 } 1247 1248 entPool.numUsed++; 1249 } 1250 1251 // If any were entities required to build the wall, but none of them could be validly positioned, return failure 1252 // (see method-level documentation). 1253 if (numRequiredPieces > 0 && result.pieces.length == 0) 1254 return false; 1255 1256 if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY) 1257 result.startSnappedEnt = start.snappedEnt; 1258 1259 // We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed, 1260 // i.e. are included in result.pieces (see docs for the result object). 1261 if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid) 1262 result.endSnappedEnt = end.snappedEnt; 1263 1264 return result; 1265 }; 1266 1267 /** 1268 * Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap 1269 * it to (if necessary/useful). 1270 * 1271 * @param data.x The X position of the foundation to snap. 1272 * @param data.z The Z position of the foundation to snap. 1273 * @param data.template The template to get the foundation snapping data for. 1274 * @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius 1275 * around the entity. Only takes effect when used in conjunction with data.snapRadius. 1276 * When this option is used and the foundation is found to snap to one of the entities passed in this list 1277 * (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent", 1278 * holding the ID of the entity that was snapped to. 1279 * @param data.snapRadius Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that 1280 * {data.x, data.z} must be located within to have it snap to that entity. 1281 */ 669 1282 GuiInterface.prototype.GetFoundationSnapData = function(player, data) 670 1283 { 671 1284 var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 672 1285 var template = cmpTemplateMgr.GetTemplate(data.template); 673 1286 1287 if (!template) 1288 { 1289 warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'"); 1290 return false; 1291 } 1292 1293 if (data.snapEntities && data.snapRadius && data.snapRadius > 0) 1294 { 1295 // 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 1296 // (TODO: break unlikely ties by choosing the lowest entity ID) 1297 1298 var minDist2 = -1; 1299 var minDistEntitySnapData = null; 1300 var radius2 = data.snapRadius * data.snapRadius; 1301 1302 for each (ent in data.snapEntities) 1303 { 1304 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 1305 if (!cmpPosition || !cmpPosition.IsInWorld()) 1306 continue; 1307 1308 var pos = cmpPosition.GetPosition(); 1309 var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z); 1310 if (dist2 > radius2) 1311 continue; 1312 1313 if (minDist2 < 0 || dist2 < minDist2) 1314 { 1315 minDist2 = dist2; 1316 minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent}; 1317 } 1318 } 1319 1320 if (minDistEntitySnapData != null) 1321 return minDistEntitySnapData; 1322 } 1323 674 1324 if (template.BuildRestrictions.Category == "Dock") 675 1325 { 676 1326 var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain); … … var exposedFunctions = { 906 1556 "SetStatusBars": 1, 907 1557 "DisplayRallyPoint": 1, 908 1558 "SetBuildingPlacementPreview": 1, 1559 "SetWallPlacementPreview": 1, 909 1560 "GetFoundationSnapData": 1, 910 1561 "PlaySound": 1, 911 1562 "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..84cacb6
- + 1 function WallSet() {} 2 3 WallSet.prototype.Schema = 4 "<a:help></a:help>" + 5 "<a:example>" + 6 "</a:example>" + 7 "<element name='Templates'>" + 8 "<interleave>" + 9 "<element name='Tower' a:help='Template name of the tower piece'>" + 10 "<text/>" + 11 "</element>" + 12 "<element name='Gate' a:help='Template name of the gate piece'>" + 13 "<text/>" + 14 "</element>" + 15 "<element name='WallLong' a:help='Template name of the long wall segment'>" + 16 "<text/>" + 17 "</element>" + 18 "<element name='WallMedium' a:help='Template name of the medium-size wall segment'>" + 19 "<text/>" + 20 "</element>" + 21 "<element name='WallShort' a:help='Template name of the short wall segment'>" + 22 "<text/>" + 23 "</element>" + 24 "</interleave>" + 25 "</element>" + 26 "<element name='MinTowerOverlap' a:help='Maximum fraction that wall segments are allowed to overlap towers, where 0 signifies no overlap and 1 full overlap'>" + 27 "<data type='decimal'><param name='minInclusive'>0.0</param><param name='maxInclusive'>1.0</param></data>" + 28 "</element>" + 29 "<element name='MaxTowerOverlap' a:help='Minimum fraction that wall segments are required to overlap towers, where 0 signifies no overlap and 1 full overlap'>" + 30 "<data type='decimal'><param name='minInclusive'>0.0</param><param name='maxInclusive'>1.0</param></data>" + 31 "</element>"; 32 33 34 WallSet.prototype.Init = function() 35 { 36 }; 37 38 WallSet.prototype.Serialize = null; 39 40 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 4205c9b..e242529 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) 203 206 break; 204 207 205 208 case "construct": 206 // Message structure: 207 // { 208 // "type": "construct", 209 // "entities": [...], 210 // "template": "...", 211 // "x": ..., 212 // "z": ..., 213 // "angle": ..., 214 // "autorepair": true, // whether to automatically start constructing/repairing the new foundation 215 // "autocontinue": true, // whether to automatically gather/build/etc after finishing this 216 // "queued": true, 217 // } 218 219 /* 220 * Construction process: 221 * . Take resources away immediately. 222 * . Create a foundation entity with 1hp, 0% build progress. 223 * . Increase hp and build progress up to 100% when people work on it. 224 * . If it's destroyed, an appropriate fraction of the resource cost is refunded. 225 * . If it's completed, it gets replaced with the real building. 226 */ 227 228 // Check that we can control these units 229 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 230 if (!entities.length) 231 break; 232 233 // Tentatively create the foundation (we might find later that it's a invalid build command) 234 var ent = Engine.AddEntity("foundation|" + cmd.template); 235 if (ent == INVALID_ENTITY) 236 { 237 // Error (e.g. invalid template names) 238 error("Error creating foundation entity for '" + cmd.template + "'"); 239 break; 240 } 241 242 // Move the foundation to the right place 243 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 244 cmpPosition.JumpTo(cmd.x, cmd.z); 245 cmpPosition.SetYRotation(cmd.angle); 246 247 // Check whether it's obstructed by other entities or invalid terrain 248 var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); 249 if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player)) 250 { 251 if (g_DebugCommands) 252 { 253 warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd)); 254 } 255 256 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 257 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" }); 258 259 // Remove the foundation because the construction was aborted 260 Engine.DestroyEntity(ent); 261 break; 262 } 263 264 // Check build limits 265 var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits); 266 if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) 267 { 268 if (g_DebugCommands) 269 { 270 warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd)); 271 } 272 273 // TODO: The UI should tell the user they can't build this (but we still need this check) 274 275 // Remove the foundation because the construction was aborted 276 Engine.DestroyEntity(ent); 277 break; 278 } 279 280 var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); 281 // TODO: Enable this check once the AI gets technology support 282 if (!cmpTechMan.CanProduce(cmd.template) && false) 283 { 284 if (g_DebugCommands) 285 { 286 warn("Invalid command: required technology check failed for player "+player+": "+uneval(cmd)); 287 } 288 289 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 290 cmpGuiInterface.PushNotification({ "player": player, "message": "Building's technology requirements are not met." }); 291 292 // Remove the foundation because the construction was aborted 293 Engine.DestroyEntity(ent); 294 } 295 296 // TODO: AI has no visibility info 297 if (!cmpPlayer.IsAI()) 298 { 299 // Check whether it's in a visible or fogged region 300 // tell GetLosVisibility to force RetainInFog because preview entities set this to false, 301 // which would show them as hidden instead of fogged 302 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 303 var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden"); 304 if (!visible) 305 { 306 if (g_DebugCommands) 307 { 308 warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd)); 309 } 310 311 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 312 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" }); 313 314 Engine.DestroyEntity(ent); 315 break; 316 } 317 } 318 319 var cmpCost = Engine.QueryInterface(ent, IID_Cost); 320 if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts())) 321 { 322 if (g_DebugCommands) 323 { 324 warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd)); 325 } 326 327 Engine.DestroyEntity(ent); 328 break; 329 } 330 331 // Make it owned by the current player 332 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 333 cmpOwnership.SetOwner(player); 334 335 // Initialise the foundation 336 var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); 337 cmpFoundation.InitialiseConstruction(player, cmd.template); 338 339 // Tell the units to start building this new entity 340 if (cmd.autorepair) 341 { 342 ProcessCommand(player, { 343 "type": "repair", 344 "entities": entities, 345 "target": ent, 346 "autocontinue": cmd.autocontinue, 347 "queued": cmd.queued 348 }); 349 } 209 TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd); 210 break; 350 211 212 case "construct-wall": 213 TryConstructWall(player, cmpPlayer, controlAllUnits, cmd); 351 214 break; 352 215 353 216 case "delete-entities": … … function ExtractFormations(ents) 550 413 } 551 414 552 415 /** 416 * Attempts to construct a building using the specified parameters. 417 * Returns true on success, false on failure. 418 */ 419 function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) 420 { 421 // Message structure: 422 // { 423 // "type": "construct", 424 // "entities": [...], // entities that will be ordered to construct the building (if applicable) 425 // "template": "...", // template name of the entity being constructed 426 // "x": ..., 427 // "z": ..., 428 // "angle": ..., 429 // "autorepair": true, // whether to automatically start constructing/repairing the new foundation 430 // "autocontinue": true, // whether to automatically gather/build/etc after finishing this 431 // "queued": true, // whether to add the construction/repairing of this foundation to entities' queue (if applicable) 432 // "obstructionControlGroup": ..., // Optional; the obstruction control group ID that should be set for this building prior to obstruction 433 // // testing to determine placement validity. If specified, must be a valid control group ID (> 0). 434 // "obstructionControlGroup2": ..., // Optional; secondary obstruction control group ID that should be set for this building prior to obstruction 435 // // testing to determine placement validity. May be INVALID_ENTITY. 436 // } 437 438 /* 439 * Construction process: 440 * . Take resources away immediately. 441 * . Create a foundation entity with 1hp, 0% build progress. 442 * . Increase hp and build progress up to 100% when people work on it. 443 * . If it's destroyed, an appropriate fraction of the resource cost is refunded. 444 * . If it's completed, it gets replaced with the real building. 445 */ 446 447 // Check whether we can control these units 448 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 449 if (!entities.length) 450 return false; 451 452 // Tentatively create the foundation (we might find later that it's a invalid build command) 453 var ent = Engine.AddEntity("foundation|" + cmd.template); 454 if (ent == INVALID_ENTITY) 455 { 456 // Error (e.g. invalid template names) 457 error("Error creating foundation entity for '" + cmd.template + "'"); 458 return false; 459 } 460 461 // Move the foundation to the right place 462 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 463 cmpPosition.JumpTo(cmd.x, cmd.z); 464 cmpPosition.SetYRotation(cmd.angle); 465 466 // Set the obstruction control group if needed 467 if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2) 468 { 469 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); 470 471 // primary control group must always be valid 472 if (cmd.obstructionControlGroup) 473 { 474 if (cmd.obstructionControlGroup <= 0) 475 warn("[TryConstructBuilding] Invalid primary obstruction control group " + cmd.obstructionControlGroup + " received; must be > 0"); 476 477 cmpObstruction.SetControlGroup(cmd.obstructionControlGroup); 478 } 479 480 if (cmd.obstructionControlGroup2) 481 cmpObstruction.SetControlGroup2(cmd.obstructionControlGroup2); 482 } 483 484 // Check whether it's obstructed by other entities or invalid terrain 485 var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); 486 if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player)) 487 { 488 if (g_DebugCommands) 489 { 490 warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd)); 491 } 492 493 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 494 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" }); 495 496 // Remove the foundation because the construction was aborted 497 Engine.DestroyEntity(ent); 498 return false; 499 } 500 501 // Check build limits 502 var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits); 503 if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) 504 { 505 if (g_DebugCommands) 506 { 507 warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd)); 508 } 509 510 // TODO: The UI should tell the user they can't build this (but we still need this check) 511 512 // Remove the foundation because the construction was aborted 513 Engine.DestroyEntity(ent); 514 return false; 515 } 516 517 var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); 518 // TODO: Enable this check once the AI gets technology support 519 if (!cmpTechMan.CanProduce(cmd.template) && false) 520 { 521 if (g_DebugCommands) 522 { 523 warn("Invalid command: required technology check failed for player "+player+": "+uneval(cmd)); 524 } 525 526 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 527 cmpGuiInterface.PushNotification({ "player": player, "message": "Building's technology requirements are not met." }); 528 529 // Remove the foundation because the construction was aborted 530 Engine.DestroyEntity(ent); 531 } 532 533 // TODO: AI has no visibility info 534 if (!cmpPlayer.IsAI()) 535 { 536 // Check whether it's in a visible or fogged region 537 // tell GetLosVisibility to force RetainInFog because preview entities set this to false, 538 // which would show them as hidden instead of fogged 539 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 540 var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden"); 541 if (!visible) 542 { 543 if (g_DebugCommands) 544 { 545 warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd)); 546 } 547 548 var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 549 cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" }); 550 551 Engine.DestroyEntity(ent); 552 return false; 553 } 554 } 555 556 var cmpCost = Engine.QueryInterface(ent, IID_Cost); 557 if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts())) 558 { 559 if (g_DebugCommands) 560 { 561 warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd)); 562 } 563 564 Engine.DestroyEntity(ent); 565 return false; 566 } 567 568 // Make it owned by the current player 569 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 570 cmpOwnership.SetOwner(player); 571 572 // Initialise the foundation 573 var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); 574 cmpFoundation.InitialiseConstruction(player, cmd.template); 575 576 // Tell the units to start building this new entity 577 if (cmd.autorepair) 578 { 579 ProcessCommand(player, { 580 "type": "repair", 581 "entities": entities, 582 "target": ent, 583 "autocontinue": cmd.autocontinue, 584 "queued": cmd.queued 585 }); 586 } 587 588 return ent; 589 } 590 591 function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) 592 { 593 // 'cmd' message structure: 594 // { 595 // "type": "construct-wall", 596 // "entities": [...], // entities that will be ordered to construct the wall (if applicable) 597 // "pieces": [ // ordered list of information about the pieces making up the wall (towers, wall segments, ...) 598 // { 599 // "template": "...", // one of the templates from the wallset 600 // "x": ..., 601 // "z": ..., 602 // "angle": ..., 603 // }, 604 // ... 605 // ], 606 // "wallSet": { 607 // "templates": { 608 // "tower": // tower template name 609 // "long": // long wall segment template name 610 // ... // etc. 611 // }, 612 // "maxTowerOverlap": ..., 613 // "minTowerOverlap": ..., 614 // }, 615 // "startSnappedEntity": // optional; entity ID of tower being snapped to at the starting side of the wall 616 // "endSnappedEntity": // optional; entity ID of tower being snapped to at the ending side of the wall 617 // "autorepair": true, // whether to automatically start constructing/repairing the new foundation 618 // "autocontinue": true, // whether to automatically gather/build/etc after finishing this 619 // "queued": true, // whether to add the construction/repairing of this wall's pieces to entities' queue (if applicable) 620 // } 621 622 if (cmd.pieces.length <= 0) 623 return; 624 625 if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.templates.tower) 626 { 627 error("[TryConstructWall] Starting wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the starting side"); 628 return; 629 } 630 631 if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.templates.tower) 632 { 633 error("[TryConstructWall] Ending wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the ending side"); 634 return; 635 } 636 637 // Assign obstruction control groups to allow the wall pieces to mutually overlap during foundation placement 638 // and during construction. The scheme here is that whatever wall pieces are inbetween two towers inherit the control 639 // groups of both of the towers they are connected to (either newly constructed ones as part of the wall, or existing 640 // towers in the case of snapping). The towers themselves all keep their default unique control groups. 641 642 // To support this, every non-tower piece registers the entity ID of the towers (or foundations thereof) that neighbour 643 // it on either side. Specifically, each non-tower wall piece has its primary control group set equal to that of the 644 // first tower encountered towards the starting side of the wall, and its secondary control group set equal to that of 645 // the first tower encountered towards the ending side of the wall (if any). 646 647 // We can't build the whole wall at once by linearly stepping through the wall pieces and build them, because the 648 // wall segments may/will need the entity IDs of towers that come afterwards. So, build it in two passes: 649 // 650 // FIRST PASS: 651 // - Go from start to end and construct wall piece foundations as far as we can without running into a piece that 652 // cannot be built (e.g. because it is obstructed). At each non-tower, set the most recently built tower's ID 653 // as the primary control group, thus allowing it to be built overlapping the previous piece. 654 // - If we encounter a new tower along the way (which will gain its own control group), do the following: 655 // o First build it using temporarily the same control group of the previous (non-tower) piece 656 // o Set the previous piece's secondary control group to the tower's entity ID 657 // o Restore the primary control group of the constructed tower back its original (unique) value. 658 // The temporary control group is necessary to allow the newer tower with its unique control group ID to be able 659 // to be placed while overlapping the previous piece. 660 // 661 // SECOND PASS: 662 // - Go end to start from the last successfully placed wall piece (which might be a tower we backtracked to), this 663 // time registering the right neighbouring tower in each non-tower piece. 664 665 // first pass; L -> R 666 667 var lastTowerIndex = -1; // index of the last tower we've encountered in cmd.pieces 668 var lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces 669 670 // If we're snapping to an existing entity at the starting end, set lastTowerControlGroup to its control group ID so that 671 // the first wall piece can be built while overlapping it. 672 if (cmd.startSnappedEntity) 673 { 674 var cmpSnappedStartObstruction = Engine.QueryInterface(cmd.startSnappedEntity, IID_Obstruction); 675 if (!cmpSnappedStartObstruction) 676 { 677 error("[TryConstructWall] Snapped entity on starting side does not have an obstruction component"); 678 return; 679 } 680 681 lastTowerControlGroup = cmpSnappedStartObstruction.GetControlGroup(); 682 //warn("setting lastTowerControlGroup to control group of start snapped entity " + cmd.startSnappedEntity + ": " + lastTowerControlGroup); 683 } 684 685 var i = 0; 686 for (; i < cmd.pieces.length; ++i) 687 { 688 var piece = cmd.pieces[i]; 689 690 // 'lastTowerControlGroup' must always be defined and valid here, except if we're at the first piece and we didn't do 691 // start position snapping (implying that the first entity we build must be a tower) 692 if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY) 693 { 694 if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity)) 695 { 696 error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")"); 697 break; 698 } 699 } 700 701 var constructPieceCmd = { 702 "type": "construct", 703 "entities": cmd.entities, 704 "template": piece.template, 705 "x": piece.x, 706 "z": piece.z, 707 "angle": piece.angle, 708 "autorepair": cmd.autorepair, 709 "autocontinue": cmd.autocontinue, 710 "queued": cmd.queued, 711 // Regardless of whether we're building a tower or an intermediate wall piece, it is always (first) constructed 712 // using the control group of the last tower (see comments above). 713 "obstructionControlGroup": lastTowerControlGroup, 714 }; 715 716 // If we're building the last piece and we're attaching to a snapped entity, we need to add in the snapped entity's 717 // control group directly at construction time (instead of setting it in the second pass) to allow it to be built 718 // while overlapping the snapped entity. 719 if (i == cmd.pieces.length - 1 && cmd.endSnappedEntity) 720 { 721 var cmpEndSnappedObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction); 722 if (cmpEndSnappedObstruction) 723 constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup(); 724 } 725 726 var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd); 727 if (pieceEntityId) 728 { 729 // wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later 730 piece.ent = pieceEntityId; 731 732 // if we built a tower, do the control group dance (see outline above) and update lastTowerControlGroup and lastTowerIndex 733 if (piece.template == cmd.wallSet.templates.tower) 734 { 735 var cmpTowerObstruction = Engine.QueryInterface(pieceEntityId, IID_Obstruction); 736 var newTowerControlGroup = pieceEntityId; 737 738 if (i > 0) 739 { 740 //warn(" updating previous wall piece's secondary control group to " + newTowerControlGroup); 741 var cmpPreviousObstruction = Engine.QueryInterface(cmd.pieces[i-1].ent, IID_Obstruction); 742 // TODO: ensure that cmpPreviousObstruction exists 743 // TODO: ensure that the previous obstruction does not yet have a secondary control group set 744 cmpPreviousObstruction.SetControlGroup2(newTowerControlGroup); 745 } 746 747 // TODO: ensure that cmpTowerObstruction exists 748 cmpTowerObstruction.SetControlGroup(newTowerControlGroup); // give the tower its own unique control group 749 750 lastTowerIndex = i; 751 lastTowerControlGroup = newTowerControlGroup; 752 } 753 } 754 else 755 { 756 // failed to build wall piece, abort 757 i = j + 1; // compensate for the -1 subtracted by lastBuiltPieceIndex below 758 break; 759 } 760 } 761 762 var lastBuiltPieceIndex = i - 1; 763 var wallComplete = (lastBuiltPieceIndex == cmd.pieces.length - 1); 764 765 // At this point, 'i' is the index of the last wall piece that was successfully constructed (which may or may not be a tower). 766 // Now do the second pass going right-to-left, registering the control groups of the towers to the right of each piece (if any) 767 // as their secondary control groups. 768 769 lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces 770 771 // only start off with the ending side's snapped tower's control group if we were able to build the entire wall 772 if (cmd.endSnappedEntity && wallComplete) 773 { 774 var cmpSnappedEndObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction); 775 if (!cmpSnappedEndObstruction) 776 { 777 error("[TryConstructWall] Snapped entity on ending side does not have an obstruction component"); 778 return; 779 } 780 781 lastTowerControlGroup = cmpSnappedEndObstruction.GetControlGroup(); 782 } 783 784 for (var j = lastBuiltPieceIndex; j >= 0; --j) 785 { 786 var piece = cmd.pieces[j]; 787 788 if (!piece.ent) 789 { 790 error("[TryConstructWall] No entity ID set for constructed entity of template '" + piece.template + "'"); 791 continue; 792 } 793 794 var cmpPieceObstruction = Engine.QueryInterface(piece.ent, IID_Obstruction); 795 if (!cmpPieceObstruction) 796 { 797 error("[TryConstructWall] Wall piece of template '" + piece.template + "' has no Obstruction component"); 798 continue; 799 } 800 801 if (piece.template == cmd.wallSet.templates.tower) 802 { 803 // encountered a tower entity, update the last tower control group 804 lastTowerControlGroup = cmpPieceObstruction.GetControlGroup(); 805 } 806 else 807 { 808 // Encountered a non-tower entity, update its secondary control group to 'lastTowerControlGroup'. 809 // Note that the wall piece may already have its secondary control group set to the tower's entity ID from a control group 810 // dance during the first pass, in which case we should validate it against 'lastTowerControlGroup'. 811 812 var existingSecondaryControlGroup = cmpPieceObstruction.GetControlGroup2(); 813 if (existingSecondaryControlGroup == INVALID_ENTITY) 814 { 815 if (lastTowerControlGroup != null && lastTowerControlGroup != INVALID_ENTITY) 816 { 817 cmpPieceObstruction.SetControlGroup2(lastTowerControlGroup); 818 } 819 } 820 else if (existingSecondaryControlGroup != lastTowerControlGroup) 821 { 822 error("[TryConstructWall] Existing secondary control group of non-tower entity does not match expected value (2nd pass, iteration " + j + ")"); 823 break; 824 } 825 } 826 } 827 } 828 829 /** 553 830 * Remove the given list of entities from their current formations. 554 831 */ 555 832 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..c6e3b8b
- + 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 primarily holds the template names for the supported wall pieces in this set (under the 14 * 'templates' key), as well as the min and max allowed overlap factors (see GetWallSegmentsRec). Expected 15 * to contain template names for keys "long" (long wall segment), "medium" (medium wall segment), "short" 16 * (short wall segment), "tower" (intermediate tower between wall segments), "gate" (replacement for long 17 * walls). 18 * @param start Object holding the starting position of the wall. Must contain keys 'x' and 'z'. 19 * @param end Object holding the ending position of the wall. Must contains keys 'x' and 'z'. 20 */ 21 function GetWallPlacement(placementData, wallSet, start, end) 22 { 23 var result = []; 24 25 var candidateSegments = [ 26 {"template": wallSet.templates.long, "len": placementData[wallSet.templates.long].templateData.wallPiece.length}, 27 {"template": wallSet.templates.medium, "len": placementData[wallSet.templates.medium].templateData.wallPiece.length}, 28 {"template": wallSet.templates.short, "len": placementData[wallSet.templates.short].templateData.wallPiece.length}, 29 ]; 30 31 var towerWidth = placementData[wallSet.templates.tower].templateData.wallPiece.length; 32 33 var dir = {"x": end.pos.x - start.pos.x, "z": end.pos.z - start.pos.z}; 34 var len = Math.sqrt(dir.x * dir.x + dir.z * dir.z); 35 36 // we'll need room for at least our starting and ending towers to fit next to eachother 37 if (len <= towerWidth) 38 return result; 39 40 var placement = GetWallSegmentsRec(len, candidateSegments, wallSet.minTowerOverlap, wallSet.maxTowerOverlap, towerWidth, 0, []); 41 42 // TODO: make sure intermediate towers are spaced out far enough for their obstructions to not overlap, implying that 43 // tower's wallpiece lengths should be > their obstruction width, which is undesirable because it prevents towers with 44 // wide bases 45 if (placement) 46 { 47 var placedEntities = placement.segments; // list of chosen candidate segments 48 var r = placement.r; // remaining distance to target without towers (must be <= (N-1) * towerWidth) 49 var s = r / (2 * placedEntities.length); // spacing 50 51 var dirNormalized = {"x": dir.x / len, "z": dir.z / len}; 52 var angle = -Math.atan2(dir.z, dir.x); // angle of this wall segment (relative to world-space X/Z axes) 53 54 var progress = 0; 55 for (var i = 0; i < placedEntities.length; i++) 56 { 57 var placedEntity = placedEntities[i]; 58 var targetX = start.pos.x + (progress + s + placedEntity.len/2) * dirNormalized.x; 59 var targetZ = start.pos.z + (progress + s + placedEntity.len/2) * dirNormalized.z; 60 61 result.push({ 62 "template": placedEntity.template, 63 "pos": {"x": targetX, "z": targetZ}, 64 "angle": angle, 65 }); 66 67 if (i < placedEntities.length - 1) 68 { 69 var towerX = start.pos.x + (progress + placedEntity.len + 2*s) * dirNormalized.x; 70 var towerZ = start.pos.z + (progress + placedEntity.len + 2*s) * dirNormalized.z; 71 72 result.push({ 73 "template": wallSet.templates.tower, 74 "pos": {"x": towerX, "z": towerZ}, 75 "angle": angle, 76 }); 77 } 78 79 progress += placedEntity.len + 2*s; 80 } 81 } 82 else 83 { 84 error("No placement possible for distance=" + Math.round(len*1000)/1000.0 + ", minOverlap=" + wallSet.minTowerOverlap + ", maxOverlap=" + wallSet.maxTowerOverlap); 85 } 86 87 return result; 88 } 89 90 /** 91 * Helper function for GetWallPlacement. Finds a list of wall segments and the corresponding remaining spacing/overlap 92 * distance "r" that will suffice to construct a wall of the given distance. It is understood that two extra towers will 93 * be placed centered at the starting and ending points of the wall. 94 * 95 * @param d Total distance between starting and ending points (constant throughout calls). 96 * @param candidateSegments List of candidate segments (constant throughout calls). Should be ordered longer-to-shorter 97 * for better execution speed. 98 * @param minOverlap Minimum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are 99 * not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely). 100 * Must be <= maxOverlap. 101 * @param maxOverlap Maximum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are 102 * not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely). 103 * Must be >= minOverlap. 104 * @param t Length of a single tower (constant throughout calls). Acts as buffer space for wall segments (see comments). 105 * @param distSoFar Sum of all the wall segments' lengths in 'segments'. 106 * @param segments Current list of wall segments placed. 107 */ 108 function GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, distSoFar, segments) 109 { 110 // The idea is to find a number N of wall segments (excluding towers) so that the sum of their lengths adds up to a 111 // value that is within certain bounds of the distance 'd' between the starting and ending points of the wall. This 112 // creates either a positive or negative 'buffer' of space, that can be compensated for by spacing the wall segments 113 // out away from each other, or inwards, overlapping each other. The spaces or overlaps can then be covered up by 114 // placing towers on top of them. In this way, the same set of wall segments can be used to span a wider range of 115 // target distances. 116 // 117 // In this function, it is understood that two extra towers will be placed centered at the starting and ending points. 118 // They are allowed to contribute to the buffer space. 119 // 120 // The buffer space equals the difference between d and the sum of the lengths of all the wall segments, and is denoted 121 // 'r' for 'remaining space'. Positive values of r mean that the walls will need to be spaced out, negative values of r 122 // mean that they will need to overlap. Clearly, there are limits to how far wall segments can be spaced out or 123 // overlapped, depending on how much 'buffer space' each tower provides, and how far 'into' towers the wall segments are 124 // allowed to overlap. 125 // 126 // Let 't' signify the width of a tower. When there are N wall segments, then the maximum distance that can be covered 127 // using only these walls (plus the towers covering up any gaps) is achieved when the walls and towers touch outer-border- 128 // to-outer-border. Therefore, the maximum value of r is then given by: 129 // 130 // rMax = t/2 + (N-1)*t + t/2 131 // = N*t 132 // 133 // where the two half-tower widths are buffer space contributed by the implied towers on the starting and ending points. 134 // 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 135 // wall segment lengths add up to exactly d, meaning that each one starts and ends right in the center of a tower. 136 // 137 // Thus, we establish: 138 // -Nt <= r <= Nt 139 // 140 // We can further generalize this by adding in parameters to control the depth to within which wall segments are allowed to 141 // overlap with a tower. The bounds above assume that a wall segment is allowed to overlap across the entire range of 0 142 // (not overlapping at all, as in the upper boundary) to 1 (overlapping maximally, as in the lower boundary). 143 // 144 // By requiring that walls overlap towers to a degree of at least 0 < minOverlap <= 1, it is clear that this lowers the 145 // distance that can be maximally reached by the same set of wall segments, compared to the value of minOverlap = 0 that 146 // we assumed to initially find Nt. 147 // 148 // Consider a value of minOverlap = 0.5, meaning that any wall segment must protrude at least halfway into towers; in this 149 // situation, wall segments must at least touch boundaries or overlap mutually, implying that the sum of their lengths 150 // must equal or exceed 'd', establishing an upper bound of 0 for r. 151 // Similarly, consider a value of minOverlap = 1, meaning that any wall segment must overlap towers maximally; this situation 152 // is equivalent to the one for finding the lower bound -Nt on r. 153 // 154 // With the implicit value minOverlap = 0 that yielded the upper bound Nt above, simple interpolation and a similar exercise 155 // for maxOverlap, we find: 156 // (1-2*maxOverlap) * Nt <= r <= (1-2*minOverlap) * Nt 157 // 158 // To find N segments that satisfy this requirement, we try placing L, M and S wall segments in turn and continue recursively 159 // as long as the value of r is not within the bounds. If continuing recursively returns an impossible configuration, we 160 // backtrack and try a wall segment of the next length instead. Note that we should prefer to use the long segments first since 161 // they can be replaced by gates. 162 163 for each (var candSegment in candidateSegments) 164 { 165 segments.push(candSegment); 166 167 var newDistSoFar = distSoFar + candSegment.len; 168 var r = d - newDistSoFar; 169 170 var rLowerBound = (1 - 2 * maxOverlap) * segments.length * t; 171 var rUpperBound = (1 - 2 * minOverlap) * segments.length * t; 172 173 if (r < rLowerBound) 174 { 175 // we've allocated too much wall length, pop the last segment and try the next 176 //warn("Distance so far exceeds target, trying next level"); 177 segments.pop(); 178 continue; 179 } 180 else if (r > rUpperBound) 181 { 182 var recursiveResult = GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, newDistSoFar, segments); 183 if (!recursiveResult) 184 { 185 // recursive search with this piece yielded no results, pop it and try the next one 186 segments.pop(); 187 continue; 188 } 189 else 190 return recursiveResult; 191 } 192 else 193 { 194 // found a placement 195 return {"segments": segments, "r": r}; 196 } 197 } 198 199 // no placement possible :( 200 return false; 201 } 202 203 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> -
new file inaries/data/mods/public/simulation/templates/other/wallset_palisade.xml
diff --git a/binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml b/binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml new file mode 100644 index 0000000..2c506e1
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>gaia</Civ> 5 <SpecificName>Palisade</SpecificName> 6 <History>A cheap, quick defensive structure constructed with sharpened tree trunks</History> 7 <Icon>gaia/special_palisade.png</Icon> 8 </Identity> 9 <WallSet> 10 <Templates> 11 <Tower>other/palisades_rocks_tower</Tower> 12 <Gate>other/palisades_rocks_gate</Gate> 13 <WallLong>other/palisades_rocks_long</WallLong> 14 <WallMedium>other/palisades_rocks_medium</WallMedium> 15 <WallShort>other/palisades_rocks_short</WallShort> 16 </Templates> 17 </WallSet> 18 </Entity> -
binaries/data/mods/public/simulation/templates/structures/cart_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/cart_wall_gate.xml index f09c78e..a78841b 100644
a b 17 17 <VisualActor> 18 18 <Actor>structures/carthaginians/wall_gate.xml</Actor> 19 19 </VisualActor> 20 <WallPiece> 21 <Length>3.0</Length> 22 </WallPiece> 20 23 </Entity> -
binaries/data/mods/public/simulation/templates/structures/cart_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/cart_wall_long.xml index f23303b..49e0c85 100644
a b 24 24 <VisualActor> 25 25 <Actor>structures/carthaginians/wall_long.xml</Actor> 26 26 </VisualActor> 27 <WallPiece> 28 <Length>46.0</Length> 29 </WallPiece> 27 30 </Entity> 31 No newline at end of file -
binaries/data/mods/public/simulation/templates/structures/cart_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/cart_wall_medium.xml index 434c78a..0422dfa 100644
a b 24 24 <VisualActor> 25 25 <Actor>structures/carthaginians/wall_medium.xml</Actor> 26 26 </VisualActor> 27 <WallPiece> 28 <Length>30.0</Length> 29 </WallPiece> 27 30 </Entity> 31 No newline at end of file -
binaries/data/mods/public/simulation/templates/structures/cart_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/cart_wall_short.xml index 7c4656a..df913e6 100644
a b 24 24 <VisualActor> 25 25 <Actor>structures/carthaginians/wall_short.xml</Actor> 26 26 </VisualActor> 27 <WallPiece> 28 <Length>16.0</Length> 29 </WallPiece> 27 30 </Entity> 31 No newline at end of file -
binaries/data/mods/public/simulation/templates/structures/cart_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/cart_wall_tower.xml index f209260..a15014f 100644
a b 25 25 <Actor>structures/carthaginians/wall_tower.xml</Actor> 26 26 <FoundationActor>structures/fndn_3x3.xml</FoundationActor> 27 27 </VisualActor> 28 <WallPiece> 29 <Length>11.0</Length> 30 </WallPiece> 28 31 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/cart_wallset_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_wallset_stone.xml b/binaries/data/mods/public/simulation/templates/structures/cart_wallset_stone.xml new file mode 100644 index 0000000..47beb5f
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>cart</Civ> 5 <SpecificName>Jdar</SpecificName> 6 <History>The Carthaginians built what are referred to as "triple walls" to fortify some of their cities; as triple walls aren't a practical construct for 0 A.D, the construction of the inner wall is to be used. This wall served not only as a defensive structure but had barracks and stables integrated right into it, and raised towers at intervals. Fodder for elephants and horses, and arms were stored onsite. The ground level consisted of housing for elephants, the second level for horses, and the third level as barracks for the troops. In Carthage alone, 200 elephants, a thousand horses and 15,000~30,000 troops could be housed within the city walls. As shown in the reference drawing, there was also a ditch at the base in front of the wall. These walls were typically built of large blocks of sandstone hewn from deposits nearby, and were never breached by invaders.</History> 7 </Identity> 8 <WallSet> 9 <Templates> 10 <Tower>structures/cart_wall_tower</Tower> 11 <Gate>structures/cart_wall_gate</Gate> 12 <WallLong>structures/cart_wall_long</WallLong> 13 <WallMedium>structures/cart_wall_medium</WallMedium> 14 <WallShort>structures/cart_wall_short</WallShort> 15 </Templates> 16 </WallSet> 17 </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..5c466ba 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_gate.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>25.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 7b333ee..72f1590 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_long.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>37.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 eace896..18cb741 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_medium.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>25.0</Length> 20 </WallPiece> 18 21 </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 b7a2f24..5a19b08 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_short.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>13.0</Length> 20 </WallPiece> 18 21 </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..4c854fc 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/celts/wall_tower.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>9.0</Length> 20 </WallPiece> 18 21 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/celt_wallset_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wallset_stone.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wallset_stone.xml new file mode 100644 index 0000000..dec4349
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>celt</Civ> 5 <SpecificName>Gwarchglawdd</SpecificName> 6 <History>The Romans called this wall 'Murus Gallicus'. Translated, it means 'Gaulish wall'. It was extremely resistant to assault by battering ram. Julius Caesar described a type of wood and stone wall, known as a Murus Gallicus, in his account of the Gallic Wars. These walls were made of a stone wall filled with rubble, with wooden logs inside for stability. Caesar noted how the flexibility of the wood added to the strength of the fort in case of battering ram attack.</History> 7 </Identity> 8 <WallSet> 9 <Templates> 10 <Tower>structures/celt_wall_tower</Tower> 11 <Gate>structures/celt_wall_gate</Gate> 12 <WallLong>structures/celt_wall_long</WallLong> 13 <WallMedium>structures/celt_wall_medium</WallMedium> 14 <WallShort>structures/celt_wall_short</WallShort> 15 </Templates> 16 <MaxTowerOverlap>0.80</MaxTowerOverlap> 17 <MinTowerOverlap>0.05</MinTowerOverlap> 18 </WallSet> 19 </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..99da09e 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_gate.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>38.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..8bdce62 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_long.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>37.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..a1e06dc 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>24.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..4154533 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>24.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..fc3a336 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>13.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..5e7017b 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/hellenes/wall_tower.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>7.5</Length> 26 </WallPiece> 24 27 </Entity> 28 No newline at end of file -
new file inaries/data/mods/public/simulation/templates/structures/hele_wallset_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wallset_stone.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wallset_stone.xml new file mode 100644 index 0000000..cbff5e2
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>hele</Civ> 5 <SpecificName>Teîkhos</SpecificName> 6 <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> 7 </Identity> 8 <WallSet> 9 <Templates> 10 <Tower>structures/hele_wall_tower</Tower> 11 <Gate>structures/hele_wall_gate</Gate> 12 <WallLong>structures/hele_wall_long</WallLong> 13 <WallMedium>structures/hele_wall_med</WallMedium> 14 <WallShort>structures/hele_wall_short</WallShort> 15 </Templates> 16 <MaxTowerOverlap>0.90</MaxTowerOverlap> 17 <MinTowerOverlap>0.05</MinTowerOverlap> 18 </WallSet> 19 </Entity> -
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 4f93a9e..3f5c865 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/iberians/wall_gate.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>36.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 8ea2372..acdc61b 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/iberians/wall_long.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>36.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 3bc17bb..6d1b894 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/iberians/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>24.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 78aa48d..0adbe5b 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/iberians/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>12.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 5c567e2..8ab95e2 100644
a b 10 10 <History>Sturdy battlements for city walls.</History> 11 11 </Identity> 12 12 <Obstruction> 13 <Static width="1 1" depth="11"/>13 <Static width="10" depth="10"/> 14 14 </Obstruction> 15 15 <VisualActor> 16 16 <Actor>structures/iberians/wall_tower.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>10</Length> 20 </WallPiece> 18 21 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/iber_wallset_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wallset_stone.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wallset_stone.xml new file mode 100644 index 0000000..c5000db
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>iber</Civ> 5 <SpecificName>Muro Ancho</SpecificName> 6 <History>High and strongly built defensive stone walls were a common structure of the Iberian Peninsula during the period, and for long thereafter.</History> 7 </Identity> 8 <WallSet> 9 <Templates> 10 <Tower>structures/iber_wall_tower</Tower> 11 <Gate>structures/iber_wall_gate</Gate> 12 <WallLong>structures/iber_wall_long</WallLong> 13 <WallMedium>structures/iber_wall_medium</WallMedium> 14 <WallShort>structures/iber_wall_short</WallShort> 15 </Templates> 16 <MaxTowerOverlap>0.80</MaxTowerOverlap> 17 <MinTowerOverlap>0.20</MinTowerOverlap> 18 </WallSet> 19 </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..26f64c8 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_gate.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>37.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..1920cad 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_long.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>37.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..759b3d6 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>24.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..317987a 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>13.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..50f4672 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/persians/wall_tower.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>8.5</Length> 26 </WallPiece> 24 27 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/pers_wallset_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wallset_stone.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wallset_stone.xml new file mode 100644 index 0000000..3c1c7c9
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>pers</Civ> 5 <SpecificName>Dida</SpecificName> 6 <History>These were the massive walls that Nebuchadnezzar built to protect the city. It is said that two four-horse chariots could easily pass by each other. Babylon, although not an official royal residence (there were 4 of them all together), was a preferred place for holidays.</History> 7 </Identity> 8 <WallSet> 9 <Templates> 10 <Tower>structures/pers_wall_tower</Tower> 11 <Gate>structures/pers_wall_gate</Gate> 12 <WallLong>structures/pers_wall_long</WallLong> 13 <WallMedium>structures/pers_wall_medium</WallMedium> 14 <WallShort>structures/pers_wall_short</WallShort> 15 </Templates> 16 </WallSet> 17 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall.xml b/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall.xml index 3607383..5b83a30 100644
a b 23 23 </Health> 24 24 <Identity> 25 25 <Civ>rome</Civ> 26 26 <GenericName>Siege Wall</GenericName> 27 27 <SpecificName>Murus Latericius</SpecificName> 28 28 <Icon>structures/palisade.png</Icon> 29 29 <Tooltip>A wooden and turf palisade buildable in enemy and neutral territories.</Tooltip> 30 30 <History>Quick building, but expensive wooden and earthen walls used to surround and siege an enemy town or fortified position. The most famous examples are the Roman sieges of the Iberian stronghold of Numantia and the Gallic stronghold of Alesia.</History> 31 31 </Identity> 32 32 <Obstruction> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_gate.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_gate.xml index 70b961a..c0b3d8f 100644
a b 37 37 <Actor>structures/romans/siege_wall_gate.xml</Actor> 38 38 <FoundationActor>structures/fndn_wall.xml</FoundationActor> 39 39 </VisualActor> 40 <WallPiece> 41 <Length>36.0</Length> 42 </WallPiece> 40 43 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml index 5d7f105..2eb11f2 100644
a b 40 40 <Actor>structures/romans/siege_wall_long.xml</Actor> 41 41 <FoundationActor>structures/fndn_wall.xml</FoundationActor> 42 42 </VisualActor> 43 <WallPiece> 44 <Length>36.0</Length> 45 </WallPiece> 43 46 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml index 633b2f0..531bc52 100644
a b 40 40 <Actor>structures/romans/siege_wall_medium.xml</Actor> 41 41 <FoundationActor>structures/fndn_wall_short.xml</FoundationActor> 42 42 </VisualActor> 43 <WallPiece> 44 <Length>24.0</Length> 45 </WallPiece> 43 46 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml index 388c17c..c531c90 100644
a b 40 40 <Actor>structures/romans/siege_wall_short.xml</Actor> 41 41 <FoundationActor>structures/fndn_1x1.xml</FoundationActor> 42 42 </VisualActor> 43 <WallPiece> 44 <Length>12.0</Length> 45 </WallPiece> 43 46 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_tower.xml index b0f1252..d8158ea 100644
a b 37 37 <Actor>structures/romans/siege_wall_tower.xml</Actor> 38 38 <FoundationActor>structures/fndn_1x1.xml</FoundationActor> 39 39 </VisualActor> 40 <WallPiece> 41 <Length>6.0</Length> 42 </WallPiece> 40 43 </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..4e1ca29 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/romans/wall_gate.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>37.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..42aa3a0 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/romans/wall_long.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>37.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..3d721cb 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/romans/wall_medium.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>25.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..8febba2 100644
a b 21 21 <VisualActor> 22 22 <Actor>structures/romans/wall_short.xml</Actor> 23 23 </VisualActor> 24 <WallPiece> 25 <Length>13.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..8041f3e 100644
a b 15 15 <VisualActor> 16 16 <Actor>structures/romans/wall_tower.xml</Actor> 17 17 </VisualActor> 18 <WallPiece> 19 <Length>9.5</Length> 20 </WallPiece> 18 21 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml new file mode 100644 index 0000000..c56ae0f
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>rome</Civ> 5 <GenericName>Siege Wall</GenericName> 6 <SpecificName>Murus Latericius</SpecificName> 7 <Icon>structures/palisade.png</Icon> 8 <Tooltip>A wooden and turf palisade buildable in enemy and neutral territories.</Tooltip> 9 <History>Quick building, but expensive wooden and earthen walls used to surround and siege an enemy town or fortified position. The most famous examples are the Roman sieges of the Iberian stronghold of Numantia and the Gallic stronghold of Alesia.</History> 10 </Identity> 11 <WallSet> 12 <Templates> 13 <Tower>structures/rome_siege_wall_tower</Tower> 14 <Gate>structures/rome_siege_wall_gate</Gate> 15 <WallLong>structures/rome_siege_wall_long</WallLong> 16 <WallMedium>structures/rome_siege_wall_medium</WallMedium> 17 <WallShort>structures/rome_siege_wall_short</WallShort> 18 </Templates> 19 <MaxTowerOverlap>1.00</MaxTowerOverlap> 20 <MinTowerOverlap>0.05</MinTowerOverlap> 21 </WallSet> 22 </Entity> -
new file inaries/data/mods/public/simulation/templates/structures/rome_wallset_stone.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wallset_stone.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wallset_stone.xml new file mode 100644 index 0000000..3991b42
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_structure_defense_wallset"> 3 <Identity> 4 <Civ>rome</Civ> 5 <SpecificName>Moenia</SpecificName> 6 <History>Roman city walls used a number of innovations to thwart besiegers.</History> 7 </Identity> 8 <WallSet> 9 <Templates> 10 <Tower>structures/rome_wall_tower</Tower> 11 <Gate>structures/rome_wall_gate</Gate> 12 <WallLong>structures/rome_wall_long</WallLong> 13 <WallMedium>structures/rome_wall_medium</WallMedium> 14 <WallShort>structures/rome_wall_short</WallShort> 15 </Templates> 16 <MaxTowerOverlap>0.80</MaxTowerOverlap> 17 <MinTowerOverlap>0.10</MinTowerOverlap> 18 </WallSet> 19 </Entity> -
new file inaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml new file mode 100644 index 0000000..ab9dfcc
- + 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.) using the WallSet component (to be overridden by child 5 templates). --> 6 <Entity> 7 <Identity> 8 <Icon>structures/wall.png</Icon> 9 <Classes datatype="tokens">Town Wall</Classes> 10 <GenericName>City Wall</GenericName> 11 <Tooltip>Wall off your town for a stout defense.</Tooltip> 12 <Icon>structures/wall.png</Icon> 13 <RequiredTechnology>phase_town</RequiredTechnology> 14 </Identity> 15 <WallSet> 16 <MaxTowerOverlap>0.85</MaxTowerOverlap> 17 <MinTowerOverlap>0.05</MinTowerOverlap> 18 </WallSet> 19 </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 b4f530d..827c191 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 structures/{civ}_wallset_stone 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 f85a53b..25831ce 100644
a b 19 19 structures/{civ}_barracks 20 20 structures/{civ}_dock 21 21 structures/{civ}_defense_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/spart_champion_infantry_sword.xml
diff --git a/binaries/data/mods/public/simulation/templates/units/spart_champion_infantry_sword.xml b/binaries/data/mods/public/simulation/templates/units/spart_champion_infantry_sword.xml index 96cb330..d5f8530 100644
a b 48 48 </Attack> 49 49 <Builder> 50 50 <Entities datatype="tokens"> 51 -structures/{civ}_wall 52 -structures/{civ}_wall_gate 53 -structures/{civ}_wall_tower 51 -structures/{civ}_wallset_stone 54 52 structures/spart_syssiton 55 53 </Entities> 56 54 </Builder> -
binaries/data/mods/public/simulation/templates/units/spart_infantry_javelinist_b.xml
diff --git a/binaries/data/mods/public/simulation/templates/units/spart_infantry_javelinist_b.xml b/binaries/data/mods/public/simulation/templates/units/spart_infantry_javelinist_b.xml index ca5172d..cc1d87a 100644
a b 2 2 <Entity parent="template_unit_infantry_ranged_javelinist"> 3 3 <Builder> 4 4 <Entities datatype="tokens"> 5 -structures/{civ}_wall 6 -structures/{civ}_wall_gate 7 -structures/{civ}_wall_tower 5 -structures/{civ}_wallset_stone 8 6 structures/spart_syssiton 9 7 </Entities> 10 8 </Builder> -
binaries/data/mods/public/simulation/templates/units/spart_infantry_spearman_b.xml
diff --git a/binaries/data/mods/public/simulation/templates/units/spart_infantry_spearman_b.xml b/binaries/data/mods/public/simulation/templates/units/spart_infantry_spearman_b.xml index 6969fe3..e1372ca 100644
a b 13 13 </Attack> 14 14 <Builder> 15 15 <Entities datatype="tokens"> 16 -structures/{civ}_wall 17 -structures/{civ}_wall_gate 18 -structures/{civ}_wall_tower 16 -structures/{civ}_wallset_stone 19 17 structures/spart_syssiton 20 18 </Entities> 21 19 </Builder> -
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 604665e..a197bb4 100644
a b 22 22 #include "graphics/Texture.h" 23 23 #include "maths/Vector2D.h" 24 24 #include "maths/Vector3D.h" 25 #include "maths/FixedVector3D.h" 25 26 #include "ps/Overlay.h" // CColor (TODO: that file has nothing to do with overlays, it should be renamed) 26 27 27 28 class CTerrain; -
source/gui/CTooltip.cpp
diff --git a/source/gui/CTooltip.cpp b/source/gui/CTooltip.cpp index 4d12296..dfacb4e 100644
a b void CTooltip::SetupText() 87 87 CPos mousepos, offset; 88 88 EVAlign anchor; 89 89 bool independent; 90 90 91 GUI<bool>::GetSetting(this, "independent", independent); 91 92 if (independent) 92 93 mousepos = GetMousePos(); 93 94 else 94 95 GUI<CPos>::GetSetting(this, "_mousepos", mousepos); 96 95 97 GUI<CPos>::GetSetting(this, "offset", offset); 96 98 GUI<EVAlign>::GetSetting(this, "anchor", anchor); 97 99 -
source/gui/scripting/ScriptFunctions.cpp
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 711cd41..d2be90a 100644
a b std::vector<entity_id_t> PickFriendlyEntitiesOnScreen(void* cbdata, int player) 142 142 return PickFriendlyEntitiesInRect(cbdata, 0, 0, g_xres, g_yres, player); 143 143 } 144 144 145 std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank )145 std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations) 146 146 { 147 return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false );147 return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations); 148 148 } 149 149 150 CFixedVector3D GetTerrainAt Point(void* UNUSED(cbdata), int x, int y)150 CFixedVector3D GetTerrainAtScreenPoint(void* UNUSED(cbdata), int x, int y) 151 151 { 152 152 CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true); 153 153 return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z)); … … void GuiScriptingInit(ScriptInterface& scriptInterface) 589 589 scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint"); 590 590 scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect"); 591 591 scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen"); 592 scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");593 scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAt Point>("GetTerrainAtPoint");592 scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities"); 593 scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtScreenPoint>("GetTerrainAtScreenPoint"); 594 594 595 595 // Network / game setup functions 596 596 scriptInterface.RegisterFunction<void, &StartNetworkGame>("StartNetworkGame"); -
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..1a244ce 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: 151 167 serialize.Bool("active", m_Active); 152 168 serialize.Bool("moving", m_Moving); 153 169 serialize.NumberU32_Unbounded("control group", m_ControlGroup); 170 serialize.NumberU32_Unbounded("control group 2", m_ControlGroup2); 154 171 serialize.NumberU32_Unbounded("tag", m_Tag.n); 155 172 serialize.NumberU8_Unbounded("flags", m_Flags); 156 173 } … … public: 194 211 // Need to create a new pathfinder shape: 195 212 if (m_Type == STATIC) 196 213 m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), 197 data.x, data.z, data.a, m_Size0, m_Size1, m_Flags );214 data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); 198 215 else 199 216 m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), 200 217 data.x, data.z, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); … … public: 241 258 if (!cmpPosition->IsInWorld()) 242 259 return; // don't need an obstruction 243 260 261 // TODO: code duplication from message handlers 244 262 CFixedVector2D pos = cmpPosition->GetPosition2D(); 245 263 if (m_Type == STATIC) 246 264 m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), 247 pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags );265 pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); 248 266 else 249 267 m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), 250 268 pos.X, pos.Y, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); … … public: 255 273 256 274 // Delete the obstruction shape 257 275 276 // TODO: code duplication from message handlers 258 277 if (m_Tag.valid()) 259 278 { 260 279 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); … … public: 346 365 if (!cmpPathfinder) 347 366 return false; // error 348 367 368 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter 369 if (m_ControlGroup == INVALID_ENTITY) 370 { 371 LOGERROR(L"[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid"); 372 return false; 373 } 374 349 375 // Get passability class 350 376 ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className); 351 377 352 // Ignore collisions with self, or with non-foundation-blocking obstructions 353 SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); 378 // Ignore collisions within the same control group, or with other non-foundation-blocking shapes. 379 // Note that, since the control group for each entity defaults to the entity's ID, this is typically 380 // equivalent to only ignoring the entity's own shape and other non-foundation-blocking shapes. 381 SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, 382 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); 354 383 355 384 if (m_Type == STATIC) 356 385 return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass); … … public: 375 404 if (!cmpObstructionManager) 376 405 return ret; // error 377 406 378 // Ignore collisions with self, or with non-construction-blocking obstructions 379 SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); 407 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter 408 if (m_ControlGroup == INVALID_ENTITY) 409 { 410 LOGERROR(L"[CmpObstruction] Cannot test for construction obstructions; primary control group must be valid"); 411 return ret; 412 } 413 414 // Ignore collisions within the same control group, or with other non-construction-blocking shapes. 415 // Note that, since the control group for each entity defaults to the entity's ID, this is typically 416 // equivalent to only ignoring the entity's own shape and other non-construction-blocking shapes. 417 SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, 418 ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); 380 419 381 420 if (m_Type == STATIC) 382 421 cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); … … public: 401 440 virtual void SetControlGroup(entity_id_t group) 402 441 { 403 442 m_ControlGroup = group; 443 UpdateControlGroups(); 444 } 404 445 405 if (m_Tag.valid() && m_Type == UNIT) 446 virtual void SetControlGroup2(entity_id_t group2) 447 { 448 m_ControlGroup2 = group2; 449 UpdateControlGroups(); 450 } 451 452 virtual entity_id_t GetControlGroup() 453 { 454 return m_ControlGroup; 455 } 456 457 virtual entity_id_t GetControlGroup2() 458 { 459 return m_ControlGroup2; 460 } 461 462 void UpdateControlGroups() 463 { 464 if (m_Tag.valid()) 406 465 { 407 466 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 408 467 if (cmpObstructionManager) 409 cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); 468 { 469 if (m_Type == UNIT) 470 { 471 cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); 472 } 473 else if (m_Type == STATIC) 474 { 475 cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); 476 } 477 } 410 478 } 411 479 } 412 480 -
source/simulation2/components/CCmpObstructionManager.cpp
diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index e1a8d6a..68089e8 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); 109 serialize.NumberU32_Unbounded("group2", value.group2); 105 110 } 106 111 }; 107 112 … … public: 257 262 return UNIT_INDEX_TO_TAG(id); 258 263 } 259 264 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 )265 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 266 { 262 267 fixed s, c; 263 268 sincos_approx(a, s, c); 264 269 CFixedVector2D u(c, -s); 265 270 CFixedVector2D v(s, c); 266 271 267 StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags };272 StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags, group, group2 }; 268 273 u32 id = m_StaticShapeNext++; 269 274 m_StaticShapes[id] = shape; 270 275 MakeDirtyStatic(flags); … … public: 367 372 } 368 373 } 369 374 375 virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) 376 { 377 ENSURE(TAG_IS_VALID(tag) && TAG_IS_STATIC(tag)); 378 379 if (TAG_IS_STATIC(tag)) 380 { 381 StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)]; 382 shape.group = group; 383 shape.group2 = group2; 384 } 385 } 386 370 387 virtual void RemoveShape(tag_t tag) 371 388 { 372 389 ENSURE(TAG_IS_VALID(tag)); … … bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti 533 550 std::map<u32, UnitShape>::iterator it = m_UnitShapes.find(unitShapes[i]); 534 551 ENSURE(it != m_UnitShapes.end()); 535 552 536 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))553 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 537 554 continue; 538 555 539 556 CFixedVector2D center(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti 548 565 std::map<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]); 549 566 ENSURE(it != m_StaticShapes.end()); 550 567 551 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))568 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 552 569 continue; 553 570 554 571 CFixedVector2D center(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestStaticShape(const IObstructionTestFilter& filte 592 609 593 610 for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it) 594 611 { 595 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))612 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 596 613 continue; 597 614 598 615 CFixedVector2D center1(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestStaticShape(const IObstructionTestFilter& filte 608 625 609 626 for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it) 610 627 { 611 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))628 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 612 629 continue; 613 630 614 631 CFixedVector2D center1(it->second.x, it->second.z); … … bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, 649 666 650 667 for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it) 651 668 { 652 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))669 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 653 670 continue; 654 671 655 672 entity_pos_t r1 = it->second.r; … … bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, 665 682 666 683 for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it) 667 684 { 668 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))685 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 669 686 continue; 670 687 671 688 CFixedVector2D center1(it->second.x, it->second.z); … … void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter 869 886 std::map<u32, UnitShape>::iterator it = m_UnitShapes.find(unitShapes[i]); 870 887 ENSURE(it != m_UnitShapes.end()); 871 888 872 if (!filter. Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))889 if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) 873 890 continue; 874 891 875 892 entity_pos_t r = it->second.r; … … void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter 890 907 std::map<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]); 891 908 ENSURE(it != m_StaticShapes.end()); 892 909 893 if (!filter. Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))910 if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2)) 894 911 continue; 895 912 896 913 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 0a52b6f..467ff1c 100644
a b void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>& 869 869 // process from there on until the entire line is checked. The output is the array of base nodes. 870 870 871 871 std::vector<CVector2D> newCoords; 872 StationaryO bstructionFilter obstructionFilter;872 StationaryOnlyObstructionFilter obstructionFilter; 873 873 entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness); 874 874 ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass); 875 875 -
source/simulation2/components/CCmpTemplateManager.cpp
diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index 7b24af7..d69cc36 100644
a b bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int 357 357 } 358 358 359 359 // Load the new file into the template data (overriding parent values) 360 CParamNode::LoadXML(m_TemplateFileData[templateName], xero );360 CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str()); 361 361 362 362 return true; 363 363 } … … void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, C 376 376 out = m_TemplateFileData[templateName]; 377 377 378 378 // Initialise the actor's name and make it an Atlas selectable entity. 379 std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(wstring_from_utf8(actorName))); 379 std::wstring actorNameW = wstring_from_utf8(actorName); 380 std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW)); 380 381 std::string xml = "<Entity>" 381 382 "<VisualActor><Actor>" + name + "</Actor></VisualActor>" 382 383 "<Selectable>" … … void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, C 385 386 "</Selectable>" 386 387 "</Entity>"; 387 388 388 CParamNode::LoadXMLString(out, xml.c_str() );389 CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str()); 389 390 } 390 391 391 392 static Status AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData) -
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..d2a3089 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. The secondary 361 * control group to reject entities from may be set to INVALID_ENTITY to not use it. 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..8210017
- + 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(1000), fixed::FromInt(1000)); 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_simple_collisions() 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_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_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_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_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_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/simulation2/system/ParamNode.cpp
diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp index 7cbf1bd..94fd500 100644
a b CParamNode::CParamNode(bool isOk) : 44 44 { 45 45 } 46 46 47 void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb )47 void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sourceIdentifier /*= NULL*/) 48 48 { 49 ret.ApplyLayer(xmb, xmb.GetRoot() );49 ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier); 50 50 } 51 51 52 52 void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path) … … void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path) 56 56 if (ok != PSRETURN_OK) 57 57 return; // (Xeromyces already logged an error) 58 58 59 LoadXML(ret, xero );59 LoadXML(ret, xero, path.string().c_str()); 60 60 } 61 61 62 PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml )62 PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier /*=NULL*/) 63 63 { 64 64 CXeromyces xero; 65 65 PSRETURN ok = xero.LoadString(xml); … … PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml) 71 71 return PSRETURN_OK; 72 72 } 73 73 74 void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element )74 void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/) 75 75 { 76 76 ResetScriptVal(); 77 77 … … void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element) 128 128 if (tokenIt != tokens.end()) 129 129 tokens.erase(tokenIt); 130 130 else 131 LOGWARNING(L"[ParamNode] Could not remove token '%ls' from node '%hs' ; not present in list nor inherited (possible typo?)",132 newTokens[i].substr(1).c_str(), name.c_str() );131 LOGWARNING(L"[ParamNode] Could not remove token '%ls' from node '%hs'%ls; not present in list nor inherited (possible typo?)", 132 newTokens[i].substr(1).c_str(), name.c_str(), sourceIdentifier ? (L" in '" + std::wstring(sourceIdentifier) + L"'").c_str() : L""); 133 133 } 134 134 else 135 135 { … … void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element) 153 153 // Recurse through the element's children 154 154 XERO_ITER_EL(element, child) 155 155 { 156 node.ApplyLayer(xmb, child );156 node.ApplyLayer(xmb, child, sourceIdentifier); 157 157 } 158 158 159 159 // Add the element's attributes, prefixing names with "@" -
source/simulation2/system/ParamNode.h
diff --git a/source/simulation2/system/ParamNode.h b/source/simulation2/system/ParamNode.h index 7caffbd..8f0d7d5 100644
a b public: 123 123 * Loads the XML data specified by @a file into the node @a ret. 124 124 * Any existing data in @a ret will be overwritten or else kept, so this 125 125 * can be called multiple times to build up a node from multiple inputs. 126 * 127 * @param sourceIdentifier Optional; string you can pass along to indicate the source of 128 * the data getting loaded. Used for output to log messages if an error occurs. 126 129 */ 127 static void LoadXML(CParamNode& ret, const XMBFile& file );130 static void LoadXML(CParamNode& ret, const XMBFile& file, const wchar_t* sourceIdentifier = NULL); 128 131 129 132 /** 130 133 * Loads the XML data specified by @a path into the node @a ret. … … public: 136 139 /** 137 140 * See LoadXML, but parses the XML string @a xml. 138 141 * @return error code if parsing failed, else @c PSRETURN_OK 142 * 143 * @param sourceIdentifier Optional; string you can pass along to indicate the source of 144 * the data getting loaded. Used for output to log messages if an error occurs. 139 145 */ 140 static PSRETURN LoadXMLString(CParamNode& ret, const char* xml );146 static PSRETURN LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier = NULL); 141 147 142 148 /** 143 149 * Finds the childs named @a name from @a src and from @a this, and copies the source child's children … … public: 228 234 static std::wstring EscapeXMLString(const std::wstring& str); 229 235 230 236 private: 231 void ApplyLayer(const XMBFile& xmb, const XMBElement& element); 237 238 /** 239 * Overlays the specified data onto this node. See class documentation for the concept and examples. 240 * 241 * @param xmb Representation of the XMB file containing an element with the data to apply. 242 * @param element Element inside the specified @p xmb file containing the data to apply. 243 * @param sourceIdentifier Optional; string you can pass along to indicate the source of 244 * the data getting applied. Used for output to log messages if an error occurs. 245 */ 246 void ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier = NULL); 232 247 233 248 void ResetScriptVal(); 234 249 -
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 414a95b..46cf352 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 -
new file wall_placement_1.html
diff --git a/wall_placement_1.html b/wall_placement_1.html new file mode 100644 index 0000000..bd16f42
- + 1 <doctype html> 2 <html> 3 <head> 4 <title>Wall Placement Demo</title> 5 </head> 6 <body> 7 <canvas id="canvas" width="1600" height="1000" style="border:1px solid black;"></canvas> 8 <script type="text/javascript"> 9 10 var canvas = document.getElementById("canvas"); 11 var ctx = canvas.getContext("2d"); 12 ctx.font = "1.5pt Arial"; 13 14 var start = null; 15 var end = null; 16 var endFixed = false; 17 18 var scale = 5; 19 var pointMarkerRadius = 0.5; 20 var pointMarkerColor = "#000"; 21 22 var candidateSegments = [ 23 {"template": "L", "len": 34.0}, 24 {"template": "M", "len": 23.0}, 25 {"template": "S", "len": 10.0}, 26 ]; 27 var len = 31.3; 28 var towerLen = 8.5; 29 var wallThickness = 2; // for drawing purposes only 30 31 function fillCircle(x, y, r, s) 32 { 33 ctx.beginPath(); 34 ctx.arc(x, y, r, 0, 2 * Math.PI, false); 35 ctx.fillStyle = s; 36 ctx.fill(); 37 } 38 39 function drawRect(x, y, w, h, a) 40 { 41 ctx.save(); 42 ctx.translate(x, y); 43 ctx.rotate(a); 44 ctx.beginPath(); 45 ctx.rect(-w/2, -h/2, w, h); 46 ctx.stroke(); 47 ctx.restore(); 48 } 49 50 function drawText(x, y, a, txt) 51 { 52 ctx.save(); 53 ctx.translate(x, y); 54 ctx.rotate(a); 55 ctx.textAlign = "center"; 56 ctx.fillText(txt, 0, 0.7); 57 ctx.restore(); 58 } 59 60 function drawTower(x, y, a, ss) 61 { 62 ctx.strokeStyle = 'gray'; 63 if (ss) 64 ctx.strokeStyle = ss; 65 66 drawRect(x, y, towerLen, towerLen, a); 67 } 68 69 function drawWall(x, y, l, a, type, ss) 70 { 71 ctx.strokeStyle = 'black'; 72 if (ss) 73 ctx.strokeStyle = ss; 74 75 drawRect(x, y, l, wallThickness, a); 76 drawText(x, y, a, type); 77 } 78 79 function drawStats(stats) 80 { 81 ctx.save(); 82 ctx.font = "3pt Courier New"; 83 ctx.textAlign = "left"; 84 //ctx.fillText("r = " + stats.r, 10, 10); 85 86 var num = 1; 87 for (var each in stats) 88 ctx.fillText(each + ": " + stats[each], 5, 2+5*(num++)); 89 90 ctx.restore(); 91 } 92 93 function draw() 94 { 95 ctx.save(); 96 ctx.translate(0.5, 0.5); // finer lines 97 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 98 ctx.scale(scale, scale); 99 ctx.lineWidth = 1/scale; 100 101 var pointMarkerStyle = "#000"; 102 var placement = null; 103 104 if (start && end) 105 { 106 var dir = {"x": end.x - start.x, "z": end.z - start.z}; 107 var len = Math.sqrt(dir.x * dir.x + dir.z * dir.z); 108 var angle = Math.atan2(dir.z, dir.x); // angle of this wall segment (relative to world-space X/Z axes) 109 110 placement = WallPlaceSegmentRec(len, 0, [], candidateSegments, towerLen, 0); 111 if (placement) 112 { 113 var placedEntities = placement.segments; // list of chosen candidate segments 114 var r = placement.r; // remaining distance to target without towers (must be <= (N-1) * towerWidth) 115 var s = r / (placedEntities.length - 1); // spacing 116 117 var dirNormalized = {"x": dir.x / len, "z": dir.z / len}; 118 119 var progress = 0; 120 for (var i = 0; i < placedEntities.length; i++) 121 { 122 var placedEntity = placedEntities[i]; 123 var targetX = start.x + (progress + placedEntity.len/2) * dirNormalized.x; 124 var targetZ = start.z + (progress + placedEntity.len/2) * dirNormalized.z; 125 126 /*result.push({ 127 "template": placedEntity.template, 128 "pos": {"x": targetX, "z": targetZ}, 129 "angle": angle, 130 });*/ 131 drawWall(targetX, targetZ, placedEntity.len, angle, placedEntity.template); 132 133 if (i < placedEntities.length - 1) 134 { 135 var towerX = start.x + (progress + placedEntity.len + s/2) * dirNormalized.x; 136 var towerZ = start.z + (progress + placedEntity.len + s/2) * dirNormalized.z; 137 138 /*result.push({ 139 "template": wallSet.tower, 140 "pos": {"x": towerX, "z": towerZ}, 141 "angle": angle, 142 });*/ 143 drawTower(towerX, towerZ, angle); 144 } 145 146 progress += placedEntity.len + s; 147 } 148 } 149 else 150 { 151 pointMarkerStyle = "#c00"; 152 } 153 } 154 155 if (start) 156 { 157 fillCircle(start.x, start.z, pointMarkerRadius, placement ? "#000" : "#c00"); 158 drawTower(start.x, start.z, angle, placement ? "gray" : "#c00"); 159 } 160 if (end) 161 { 162 fillCircle(end.x, end.z, pointMarkerRadius, placement ? "#000" : "#c00"); 163 drawTower(end.x, end.z, angle, placement ? "gray" : "#c00"); 164 } 165 166 var stats = {"r ": "", "N ": "", "Nt": ""}; 167 if (placement) 168 { 169 stats["r "] = Math.round(placement.r*100)/100; 170 stats["N "] = placement.segments.length; 171 stats["Nt"] = stats["N "] * towerLen; 172 } 173 drawStats(stats); 174 175 ctx.restore(); 176 } 177 178 function onCanvasMouseDown(ev) 179 { 180 if (start === null) 181 { 182 start = {"x": (ev.clientX - canvas.offsetLeft)/scale, "z": (ev.clientY - canvas.offsetTop)/scale}; 183 } 184 else if (!endFixed) 185 { 186 endFixed = true; 187 end = {"x": (ev.clientX - canvas.offsetLeft)/scale, "z": (ev.clientY - canvas.offsetTop)/scale}; 188 } 189 else 190 { 191 endFixed = false; 192 start = null; 193 end = null; 194 } 195 draw(); 196 } 197 198 function onCanvasMouseMove(ev) 199 { 200 if (start !== null && !endFixed) 201 end = {"x": (ev.clientX - canvas.offsetLeft)/scale, "z": (ev.clientY - canvas.offsetTop)/scale}; 202 draw(); 203 } 204 205 canvas.addEventListener("mousedown", onCanvasMouseDown, false); 206 canvas.addEventListener("mousemove", onCanvasMouseMove, false); 207 208 function WallPlaceSegmentRec(totalDist, distSoFar, segments, candidateSegments, bufferLen, depth) 209 { 210 //var prefix = "[" + depth + "] "; 211 //for (var d = 0; d < depth; d++) 212 //prefix += " "; 213 214 //console.log("--------------------------"); 215 //console.log("%sdistSoFar = %f/%f", prefix, distSoFar, totalDist); 216 //console.log("%ssegments (%d) = %s", prefix, segments.length, uneval(segments)); 217 218 // in turn, try placing L, M and S wall segments; if continuing recursively returns an impossible configuration, try the next 219 for each (var candSegment in candidateSegments) 220 { 221 //console.log("%sTrying to place segment %s of length %d", prefix, candSegment.template, candSegment.len); 222 segments.push(candSegment); 223 224 var newDistSoFar = distSoFar + candSegment.len; 225 var r = totalDist - newDistSoFar; // remaining distance 226 227 if (newDistSoFar > totalDist) 228 { 229 // won't fit, try next one 230 //console.log("%sDistance so far (%d) exceeds target (%d), trying next piece", prefix, newDistSoFar, totalDist); 231 segments.pop(); 232 continue; 233 } 234 else if (r > (segments.length - 1) * bufferLen) 235 { 236 // current placement doesn't exceed the total distance, but we still have some distance to go before we hit 237 // (d - newDistSoFar) <= (segments.len - 1 ) * bufferLen), continue building recursively 238 // if none of the recursively evaluated placements succeed, then try the next one 239 //console.log("%sr > (#segments - 1) * bufferLen", prefix); 240 //console.log("%s%f > %d * %f", prefix, r, segments.length - 1, bufferLen); 241 //console.log("%sContinue checking recursively", prefix); 242 243 var recursiveResult = WallPlaceSegmentRec(totalDist, newDistSoFar, segments, candidateSegments, bufferLen, depth + 1); 244 if (!recursiveResult) 245 { 246 //console.log("%sNo solution found in recursive tests, trying next piece (segments = %s)", prefix, uneval(segments)); 247 segments.pop(); 248 continue; 249 } 250 else 251 { 252 //console.log("%sFound solution in recursive search, returning", prefix); 253 return recursiveResult; 254 } 255 } 256 else 257 { 258 // found a placement 259 //console.log("%sFound a placement!", prefix); 260 return {"segments": segments, "r": r}; 261 } 262 } 263 264 // no placement possible :( 265 return false; 266 } 267 268 //var placement = WallPlaceSegmentRec(len, 0, [], candidateSegments, towerLen, 0); 269 //console.log(placement); 270 </script> 271 </body> 272 </html> 273 No newline at end of file -
new file wall_placement_2.html
diff --git a/wall_placement_2.html b/wall_placement_2.html new file mode 100644 index 0000000..8c86e0b
- + 1 <doctype html> 2 <html> 3 <head> 4 <title>Wall Placement Demo</title> 5 </head> 6 <body> 7 <canvas id="canvas" width="1600" height="1000" style="border:1px solid black;"></canvas> 8 <script type="text/javascript"> 9 10 var canvas = document.getElementById("canvas"); 11 var ctx = canvas.getContext("2d"); 12 ctx.font = "1.5pt Arial"; 13 14 var start = null; 15 var end = null; 16 var endFixed = false; 17 18 var scale = 5; 19 var pointMarkerRadius = 0.5; 20 var pointMarkerColor = "#000"; 21 22 var candidateSegments = [ 23 {"template": "L", "len": 34.0}, 24 {"template": "M", "len": 23.0}, 25 {"template": "S", "len": 10.0}, 26 ]; 27 var len = 31.3; 28 var towerLen = 8.5; 29 var wallThickness = 2; // for drawing purposes only 30 31 function fillCircle(x, y, r, s) 32 { 33 ctx.beginPath(); 34 ctx.arc(x, y, r, 0, 2 * Math.PI, false); 35 ctx.fillStyle = s; 36 ctx.fill(); 37 } 38 39 function drawRect(x, y, w, h, a) 40 { 41 ctx.save(); 42 ctx.translate(x, y); 43 ctx.rotate(a); 44 ctx.beginPath(); 45 ctx.rect(-w/2, -h/2, w, h); 46 ctx.stroke(); 47 ctx.restore(); 48 } 49 50 function drawText(x, y, a, txt) 51 { 52 ctx.save(); 53 ctx.translate(x, y); 54 ctx.rotate(a); 55 ctx.textAlign = "center"; 56 ctx.fillText(txt, 0, 0.7); 57 ctx.restore(); 58 } 59 60 function drawTower(x, y, a, ss) 61 { 62 ctx.strokeStyle = 'gray'; 63 if (ss) 64 ctx.strokeStyle = ss; 65 66 drawRect(x, y, towerLen, towerLen, a); 67 } 68 69 function drawWall(x, y, l, a, type, ss) 70 { 71 ctx.strokeStyle = 'black'; 72 if (ss) 73 ctx.strokeStyle = ss; 74 75 drawRect(x, y, l, wallThickness, a); 76 drawText(x, y, a, type); 77 } 78 79 function drawStats(stats) 80 { 81 ctx.save(); 82 ctx.font = "3pt Courier New"; 83 ctx.textAlign = "left"; 84 //ctx.fillText("r = " + stats.r, 10, 10); 85 86 var num = 1; 87 for (var each in stats) 88 ctx.fillText(each + ": " + stats[each], 5, 2+5*(num++)); 89 90 ctx.restore(); 91 } 92 93 function draw() 94 { 95 ctx.save(); 96 ctx.translate(0.5, 0.5); // finer lines 97 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 98 ctx.scale(scale, scale); 99 ctx.lineWidth = 1/scale; 100 101 var pointMarkerStyle = "#000"; 102 var placement = null; 103 104 if (start && end) 105 { 106 var dir = {"x": end.x - start.x, "z": end.z - start.z}; 107 var len = Math.sqrt(dir.x * dir.x + dir.z * dir.z); 108 var dirNormalized = {"x": dir.x / len, "z": dir.z / len}; 109 110 // require a minimal distance of 'towerLen' 111 if(len < towerLen) 112 { 113 end = {"x": start.x + towerLen * dirNormalized.x, "z": start.z + towerLen * dirNormalized.z}; 114 len = towerLen; 115 } 116 117 var angle = Math.atan2(dir.z, dir.x); // angle of this wall segment (relative to world-space X/Z axes) 118 119 120 placement = WallPlaceSegmentRec(len, 0, 0.9, 0.1, [], candidateSegments, towerLen, 0); 121 if (placement) 122 { 123 var placedEntities = placement.segments; // list of chosen candidate segments 124 var r = placement.r; // remaining distance to target without towers (must be <= (N) * towerWidth) 125 //var s = r / (placedEntities.length - 1); // spacing 126 var s = r / (2 * placedEntities.length); 127 128 var progress = 0; 129 var remainingR = r; 130 for (var i = 0; i < placedEntities.length; i++) 131 { 132 var placedEntity = placedEntities[i]; 133 var targetX = start.x + (progress + s + placedEntity.len/2) * dirNormalized.x; 134 var targetZ = start.z + (progress + s + placedEntity.len/2) * dirNormalized.z; 135 136 /*result.push({ 137 "template": placedEntity.template, 138 "pos": {"x": targetX, "z": targetZ}, 139 "angle": angle, 140 });*/ 141 drawWall(targetX, targetZ, placedEntity.len, angle, placedEntity.template); 142 143 if (i < placedEntities.length - 1) 144 { 145 var towerX = start.x + (progress + s + placedEntity.len + s) * dirNormalized.x; 146 var towerZ = start.z + (progress + s + placedEntity.len + s) * dirNormalized.z; 147 148 /*result.push({ 149 "template": wallSet.tower, 150 "pos": {"x": towerX, "z": towerZ}, 151 "angle": angle, 152 });*/ 153 drawTower(towerX, towerZ, angle); 154 } 155 156 progress += placedEntity.len + 2*s; 157 } 158 } 159 else 160 { 161 pointMarkerStyle = "#c00"; 162 } 163 } 164 165 if (start) 166 { 167 fillCircle(start.x, start.z, pointMarkerRadius, placement ? "#000" : "#c00"); 168 drawTower(start.x, start.z, angle, placement ? "gray" : "#c00"); 169 } 170 if (end) 171 { 172 fillCircle(end.x, end.z, pointMarkerRadius, placement ? "#000" : "#c00"); 173 drawTower(end.x, end.z, angle, placement ? "gray" : "#c00"); 174 } 175 176 var stats = {"r ": "", "N ": "", "Nt": ""}; 177 if (placement) 178 { 179 stats["r "] = Math.round(placement.r*100)/100; 180 stats["N "] = placement.segments.length; 181 stats["Nt"] = stats["N "] * towerLen; 182 } 183 drawStats(stats); 184 185 ctx.restore(); 186 } 187 188 function onCanvasMouseDown(ev) 189 { 190 if (start === null) 191 { 192 start = {"x": (ev.clientX - canvas.offsetLeft)/scale, "z": (ev.clientY - canvas.offsetTop)/scale}; 193 } 194 else if (!endFixed) 195 { 196 endFixed = true; 197 end = {"x": (ev.clientX - canvas.offsetLeft)/scale, "z": (ev.clientY - canvas.offsetTop)/scale}; 198 } 199 else 200 { 201 endFixed = false; 202 start = null; 203 end = null; 204 } 205 draw(); 206 } 207 208 function onCanvasMouseMove(ev) 209 { 210 if (start !== null && !endFixed) 211 end = {"x": (ev.clientX - canvas.offsetLeft)/scale, "z": (ev.clientY - canvas.offsetTop)/scale}; 212 213 draw(); 214 } 215 216 canvas.addEventListener("mousedown", onCanvasMouseDown, false); 217 canvas.addEventListener("mousemove", onCanvasMouseMove, false); 218 219 function WallPlaceSegmentRec(totalDist, distSoFar, maxOverlap, minOverlap, segments, candidateSegments, bufferLen, depth) 220 { 221 //var prefix = "[" + depth + "] "; 222 //for (var d = 0; d < depth; d++) 223 //prefix += " "; 224 225 //console.log("--------------------------"); 226 //console.log("%sdistSoFar = %f/%f", prefix, distSoFar, totalDist); 227 //console.log("%ssegments (%d) = %s", prefix, segments.length, uneval(segments)); 228 229 // in turn, try placing L, M and S wall segments; if continuing recursively returns an impossible configuration, try the next 230 for (var k in candidateSegments) 231 { 232 var candSegment = candidateSegments[k]; 233 //console.log("%sTrying to place segment %s of length %d", prefix, candSegment.template, candSegment.len); 234 segments.push(candSegment); 235 236 var newDistSoFar = distSoFar + candSegment.len; 237 var r = totalDist - newDistSoFar; // remaining distance 238 239 if (r < (1 - 2*maxOverlap)*segments.length*bufferLen) 240 { 241 // we placed 'too much' wall, pop it back out and try the next 242 //console.log("%sDistance so far (%d) exceeds target (%d), trying next piece", prefix, newDistSoFar, totalDist); 243 segments.pop(); 244 continue; 245 } 246 else if (r > (1-2*minOverlap)*segments.length*bufferLen) 247 { 248 // current placement doesn't exceed the total distance, but we still have some distance to go before we hit 249 // (d - newDistSoFar) <= (segments.len - 1 ) * bufferLen), continue building recursively 250 // if none of the recursively evaluated placements succeed, then try the next one 251 //console.log("%sr > (#segments - 1) * bufferLen", prefix); 252 //console.log("%s%f > %d * %f", prefix, r, segments.length - 1, bufferLen); 253 //console.log("%sContinue checking recursively", prefix); 254 255 var recursiveResult = WallPlaceSegmentRec(totalDist, newDistSoFar, maxOverlap, minOverlap, segments, candidateSegments, bufferLen, depth + 1); 256 if (!recursiveResult) 257 { 258 //console.log("%sNo solution found in recursive tests, trying next piece (segments = %s)", prefix, uneval(segments)); 259 segments.pop(); 260 continue; 261 } 262 else 263 { 264 //console.log("%sFound solution in recursive search, returning", prefix); 265 return recursiveResult; 266 } 267 } 268 else 269 { 270 // found a placement 271 //console.log("%sFound a placement!", prefix); 272 return {"segments": segments, "r": r}; 273 } 274 } 275 276 // no placement possible :( 277 return false; 278 } 279 280 //var placement = WallPlaceSegmentRec(len, 0, [], candidateSegments, towerLen, 0); 281 //console.log(placement); 282 </script> 283 </body> 284 </html> 285 No newline at end of file