├── LICENSE
├── ProductionQueue.modinfo
├── README.md
└── UI
├── Panels
└── productionpanel.lua
├── productionpanel.xml
├── strategicview_mapplacement.lua
└── supportfunctions.lua
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Kevin Blease
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ProductionQueue.modinfo:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Production Queue
5 | Adds production queues to cities.
6 | Add production queues to cities.
7 | Lozenged
8 | 2.0
9 |
10 |
11 |
12 |
13 | UI/Panels/productionpanel.lua
14 | UI/productionpanel.xml
15 | UI/strategicview_mapplacement.lua
16 | UI/supportfunctions.lua
17 |
18 |
19 |
20 |
21 | UI/Panels/productionpanel.lua
22 | UI/productionpanel.xml
23 | UI/strategicview_mapplacement.lua
24 | UI/supportfunctions.lua
25 |
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProductionQueue
2 | ### Add production queuing to Civ VI!
3 |
4 | 
5 |
6 | Installation
7 | ============
8 | * Download the mod and extract the contents to the Civilization VI Mods folder.
9 | * Windows - C:\Users\USERNAME\Documents\My Games\Sid Meier's Civilization VI\Mods
10 | * OSX - /Users/USERNAME/Library/Application Support/Sid Meier's Civilization VI/Mods
11 | * Linux - /home/USERNAME/.local/share/aspyr-media/Sid Meier's Civilization VI/Mods
12 | * Enable the mod in the Additional Content section of the main menu.
13 |
14 | Controls
15 | ========
16 | Actions when clicking an item in the "Choose Production" panel:
17 | * Left-Click: Add the item to the bottom of the queue.
18 | * Control+Left-Click or Middle-Click: Add the item to the top of the queue.
19 |
20 | Actions when click an item in the "Production Queue" panel:
21 | * Left-Click and drag to new position: Move the item to the position when the mouse button is released. i.e. Drag and drop.
22 | * Double Left-Click or Middle-Click: Move the item to the top of the queue.
23 | * Left-Click and drag off queue: Remove the item from the queue.
24 | * Hover: Reveal reserved plot of placed item (district or wonder) on the map.
25 |
26 | Instructions
27 | ============
28 | When you first load a new game with the mod enabled, the production queue panel will be visible by default to the left of a city's production menu. The panel can be collapsed by clicking the small tab on the upper-left. Using the controls listed in the section below, you can add/remove and manipulate production items in a per-city queue.
29 |
30 | All types of production items are eligible to be added to the queue. This includes districts and wonders! When adding a district or wonder to the queue, you will choose a plot for it like normal. However, the actual placing of the plot will not occur until the item is in the top position of the queue. At that time, it will automatically place the item on the plot that was selected upon adding it to the queue. Anytime before it reaches the top of the queue, you are free to remove it from the queue and re-place it. But remember, as soon as it reaches the top of the queue, it will be placed and be permanent (as occurs when placing a district or wonder in the base game).
31 |
32 | Districts and buildings that are prerequisites for other production items that are already researched (techs and civics) will unlock the next one. For example, you can queue a Commercial Hub and then immediately queue a Market if the required techs are researched. Continuing with this same example, the Market added below the Commercial Hub would be incapable of being moved ahead of it. In other words, the required order of the queue will be maintained. If you attempt to reorder an item in a way that would be impossible, it will move as far as it can before stopping. If you attempt to remove an item which is depended upon by other items below it, the removal will cascade and remove all items within the same dependency chain. In our previous example, this would mean that removing the Commercial Hub would also result in the Market being removed.
33 |
34 | Units in a city's queue which become obsolete will be automatically switched to the unit that is replacing it. In the event the unit that replaces the now obsolete unit is ineligible for production, the units will be removed from your queue. For example: Your queued Warriors are forced to become obsolete upon learning the Gunpowder tech, but the unit which replaces them is the Swordsman which requires Iron. If you do not have Iron, the Warriors will be removed from the queue rather than being upgraded to Swordsmen.
35 |
36 | Altered Game Assets
37 | -------------------
38 | In case you are curious up front which game assets have been modified, here is a list:
39 | * UI\Panels\ProductionPanel.lua
40 | * UI\ProductionPanel.xml
41 | * UI\StrategicView_MapPlacement.lua
42 | * UI\SupportFunctions.lua
43 |
44 | Copyright 2019 Kevin Blease. This item is not authorized for posting on Steam, except under the Steam account named kevin@thefixed.com.
45 |
--------------------------------------------------------------------------------
/UI/productionpanel.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
--------------------------------------------------------------------------------
/UI/strategicview_mapplacement.lua:
--------------------------------------------------------------------------------
1 | if string.sub(UI.GetAppVersion(),1,9) ~= "1.0.0.262" then
2 | -- ===========================================================================
3 | -- Input for placing items on the world map.
4 | -- Copyright 2015-2016, Firaxis Games
5 | --
6 | -- To hot-reload, save this then re-save the file that imports the file
7 | -- (e.g., WorldInput)
8 | -- ===========================================================================
9 | include("SupportFunctions.lua");
10 | include("AdjacencyBonusSupport.lua");
11 | include("PopupDialog");
12 | include("Civ6Common.lua");
13 |
14 |
15 | -- ===========================================================================
16 | -- MEMBERS
17 | -- ===========================================================================
18 | local m_hexesDistrictPlacement :table = {}; -- Re-usable collection of hexes; what is sent across the wire.
19 | local m_cachedSelectedPlacementPlotId :number = -1; -- Hex the cursor is currently focused on
20 |
21 | local m_AdjacencyBonusDistricts : number = UILens.CreateLensLayerHash("Adjacency_Bonus_Districts");
22 | local m_Districts : number = UILens.CreateLensLayerHash("Districts");
23 |
24 | local bWasCancelled:boolean = true;
25 |
26 | -- ===========================================================================
27 | function SetInsertModeParams( tParameters:table )
28 | tParameters[CityOperationTypes.PARAM_INSERT_MODE] = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_INSERT_MODE);
29 | tParameters[CityOperationTypes.PARAM_QUEUE_LOCATION] = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_QUEUE_LOCATION);
30 | tParameters[CityOperationTypes.PARAM_QUEUE_SOURCE_LOCATION] = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_QUEUE_SOURCE_LOCATION);
31 | tParameters[CityOperationTypes.PARAM_QUEUE_DESTINATION_LOCATION] = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_QUEUE_DESTINATION_LOCATION);
32 | end
33 |
34 | -- ===========================================================================
35 | -- Code related to the Wonder Placement interface mode
36 | -- ===========================================================================
37 | function ConfirmPlaceWonder( pInputStruct:table )
38 | local plotId = UI.GetCursorPlotID();
39 | local pSelectedCity = UI.GetHeadSelectedCity();
40 | if (not Map.IsPlot(plotId) or not GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
41 | return false;
42 | end
43 |
44 | local kPlot = Map.GetPlotByIndex(plotId);
45 |
46 | local eBuilding = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_BUILDING_TYPE);
47 |
48 | local tParameters = {};
49 | tParameters[CityOperationTypes.PARAM_X] = kPlot:GetX();
50 | tParameters[CityOperationTypes.PARAM_Y] = kPlot:GetY();
51 | tParameters[CityOperationTypes.PARAM_BUILDING_TYPE] = eBuilding;
52 |
53 | SetInsertModeParams(tParameters);
54 |
55 | if (pSelectedCity ~= nil) then
56 | local pBuildingInfo = GameInfo.Buildings[eBuilding];
57 | local bCanStart, tResults = CityManager.CanStartOperation( pSelectedCity, CityOperationTypes.BUILD, tParameters, true);
58 | if pBuildingInfo ~= nil and bCanStart then
59 |
60 | local sConfirmText :string = Locale.Lookup("LOC_DISTRICT_ZONE_CONFIRM_WONDER_POPUP", pBuildingInfo.Name);
61 |
62 | if (tResults ~= nil and tResults[CityOperationResults.SUCCESS_CONDITIONS] ~= nil) then
63 | if (table.count(tResults[CityOperationResults.SUCCESS_CONDITIONS]) ~= 0) then
64 | sConfirmText = sConfirmText .. "[NEWLINE]";
65 | end
66 | for i,v in ipairs(tResults[CityOperationResults.SUCCESS_CONDITIONS]) do
67 | sConfirmText = sConfirmText .. "[NEWLINE]" .. Locale.Lookup(v);
68 | end
69 | end
70 | local pPopupDialog :table = PopupDialogInGame:new("PlaceWonderAt_X" .. kPlot:GetX() .. "_Y" .. kPlot:GetY()); -- unique identifier
71 | pPopupDialog:AddText(sConfirmText);
72 | pPopupDialog:AddConfirmButton(Locale.Lookup("LOC_YES"), function()
73 | --CityManager.RequestOperation(pSelectedCity, CityOperationTypes.BUILD, tParameters);
74 | local tProductionQueueParameters = { tParameters=tParameters, plotId=plotId, pSelectedCity=pSelectedCity, buildingHash=eBuilding }
75 | LuaEvents.StrageticView_MapPlacement_ProductionClose(tProductionQueueParameters);
76 | UI.PlaySound("Build_Wonder");
77 | ExitPlacementMode();
78 | end);
79 | pPopupDialog:AddCancelButton(Locale.Lookup("LOC_NO"), nil);
80 | pPopupDialog:Open();
81 | end
82 | else
83 | ExitPlacementMode( true );
84 | end
85 |
86 | return true;
87 | end
88 |
89 | -- ===========================================================================
90 | -- Find the artdef (texture) for the plots we are considering
91 | -- ===========================================================================
92 | function RealizePlotArtForWonderPlacement()
93 | -- Reset the master table of hexes, tracking what will be sent to the engine.
94 | m_hexesDistrictPlacement = {};
95 | m_cachedSelectedPlacementPlotId = -1;
96 | local kNonShadowHexes:table = {}; -- Holds plot IDs of hexes to not be shadowed.
97 |
98 | UIManager:SetUICursor(CursorTypes.RANGE_ATTACK);
99 | UILens.SetActive("DistrictPlacement"); -- turn on all district layers and district adjacency bonus layers
100 |
101 | local pSelectedCity = UI.GetHeadSelectedCity();
102 | if pSelectedCity ~= nil then
103 |
104 | local buildingHash:number = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_BUILDING_TYPE);
105 | local building:table = GameInfo.Buildings[buildingHash];
106 | local tParameters :table = {};
107 | tParameters[CityOperationTypes.PARAM_BUILDING_TYPE] = buildingHash;
108 |
109 | local tResults :table = CityManager.GetOperationTargets( pSelectedCity, CityOperationTypes.BUILD, tParameters );
110 | -- Highlight the plots where the city can place the wonder
111 | if (tResults[CityOperationResults.PLOTS] ~= nil and table.count(tResults[CityOperationResults.PLOTS]) ~= 0) then
112 | local kPlots = tResults[CityOperationResults.PLOTS];
113 | for i, plotId in ipairs(kPlots) do
114 | if(GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
115 | local kPlot :table = Map.GetPlotByIndex(plotId);
116 | local plotInfo :table = GetViewPlotInfo( kPlot, m_hexesDistrictPlacement );
117 | plotInfo.hexArtdef = "Placement_Valid";
118 | plotInfo.selectable = true;
119 | m_hexesDistrictPlacement[plotId]= plotInfo;
120 |
121 | table.insert( kNonShadowHexes, plotId );
122 | else
123 | -- TODO: Perhaps make it clear that it is reserved by the queue
124 | end
125 | end
126 | end
127 |
128 | -- Plots that aren't owned, but could be (and hence, could be a great spot for that wonder!)
129 | tParameters = {};
130 | tParameters[CityCommandTypes.PARAM_PLOT_PURCHASE] = UI.GetInterfaceModeParameter(CityCommandTypes.PARAM_PLOT_PURCHASE);
131 | local tResults = CityManager.GetCommandTargets( pSelectedCity, CityCommandTypes.PURCHASE, tParameters );
132 | if (tResults[CityCommandResults.PLOTS] ~= nil and table.count(tResults[CityCommandResults.PLOTS]) ~= 0) then
133 | local kPurchasePlots = tResults[CityCommandResults.PLOTS];
134 | for i, plotId in ipairs(kPurchasePlots) do
135 |
136 | -- Highlight any purchaseable plot the Wonder could go on
137 | local kPlot :table = Map.GetPlotByIndex(plotId);
138 |
139 | if kPlot:CanHaveWonder(building.Index, pSelectedCity:GetOwner(), pSelectedCity:GetID()) then
140 | local plotInfo :table = GetViewPlotInfo( kPlot, m_hexesDistrictPlacement );
141 | plotInfo.hexArtdef = "Placement_Purchase";
142 | plotInfo.selectable = true;
143 | plotInfo.purchasable = true;
144 | m_hexesDistrictPlacement[plotId]= plotInfo;
145 | end
146 | end
147 | end
148 |
149 | -- Send all the hex information to the engine for visualization.
150 | for i,plotInfo in pairs(m_hexesDistrictPlacement) do
151 | UILens.SetAdjacencyBonusDistict( plotInfo.index, plotInfo.hexArtdef, plotInfo.adjacent );
152 | end
153 |
154 | LuaEvents.StrategicView_MapPlacement_AddDistrictPlacementShadowHexes( kNonShadowHexes );
155 | end
156 | end
157 |
158 | -- ===========================================================================
159 | -- Mode to place a Wonder Building
160 | -- ===========================================================================
161 | function OnInterfaceModeEnter_BuildingPlacement( eNewMode:number )
162 | UI.SetFixedTiltMode( true );
163 | UIManager:SetUICursor(CursorTypes.RANGE_ATTACK); --here?
164 | bWasCancelled = true; -- We assume it was cancelled unless explicitly not cancelled
165 | RealizePlotArtForWonderPlacement();
166 | end
167 |
168 | -- ===========================================================================
169 | -- Guaranteed to be called when leaving building placement
170 | -- ===========================================================================
171 | function OnInterfaceModeLeave_BuildingPlacement( eNewMode:number )
172 | LuaEvents.StrategicView_MapPlacement_ClearDistrictPlacementShadowHexes();
173 | UI.SetFixedTiltMode( false );
174 | local eCurrentMode:number = UI.GetInterfaceMode();
175 | if eCurrentMode ~= InterfaceModeTypes.VIEW_MODAL_LENS then
176 | -- Don't open the production panel if we're going to a modal lens as it will overwrite the modal lens
177 | LuaEvents.StrageticView_MapPlacement_ProductionOpen(bWasCancelled);
178 | end
179 | end
180 |
181 | -- ===========================================================================
182 | -- Explicitly leaving district placement; may not be called if the user
183 | -- is entering another mode by selecting a different UI element which in-turn
184 | -- triggers the exit.
185 | -- ===========================================================================
186 | function ExitPlacementMode( isCancelled:boolean )
187 | bWasCancelled = isCancelled ~= nil and isCancelled or false;
188 | UI.SetInterfaceMode(InterfaceModeTypes.SELECTION);
189 | end
190 |
191 | -- ===========================================================================
192 | -- Confirm before placing a district down
193 | -- ===========================================================================
194 | function ConfirmPlaceDistrict(pInputStruct:table)
195 |
196 | local plotId = UI.GetCursorPlotID();
197 | local pSelectedCity = UI.GetHeadSelectedCity();
198 | if (not Map.IsPlot(plotId) or not GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
199 | return;
200 | end
201 |
202 | local kPlot = Map.GetPlotByIndex(plotId);
203 |
204 | local districtHash:number = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_DISTRICT_TYPE);
205 | local purchaseYield = UI.GetInterfaceModeParameter(CityCommandTypes.PARAM_YIELD_TYPE);
206 | local bIsPurchase:boolean = false;
207 | if (purchaseYield ~= nil and (purchaseYield == YieldTypes.GOLD or purchaseYield == YieldTypes.FAITH)) then
208 | bIsPurchase = true;
209 | end
210 |
211 | local tParameters = {};
212 | tParameters[CityOperationTypes.PARAM_X] = kPlot:GetX();
213 | tParameters[CityOperationTypes.PARAM_Y] = kPlot:GetY();
214 | tParameters[CityOperationTypes.PARAM_DISTRICT_TYPE] = districtHash;
215 | tParameters[CityCommandTypes.PARAM_YIELD_TYPE] = purchaseYield;
216 |
217 | SetInsertModeParams(tParameters);
218 |
219 | if (pSelectedCity ~= nil) then
220 | local pDistrictInfo = GameInfo.Districts[districtHash];
221 | local bCanStart;
222 | local tResults;
223 |
224 | if (bIsPurchase) then
225 | bCanStart, tResults = CityManager.CanStartCommand( pSelectedCity, CityCommandTypes.PURCHASE, tParameters, true);
226 | else
227 | bCanStart, tResults = CityManager.CanStartOperation( pSelectedCity, CityOperationTypes.BUILD, tParameters, true);
228 | end
229 |
230 | if pDistrictInfo ~= nil and bCanStart then
231 |
232 | local sConfirmText :string = Locale.Lookup("LOC_DISTRICT_ZONE_CONFIRM_DISTRICT_POPUP", pDistrictInfo.Name);
233 |
234 | if (tResults ~= nil and tResults[CityOperationResults.SUCCESS_CONDITIONS] ~= nil) then
235 | if (table.count(tResults[CityOperationResults.SUCCESS_CONDITIONS]) ~= 0) then
236 | sConfirmText = sConfirmText .. "[NEWLINE]";
237 | end
238 | for i,v in ipairs(tResults[CityOperationResults.SUCCESS_CONDITIONS]) do
239 | sConfirmText = sConfirmText .. "[NEWLINE]" .. Locale.Lookup(v);
240 | end
241 | end
242 | local pPopupDialog :table = PopupDialogInGame:new("PlaceDistrictAt_X" .. kPlot:GetX() .. "_Y" .. kPlot:GetY()); -- unique identifier
243 | pPopupDialog:AddText(sConfirmText);
244 |
245 | if (bIsPurchase) then
246 | if (IsTutorialRunning()) then
247 | CityManager.RequestCommand(pSelectedCity, CityCommandTypes.PURCHASE, tParameters);
248 | ExitPlacementMode();
249 | else
250 | pPopupDialog:AddConfirmButton(Locale.Lookup("LOC_YES"), function()
251 | CityManager.RequestCommand(pSelectedCity, CityCommandTypes.PURCHASE, tParameters);
252 | ExitPlacementMode();
253 | end);
254 | end
255 | else
256 | if (IsTutorialRunning()) then
257 | CityManager.RequestOperation(pSelectedCity, CityOperationTypes.BUILD, tParameters);
258 | ExitPlacementMode();
259 | else
260 | pPopupDialog:AddConfirmButton(Locale.Lookup("LOC_YES"), function()
261 | --CityManager.RequestOperation(pSelectedCity, CityOperationTypes.BUILD, tParameters);
262 | local tProductionQueueParameters = { tParameters=tParameters, plotId=plotId, pSelectedCity=pSelectedCity, buildingHash=districtHash }
263 | LuaEvents.StrageticView_MapPlacement_ProductionClose(tProductionQueueParameters);
264 | ExitPlacementMode();
265 | end);
266 | end
267 | end
268 | if (not IsTutorialRunning()) then
269 | pPopupDialog:AddCancelButton(Locale.Lookup("LOC_NO"), nil);
270 | pPopupDialog:Open();
271 | end
272 | end
273 | else
274 | ExitPlacementMode( true );
275 | end
276 | end
277 |
278 |
279 | -- ===========================================================================
280 | -- Find the artdef (texture) for the plot itself as well as the icons
281 | -- that are on the borders signifying why a hex receives a certain bonus.
282 | -- ===========================================================================
283 | function RealizePlotArtForDistrictPlacement()
284 | -- Reset the master table of hexes, tracking what will be sent to the engine.
285 | m_hexesDistrictPlacement = {};
286 | m_cachedSelectedPlacementPlotId = -1;
287 | local kNonShadowHexes:table = {}; -- Holds plot IDs of hexes to not be shadowed.
288 |
289 | UIManager:SetUICursor(CursorTypes.RANGE_ATTACK);
290 | UILens.SetActive("DistrictPlacement"); -- turn on all district layers and district adjacency bonus layers
291 |
292 | local pSelectedCity = UI.GetHeadSelectedCity();
293 | if pSelectedCity ~= nil then
294 |
295 | local districtHash:number = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_DISTRICT_TYPE);
296 | local district:table = GameInfo.Districts[districtHash];
297 | local tParameters :table = {};
298 | tParameters[CityOperationTypes.PARAM_DISTRICT_TYPE] = districtHash;
299 |
300 | local tResults :table = CityManager.GetOperationTargets( pSelectedCity, CityOperationTypes.BUILD, tParameters );
301 | -- Highlight the plots where the city can place the district
302 | if (tResults[CityOperationResults.PLOTS] ~= nil and table.count(tResults[CityOperationResults.PLOTS]) ~= 0) then
303 | local kPlots = tResults[CityOperationResults.PLOTS];
304 | for i, plotId in ipairs(kPlots) do
305 | if(GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
306 |
307 | local kPlot :table = Map.GetPlotByIndex(plotId);
308 | local plotInfo :table = GetViewPlotInfo( kPlot, m_hexesDistrictPlacement );
309 | plotInfo.hexArtdef = "Placement_Valid";
310 | plotInfo.selectable = true;
311 | m_hexesDistrictPlacement[plotId]= plotInfo;
312 |
313 | local kAdjacentPlotBonuses:table = AddAdjacentPlotBonuses( kPlot, district.DistrictType, pSelectedCity, m_hexesDistrictPlacement );
314 | for plotIndex, districtViewInfo in pairs(kAdjacentPlotBonuses) do
315 | m_hexesDistrictPlacement[plotIndex] = districtViewInfo;
316 | end
317 | end
318 | table.insert( kNonShadowHexes, plotId );
319 | end
320 | end
321 |
322 | -- Plots that arent't owned, but could be (and hence, could be a great spot for that district!)
323 | tParameters = {};
324 | tParameters[CityCommandTypes.PARAM_PLOT_PURCHASE] = UI.GetInterfaceModeParameter(CityCommandTypes.PARAM_PLOT_PURCHASE);
325 | local tResults = CityManager.GetCommandTargets( pSelectedCity, CityCommandTypes.PURCHASE, tParameters );
326 | if (tResults[CityCommandResults.PLOTS] ~= nil and table.count(tResults[CityCommandResults.PLOTS]) ~= 0) then
327 | local kPurchasePlots = tResults[CityCommandResults.PLOTS];
328 | for i, plotId in ipairs(kPurchasePlots) do
329 |
330 | -- Only highlight certain plots (usually if there is a bonus to be gained).
331 | local kPlot :table = Map.GetPlotByIndex(plotId);
332 |
333 | if kPlot:CanHaveDistrict(district.Index, pSelectedCity:GetOwner(), pSelectedCity:GetID()) then
334 | local plotInfo :table = GetViewPlotInfo( kPlot, m_hexesDistrictPlacement );
335 | plotInfo.hexArtdef = "Placement_Purchase";
336 | plotInfo.selectable = true;
337 | plotInfo.purchasable = true;
338 | m_hexesDistrictPlacement[plotId]= plotInfo;
339 | end
340 | end
341 | end
342 |
343 |
344 | -- Send all the hex information to the engine for visualization.
345 | for i,plotInfo in pairs(m_hexesDistrictPlacement) do
346 | UILens.SetAdjacencyBonusDistict( plotInfo.index, plotInfo.hexArtdef, plotInfo.adjacent );
347 | end
348 |
349 | LuaEvents.StrategicView_MapPlacement_AddDistrictPlacementShadowHexes( kNonShadowHexes );
350 | end
351 | end
352 |
353 | -- ===========================================================================
354 | -- Show the different potential district placement areas...
355 | -- ===========================================================================
356 | function OnInterfaceModeEnter_DistrictPlacement( eNewMode:number )
357 | RealizePlotArtForDistrictPlacement();
358 | bWasCancelled = true; -- We assume it was cancelled unless explicitly not cancelled
359 | UI.SetFixedTiltMode( true );
360 | end
361 |
362 | function OnInterfaceModeLeave_DistrictPlacement( eNewMode:number )
363 | LuaEvents.StrategicView_MapPlacement_ClearDistrictPlacementShadowHexes();
364 | UI.SetFixedTiltMode( false );
365 | local eCurrentMode:number = UI.GetInterfaceMode();
366 | if eCurrentMode ~= InterfaceModeTypes.VIEW_MODAL_LENS then
367 | -- Don't open the production panel if we're going to a modal lens as it will overwrite the modal lens
368 | LuaEvents.StrageticView_MapPlacement_ProductionOpen(bWasCancelled);
369 | end
370 | end
371 |
372 | -- ===========================================================================
373 | --
374 | -- ===========================================================================
375 | function OnCityMadePurchase_StrategicView_MapPlacement(owner:number, cityID:number, plotX:number, plotY:number, purchaseType, objectType)
376 | if owner ~= Game.GetLocalPlayer() then
377 | return;
378 | end
379 | if purchaseType == EventSubTypes.PLOT then
380 |
381 | -- Make sure city made purchase and it's the right mode.
382 | if (UI.GetInterfaceMode() == InterfaceModeTypes.DISTRICT_PLACEMENT) then
383 | -- Clear existing art then re-realize
384 | UILens.ClearLayerHexes( m_AdjacencyBonusDistricts );
385 | UILens.ClearLayerHexes( m_Districts );
386 | RealizePlotArtForDistrictPlacement();
387 | elseif (UI.GetInterfaceMode() == InterfaceModeTypes.BUILDING_PLACEMENT) then
388 | -- Clear existing art then re-realize
389 | UILens.ClearLayerHexes( m_AdjacencyBonusDistricts );
390 | UILens.ClearLayerHexes( m_Districts );
391 | RealizePlotArtForWonderPlacement();
392 | end
393 | end
394 | end
395 |
396 | -- ===========================================================================
397 | -- Whenever the mouse moves while in district or wonder placement mode.
398 | -- ===========================================================================
399 | function RealizeCurrentPlaceDistrictOrWonderPlot()
400 | local currentPlotId :number = UI.GetCursorPlotID();
401 | if (not Map.IsPlot(currentPlotId)) then
402 | return;
403 | end
404 |
405 | if currentPlotId == m_cachedSelectedPlacementPlotId then
406 | return;
407 | end
408 |
409 | -- Reset the artdef for the currently selected hex
410 | if m_cachedSelectedPlacementPlotId ~= nil and m_cachedSelectedPlacementPlotId ~= -1 then
411 | local hex:table = m_hexesDistrictPlacement[m_cachedSelectedPlacementPlotId];
412 | if hex ~= nil and hex.hexArtdef ~= nil and hex.selectable then
413 | UILens.UnFocusHex( m_Districts, hex.index, hex.hexArtdef );
414 | end
415 | end
416 |
417 | m_cachedSelectedPlacementPlotId = currentPlotId;
418 |
419 | -- New HEX update it to the selected form.
420 | if m_cachedSelectedPlacementPlotId ~= -1 then
421 | local hex:table = m_hexesDistrictPlacement[m_cachedSelectedPlacementPlotId];
422 | if hex ~= nil and hex.hexArtdef ~= nil and hex.selectable then
423 | UILens.FocusHex( m_Districts, hex.index, hex.hexArtdef );
424 | end
425 | end
426 | end
427 | else
428 | -- ===========================================================================
429 | -- Input for placing items on the world map.
430 | -- Copyright 2015-2016, Firaxis Games
431 | --
432 | -- To hot-reload, save this then re-save the file that imports the file
433 | -- (e.g., WorldInput)
434 | -- ===========================================================================
435 | include("SupportFunctions.lua");
436 | include("AdjacencyBonusSupport.lua");
437 | include("PopupDialog");
438 |
439 | -- ===========================================================================
440 | -- MEMBERS
441 | -- ===========================================================================
442 | local m_hexesDistrictPlacement :table = {}; -- Re-usable collection of hexes; what is sent across the wire.
443 | local m_cachedSelectedPlacementPlotId :number = -1; -- Hex the cursor is currently focused on
444 |
445 |
446 | -- ===========================================================================
447 | -- Code related to the Wonder Placement interface mode
448 | -- ===========================================================================
449 | function ConfirmPlaceWonder( pInputStruct:table )
450 | local plotId = UI.GetCursorPlotID();
451 | local pSelectedCity = UI.GetHeadSelectedCity();
452 | if (not Map.IsPlot(plotId) or not GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
453 | return false;
454 | end
455 |
456 | local kPlot = Map.GetPlotByIndex(plotId);
457 |
458 | local eBuilding = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_BUILDING_TYPE);
459 |
460 | local tParameters = {};
461 | tParameters[CityOperationTypes.PARAM_X] = kPlot:GetX();
462 | tParameters[CityOperationTypes.PARAM_Y] = kPlot:GetY();
463 | tParameters[CityOperationTypes.PARAM_BUILDING_TYPE] = eBuilding;
464 | tParameters[CityOperationTypes.PARAM_INSERT_MODE] = CityOperationTypes.VALUE_EXCLUSIVE;
465 |
466 | if (pSelectedCity ~= nil) then
467 | local pBuildingInfo = GameInfo.Buildings[eBuilding];
468 | local bCanStart, tResults = CityManager.CanStartOperation( pSelectedCity, CityOperationTypes.BUILD, tParameters, true);
469 | if pBuildingInfo ~= nil and bCanStart then
470 |
471 | local sConfirmText :string = Locale.Lookup("LOC_DISTRICT_ZONE_CONFIRM_WONDER_POPUP", pBuildingInfo.Name);
472 |
473 | if (tResults ~= nil and tResults[CityOperationResults.SUCCESS_CONDITIONS] ~= nil) then
474 | if (table.count(tResults[CityOperationResults.SUCCESS_CONDITIONS]) ~= 0) then
475 | sConfirmText = sConfirmText .. "[NEWLINE]";
476 | end
477 | for i,v in ipairs(tResults[CityOperationResults.SUCCESS_CONDITIONS]) do
478 | sConfirmText = sConfirmText .. "[NEWLINE]" .. Locale.Lookup(v);
479 | end
480 | end
481 |
482 | local pPopupDialog :table = {};
483 |
484 | pPopupDialog = PopupDialogInGame:new("PlaceWonderAt_X" .. kPlot:GetX() .. "_Y" .. kPlot:GetY());
485 |
486 | pPopupDialog:AddText(sConfirmText);
487 | pPopupDialog:AddConfirmButton(Locale.Lookup("LOC_YES"), function()
488 | --CityManager.RequestOperation(pSelectedCity, CityOperationTypes.BUILD, tParameters);
489 | local tProductionQueueParameters = { tParameters=tParameters, plotId=plotId, pSelectedCity=pSelectedCity, buildingHash=eBuilding }
490 | LuaEvents.StrageticView_MapPlacement_ProductionClose(tProductionQueueParameters);
491 | UI.PlaySound("Build_Wonder");
492 | ExitPlacementMode();
493 | end);
494 | pPopupDialog:AddCancelButton(Locale.Lookup("LOC_NO"), nil);
495 | pPopupDialog:Open();
496 | end
497 | else
498 | ExitPlacementMode( true );
499 | end
500 |
501 | return true;
502 | end
503 |
504 | -- ===========================================================================
505 | -- Find the artdef (texture) for the plots we are considering
506 | -- ===========================================================================
507 | function RealizePlotArtForWonderPlacement()
508 | -- Reset the master table of hexes, tracking what will be sent to the engine.
509 | m_hexesDistrictPlacement = {};
510 | m_cachedSelectedPlacementPlotId = -1;
511 | local kNonShadowHexes:table = {}; -- Holds plot IDs of hexes to not be shadowed.
512 |
513 | UIManager:SetUICursor(CursorTypes.RANGE_ATTACK);
514 | UILens.SetActive("DistrictPlacement"); -- turn on all district layers and district adjacency bonus layers
515 |
516 | local pSelectedCity = UI.GetHeadSelectedCity();
517 | if pSelectedCity ~= nil then
518 |
519 | local buildingHash:number = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_BUILDING_TYPE);
520 | local building:table = GameInfo.Buildings[buildingHash];
521 | local tParameters :table = {};
522 | tParameters[CityOperationTypes.PARAM_BUILDING_TYPE] = buildingHash;
523 |
524 | local tResults :table = CityManager.GetOperationTargets( pSelectedCity, CityOperationTypes.BUILD, tParameters );
525 | -- Highlight the plots where the city can place the wonder
526 | if (tResults[CityOperationResults.PLOTS] ~= nil and table.count(tResults[CityOperationResults.PLOTS]) ~= 0) then
527 | local kPlots = tResults[CityOperationResults.PLOTS];
528 | for i, plotId in ipairs(kPlots) do
529 | if(GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
530 | local kPlot :table = Map.GetPlotByIndex(plotId);
531 | local plotInfo :table = GetViewPlotInfo( kPlot );
532 | plotInfo.hexArtdef = "Placement_Valid";
533 | plotInfo.selectable = true;
534 | m_hexesDistrictPlacement[plotId]= plotInfo;
535 |
536 | table.insert( kNonShadowHexes, plotId );
537 | else
538 | -- TODO: Perhaps make it clear that it is reserved by the queue
539 | end
540 | end
541 | end
542 |
543 | -- Plots that aren't owned, but could be (and hence, could be a great spot for that wonder!)
544 | tParameters = {};
545 | tParameters[CityCommandTypes.PARAM_PLOT_PURCHASE] = UI.GetInterfaceModeParameter(CityCommandTypes.PARAM_PLOT_PURCHASE);
546 | local tResults = CityManager.GetCommandTargets( pSelectedCity, CityCommandTypes.PURCHASE, tParameters );
547 | if (tResults[CityCommandResults.PLOTS] ~= nil and table.count(tResults[CityCommandResults.PLOTS]) ~= 0) then
548 | local kPurchasePlots = tResults[CityCommandResults.PLOTS];
549 | for i, plotId in ipairs(kPurchasePlots) do
550 |
551 | -- Highlight any purchaseable plot the Wonder could go on
552 | local kPlot :table = Map.GetPlotByIndex(plotId);
553 |
554 | if kPlot:CanHaveWonder(building.Index, pSelectedCity:GetOwner(), pSelectedCity:GetID()) then
555 | local plotInfo :table = GetViewPlotInfo( kPlot );
556 | plotInfo.hexArtdef = "Placement_Purchase";
557 | plotInfo.selectable = true;
558 | plotInfo.purchasable = true;
559 | m_hexesDistrictPlacement[plotId]= plotInfo;
560 | end
561 | end
562 | end
563 |
564 | -- Send all the hex information to the engine for visualization.
565 | local hexIndexes:table = {};
566 | for i,plotInfo in pairs(m_hexesDistrictPlacement) do
567 | UILens.SetAdjacencyBonusDistict( plotInfo.index, plotInfo.hexArtdef, plotInfo.adjacent );
568 | end
569 |
570 | LuaEvents.StrategicView_MapPlacement_AddDistrictPlacementShadowHexes( kNonShadowHexes );
571 | end
572 | end
573 |
574 | -- ===========================================================================
575 | -- Mode to place a Wonder Building
576 | -- ===========================================================================
577 | function OnInterfaceModeEnter_BuildingPlacement( eNewMode:number )
578 | UI.SetFixedTiltMode( true );
579 | UIManager:SetUICursor(CursorTypes.RANGE_ATTACK); --here?
580 | RealizePlotArtForWonderPlacement();
581 | end
582 |
583 | -- ===========================================================================
584 | -- Guaranteed to be called when leaving building placement
585 | -- ===========================================================================
586 | function OnInterfaceModeLeave_BuildingPlacement( eNewMode:number )
587 | LuaEvents.StrategicView_MapPlacement_ClearDistrictPlacementShadowHexes();
588 | UI.SetFixedTiltMode( false );
589 | end
590 |
591 | -- ===========================================================================
592 | -- Explicitly leaving district placement; may not be called if the user
593 | -- is entering another mode by selecting a different UI element which in-turn
594 | -- triggers the exit.
595 | -- ===========================================================================
596 | function ExitPlacementMode( isCancelled:boolean )
597 | UI.SetInterfaceMode(InterfaceModeTypes.SELECTION);
598 | if isCancelled then
599 | LuaEvents.StrageticView_MapPlacement_ProductionOpen();
600 | end
601 | end
602 |
603 | -- ===========================================================================
604 | -- Confirm before placing a district down
605 | -- ===========================================================================
606 | function ConfirmPlaceDistrict(pInputStruct:table)
607 |
608 | local plotId = UI.GetCursorPlotID();
609 | local pSelectedCity = UI.GetHeadSelectedCity();
610 | if (not Map.IsPlot(plotId) or not GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
611 | return;
612 | end
613 |
614 | local kPlot = Map.GetPlotByIndex(plotId);
615 |
616 | local districtHash:number = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_DISTRICT_TYPE);
617 | local purchaseYield = UI.GetInterfaceModeParameter(CityCommandTypes.PARAM_YIELD_TYPE);
618 | local bIsPurchase:boolean = false;
619 | if (purchaseYield ~= nil and purchaseYield == YieldTypes.GOLD) then
620 | bIsPurchase = true;
621 | end
622 |
623 | local tParameters = {};
624 | tParameters[CityOperationTypes.PARAM_X] = kPlot:GetX();
625 | tParameters[CityOperationTypes.PARAM_Y] = kPlot:GetY();
626 | tParameters[CityOperationTypes.PARAM_DISTRICT_TYPE] = districtHash;
627 | tParameters[CityOperationTypes.PARAM_INSERT_MODE] = CityOperationTypes.VALUE_EXCLUSIVE;
628 |
629 | if (pSelectedCity ~= nil) then
630 | local pDistrictInfo = GameInfo.Districts[districtHash];
631 | local bCanStart;
632 | local tResults;
633 |
634 | if (bIsPurchase) then
635 | bCanStart, tResults = CityManager.CanStartCommand( pSelectedCity, CityCommandTypes.PURCHASE, tParameters, true);
636 | else
637 | bCanStart, tResults = CityManager.CanStartOperation( pSelectedCity, CityOperationTypes.BUILD, tParameters, true);
638 | end
639 |
640 | if pDistrictInfo ~= nil and bCanStart then
641 |
642 | local sConfirmText :string = Locale.Lookup("LOC_DISTRICT_ZONE_CONFIRM_DISTRICT_POPUP", pDistrictInfo.Name);
643 |
644 | if (tResults ~= nil and tResults[CityOperationResults.SUCCESS_CONDITIONS] ~= nil) then
645 | if (table.count(tResults[CityOperationResults.SUCCESS_CONDITIONS]) ~= 0) then
646 | sConfirmText = sConfirmText .. "[NEWLINE]";
647 | end
648 | for i,v in ipairs(tResults[CityOperationResults.SUCCESS_CONDITIONS]) do
649 | sConfirmText = sConfirmText .. "[NEWLINE]" .. Locale.Lookup(v);
650 | end
651 | end
652 |
653 | local pPopupDialog :table = {};
654 | print("yes")
655 |
656 | pPopupDialog = PopupDialogInGame:new("PlaceDistrictAt_X" .. kPlot:GetX() .. "_Y" .. kPlot:GetY());
657 |
658 | pPopupDialog:AddText(sConfirmText);
659 |
660 | if (bIsPurchase) then
661 | pPopupDialog:AddConfirmButton(Locale.Lookup("LOC_YES"), function()
662 | CityManager.RequestCommand(pSelectedCity, CityCommandTypes.PURCHASE, tParameters);
663 | ExitPlacementMode();
664 | end);
665 | else
666 | pPopupDialog:AddConfirmButton(Locale.Lookup("LOC_YES"), function()
667 | --CityManager.RequestOperation(pSelectedCity, CityOperationTypes.BUILD, tParameters);
668 | local tProductionQueueParameters = { tParameters=tParameters, plotId=plotId, pSelectedCity=pSelectedCity, buildingHash=districtHash }
669 | LuaEvents.StrageticView_MapPlacement_ProductionClose(tProductionQueueParameters);
670 | ExitPlacementMode();
671 | end);
672 | end
673 | pPopupDialog:AddCancelButton(Locale.Lookup("LOC_NO"), nil);
674 | pPopupDialog:Open();
675 | end
676 | else
677 | ExitPlacementMode( true );
678 | end
679 | end
680 |
681 | -- ===========================================================================
682 | -- Adds a plot and all the adjacencent plots, unless already added.
683 | -- ARGS: plot, gamecore plot object
684 | -- RETURNS: A new/updated plotInfo table
685 | -- ===========================================================================
686 | function GetViewPlotInfo( kPlot:table )
687 | local plotId :number = kPlot:GetIndex();
688 | local plotInfo :table = m_hexesDistrictPlacement[plotId];
689 | if plotInfo == nil then
690 | plotInfo = {
691 | index = plotId,
692 | x = kPlot:GetX(),
693 | y = kPlot:GetY(),
694 | adjacent= {}, -- adjacent edge bonuses
695 | selectable = false, -- change state with mouse over?
696 | purchasable = false
697 | };
698 | end
699 | --print( " plot: " .. plotInfo.x .. "," .. plotInfo.y..": " .. tostring(plotInfo.iconArtdef) );
700 | return plotInfo;
701 | end
702 |
703 |
704 | -- ===========================================================================
705 | -- Obtain a table of adjacency bonuses
706 | -- ===========================================================================
707 | function AddAdjacentPlotBonuses( kPlot:table, districtType:string, pSelectedCity:table )
708 | local x :number = kPlot:GetX();
709 | local y :number = kPlot:GetY();
710 |
711 | for _,direction in pairs(DirectionTypes) do
712 | if direction ~= DirectionTypes.NO_DIRECTION and direction ~= DirectionTypes.NUM_DIRECTION_TYPES then
713 | local adjacentPlot :table= Map.GetAdjacentPlot( x, y, direction);
714 | if adjacentPlot ~= nil then
715 | local artdefIconName:string = GetAdjacentIconArtdefName( districtType, adjacentPlot, pSelectedCity, direction );
716 |
717 | --print( "Checking from: (" .. tostring(x) .. ", " .. tostring(y) .. ") to (" .. tostring(adjacentPlot:GetX()) .. ", " .. tostring(adjacentPlot:GetY()) .. ") Artdef:'"..artdefIconName.."'");
718 |
719 | if artdefIconName ~= nil and artdefIconName ~= "" then
720 |
721 |
722 | local districtViewInfo :table = GetViewPlotInfo( adjacentPlot );
723 | local oppositeDirection :number = -1;
724 | if direction == DirectionTypes.DIRECTION_NORTHEAST then oppositeDirection = DirectionTypes.DIRECTION_SOUTHWEST; end
725 | if direction == DirectionTypes.DIRECTION_EAST then oppositeDirection = DirectionTypes.DIRECTION_WEST; end
726 | if direction == DirectionTypes.DIRECTION_SOUTHEAST then oppositeDirection = DirectionTypes.DIRECTION_NORTHWEST; end
727 | if direction == DirectionTypes.DIRECTION_SOUTHWEST then oppositeDirection = DirectionTypes.DIRECTION_NORTHEAST; end
728 | if direction == DirectionTypes.DIRECTION_WEST then oppositeDirection = DirectionTypes.DIRECTION_EAST; end
729 | if direction == DirectionTypes.DIRECTION_NORTHWEST then oppositeDirection = DirectionTypes.DIRECTION_SOUTHEAST; end
730 |
731 | table.insert( districtViewInfo.adjacent, {
732 | direction = oppositeDirection,
733 | iconArtdef = artdefIconName,
734 | inBonus = false,
735 | outBonus = true
736 | }
737 | );
738 |
739 | m_hexesDistrictPlacement[adjacentPlot:GetIndex()] = districtViewInfo;
740 | end
741 | end
742 | end
743 | end
744 | end
745 |
746 | -- ===========================================================================
747 | -- Find the artdef (texture) for the plot itself as well as the icons
748 | -- that are on the borders signifying why a hex receives a certain bonus.
749 | -- ===========================================================================
750 | function RealizePlotArtForDistrictPlacement()
751 | -- Reset the master table of hexes, tracking what will be sent to the engine.
752 | m_hexesDistrictPlacement = {};
753 | m_cachedSelectedPlacementPlotId = -1;
754 | local kNonShadowHexes:table = {}; -- Holds plot IDs of hexes to not be shadowed.
755 |
756 | UIManager:SetUICursor(CursorTypes.RANGE_ATTACK);
757 | UILens.SetActive("DistrictPlacement"); -- turn on all district layers and district adjacency bonus layers
758 |
759 | local pSelectedCity = UI.GetHeadSelectedCity();
760 | if pSelectedCity ~= nil then
761 |
762 | local districtHash:number = UI.GetInterfaceModeParameter(CityOperationTypes.PARAM_DISTRICT_TYPE);
763 | local district:table = GameInfo.Districts[districtHash];
764 | local tParameters :table = {};
765 | tParameters[CityOperationTypes.PARAM_DISTRICT_TYPE] = districtHash;
766 |
767 | local tResults :table = CityManager.GetOperationTargets( pSelectedCity, CityOperationTypes.BUILD, tParameters );
768 | -- Highlight the plots where the city can place the district
769 | if (tResults[CityOperationResults.PLOTS] ~= nil and table.count(tResults[CityOperationResults.PLOTS]) ~= 0) then
770 | local kPlots = tResults[CityOperationResults.PLOTS];
771 | for i, plotId in ipairs(kPlots) do
772 | if(GameInfo.Districts['DISTRICT_CITY_CENTER'].IsPlotValid(pSelectedCity, plotId)) then
773 |
774 | local kPlot :table = Map.GetPlotByIndex(plotId);
775 | local plotInfo :table = GetViewPlotInfo( kPlot );
776 | plotInfo.hexArtdef = "Placement_Valid";
777 | plotInfo.selectable = true;
778 | m_hexesDistrictPlacement[plotId]= plotInfo;
779 |
780 | AddAdjacentPlotBonuses( kPlot, district.DistrictType, pSelectedCity );
781 | table.insert( kNonShadowHexes, plotId );
782 | end
783 | end
784 | end
785 |
786 | --[[
787 | -- antonjs: Removing blocked plots from the UI display. Now that district placement can automatically remove features, resources, and improvements,
788 | -- as long as the player has the tech, there is not much need to show blocked plots and they end up being confusing.
789 | -- Plots that can host a district, after some action(s) are first taken.
790 | if (tResults[CityOperationResults.BLOCKED_PLOTS] ~= nil and table.count(tResults[CityOperationResults.BLOCKED_PLOTS]) ~= 0) then
791 | local kPlots = tResults[CityOperationResults.BLOCKED_PLOTS];
792 | for i, plotId in ipairs(kPlots) do
793 | local kPlot :table = Map.GetPlotByIndex(plotId);
794 | local plotInfo :table = GetViewPlotInfo( kPlot );
795 | plotInfo.hexArtdef = "Placement_Blocked";
796 | m_hexesDistrictPlacement[plotId]= plotInfo;
797 |
798 | AddAdjacentPlotBonuses( kPlot, district.DistrictType, pSelectedCity );
799 | table.insert( kNonShadowHexes, plotId );
800 | end
801 | end
802 | --]]
803 |
804 |
805 | -- Plots that a player will NEVER be able to place a district on
806 | -- if (tResults[CityOperationResults.MOUNTAIN_PLOTS] ~= nil and table.count(tResults[CityOperationResults.MOUNTAIN_PLOTS]) ~= 0) then
807 | -- local kPlots = tResults[CityOperationResults.MOUNTAIN_PLOTS];
808 | -- for i, plotId in ipairs(kPlots) do
809 | -- local kPlot :table = Map.GetPlotByIndex(plotId);
810 | -- local plotInfo :table = GetViewPlotInfo( kPlot );
811 | -- plotInfo.hexArtdef = "Placement_Invalid";
812 | -- m_hexesDistrictPlacement[plotId]= plotInfo;
813 | -- end
814 | -- end
815 |
816 | -- Plots that arent't owned, but could be (and hence, could be a great spot for that district!)
817 | tParameters = {};
818 | tParameters[CityCommandTypes.PARAM_PLOT_PURCHASE] = UI.GetInterfaceModeParameter(CityCommandTypes.PARAM_PLOT_PURCHASE);
819 | local tResults = CityManager.GetCommandTargets( pSelectedCity, CityCommandTypes.PURCHASE, tParameters );
820 | if (tResults[CityCommandResults.PLOTS] ~= nil and table.count(tResults[CityCommandResults.PLOTS]) ~= 0) then
821 | local kPurchasePlots = tResults[CityCommandResults.PLOTS];
822 | for i, plotId in ipairs(kPurchasePlots) do
823 |
824 | -- Only highlight certain plots (usually if there is a bonus to be gained).
825 | local kPlot :table = Map.GetPlotByIndex(plotId);
826 | local isValid :boolean = IsShownIfPlotPurchaseable( district.Index, pSelectedCity, kPlot );
827 |
828 | if isValid and kPlot:CanHaveDistrict(district.DistrictType, pSelectedCity:GetOwner(), pSelectedCity:GetID()) then
829 | local plotInfo :table = GetViewPlotInfo( kPlot );
830 | plotInfo.hexArtdef = "Placement_Purchase";
831 | plotInfo.selectable = true;
832 | plotInfo.purchasable = true;
833 | m_hexesDistrictPlacement[plotId]= plotInfo;
834 | end
835 | end
836 | end
837 |
838 |
839 | -- Send all the hex information to the engine for visualization.
840 | local hexIndexes:table = {};
841 | for i,plotInfo in pairs(m_hexesDistrictPlacement) do
842 | UILens.SetAdjacencyBonusDistict( plotInfo.index, plotInfo.hexArtdef, plotInfo.adjacent );
843 | end
844 |
845 | LuaEvents.StrategicView_MapPlacement_AddDistrictPlacementShadowHexes( kNonShadowHexes );
846 | end
847 | end
848 |
849 | -- ===========================================================================
850 | -- Show the different potential district placement areas...
851 | -- ===========================================================================
852 | function OnInterfaceModeEnter_DistrictPlacement( eNewMode:number )
853 | RealizePlotArtForDistrictPlacement();
854 | UI.SetFixedTiltMode( true );
855 | end
856 |
857 | function OnInterfaceModeLeave_DistrictPlacement( eNewMode:number )
858 | LuaEvents.StrategicView_MapPlacement_ClearDistrictPlacementShadowHexes();
859 | UI.SetFixedTiltMode( false );
860 | end
861 |
862 | -- ===========================================================================
863 | --
864 | -- ===========================================================================
865 | function OnCityMadePurchase_StrategicView_MapPlacement(owner:number, cityID:number, plotX:number, plotY:number, purchaseType, objectType)
866 | if owner ~= Game.GetLocalPlayer() then
867 | return;
868 | end
869 | if purchaseType == EventSubTypes.PLOT then
870 |
871 | -- Make sure city made purchase and it's the right mode.
872 | if (UI.GetInterfaceMode() == InterfaceModeTypes.DISTRICT_PLACEMENT) then
873 | -- Clear existing art then re-realize
874 | UILens.ClearLayerHexes( LensLayers.ADJACENCY_BONUS_DISTRICTS );
875 | UILens.ClearLayerHexes( LensLayers.DISTRICTS );
876 | RealizePlotArtForDistrictPlacement();
877 | elseif (UI.GetInterfaceMode() == InterfaceModeTypes.BUILDING_PLACEMENT) then
878 | -- Clear existing art then re-realize
879 | UILens.ClearLayerHexes( LensLayers.ADJACENCY_BONUS_DISTRICTS );
880 | UILens.ClearLayerHexes( LensLayers.DISTRICTS );
881 | RealizePlotArtForWonderPlacement();
882 | end
883 | end
884 | end
885 |
886 | -- ===========================================================================
887 | -- Whenever the mouse moves while in district or wonder placement mode.
888 | -- ===========================================================================
889 | function RealizeCurrentPlaceDistrictOrWonderPlot()
890 | local currentPlotId :number = UI.GetCursorPlotID();
891 | if (not Map.IsPlot(currentPlotId)) then
892 | return;
893 | end
894 |
895 | if currentPlotId == m_cachedSelectedPlacementPlotId then
896 | return;
897 | end
898 |
899 | -- Reset the artdef for the currently selected hex
900 | if m_cachedSelectedPlacementPlotId ~= nil and m_cachedSelectedPlacementPlotId ~= -1 then
901 | local hex:table = m_hexesDistrictPlacement[m_cachedSelectedPlacementPlotId];
902 | if hex ~= nil and hex.hexArtdef ~= nil and hex.selectable then
903 | UILens.UnFocusHex( LensLayers.DISTRICTS, hex.index, hex.hexArtdef );
904 | end
905 | end
906 |
907 | m_cachedSelectedPlacementPlotId = currentPlotId;
908 |
909 | -- New HEX update it to the selected form.
910 | if m_cachedSelectedPlacementPlotId ~= -1 then
911 | local hex:table = m_hexesDistrictPlacement[m_cachedSelectedPlacementPlotId];
912 | if hex ~= nil and hex.hexArtdef ~= nil and hex.selectable then
913 | UILens.FocusHex( LensLayers.DISTRICTS, hex.index, hex.hexArtdef );
914 | end
915 | end
916 | end
917 | end
--------------------------------------------------------------------------------
/UI/supportfunctions.lua:
--------------------------------------------------------------------------------
1 | if string.sub(UI.GetAppVersion(),1,9) ~= "1.0.0.167" then
2 | ------------------------------------------------------------------------------
3 | -- Misc Support Functions
4 | ------------------------------------------------------------------------------
5 |
6 | -- ===========================================================================
7 | -- CONSTANTS
8 | -- ===========================================================================
9 | local m_strEllipsis = Locale.Lookup("LOC_GENERIC_DOT_DOT_DOT");
10 |
11 |
12 | -- ===========================================================================
13 | -- Sets a Label or control that contains a label (e.g., GridButton) with
14 | -- a string that, if necessary, will be truncated.
15 | --
16 | -- RETURNS: true if truncated.
17 | -- ===========================================================================
18 | function TruncateString(control, resultSize, longStr, trailingText)
19 |
20 | -- Ensure this has the actual text control
21 | if control.GetTextControl ~= nil then
22 | control = control:GetTextControl();
23 | UI.AssertMsg(control.SetTruncateWidth ~= nil, "Calling TruncateString with an unsupported control");
24 | end
25 |
26 | -- TODO if trailingText is ever used, add a way to do it to TextControl
27 | UI.AssertMsg(trailingText == nil or trailingText == "", "trailingText is not supported");
28 |
29 | if(longStr == nil) then
30 | longStr = control:GetText();
31 | end
32 |
33 | --TODO a better solution than this function would be ideal
34 | --calling SetText implicitly truncates if the flag is set
35 | --a AutoToolTip flag could be made to avoid setting the tooltip from lua
36 | --trailingText could be added, right now its just an ellipsis but it could be arbitrary
37 | --this would avoid the weird type shenanigans when truncating TextButtons, TextControls, etc
38 |
39 | if(control ~= nil)then
40 | control:SetTruncateWidth(resultSize);
41 | control:SetText(longStr);
42 | else
43 | UI.AssertMsg(false, "Attempting to truncate a NIL control");
44 | end
45 |
46 | if control.IsTextTruncated ~= nil then
47 | return control:IsTextTruncated();
48 | else
49 | UI.AssertMsg(false, "Calling IsTextTruncated with an unsupported control");
50 | return true;
51 | end
52 | end
53 |
54 |
55 | -- ===========================================================================
56 | -- Same as TruncateString(), but if truncation occurs automatically adds
57 | -- the full text as a tooltip.
58 | -- ===========================================================================
59 | function TruncateStringWithTooltip(control, resultSize, longStr, trailingText)
60 | local isTruncated = TruncateString( control, resultSize, longStr, trailingText );
61 | if isTruncated then
62 | control:SetToolTipString( longStr );
63 | else
64 | control:SetToolTipString( nil );
65 | end
66 | return isTruncated;
67 | end
68 |
69 | -- ===========================================================================
70 | -- Same as TruncateStringWithTooltip(), but removes leading white space
71 | -- before truncation
72 | -- ===========================================================================
73 | function TruncateStringWithTooltipClean(control, resultSize, longStr, trailingText)
74 | local cleanString = longStr:match("^%s*(.-)%s*$");
75 | local isTruncated = TruncateString( control, resultSize, longStr, trailingText );
76 | if isTruncated then
77 | control:SetToolTipString( cleanString );
78 | else
79 | control:SetToolTipString( nil );
80 | end
81 | return isTruncated;
82 | end
83 |
84 |
85 | -- ===========================================================================
86 | -- Performs a truncation based on the control's contents
87 | -- ===========================================================================
88 | function TruncateSelfWithTooltip( control )
89 | local resultSize = control:GetSizeX();
90 | local longStr = control:GetText();
91 | return TruncateStringWithTooltip(control, resultSize, longStr);
92 | end
93 |
94 |
95 | -- ===========================================================================
96 | -- Truncate string based on # of characters
97 | -- (Most useful when having to truncate a string *in* a tooltip.
98 | -- ===========================================================================
99 | function TruncateStringByLength( textString, textLen )
100 | if ( Locale.Length(textString) > textLen ) then
101 | return Locale.SubString(textString, 1, textLen) .. m_strEllipsis;
102 | end
103 | return textString;
104 | end
105 |
106 | function GetGreatWorksForCity(pCity:table)
107 | local result:table = {};
108 | if pCity then
109 | local pCityBldgs:table = pCity:GetBuildings();
110 | for buildingInfo in GameInfo.Buildings() do
111 | local buildingIndex:number = buildingInfo.Index;
112 | local buildingType:string = buildingInfo.BuildingType;
113 | if(pCityBldgs:HasBuilding(buildingIndex)) then
114 | local numSlots:number = pCityBldgs:GetNumGreatWorkSlots(buildingIndex);
115 | if (numSlots ~= nil and numSlots > 0) then
116 | local greatWorksInBuilding:table = {};
117 |
118 | -- populate great works
119 | for index:number=0, numSlots - 1 do
120 | local greatWorkIndex:number = pCityBldgs:GetGreatWorkInSlot(buildingIndex, index);
121 | if greatWorkIndex ~= -1 then
122 | local greatWorkType:number = pCityBldgs:GetGreatWorkTypeFromIndex(greatWorkIndex);
123 | table.insert(greatWorksInBuilding, GameInfo.GreatWorks[greatWorkType]);
124 | end
125 | end
126 |
127 | -- create association between building type and great works
128 | if #greatWorksInBuilding > 0 then
129 | result[buildingType] = greatWorksInBuilding;
130 | end
131 | end
132 | end
133 | end
134 | end
135 | return result;
136 | end
137 |
138 | -- ===========================================================================
139 | -- Convert a set of values (red, green, blue, alpha) into a single hex value.
140 | -- Values are from 0.0 to 1.0
141 | -- return math.floor(value is a single, unsigned uint as ABGR
142 | -- ===========================================================================
143 | function RGBAValuesToABGRHex( red, green, blue, alpha )
144 |
145 | -- optionally pass in alpha, to taste
146 | if alpha==nil then
147 | alpha = 1.0;
148 | end
149 |
150 | -- prepare ingredients so they are clamped from 0 to 255
151 | red = math.max( 0, math.min( 255, red*255 ));
152 | green = math.max( 0, math.min( 255, green*255 ));
153 | blue = math.max( 0, math.min( 255, blue*255 ));
154 | alpha = math.max( 0, math.min( 255, alpha*255 ));
155 |
156 | -- combine the ingredients, stiring gently
157 | local value = lshift( alpha, 24 ) + lshift( blue, 16 ) + lshift( green, 8 ) + red;
158 |
159 | -- return the baked goodness
160 | return math.floor(value);
161 | end
162 |
163 | -- ===========================================================================
164 | -- Use to convert from CivBE style colors to ForgeUI color
165 | -- ===========================================================================
166 | function RGBAObjectToABGRHex( colorObject )
167 | return RGBAValuesToABGRHex( colorObject.x, colorObject.y, colorObject.z, colorObject.w );
168 | end
169 |
170 | -- ===========================================================================
171 | -- Guess what, TextControls still use legacy color; use to convert to it.
172 | -- RETURNS: Object with R G B A to a vector like format with fields X Y Z W
173 | -- ===========================================================================
174 | function ABGRHExToRGBAObject( hexColor )
175 | local ret = {};
176 | ret.w = math.floor( math.fmod( rshift(hexColor,24), 256));
177 | ret.z = math.floor( math.fmod( rshift(hexColor,16), 256));
178 | ret.y = math.floor( math.fmod( rshift(hexColor,8), 256));
179 | ret.x = math.floor( math.fmod( hexColor, 0x256 )); -- lower MODs are messed up due what is in higher bits, need an AND!
180 | return ret;
181 | end
182 |
183 |
184 |
185 | -- ===========================================================================
186 | -- Support for shifts
187 | -- ===========================================================================
188 | local g_supportFunctions_shiftTable = {};
189 | g_supportFunctions_shiftTable[0] = 1;
190 | g_supportFunctions_shiftTable[1] = 2;
191 | g_supportFunctions_shiftTable[2] = 4;
192 | g_supportFunctions_shiftTable[3] = 8;
193 | g_supportFunctions_shiftTable[4] = 16;
194 | g_supportFunctions_shiftTable[5] = 32;
195 | g_supportFunctions_shiftTable[6] = 64;
196 | g_supportFunctions_shiftTable[7] = 128;
197 | g_supportFunctions_shiftTable[8] = 256;
198 | g_supportFunctions_shiftTable[9] = 512;
199 | g_supportFunctions_shiftTable[10] = 1024;
200 | g_supportFunctions_shiftTable[11] = 2048;
201 | g_supportFunctions_shiftTable[12] = 4096;
202 | g_supportFunctions_shiftTable[13] = 8192;
203 | g_supportFunctions_shiftTable[14] = 16384;
204 | g_supportFunctions_shiftTable[15] = 32768;
205 | g_supportFunctions_shiftTable[16] = 65536;
206 | g_supportFunctions_shiftTable[17] = 131072;
207 | g_supportFunctions_shiftTable[18] = 262144;
208 | g_supportFunctions_shiftTable[19] = 524288;
209 | g_supportFunctions_shiftTable[20] = 1048576;
210 | g_supportFunctions_shiftTable[21] = 2097152;
211 | g_supportFunctions_shiftTable[22] = 4194304;
212 | g_supportFunctions_shiftTable[23] = 8388608;
213 | g_supportFunctions_shiftTable[24] = 16777216;
214 |
215 |
216 |
217 | -- ===========================================================================
218 | -- Bit Helper function
219 | -- Converts a number into a table of bits.
220 | -- ===========================================================================
221 | function numberToBitsTable( value:number )
222 | if value < 0 then
223 | return numberToBitsTable( bitNot(math.abs(value))+1 ); -- Recurse
224 | end
225 |
226 | local kReturn :table = {};
227 | local i :number = 1;
228 | while value > 0 do
229 | local digit:number = math.fmod(value, 2);
230 | if digit == 1 then
231 | kReturn[i] = 1;
232 | else
233 | kReturn[i] = 0;
234 | end
235 | value = (value - digit) * 0.5;
236 | i = i + 1;
237 | end
238 |
239 | return kReturn;
240 | end
241 |
242 | -- ===========================================================================
243 | -- Bit Helper function
244 | -- Converts a table of bits into it's corresponding number.
245 | -- ===========================================================================
246 | function bitsTableToNumber( kTable:table )
247 | local bits :number = table.count(kTable);
248 | local n :number = 0;
249 | local power :number = 1;
250 | for i = 1, bits,1 do
251 | n = n + kTable[i] * power;
252 | power = power * 2;
253 | end
254 | return n;
255 | end
256 |
257 | -- ===========================================================================
258 | -- Bitwise not (because LUA 5.2 support doesn't exist yet in Havok script)
259 | -- ===========================================================================
260 | function bitNot( value:number )
261 | local kBits:table = numberToBitsTable(value);
262 | local size:number = math.max(table.getn(kBits), 32)
263 | for i = 1, size do
264 | if(kBits[i] == 1) then
265 | kBits[i] = 0
266 | else
267 | kBits[i] = 1
268 | end
269 | end
270 | return bitsTableToNumber(kBits);
271 | end
272 |
273 | -- ===========================================================================
274 | -- Bitwise or (because LUA 5.2 support doesn't exist yet in Havok script)
275 | -- ===========================================================================
276 | local function bitOr( na:number, nb:number)
277 | local ka :table = numberToBitsTable(na);
278 | local kb :table = numberToBitsTable(nb);
279 |
280 | -- Make sure both are the same size; pad with 0's if necessary.
281 | while table.count(ka) < table.count(kb) do ka[table.count(ka)+1] = 0; end
282 | while table.count(kb) < table.count(ka) do kb[table.count(kb)+1] = 0; end
283 |
284 | local kResult :table = {};
285 | local digits :number = table.count(ka);
286 | for i:number = 1, digits, 1 do
287 | kResult[i] = (ka[i]==1 or kb[i]==1) and 1 or 0;
288 | end
289 | return bitsTableToNumber( kResult );
290 | end
291 |
292 |
293 | -- ===========================================================================
294 | -- Left shift (because LUA 5.2 support doesn't exist yet in Havok script)
295 | -- ===========================================================================
296 | function lshift( value, shift )
297 | return math.floor(value) * g_supportFunctions_shiftTable[shift];
298 | end
299 |
300 | -- ===========================================================================
301 | -- Right shift (because LUA 5.2 support doesn't exist yet in Havok script)
302 | -- ===========================================================================
303 | function rshift( value:number, shift:number )
304 | local highBit:number = 0;
305 |
306 | if value < 0 then
307 | value = bitNot(math.abs(value)) + 1;
308 | highBit = 0x80000000;
309 | end
310 |
311 | for i=1, shift, 1 do
312 | value = bitOr( math.floor(value*0.5), highBit );
313 | end
314 | return math.floor(value);
315 | end
316 |
317 |
318 | -- ===========================================================================
319 | -- Determine if string is IP4, IP6, or invalid
320 | --
321 | -- Based off of:
322 | -- http://stackoverflow.com/questions/10975935/lua-function-check-if-ipv4-or-ipv6-or-string
323 | --
324 | -- Returns: 4 if IP4, 6 if IP6, or 0 if not valid
325 | -- ===========================================================================
326 | function GetIPType( ip )
327 |
328 | if ip == nil or type(ip) ~= "string" then
329 | return 0;
330 | end
331 |
332 | -- Check for IPv4 format, 4 chunks between 0 and 255 (e.g., 1.11.111.111)
333 | local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
334 | if (table.count(chunks) == 4) then
335 | for _,v in pairs(chunks) do
336 | if (tonumber(v) < 0 or tonumber(v) > 255) then
337 | return 0;
338 | end
339 | end
340 | return 4; -- This is IP4
341 | end
342 |
343 | -- Check for ipv6 format, should be 8 'chunks' of numbers/letters without trailing chars
344 | local chunks = {ip:match(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$"))}
345 | if table.count(chunks) == 8 then
346 | for _,v in pairs(chunks) do
347 | if table.count(v) > 0 and tonumber(v, 16) > 65535 then
348 | return 0;
349 | end
350 | end
351 | return 6; -- This is IP6
352 | end
353 | return 0;
354 | end
355 |
356 |
357 |
358 |
359 | -- ===========================================================================
360 | -- LUA Helper function
361 | -- ===========================================================================
362 | function RemoveTableEntry( T:table, key:string, theValue )
363 | local pos = nil;
364 | for i,v in ipairs(T) do
365 | if (v[key]==theValue) then
366 | pos=i;
367 | break;
368 | end
369 | end
370 | if(pos ~= nil) then
371 | table.remove(T, pos);
372 | return true;
373 | end
374 | return false;
375 | end
376 |
377 | -- ===========================================================================
378 | -- orderedPairs()
379 | -- Allows an ordered iteratation of the pairs in a table. Use like pairs().
380 | -- Original version from: http://lua-users.org/wiki/SortedIteration
381 | -- ===========================================================================
382 | function __genOrderedIndex( t )
383 | local orderedIndex = {}
384 | for key in pairs(t) do
385 | table.insert( orderedIndex, key )
386 | end
387 | table.sort( orderedIndex )
388 | return orderedIndex
389 | end
390 | function orderedNext(t, state)
391 | -- Equivalent of the next function, but returns the keys in the alphabetic order.
392 | -- Using a temporary ordered key table that is stored in the table being iterated.
393 | key = nil;
394 | if state == nil then
395 | -- Is first time; generate the index.
396 | t.__orderedIndex = __genOrderedIndex( t );
397 | key = t.__orderedIndex[1];
398 | else
399 | -- Fetch next value.
400 | for i = 1,table.count(t.__orderedIndex) do
401 | if t.__orderedIndex[i] == state then
402 | key = t.__orderedIndex[i+1];
403 | end
404 | end
405 | end
406 |
407 | if key then
408 | return key, t[key];
409 | else
410 | t.__orderedIndex = nil; -- No more value to return, cleanup.
411 | end
412 | end
413 | function orderedPairs(t)
414 | return orderedNext, t, nil;
415 | end
416 |
417 |
418 | -- ===========================================================================
419 | -- Split()
420 | -- Allows splitting a string (tokenizing) into an array based on a delimeter.
421 | -- Original version from: http://lua-users.org/wiki/SplitJoin
422 | -- RETURNS: Table of tokenized strings
423 | -- ===========================================================================
424 | function Split(str:string, delim:string, maxNb:number)
425 | -- Eliminate bad cases...
426 | if string.find(str, delim) == nil then
427 | return { str };
428 | end
429 | if maxNb == nil or maxNb < 1 then
430 | maxNb = 0; -- No limit
431 | end
432 | local result:table = {};
433 | local pat :string = "(.-)" .. delim .. "()";
434 | local nb :number = 0;
435 | local lastPos:number;
436 | for part, pos in string.gmatch(str, pat) do
437 | nb = nb + 1;
438 | result[nb] = part;
439 | lastPos = pos;
440 | if nb == maxNb then
441 | break;
442 | end
443 | end
444 | -- Handle the last field
445 | if nb ~= maxNb then
446 | result[nb + 1] = string.sub(str, lastPos);
447 | end
448 | return result;
449 | end
450 |
451 |
452 | -- ===========================================================================
453 | -- Clamp()
454 | -- Returns the value passed, only changing if it's above or below the min/max
455 | -- ===========================================================================
456 | function Clamp( value:number, min:number, max:number )
457 | if value < min then
458 | return min;
459 | elseif value > max then
460 | return max;
461 | else
462 | return value;
463 | end
464 | end
465 |
466 |
467 |
468 | -- ===========================================================================
469 | -- Round()
470 | -- Rounds a number to X decimal places.
471 | -- Original version from: http://lua-users.org/wiki/SimpleRound
472 | -- ===========================================================================
473 | function Round(num:number, idp:number)
474 | local mult:number = 10^(idp or 0);
475 | return math.floor(num * mult + 0.5) / mult;
476 | end
477 |
478 |
479 | -- ===========================================================================
480 | -- Convert polar coordiantes to Cartesian plane.
481 | -- ARGS: r radius
482 | -- phi angle in degrees (90 is facing down, 0 is pointing right)
483 | -- ratio y-axis to x-axis to "squash" the circle if desired
484 | --
485 | -- Unwrapped Circle: local x = r * math.cos( math.rad(phi) );
486 | -- local y = r * math.sin( math.rad(phi) );
487 | -- return x,y;
488 | -- ===========================================================================
489 | function PolarToCartesian( r:number, phi:number )
490 | return r * math.cos( math.rad(phi) ), r * math.sin( math.rad(phi) );
491 | end
492 | function PolarToRatioCartesian( r:number, phi:number, ratio:number )
493 | return r * math.cos( math.rad(phi) ), r * math.sin( math.rad(phi) ) * ratio;
494 | end
495 |
496 | -- ===========================================================================
497 | -- Transforms a ABGR color by some amount
498 | -- ARGS: hexColor Hex color value (0xAAGGBBRR)
499 | -- amt (0-255) the amount to darken or lighten the color
500 | -- alpha ???
501 | --RETURNS: transformed color (0xAAGGBBRR)
502 | -- ===========================================================================
503 | function DarkenLightenColor( hexColor:number, amt:number, alpha:number )
504 |
505 | --Parse the a,g,b,r hex values from the string
506 | local hexString :string = string.format("%x",hexColor);
507 | local b = string.sub(hexString,3,4);
508 | local g = string.sub(hexString,5,6);
509 | local r = string.sub(hexString,7,8);
510 | b = tonumber(b,16);
511 | g = tonumber(g,16);
512 | r = tonumber(r,16);
513 |
514 | if (b == nil) then b = 0; end
515 | if (g == nil) then g = 0; end
516 | if (r == nil) then r = 0; end
517 |
518 | local a = string.format("%x",alpha);
519 | if (string.len(a)==1) then
520 | a = "0"..a;
521 | end
522 |
523 | b = b + amt;
524 | if (b < 0 or b == 0) then
525 | b = "00";
526 | elseif (b > 255 or b == 255) then
527 | b = "FF";
528 | else
529 | b = string.format("%x",b);
530 | if (string.len(b)==1) then
531 | b = "0"..b;
532 | end
533 | end
534 |
535 | g = g + amt;
536 | if (g < 0 or g == 0) then
537 | g = "00";
538 | elseif (g > 255 or g == 255) then
539 | g = "FF";
540 | else
541 | g = string.format("%x",g);
542 | if (string.len(g)==1) then
543 | g = "0"..g;
544 | end
545 | end
546 |
547 | r = r + amt;
548 | if (r < 0 or r == 0) then
549 | r = "00";
550 | elseif (r > 255 or r == 255) then
551 | r = "FF";
552 | else
553 | r = string.format("%x",r);
554 | if (string.len(r)==1) then
555 | r = "0"..r;
556 | end
557 | end
558 |
559 | hexString = a..b..g..r;
560 | return tonumber(hexString,16);
561 | end
562 |
563 |
564 | -- ===========================================================================
565 | -- Recursively duplicate (deep copy)
566 | -- Original from: http://lua-users.org/wiki/CopyTable
567 | -- ===========================================================================
568 | function DeepCopy( orig )
569 | local orig_type = type(orig);
570 | local copy;
571 | if orig_type == 'table' then
572 | copy = {};
573 | for orig_key, orig_value in next, orig, nil do
574 | copy[DeepCopy(orig_key)] = DeepCopy(orig_value);
575 | end
576 | setmetatable(copy, DeepCopy(getmetatable(orig)));
577 | else -- number, string, boolean, etc
578 | copy = orig;
579 | end
580 | return copy;
581 | end
582 |
583 | -- ===========================================================================
584 | -- Sizes a control to fit a maximum height, while maintaining the aspect ratio
585 | -- of the original control. If no Y is specified, we will use the height of the screen.
586 | -- ARG 1: control (table) - expects a control to be resized
587 | -- ARG 5: OPTIONAL maxY (number) - the minimum height of the control.
588 | -- ===========================================================================
589 | function UniformToFillY( control:table, maxY:number )
590 | local currentX = control:GetSizeX();
591 | local currentY = control:GetSizeY();
592 | local newX = 0;
593 | local newY = 0;
594 | if (maxY == nil) then
595 | local _, screenY:number = UIManager:GetScreenSizeVal();
596 | newY = screenY;
597 | else
598 | newY = maxY;
599 | end
600 | newX = (currentX * newY)/currentY;
601 | control:SetSizeVal(newX,newY);
602 | end
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 | --[[ DataDumper.lua
611 | Copyright (c) 2007 Olivetti-Engineering SA
612 |
613 | Permission is hereby granted, free of charge, to any person
614 | obtaining a copy of this software and associated documentation
615 | files (the "Software"), to deal in the Software without
616 | restriction, including without limitation the rights to use,
617 | copy, modify, merge, publish, distribute, sublicense, and/or sell
618 | copies of the Software, and to permit persons to whom the
619 | Software is furnished to do so, subject to the following
620 | conditions:
621 |
622 | The above copyright notice and this permission notice shall be
623 | included in all copies or substantial portions of the Software.
624 |
625 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
626 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
627 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
628 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
629 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
630 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
631 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
632 | OTHER DEALINGS IN THE SOFTWARE.
633 | ]]
634 |
635 | function dump(...)
636 | print(DataDumper(...), "\n---")
637 | end
638 |
639 | local dumplua_closure = [[
640 | local closures = {}
641 | local function closure(t)
642 | closures[#closures+1] = t
643 | t[1] = assert(loadstring(t[1]))
644 | return t[1]
645 | end
646 |
647 | for _,t in pairs(closures) do
648 | for i = 2,#t do
649 | debug.setupvalue(t[1], i-1, t[i])
650 | end
651 | end
652 | ]]
653 |
654 | local lua_reserved_keywords = {
655 | 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
656 | 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
657 | 'return', 'then', 'true', 'until', 'while' }
658 |
659 | local function keys(t)
660 | local res = {}
661 | local oktypes = { stringstring = true, numbernumber = true }
662 | local function cmpfct(a,b)
663 | if oktypes[type(a)..type(b)] then
664 | return a < b
665 | else
666 | return type(a) < type(b)
667 | end
668 | end
669 | for k in pairs(t) do
670 | res[#res+1] = k
671 | end
672 | table.sort(res, cmpfct)
673 | return res
674 | end
675 |
676 | local c_functions = {}
677 | for _,lib in pairs{'_G', 'string', 'table', 'math',
678 | 'io', 'os', 'coroutine', 'package', 'debug'} do
679 | local t = {}
680 | lib = lib .. "."
681 | if lib == "_G." then lib = "" end
682 | for k,v in pairs(t) do
683 | if type(v) == 'function' and not pcall(string.dump, v) then
684 | c_functions[v] = lib..k
685 | end
686 | end
687 | end
688 |
689 | function DataDumper(value, varname, fastmode, ident)
690 | local defined, dumplua = {}
691 | -- Local variables for speed optimization
692 | local string_format, type, string_dump, string_rep =
693 | string.format, type, string.dump, string.rep
694 | local tostring, pairs, table_concat =
695 | tostring, pairs, table.concat
696 | local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0
697 | setmetatable(strvalcache, {__index = function(t,value)
698 | local res = string_format('%q', value)
699 | t[value] = res
700 | return res
701 | end})
702 | local fcts = {
703 | string = function(value) return strvalcache[value] end,
704 | number = function(value) return value end,
705 | boolean = function(value) return tostring(value) end,
706 | ['nil'] = function(value) return 'nil' end,
707 | ['function'] = function(value)
708 | return string_format("loadstring(%q)", string_dump(value))
709 | end,
710 | userdata = function() error("Cannot dump userdata") end,
711 | thread = function() error("Cannot dump threads") end,
712 | }
713 | local function test_defined(value, path)
714 | if defined[value] then
715 | if path:match("^getmetatable.*%)$") then
716 | out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value])
717 | else
718 | out[#out+1] = path .. " = " .. defined[value] .. "\n"
719 | end
720 | return true
721 | end
722 | defined[value] = path
723 | end
724 | local function make_key(t, key)
725 | local s
726 | if type(key) == 'string' and key:match('^[_%a][_%w]*$') then
727 | s = key .. "="
728 | else
729 | s = "[" .. dumplua(key, 0) .. "]="
730 | end
731 | t[key] = s
732 | return s
733 | end
734 | for _,k in ipairs(lua_reserved_keywords) do
735 | keycache[k] = '["'..k..'"] = '
736 | end
737 | if fastmode then
738 | fcts.table = function (value)
739 | -- Table value
740 | local numidx = 1
741 | out[#out+1] = "{"
742 | for key,val in pairs(value) do
743 | if key == numidx then
744 | numidx = numidx + 1
745 | else
746 | out[#out+1] = keycache[key]
747 | end
748 | local str = dumplua(val)
749 | out[#out+1] = str..","
750 | end
751 | if string.sub(out[#out], -1) == "," then
752 | out[#out] = string.sub(out[#out], 1, -2);
753 | end
754 | out[#out+1] = "}"
755 | return ""
756 | end
757 | else
758 | fcts.table = function (value, ident, path)
759 | if test_defined(value, path) then return "nil" end
760 | -- Table value
761 | local sep, str, numidx, totallen = " ", {}, 1, 0
762 | local meta, metastr = getmetatable(value)
763 | if meta then
764 | ident = ident + 1
765 | metastr = dumplua(meta, ident, "getmetatable("..path..")")
766 | totallen = totallen + #metastr + 16
767 | end
768 | for _,key in pairs(keys(value)) do
769 | local val = value[key]
770 | local s = ""
771 | local subpath = path or ""
772 | if key == numidx then
773 | subpath = subpath .. "[" .. numidx .. "]"
774 | numidx = numidx + 1
775 | else
776 | s = keycache[key]
777 | if not s:match "^%[" then subpath = subpath .. "." end
778 | subpath = subpath .. s:gsub("%s*=%s*$","")
779 | end
780 | s = s .. dumplua(val, ident+1, subpath)
781 | str[#str+1] = s
782 | totallen = totallen + #s + 2
783 | end
784 | if totallen > 80 then
785 | sep = "\n" .. string_rep(" ", ident+1)
786 | end
787 | str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-3).."}"
788 | if meta then
789 | sep = sep:sub(1,-3)
790 | return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")"
791 | end
792 | return str
793 | end
794 | fcts['function'] = function (value, ident, path)
795 | if test_defined(value, path) then return "nil" end
796 | if c_functions[value] then
797 | return c_functions[value]
798 | elseif debug == nil or debug.getupvalue(value, 1) == nil then
799 | return string_format("loadstring(%q)", string_dump(value))
800 | end
801 | closure_cnt = closure_cnt + 1
802 | local res = {string.dump(value)}
803 | for i = 1,math.huge do
804 | local name, v = debug.getupvalue(value,i)
805 | if name == nil then break end
806 | res[i+1] = v
807 | end
808 | return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]")
809 | end
810 | end
811 | function dumplua(value, ident, path)
812 | return fcts[type(value)](value, ident, path)
813 | end
814 | if varname == nil then
815 | varname = ""
816 | elseif varname:match("^[%a_][%w_]*$") then
817 | varname = varname .. " = "
818 | end
819 | if fastmode then
820 | setmetatable(keycache, {__index = make_key })
821 | out[1] = varname
822 | table.insert(out,dumplua(value, 0))
823 | return table.concat(out)
824 | else
825 | setmetatable(keycache, {__index = make_key })
826 | local items = {}
827 | for i=1,10 do items[i] = '' end
828 | items[3] = dumplua(value, ident or 0, "t")
829 | if closure_cnt > 0 then
830 | items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)")
831 | out[#out+1] = ""
832 | end
833 | if #out > 0 then
834 | items[2], items[4] = "local t = ", "\n"
835 | items[5] = table.concat(out)
836 | items[7] = varname .. "t"
837 | else
838 | items[2] = varname
839 | end
840 | return table.concat(items)
841 | end
842 | end
843 | else
844 | -- Still running Summer 2017 update
845 | ------------------------------------------------------------------------------
846 | -- Misc Support Functions
847 | ------------------------------------------------------------------------------
848 |
849 | -- ===========================================================================
850 | -- CONSTANTS
851 | -- ===========================================================================
852 | local m_strEllipsis = Locale.Lookup("LOC_GENERIC_DOT_DOT_DOT");
853 |
854 |
855 | -- ===========================================================================
856 | -- Sets a Label or control that contains a label (e.g., GridButton) with
857 | -- a string that, if necessary, will be truncated.
858 | --
859 | -- RETURNS: true if truncated.
860 | -- ===========================================================================
861 | function TruncateString(control, resultSize, longStr, trailingText)
862 |
863 | -- Ensure this has the actual text control
864 | if control.GetTextControl ~= nil then
865 | control = control:GetTextControl();
866 | end
867 |
868 | local isTruncated = false;
869 | if(longStr == nil)then
870 | longStr = control:GetText();
871 |
872 | if(trailingText == nil)then
873 | longStr = "";
874 | end
875 | end
876 |
877 | if(control ~= nil)then
878 |
879 | -- Determine full length of control.
880 | control:SetText(longStr);
881 | local fullStrExtent = control:GetSizeX();
882 |
883 | -- Determine how long a trailing text portion will be.
884 | if(trailingText == nil)then
885 | trailingText = "";
886 | end
887 | control:SetText(trailingText);
888 | local trailingExtent = control:GetSizeX();
889 |
890 | local sizeAfterTruncate = resultSize - trailingExtent;
891 | if(sizeAfterTruncate > 0)then
892 | local truncatedSize = fullStrExtent;
893 | local newString = longStr;
894 |
895 | local ellipsis = "";
896 |
897 | if( sizeAfterTruncate < truncatedSize ) then
898 | ellipsis = m_strEllipsis;
899 | isTruncated = true;
900 | end
901 |
902 | control:SetText(ellipsis);
903 | local ellipsisExtent = control:GetSizeX();
904 | sizeAfterTruncate = sizeAfterTruncate - ellipsisExtent;
905 |
906 | while (sizeAfterTruncate < truncatedSize and Locale.Length(newString) > 1) do
907 | newString = Locale.SubString(newString, 1, Locale.Length(newString) - 1);
908 | control:SetText(newString);
909 | truncatedSize = control:GetSizeX();
910 | end
911 |
912 | control:SetText(newString .. ellipsis .. trailingText);
913 | end
914 | else
915 | print("ERROR: Attempt to TruncateString but NIL control passed in!. string=", longStr);
916 | end
917 | return isTruncated;
918 | end
919 |
920 |
921 | -- ===========================================================================
922 | -- Same as TruncateString(), but if truncation occurs automatically adds
923 | -- the full text as a tooltip.
924 | -- ===========================================================================
925 | function TruncateStringWithTooltip(control, resultSize, longStr, trailingText)
926 | local isTruncated = TruncateString( control, resultSize, longStr, trailingText );
927 | if isTruncated then
928 | control:SetToolTipString( longStr );
929 | else
930 | control:SetToolTipString( nil );
931 | end
932 | return isTruncated;
933 | end
934 |
935 |
936 | -- ===========================================================================
937 | -- Performs a truncation based on the control's contents
938 | -- ===========================================================================
939 | function TruncateSelfWithTooltip( control )
940 | local resultSize = control:GetSizeX();
941 | local longStr = control:GetText();
942 | return TruncateStringWithTooltip(control, resultSize, longStr);
943 | end
944 |
945 |
946 | -- ===========================================================================
947 | -- Truncate string based on # of characters
948 | -- (Most useful when having to truncate a string *in* a tooltip.
949 | -- ===========================================================================
950 | function TruncateStringByLength( textString, textLen )
951 | if ( Locale.Length(textString) > textLen ) then
952 | return Locale.SubString(textString, 1, textLen) .. Locale.Lookup("TXT_KEY_GENERIC_DOT_DOT_DOT");
953 | end
954 | return textString;
955 | end
956 |
957 |
958 | -- ===========================================================================
959 | -- Convert a set of values (red, green, blue, alpha) into a single hex value.
960 | -- Values are from 0.0 to 1.0
961 | -- return math.floor(value is a single, unsigned uint as ABGR
962 | -- ===========================================================================
963 | function RGBAValuesToABGRHex( red, green, blue, alpha )
964 |
965 | -- optionally pass in alpha, to taste
966 | if alpha==nil then
967 | alpha = 1.0;
968 | end
969 |
970 | -- prepare ingredients so they are clamped from 0 to 255
971 | red = math.max( 0, math.min( 255, red*255 ));
972 | green = math.max( 0, math.min( 255, green*255 ));
973 | blue = math.max( 0, math.min( 255, blue*255 ));
974 | alpha = math.max( 0, math.min( 255, alpha*255 ));
975 |
976 | -- combine the ingredients, stiring gently
977 | local value = lshift( alpha, 24 ) + lshift( blue, 16 ) + lshift( green, 8 ) + red;
978 |
979 | -- return the baked goodness
980 | return math.floor(value);
981 | end
982 |
983 | -- ===========================================================================
984 | -- Use to convert from CivBE style colors to ForgeUI color
985 | -- ===========================================================================
986 | function RGBAObjectToABGRHex( colorObject )
987 | return RGBAValuesToABGRHex( colorObject.x, colorObject.y, colorObject.z, colorObject.w );
988 | end
989 |
990 | -- ===========================================================================
991 | -- Guess what, TextControls still use legacy color; use to convert to it.
992 | -- RETURNS: Object with R G B A to a vector like format with fields X Y Z W
993 | -- ===========================================================================
994 | function ABGRHExToRGBAObject( hexColor )
995 | local ret = {};
996 | ret.w = math.floor( math.fmod( rshift(hexColor,24), 256));
997 | ret.z = math.floor( math.fmod( rshift(hexColor,16), 256));
998 | ret.y = math.floor( math.fmod( rshift(hexColor,8), 256));
999 | ret.x = math.floor( math.fmod( hexColor, 0x256 )); -- lower MODs are messed up due what is in higher bits, need an AND!
1000 | return ret;
1001 | end
1002 |
1003 |
1004 |
1005 | -- ===========================================================================
1006 | -- Support for shifts
1007 | -- ===========================================================================
1008 | local g_supportFunctions_shiftTable = {};
1009 | g_supportFunctions_shiftTable[0] = 1;
1010 | g_supportFunctions_shiftTable[1] = 2;
1011 | g_supportFunctions_shiftTable[2] = 4;
1012 | g_supportFunctions_shiftTable[3] = 8;
1013 | g_supportFunctions_shiftTable[4] = 16;
1014 | g_supportFunctions_shiftTable[5] = 32;
1015 | g_supportFunctions_shiftTable[6] = 64;
1016 | g_supportFunctions_shiftTable[7] = 128;
1017 | g_supportFunctions_shiftTable[8] = 256;
1018 | g_supportFunctions_shiftTable[9] = 512;
1019 | g_supportFunctions_shiftTable[10] = 1024;
1020 | g_supportFunctions_shiftTable[11] = 2048;
1021 | g_supportFunctions_shiftTable[12] = 4096;
1022 | g_supportFunctions_shiftTable[13] = 8192;
1023 | g_supportFunctions_shiftTable[14] = 16384;
1024 | g_supportFunctions_shiftTable[15] = 32768;
1025 | g_supportFunctions_shiftTable[16] = 65536;
1026 | g_supportFunctions_shiftTable[17] = 131072;
1027 | g_supportFunctions_shiftTable[18] = 262144;
1028 | g_supportFunctions_shiftTable[19] = 524288;
1029 | g_supportFunctions_shiftTable[20] = 1048576;
1030 | g_supportFunctions_shiftTable[21] = 2097152;
1031 | g_supportFunctions_shiftTable[22] = 4194304;
1032 | g_supportFunctions_shiftTable[23] = 8388608;
1033 | g_supportFunctions_shiftTable[24] = 16777216;
1034 |
1035 |
1036 |
1037 | -- ===========================================================================
1038 | -- Bit Helper function
1039 | -- Converts a number into a table of bits.
1040 | -- ===========================================================================
1041 | function numberToBitsTable( value:number )
1042 | if value < 0 then
1043 | return numberToBitsTable( bitNot(math.abs(value))+1 ); -- Recurse
1044 | end
1045 |
1046 | local kReturn :table = {};
1047 | local i :number = 1;
1048 | while value > 0 do
1049 | local digit:number = math.fmod(value, 2);
1050 | if digit == 1 then
1051 | kReturn[i] = 1;
1052 | else
1053 | kReturn[i] = 0;
1054 | end
1055 | value = (value - digit) * 0.5;
1056 | i = i + 1;
1057 | end
1058 |
1059 | return kReturn;
1060 | end
1061 |
1062 | -- ===========================================================================
1063 | -- Bit Helper function
1064 | -- Converts a table of bits into it's corresponding number.
1065 | -- ===========================================================================
1066 | function bitsTableToNumber( kTable:table )
1067 | local bits :number = table.count(kTable);
1068 | local n :number = 0;
1069 | local power :number = 1;
1070 | for i = 1, bits,1 do
1071 | n = n + kTable[i] * power;
1072 | power = power * 2;
1073 | end
1074 | return n;
1075 | end
1076 |
1077 | -- ===========================================================================
1078 | -- Bitwise not (because LUA 5.2 support doesn't exist yet in Havok script)
1079 | -- ===========================================================================
1080 | function bitNot( value:number )
1081 | local kBits:table = numberToBitsTable(value);
1082 | local size:number = math.max(table.getn(kBits), 32)
1083 | for i = 1, size do
1084 | if(kBits[i] == 1) then
1085 | kBits[i] = 0
1086 | else
1087 | kBits[i] = 1
1088 | end
1089 | end
1090 | return bitsTableToNumber(kBits);
1091 | end
1092 |
1093 | -- ===========================================================================
1094 | -- Bitwise or (because LUA 5.2 support doesn't exist yet in Havok script)
1095 | -- ===========================================================================
1096 | local function bitOr( na:number, nb:number)
1097 | local ka :table = numberToBitsTable(na);
1098 | local kb :table = numberToBitsTable(nb);
1099 |
1100 | -- Make sure both are the same size; pad with 0's if necessary.
1101 | while table.count(ka) < table.count(kb) do ka[table.count(ka)+1] = 0; end
1102 | while table.count(kb) < table.count(ka) do kb[table.count(kb)+1] = 0; end
1103 |
1104 | local kResult :table = {};
1105 | local digits :number = table.count(ka);
1106 | for i:number = 1, digits, 1 do
1107 | kResult[i] = (ka[i]==1 or kb[i]==1) and 1 or 0;
1108 | end
1109 | return bitsTableToNumber( kResult );
1110 | end
1111 |
1112 |
1113 | -- ===========================================================================
1114 | -- Left shift (because LUA 5.2 support doesn't exist yet in Havok script)
1115 | -- ===========================================================================
1116 | function lshift( value, shift )
1117 | return math.floor(value) * g_supportFunctions_shiftTable[shift];
1118 | end
1119 |
1120 | -- ===========================================================================
1121 | -- Right shift (because LUA 5.2 support doesn't exist yet in Havok script)
1122 | -- ===========================================================================
1123 | function rshift( value:number, shift:number )
1124 | local highBit:number = 0;
1125 |
1126 | if value < 0 then
1127 | value = bitNot(math.abs(value)) + 1;
1128 | highBit = 0x80000000;
1129 | end
1130 |
1131 | for i=1, shift, 1 do
1132 | value = bitOr( math.floor(value*0.5), highBit );
1133 | end
1134 | return math.floor(value);
1135 | end
1136 |
1137 |
1138 | -- ===========================================================================
1139 | -- Determine if string is IP4, IP6, or invalid
1140 | --
1141 | -- Based off of:
1142 | -- http://stackoverflow.com/questions/10975935/lua-function-check-if-ipv4-or-ipv6-or-string
1143 | --
1144 | -- Returns: 4 if IP4, 6 if IP6, or 0 if not valid
1145 | -- ===========================================================================
1146 | function GetIPType( ip )
1147 |
1148 | if ip == nil or type(ip) ~= "string" then
1149 | return 0;
1150 | end
1151 |
1152 | -- Check for IPv4 format, 4 chunks between 0 and 255 (e.g., 1.11.111.111)
1153 | local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
1154 | if (table.count(chunks) == 4) then
1155 | for _,v in pairs(chunks) do
1156 | if (tonumber(v) < 0 or tonumber(v) > 255) then
1157 | return 0;
1158 | end
1159 | end
1160 | return 4; -- This is IP4
1161 | end
1162 |
1163 | -- Check for ipv6 format, should be 8 'chunks' of numbers/letters without trailing chars
1164 | local chunks = {ip:match(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$"))}
1165 | if table.count(chunks) == 8 then
1166 | for _,v in pairs(chunks) do
1167 | if table.count(v) > 0 and tonumber(v, 16) > 65535 then
1168 | return 0;
1169 | end
1170 | end
1171 | return 6; -- This is IP6
1172 | end
1173 | return 0;
1174 | end
1175 |
1176 |
1177 |
1178 |
1179 | -- ===========================================================================
1180 | -- LUA Helper function
1181 | -- ===========================================================================
1182 | function RemoveTableEntry( T:table, key:string, theValue )
1183 | local pos = nil;
1184 | for i,v in ipairs(T) do
1185 | if (v[key]==theValue) then
1186 | pos=i;
1187 | break;
1188 | end
1189 | end
1190 | if(pos ~= nil) then
1191 | table.remove(T, pos);
1192 | return true;
1193 | end
1194 | return false;
1195 | end
1196 |
1197 | -- ===========================================================================
1198 | -- orderedPairs()
1199 | -- Allows an ordered iteratation of the pairs in a table. Use like pairs().
1200 | -- Original version from: http://lua-users.org/wiki/SortedIteration
1201 | -- ===========================================================================
1202 | function __genOrderedIndex( t )
1203 | local orderedIndex = {}
1204 | for key in pairs(t) do
1205 | table.insert( orderedIndex, key )
1206 | end
1207 | table.sort( orderedIndex )
1208 | return orderedIndex
1209 | end
1210 | function orderedNext(t, state)
1211 | -- Equivalent of the next function, but returns the keys in the alphabetic order.
1212 | -- Using a temporary ordered key table that is stored in the table being iterated.
1213 | key = nil;
1214 | if state == nil then
1215 | -- Is first time; generate the index.
1216 | t.__orderedIndex = __genOrderedIndex( t );
1217 | key = t.__orderedIndex[1];
1218 | else
1219 | -- Fetch next value.
1220 | for i = 1,table.count(t.__orderedIndex) do
1221 | if t.__orderedIndex[i] == state then
1222 | key = t.__orderedIndex[i+1];
1223 | end
1224 | end
1225 | end
1226 |
1227 | if key then
1228 | return key, t[key];
1229 | else
1230 | t.__orderedIndex = nil; -- No more value to return, cleanup.
1231 | end
1232 | end
1233 | function orderedPairs(t)
1234 | return orderedNext, t, nil;
1235 | end
1236 |
1237 |
1238 | -- ===========================================================================
1239 | -- Split()
1240 | -- Allows splitting a string (tokenizing) into an array based on a delimeter.
1241 | -- Original version from: http://lua-users.org/wiki/SplitJoin
1242 | -- RETURNS: Table of tokenized strings
1243 | -- ===========================================================================
1244 | function Split(str:string, delim:string, maxNb:number)
1245 | -- Eliminate bad cases...
1246 | if string.find(str, delim) == nil then
1247 | return { str };
1248 | end
1249 | if maxNb == nil or maxNb < 1 then
1250 | maxNb = 0; -- No limit
1251 | end
1252 | local result:table = {};
1253 | local pat :string = "(.-)" .. delim .. "()";
1254 | local nb :number = 0;
1255 | local lastPos:number;
1256 | for part, pos in string.gmatch(str, pat) do
1257 | nb = nb + 1;
1258 | result[nb] = part;
1259 | lastPos = pos;
1260 | if nb == maxNb then
1261 | break;
1262 | end
1263 | end
1264 | -- Handle the last field
1265 | if nb ~= maxNb then
1266 | result[nb + 1] = string.sub(str, lastPos);
1267 | end
1268 | return result;
1269 | end
1270 |
1271 |
1272 | -- ===========================================================================
1273 | -- Clamp()
1274 | -- Returns the value passed, only changing if it's above or below the min/max
1275 | -- ===========================================================================
1276 | function Clamp( value:number, min:number, max:number )
1277 | if value < min then
1278 | return min;
1279 | elseif value > max then
1280 | return max;
1281 | else
1282 | return value;
1283 | end
1284 | end
1285 |
1286 |
1287 |
1288 | -- ===========================================================================
1289 | -- Round()
1290 | -- Rounds a number to X decimal places.
1291 | -- Original version from: http://lua-users.org/wiki/SimpleRound
1292 | -- ===========================================================================
1293 | function Round(num:number, idp:number)
1294 | local mult:number = 10^(idp or 0);
1295 | return math.floor(num * mult + 0.5) / mult;
1296 | end
1297 |
1298 |
1299 | -- ===========================================================================
1300 | -- Convert polar coordiantes to Cartesian plane.
1301 | -- ARGS: r radius
1302 | -- phi angle in degrees (90 is facing down, 0 is pointing right)
1303 | -- ratio y-axis to x-axis to "squash" the circle if desired
1304 | --
1305 | -- Unwrapped Circle: local x = r * math.cos( math.rad(phi) );
1306 | -- local y = r * math.sin( math.rad(phi) );
1307 | -- return x,y;
1308 | -- ===========================================================================
1309 | function PolarToCartesian( r:number, phi:number )
1310 | return r * math.cos( math.rad(phi) ), r * math.sin( math.rad(phi) );
1311 | end
1312 | function PolarToRatioCartesian( r:number, phi:number, ratio:number )
1313 | return r * math.cos( math.rad(phi) ), r * math.sin( math.rad(phi) ) * ratio;
1314 | end
1315 |
1316 | -- ===========================================================================
1317 | -- Transforms a ABGR color by some amount
1318 | -- ARGS: hexColor Hex color value (0xAAGGBBRR)
1319 | -- amt (0-255) the amount to darken or lighten the color
1320 | -- alpha ???
1321 | --RETURNS: transformed color (0xAAGGBBRR)
1322 | -- ===========================================================================
1323 | function DarkenLightenColor( hexColor:number, amt:number, alpha:number )
1324 |
1325 | --Parse the a,g,b,r hex values from the string
1326 | local hexString :string = string.format("%x",hexColor);
1327 | local b = string.sub(hexString,3,4);
1328 | local g = string.sub(hexString,5,6);
1329 | local r = string.sub(hexString,7,8);
1330 | b = tonumber(b,16);
1331 | g = tonumber(g,16);
1332 | r = tonumber(r,16);
1333 |
1334 | if (b == nil) then b = 0; end
1335 | if (g == nil) then g = 0; end
1336 | if (r == nil) then r = 0; end
1337 |
1338 | local a = string.format("%x",alpha);
1339 | if (string.len(a)==1) then
1340 | a = "0"..a;
1341 | end
1342 |
1343 | b = b + amt;
1344 | if (b < 0 or b == 0) then
1345 | b = "00";
1346 | elseif (b > 255 or b == 255) then
1347 | b = "FF";
1348 | else
1349 | b = string.format("%x",b);
1350 | if (string.len(b)==1) then
1351 | b = "0"..b;
1352 | end
1353 | end
1354 |
1355 | g = g + amt;
1356 | if (g < 0 or g == 0) then
1357 | g = "00";
1358 | elseif (g > 255 or g == 255) then
1359 | g = "FF";
1360 | else
1361 | g = string.format("%x",g);
1362 | if (string.len(g)==1) then
1363 | g = "0"..g;
1364 | end
1365 | end
1366 |
1367 | r = r + amt;
1368 | if (r < 0 or r == 0) then
1369 | r = "00";
1370 | elseif (r > 255 or r == 255) then
1371 | r = "FF";
1372 | else
1373 | r = string.format("%x",r);
1374 | if (string.len(r)==1) then
1375 | r = "0"..r;
1376 | end
1377 | end
1378 |
1379 | hexString = a..b..g..r;
1380 | return tonumber(hexString,16);
1381 | end
1382 |
1383 |
1384 | -- ===========================================================================
1385 | -- Recursively duplicate (deep copy)
1386 | -- Original from: http://lua-users.org/wiki/CopyTable
1387 | -- ===========================================================================
1388 | function DeepCopy( orig )
1389 | local orig_type = type(orig);
1390 | local copy;
1391 | if orig_type == 'table' then
1392 | copy = {};
1393 | for orig_key, orig_value in next, orig, nil do
1394 | copy[DeepCopy(orig_key)] = DeepCopy(orig_value);
1395 | end
1396 | setmetatable(copy, DeepCopy(getmetatable(orig)));
1397 | else -- number, string, boolean, etc
1398 | copy = orig;
1399 | end
1400 | return copy;
1401 | end
1402 |
1403 | -- ===========================================================================
1404 | -- Sizes a control to fit a maximum height, while maintaining the aspect ratio
1405 | -- of the original control. If no Y is specified, we will use the height of the screen.
1406 | -- ARG 1: control (table) - expects a control to be resized
1407 | -- ARG 5: OPTIONAL maxY (number) - the minimum height of the control.
1408 | -- ===========================================================================
1409 | function UniformToFillY( control:table, maxY:number )
1410 | local currentX = control:GetSizeX();
1411 | local currentY = control:GetSizeY();
1412 | local newX = 0;
1413 | local newY = 0;
1414 | if (maxY == nil) then
1415 | local _, screenY:number = UIManager:GetScreenSizeVal();
1416 | newY = screenY;
1417 | else
1418 | newY = maxY;
1419 | end
1420 | newX = (currentX * newY)/currentY;
1421 | control:SetSizeVal(newX,newY);
1422 | end
1423 |
1424 |
1425 |
1426 |
1427 |
1428 |
1429 |
1430 | --[[ DataDumper.lua
1431 | Copyright (c) 2007 Olivetti-Engineering SA
1432 |
1433 | Permission is hereby granted, free of charge, to any person
1434 | obtaining a copy of this software and associated documentation
1435 | files (the "Software"), to deal in the Software without
1436 | restriction, including without limitation the rights to use,
1437 | copy, modify, merge, publish, distribute, sublicense, and/or sell
1438 | copies of the Software, and to permit persons to whom the
1439 | Software is furnished to do so, subject to the following
1440 | conditions:
1441 |
1442 | The above copyright notice and this permission notice shall be
1443 | included in all copies or substantial portions of the Software.
1444 |
1445 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1446 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
1447 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1448 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
1449 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
1450 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1451 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1452 | OTHER DEALINGS IN THE SOFTWARE.
1453 | ]]
1454 |
1455 | function dump(...)
1456 | print(DataDumper(...), "\n---")
1457 | end
1458 |
1459 | local dumplua_closure = [[
1460 | local closures = {}
1461 | local function closure(t)
1462 | closures[#closures+1] = t
1463 | t[1] = assert(loadstring(t[1]))
1464 | return t[1]
1465 | end
1466 |
1467 | for _,t in pairs(closures) do
1468 | for i = 2,#t do
1469 | debug.setupvalue(t[1], i-1, t[i])
1470 | end
1471 | end
1472 | ]]
1473 |
1474 | local lua_reserved_keywords = {
1475 | 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
1476 | 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
1477 | 'return', 'then', 'true', 'until', 'while' }
1478 |
1479 | local function keys(t)
1480 | local res = {}
1481 | local oktypes = { stringstring = true, numbernumber = true }
1482 | local function cmpfct(a,b)
1483 | if oktypes[type(a)..type(b)] then
1484 | return a < b
1485 | else
1486 | return type(a) < type(b)
1487 | end
1488 | end
1489 | for k in pairs(t) do
1490 | res[#res+1] = k
1491 | end
1492 | table.sort(res, cmpfct)
1493 | return res
1494 | end
1495 |
1496 | local c_functions = {}
1497 | for _,lib in pairs{'_G', 'string', 'table', 'math',
1498 | 'io', 'os', 'coroutine', 'package', 'debug'} do
1499 | local t = {}
1500 | lib = lib .. "."
1501 | if lib == "_G." then lib = "" end
1502 | for k,v in pairs(t) do
1503 | if type(v) == 'function' and not pcall(string.dump, v) then
1504 | c_functions[v] = lib..k
1505 | end
1506 | end
1507 | end
1508 |
1509 | function DataDumper(value, varname, fastmode, ident)
1510 | local defined, dumplua = {}
1511 | -- Local variables for speed optimization
1512 | local string_format, type, string_dump, string_rep =
1513 | string.format, type, string.dump, string.rep
1514 | local tostring, pairs, table_concat =
1515 | tostring, pairs, table.concat
1516 | local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0
1517 | setmetatable(strvalcache, {__index = function(t,value)
1518 | local res = string_format('%q', value)
1519 | t[value] = res
1520 | return res
1521 | end})
1522 | local fcts = {
1523 | string = function(value) return strvalcache[value] end,
1524 | number = function(value) return value end,
1525 | boolean = function(value) return tostring(value) end,
1526 | ['nil'] = function(value) return 'nil' end,
1527 | ['function'] = function(value)
1528 | return string_format("loadstring(%q)", string_dump(value))
1529 | end,
1530 | userdata = function() error("Cannot dump userdata") end,
1531 | thread = function() error("Cannot dump threads") end,
1532 | }
1533 | local function test_defined(value, path)
1534 | if defined[value] then
1535 | if path:match("^getmetatable.*%)$") then
1536 | out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value])
1537 | else
1538 | out[#out+1] = path .. " = " .. defined[value] .. "\n"
1539 | end
1540 | return true
1541 | end
1542 | defined[value] = path
1543 | end
1544 | local function make_key(t, key)
1545 | local s
1546 | if type(key) == 'string' and key:match('^[_%a][_%w]*$') then
1547 | s = key .. "="
1548 | else
1549 | s = "[" .. dumplua(key, 0) .. "]="
1550 | end
1551 | t[key] = s
1552 | return s
1553 | end
1554 | for _,k in ipairs(lua_reserved_keywords) do
1555 | keycache[k] = '["'..k..'"] = '
1556 | end
1557 | if fastmode then
1558 | fcts.table = function (value)
1559 | -- Table value
1560 | local numidx = 1
1561 | out[#out+1] = "{"
1562 | for key,val in pairs(value) do
1563 | if key == numidx then
1564 | numidx = numidx + 1
1565 | else
1566 | out[#out+1] = keycache[key]
1567 | end
1568 | local str = dumplua(val)
1569 | out[#out+1] = str..","
1570 | end
1571 | if string.sub(out[#out], -1) == "," then
1572 | out[#out] = string.sub(out[#out], 1, -2);
1573 | end
1574 | out[#out+1] = "}"
1575 | return ""
1576 | end
1577 | else
1578 | fcts.table = function (value, ident, path)
1579 | if test_defined(value, path) then return "nil" end
1580 | -- Table value
1581 | local sep, str, numidx, totallen = " ", {}, 1, 0
1582 | local meta, metastr = getmetatable(value)
1583 | if meta then
1584 | ident = ident + 1
1585 | metastr = dumplua(meta, ident, "getmetatable("..path..")")
1586 | totallen = totallen + #metastr + 16
1587 | end
1588 | for _,key in pairs(keys(value)) do
1589 | local val = value[key]
1590 | local s = ""
1591 | local subpath = path or ""
1592 | if key == numidx then
1593 | subpath = subpath .. "[" .. numidx .. "]"
1594 | numidx = numidx + 1
1595 | else
1596 | s = keycache[key]
1597 | if not s:match "^%[" then subpath = subpath .. "." end
1598 | subpath = subpath .. s:gsub("%s*=%s*$","")
1599 | end
1600 | s = s .. dumplua(val, ident+1, subpath)
1601 | str[#str+1] = s
1602 | totallen = totallen + #s + 2
1603 | end
1604 | if totallen > 80 then
1605 | sep = "\n" .. string_rep(" ", ident+1)
1606 | end
1607 | str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-3).."}"
1608 | if meta then
1609 | sep = sep:sub(1,-3)
1610 | return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")"
1611 | end
1612 | return str
1613 | end
1614 | fcts['function'] = function (value, ident, path)
1615 | if test_defined(value, path) then return "nil" end
1616 | if c_functions[value] then
1617 | return c_functions[value]
1618 | elseif debug == nil or debug.getupvalue(value, 1) == nil then
1619 | return string_format("loadstring(%q)", string_dump(value))
1620 | end
1621 | closure_cnt = closure_cnt + 1
1622 | local res = {string.dump(value)}
1623 | for i = 1,math.huge do
1624 | local name, v = debug.getupvalue(value,i)
1625 | if name == nil then break end
1626 | res[i+1] = v
1627 | end
1628 | return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]")
1629 | end
1630 | end
1631 | function dumplua(value, ident, path)
1632 | return fcts[type(value)](value, ident, path)
1633 | end
1634 | if varname == nil then
1635 | varname = ""
1636 | elseif varname:match("^[%a_][%w_]*$") then
1637 | varname = varname .. " = "
1638 | end
1639 | if fastmode then
1640 | setmetatable(keycache, {__index = make_key })
1641 | out[1] = varname
1642 | table.insert(out,dumplua(value, 0))
1643 | return table.concat(out)
1644 | else
1645 | setmetatable(keycache, {__index = make_key })
1646 | local items = {}
1647 | for i=1,10 do items[i] = '' end
1648 | items[3] = dumplua(value, ident or 0, "t")
1649 | if closure_cnt > 0 then
1650 | items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)")
1651 | out[#out+1] = ""
1652 | end
1653 | if #out > 0 then
1654 | items[2], items[4] = "local t = ", "\n"
1655 | items[5] = table.concat(out)
1656 | items[7] = varname .. "t"
1657 | else
1658 | items[2] = varname
1659 | end
1660 | return table.concat(items)
1661 | end
1662 | end
1663 | end
--------------------------------------------------------------------------------