Skip to content

Commit 72babb5

Browse files
committed
Replace Area Guard mission assignment cursor action script with new generic mission assignment cursor action script
1 parent f0fc61d commit 72babb5

File tree

3 files changed

+388
-127
lines changed

3 files changed

+388
-127
lines changed
Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
// Script for activating a mission assignment cursor
2+
3+
// Using clauses.
4+
// Unless you know what's in the WAE code-base, you want to always include
5+
// these "standard usings".
6+
using Microsoft.Xna.Framework;
7+
using Rampastring.Tools;
8+
using Rampastring.XNAUI;
9+
using Rampastring.XNAUI.XNAControls;
10+
using System;
11+
using System.Collections.Generic;
12+
using System.Diagnostics;
13+
using System.IO;
14+
using System.Linq;
15+
using System.Text;
16+
using TSMapEditor;
17+
using TSMapEditor.GameMath;
18+
using TSMapEditor.Misc;
19+
using TSMapEditor.Models;
20+
using TSMapEditor.Mutations;
21+
using TSMapEditor.Rendering;
22+
using TSMapEditor.Scripts;
23+
using TSMapEditor.UI;
24+
using TSMapEditor.UI.Controls;
25+
using TSMapEditor.UI.Windows;
26+
27+
namespace WAEScript
28+
{
29+
/// <summary>
30+
/// Script for activating a "Assign Mission" cursor action.
31+
/// Also serves an example for creating custom INItializableWindow interfaces in scripts.
32+
/// </summary>
33+
public class ActivateAssignMissionCursorActionScript
34+
{
35+
36+
37+
/// <summary>
38+
/// Our custom window class.
39+
/// </summary>
40+
class SelectMissionWindow : INItializableWindow
41+
{
42+
public SelectMissionWindow(WindowManager windowManager, ScriptDependencies scriptDependencies) : base(windowManager)
43+
{
44+
this.scriptDependencies = scriptDependencies;
45+
}
46+
47+
private readonly ScriptDependencies scriptDependencies;
48+
49+
private XNADropDown ddMission;
50+
51+
public override void Initialize()
52+
{
53+
Name = nameof(SelectMissionWindow);
54+
string uiConfig =
55+
@"
56+
[SelectMissionWindow]
57+
$Width=300
58+
$CC0=lblDescription:XNALabel
59+
$CC1=ddMission:XNADropDown
60+
$CC2=btnOK:EditorButton
61+
$Height=getBottom(btnOK) + EMPTY_SPACE_BOTTOM
62+
HasCloseButton=yes
63+
64+
[lblDescription]
65+
$X=EMPTY_SPACE_SIDES
66+
$Y=EMPTY_SPACE_TOP
67+
Text=Select Mission:
68+
69+
[ddMission]
70+
$Y=getBottom(lblDescription) + (VERTICAL_SPACING * 2)
71+
$Width=200
72+
$X=horizontalCenterOnParent()
73+
Option00=Ambush
74+
Option01=Area Guard
75+
Option02=Attack
76+
Option03=Capture
77+
Option04=Construction
78+
Option05=Enter
79+
Option06=Guard
80+
Option07=Harmless
81+
Option08=Harvest
82+
Option09=Hunt
83+
Option10=Missile
84+
Option11=Move
85+
Option12=Open
86+
Option13=Patrol
87+
Option14=QMove
88+
Option15=Repair
89+
Option16=Rescue
90+
Option17=Retreat
91+
Option18=Return
92+
Option19=Sabotage
93+
Option20=Selling
94+
Option21=Sleep
95+
Option22=Sticky
96+
Option23=Stop
97+
Option24=Unload
98+
99+
[btnOK]
100+
$Y=getBottom(ddMission) + (VERTICAL_SPACING * 2)
101+
$Width=100
102+
$X=horizontalCenterOnParent()
103+
Text=OK
104+
";
105+
106+
// Because as a script we don't rely on external files, we create
107+
// our UI configuration INI in memory instead
108+
// of the default behaviour of reading it from a file.
109+
// base.Initialize will skip seeking ConfigIni from a file
110+
// if it is already initialized.
111+
var bytes = Encoding.UTF8.GetBytes(uiConfig);
112+
using (var stream = new MemoryStream(bytes))
113+
{
114+
ConfigIni = new IniFile(stream, Encoding.UTF8);
115+
}
116+
117+
base.Initialize();
118+
119+
// Need to specify even optional parameters here because at runtime
120+
// the compiler has no information about them being optional.
121+
ddMission = FindChild<XNADropDown>(nameof(ddMission), false);
122+
ddMission.SelectedIndex = ddMission.Items.FindIndex(ddi => ddi.Text == "Guard");
123+
FindChild<EditorButton>("btnOK", false).LeftClick += BtnOK_LeftClick;
124+
}
125+
126+
/// <summary>
127+
/// Unsubscribe from event handlers to allow the garbage collector to clean up memory when the window is destroyed.
128+
/// </summary>
129+
public override void Kill()
130+
{
131+
FindChild<EditorButton>("btnOK", false).LeftClick -= BtnOK_LeftClick;
132+
base.Kill();
133+
}
134+
135+
private void BtnOK_LeftClick(object sender, EventArgs e)
136+
{
137+
if (ddMission.SelectedItem == null)
138+
return;
139+
140+
scriptDependencies.EditorState.CursorAction = new AssignMissionCursorAction(scriptDependencies.CursorActionTarget, scriptDependencies.EditorState, ddMission.SelectedItem.Text);
141+
Hide();
142+
}
143+
144+
private void BtnDeleteAllTerrainObjects_LeftClick(object sender, EventArgs e)
145+
{
146+
var messageBox = EditorMessageBox.Show(WindowManager, "Are you sure?", "This will delete all terrain objects from the map. Do you want to continue?", MessageBoxButtons.YesNo);
147+
148+
messageBox.YesClickedAction = msgBox =>
149+
{
150+
scriptDependencies.Map.DoForAllTerrainObjects(scriptDependencies.Map.RemoveTerrainObject);
151+
scriptDependencies.CursorActionTarget.InvalidateMap();
152+
};
153+
}
154+
155+
/// <summary>
156+
/// A public method to allow the main script class to open
157+
/// the window by calling Show, which is a protected method.
158+
/// Of course, it also allows you to do any required initialization on opening the window.
159+
/// </summary>
160+
public void Open()
161+
{
162+
Show();
163+
}
164+
}
165+
166+
/// <summary>
167+
/// Our custom mutation class, allowing undo/redo of mission assignment.
168+
/// Needs to be a class within a class, as the script runner
169+
/// only creates an instance of the first top-level class in a script file.
170+
/// </summary>
171+
public class AssignMissionMutation : Mutation
172+
{
173+
public AssignMissionMutation(IMutationTarget mutationTarget, string missionName, Point2D cellCoords, BrushSize brushSize) : base(mutationTarget)
174+
{
175+
this.missionName = missionName;
176+
this.cellCoords = cellCoords;
177+
this.brushSize = brushSize;
178+
}
179+
180+
private readonly string missionName;
181+
private readonly Point2D cellCoords;
182+
private readonly BrushSize brushSize;
183+
184+
private List<(TechnoBase techno, string originalMission)> technos = new List<(TechnoBase techno, string originalMission)>();
185+
private int unitCount;
186+
187+
public override string GetDisplayString()
188+
{
189+
return string.Format(Translator.Translate("MapScripts.AssignMission.AssignMissionMutation.DisplayString", "Change Mission to {0} for {1} unit(s)"), missionName, unitCount);
190+
}
191+
192+
public override void Perform()
193+
{
194+
brushSize.DoForBrushSize(offset =>
195+
{
196+
Point2D cellCoordsWithOffset = cellCoords + offset;
197+
198+
if (!Map.IsCoordWithinMap(cellCoordsWithOffset))
199+
return;
200+
201+
var cell = Map.GetTile(cellCoordsWithOffset);
202+
203+
cell.DoForAllVehicles(unit =>
204+
{
205+
if (unit.Mission != missionName)
206+
{
207+
technos.Add((unit, unit.Mission));
208+
unit.Mission = missionName;
209+
}
210+
});
211+
212+
cell.DoForAllInfantry(infantry =>
213+
{
214+
if (infantry.Mission != missionName)
215+
{
216+
technos.Add((infantry, infantry.Mission));
217+
infantry.Mission = missionName;
218+
}
219+
});
220+
221+
cell.DoForAllAircraft(aircraft =>
222+
{
223+
if (aircraft.Mission != missionName)
224+
{
225+
technos.Add((aircraft, aircraft.Mission));
226+
aircraft.Mission = missionName;
227+
}
228+
});
229+
});
230+
231+
unitCount = technos.Count;
232+
}
233+
234+
public override void Undo()
235+
{
236+
foreach (var technoInfo in technos)
237+
{
238+
switch (technoInfo.techno.WhatAmI())
239+
{
240+
case RTTIType.Unit:
241+
((Unit)technoInfo.techno).Mission = technoInfo.originalMission;
242+
break;
243+
case RTTIType.Infantry:
244+
((Infantry)technoInfo.techno).Mission = technoInfo.originalMission;
245+
break;
246+
case RTTIType.Aircraft:
247+
((Aircraft)technoInfo.techno).Mission = technoInfo.originalMission;
248+
break;
249+
default:
250+
throw new NotImplementedException("AssignMissionMutation: Unknown techno type " + technoInfo.techno.WhatAmI());
251+
}
252+
}
253+
254+
technos.Clear();
255+
}
256+
}
257+
258+
/// <summary>
259+
/// Our custom cursor action class activated by this script.
260+
/// Needs to be a class within a class, as the script runner
261+
/// only creates an instance of the first top-level class in a script file.
262+
/// </summary>
263+
public class AssignMissionCursorAction : CursorAction
264+
{
265+
public AssignMissionCursorAction(ICursorActionTarget cursorActionTarget, EditorState editorState, string missionName) : base(cursorActionTarget)
266+
{
267+
this.editorState = editorState;
268+
this.missionName = missionName;
269+
}
270+
271+
private readonly EditorState editorState;
272+
273+
private readonly string missionName;
274+
275+
public override string GetName() => Translator.Translate("MapScripts.AssignMission.AssignMissionCursorAction.Name", "Apply Mission To Units");
276+
277+
public override void LeftClick(Point2D cellCoords)
278+
{
279+
base.LeftClick(cellCoords);
280+
LeftDown(cellCoords);
281+
}
282+
283+
public override void LeftDown(Point2D cellCoords)
284+
{
285+
var tile = Map.GetTile(cellCoords);
286+
287+
int totalObjectCount = 0;
288+
289+
editorState.BrushSize.DoForBrushSize(offset =>
290+
{
291+
Point2D cellCoordsWithOffset = cellCoords + offset;
292+
293+
if (!Map.IsCoordWithinMap(cellCoordsWithOffset))
294+
return;
295+
296+
var cell = Map.GetTile(cellCoordsWithOffset);
297+
298+
totalObjectCount += cell.Vehicles.Count(vehicle => vehicle.Mission != missionName);
299+
totalObjectCount += cell.Infantry.Count(infantry => infantry != null && infantry.Mission != missionName);
300+
totalObjectCount += cell.Aircraft.Count(aircraft => aircraft.Mission != missionName);
301+
});
302+
303+
if (totalObjectCount > 0)
304+
{
305+
var mutation = new AssignMissionMutation(MutationTarget, missionName, cellCoords, editorState.BrushSize);
306+
PerformMutation(mutation);
307+
}
308+
}
309+
310+
/// <summary>
311+
/// Draws the preview for the cursor action.
312+
/// </summary>
313+
public override void DrawPreview(Point2D cellCoords, Point2D cameraTopLeftPoint)
314+
{
315+
// Draw rectangle for the brush.
316+
317+
Func<Point2D, Map, Point2D> func = Is2DMode ? CellMath.CellTopLeftPointFromCellCoords : CellMath.CellTopLeftPointFromCellCoords_3D;
318+
319+
Point2D bottomCellCoords = new Point2D(cellCoords.X + editorState.BrushSize.Width - 1, cellCoords.Y + editorState.BrushSize.Height - 1);
320+
Point2D leftCellCoords = new Point2D(cellCoords.X, cellCoords.Y + editorState.BrushSize.Height - 1);
321+
Point2D rightCellCoords = new Point2D(cellCoords.X + editorState.BrushSize.Width - 1, cellCoords.Y);
322+
323+
Point2D topPixelPoint = func(cellCoords, CursorActionTarget.Map) - cameraTopLeftPoint + new Point2D(Constants.CellSizeX / 2, 0);
324+
Point2D bottomPixelPoint = func(bottomCellCoords, CursorActionTarget.Map) - cameraTopLeftPoint + new Point2D(Constants.CellSizeX / 2, Constants.CellSizeY);
325+
Point2D leftPixelPoint = func(leftCellCoords, CursorActionTarget.Map) - cameraTopLeftPoint + new Point2D(0, Constants.CellSizeY / 2);
326+
Point2D rightPixelPoint = func(rightCellCoords, CursorActionTarget.Map) - cameraTopLeftPoint + new Point2D(Constants.CellSizeX, Constants.CellSizeY / 2);
327+
328+
topPixelPoint = topPixelPoint.ScaleBy(CursorActionTarget.Camera.ZoomLevel);
329+
bottomPixelPoint = bottomPixelPoint.ScaleBy(CursorActionTarget.Camera.ZoomLevel);
330+
rightPixelPoint = rightPixelPoint.ScaleBy(CursorActionTarget.Camera.ZoomLevel);
331+
leftPixelPoint = leftPixelPoint.ScaleBy(CursorActionTarget.Camera.ZoomLevel);
332+
333+
Color lineColor = Color.Orange;
334+
int thickness = 2;
335+
Renderer.DrawLine(topPixelPoint.ToXNAVector(), rightPixelPoint.ToXNAVector(), lineColor, thickness);
336+
Renderer.DrawLine(topPixelPoint.ToXNAVector(), leftPixelPoint.ToXNAVector(), lineColor, thickness);
337+
Renderer.DrawLine(rightPixelPoint.ToXNAVector(), bottomPixelPoint.ToXNAVector(), lineColor, thickness);
338+
Renderer.DrawLine(leftPixelPoint.ToXNAVector(), bottomPixelPoint.ToXNAVector(), lineColor, thickness);
339+
340+
// Draw "Assign Mission" in the center of the area.
341+
Point2D centerPixelPoint = new Point2D((leftPixelPoint.X + rightPixelPoint.X) / 2, (topPixelPoint.Y + bottomPixelPoint.Y) / 2);
342+
343+
string text = Translator.Translate("MapScripts.AssignMission.AssignMissionCursorAction.CursorActionText", "Assign Mission");
344+
var textDimensions = Renderer.GetTextDimensions(text, Constants.UIBoldFont);
345+
int x = centerPixelPoint.X - (int)(textDimensions.X / 2);
346+
int y = centerPixelPoint.Y - (int)(textDimensions.Y / 2);
347+
348+
Renderer.DrawStringWithShadow(text,
349+
Constants.UIBoldFont,
350+
new Vector2(x, y),
351+
lineColor);
352+
}
353+
}
354+
355+
/// <summary>
356+
/// This needs to be declared as 2 so the script runner knows we support ScriptDependencies.
357+
/// </summary>
358+
public int ApiVersion { get; } = 2;
359+
360+
/// <summary>
361+
/// Script dependencies object that is assigned by editor when the script is run.
362+
/// Contains Map, CursorActionTarget (MapView instance), EditorState, WindowManager, and WindowController.
363+
/// </summary>
364+
public ScriptDependencies ScriptDependencies { get; set; }
365+
366+
private SelectMissionWindow selectMissionWindow;
367+
368+
/// <summary>
369+
/// Main function for executing the script.
370+
/// </summary>
371+
public void Perform()
372+
{
373+
selectMissionWindow = new SelectMissionWindow(ScriptDependencies.WindowManager, ScriptDependencies);
374+
ScriptDependencies.WindowController.AddWindow(selectMissionWindow);
375+
selectMissionWindow.Closed += SelectMissionWindow_Closed;
376+
selectMissionWindow.Open();
377+
}
378+
379+
private void SelectMissionWindow_Closed(object sender, EventArgs e)
380+
{
381+
selectMissionWindow.Closed -= SelectMissionWindow_Closed;
382+
ScriptDependencies.WindowController.RemoveWindow(selectMissionWindow);
383+
}
384+
}
385+
}

0 commit comments

Comments
 (0)