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