11local widget = widget --- @type RulesUnsyncedCallins
22
33-- When performing an area command for one of the `allowedCommands` below:
4- -- - If Ctrl is pressed and hovering over a unit, targets all units with the same alliance (enemy/allied) in the area.
5- -- - If Alt is pressed and hovering over a unit, targets all units that share the same unitdefid in the area.
4+ -- - If enemy unit is targeted then targetAllegiance=ENEMY_UNITS otherwise targetAllegiance=targetTeamId
5+ -- - If Ctrl is pressed and hovering over a unit, targets all units in the area. For wrecks, it targets all wrecks with the same tech level
6+ -- - If Alt is pressed and hovering over a unit, targets all units that share the same unitDefId in the area.
67-- - If Meta is pressed, orders are put in front of the order queue.
78-- - If Meta and Shift are pressed, splits orders between selected units. Orders are placed at the end of the queue
89function widget :GetInfo ()
@@ -29,20 +30,23 @@ local spWorldToScreenCoords = Spring.WorldToScreenCoords
2930local spTraceScreenRay = Spring .TraceScreenRay
3031local spGetUnitDefID = Spring .GetUnitDefID
3132local spGetUnitAllyTeam = Spring .GetUnitAllyTeam
33+ local spGetUnitTeam = Spring .GetUnitTeam
3234local spGetFeatureDefID = Spring .GetFeatureDefID
3335local spGetFeaturesInCylinder = Spring .GetFeaturesInCylinder
3436local spGetSpectatingState = Spring .GetSpectatingState
35- local spGetMyTeamID = Spring .GetMyTeamID
3637local spGetMyAllyTeamID = Spring .GetMyAllyTeamID
3738local spGetUnitIsTransporting = Spring .GetUnitIsTransporting
3839local spGetUnitPosition = Spring .GetUnitPosition
3940local spGetFeaturePosition = Spring .GetFeaturePosition
4041local spGetUnitArrayCentroid = Spring .GetUnitArrayCentroid
4142local spGetFeatureResurrect = Spring .GetFeatureResurrect
42- local spGetUnitTeam = Spring .GetUnitTeam
43- local spAreTeamsAllied = Spring .AreTeamsAllied
4443
45- local myTeamID
44+ local ENEMY_UNITS = Spring .ENEMY_UNITS
45+ local ALLY_UNITS = Spring .ALLY_UNITS
46+ local ALL_UNITS = Spring .ALL_UNITS
47+ local FEATURE = " feature"
48+ local UNIT = " unit"
49+
4650local myAllyTeamID
4751
4852---- -----------------------------------------------------------------------------------
@@ -303,18 +307,23 @@ local function sortTargetsByDistance(selectedUnits, filteredTargets, closestFirs
303307end
304308
305309local function giveOrders (cmdId , selectedUnits , filteredTargets , options )
306- local count = 0
307- for _ , targetId in ipairs (filteredTargets ) do
310+ local firstTarget = true
311+ local selectedUnitsLen = # selectedUnits
312+ for i , targetId in ipairs (filteredTargets ) do
308313 local cmdOpts = {}
309- if count > 0 or options .shift then
314+ if not firstTarget or options .shift then
310315 tableInsert (cmdOpts , " shift" )
311316 end
312317 if options .meta and not options .shift then
313318 spGiveOrderToUnitArray (selectedUnits , CMD .INSERT , { 0 , cmdId , 0 , targetId }, CMD .OPT_ALT )
314319 else
315320 spGiveOrderToUnitArray (selectedUnits , cmdId , { targetId }, cmdOpts )
316321 end
317- count = count + 1
322+ firstTarget = false
323+ if i * selectedUnitsLen > 1000 then
324+ Spring .Log (widget :GetInfo ().name , LOG .WARNING , " Command count exceeded, target selection may be incomplete" )
325+ return
326+ end
318327 end
319328end
320329
@@ -373,62 +382,64 @@ local function loadUnitsHandler(cmdId, selectedUnits, filteredTargets, options)
373382 end
374383end
375384
376- local FEATURE = " feature"
377- local UNIT = " unit"
378-
379385--- @class CommandConfig
380386--- @field handle function
381387--- @field allowedTargetTypes table
382- --- @field skipAlliedUnits ? boolean
388+ --- @field targetAllegiance number AllUnits = -1, MyUnits = -2, AllyUnits = -3, EnemyUnits = -4
389+
390+ local function commandConfig (targetTypes , targetAllegiance , handler )
391+ local allowedTargetTypes = {}
392+ for _ , targetType in ipairs (targetTypes ) do
393+ allowedTargetTypes [targetType ] = true
394+ end
395+ local config = {} --- @type CommandConfig
396+ config .handle = handler or defaultHandler
397+ config .allowedTargetTypes = allowedTargetTypes
398+ config .targetAllegiance = targetAllegiance
399+ return config
400+ end
383401
384402--- @type table<number , CommandConfig>
385403local allowedCommands = {
386- [CMD .ATTACK ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true }, skipAlliedUnits = true } ,
387- [CMD .GUARD ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true } } ,
388- [CMD . RECLAIM ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true , [ FEATURE ] = true } } ,
389- [CMD . REPAIR ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true } } ,
390- [CMD .CAPTURE ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true } } ,
391- [GameCMD . UNIT_SET_TARGET ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true } } ,
392- [GameCMD . UNIT_SET_TARGET_NO_GROUND ] = { handle = defaultHandler , allowedTargetTypes = { [ UNIT ] = true } } ,
393- [CMD .RESURRECT ] = { handle = defaultHandler , allowedTargetTypes = { [ FEATURE ] = true } } ,
394- [CMD .LOAD_UNITS ] = { handle = loadUnitsHandler , allowedTargetTypes = { [ UNIT ] = true } } ,
404+ [CMD .ATTACK ] = commandConfig ({ UNIT }, ENEMY_UNITS ) ,
405+ [CMD .CAPTURE ] = commandConfig ({ UNIT }, ENEMY_UNITS ) ,
406+ [GameCMD . UNIT_SET_TARGET ] = commandConfig ({ UNIT }, ENEMY_UNITS ) ,
407+ [GameCMD . UNIT_SET_TARGET_NO_GROUND ] = commandConfig ({ UNIT }, ENEMY_UNITS ) ,
408+ [CMD .GUARD ] = commandConfig ({ UNIT }, ALLY_UNITS ) ,
409+ [CMD . REPAIR ] = commandConfig ({ UNIT }, ALLY_UNITS ) ,
410+ [CMD . RECLAIM ] = commandConfig ({ UNIT , FEATURE }, ALL_UNITS ) ,
411+ [CMD .LOAD_UNITS ] = commandConfig ({ UNIT }, ALL_UNITS , loadUnitsHandler ) ,
412+ [CMD .RESURRECT ] = commandConfig ({ FEATURE }) ,
395413}
396414
397- local function filterUnits (targetId , cmdX , cmdZ , radius , options , skipAlliedUnits )
415+ local function filterUnits (targetId , cmdX , cmdZ , radius , options , targetAllegiance )
416+ local alt = options .alt
417+ local ctrl = options .ctrl
398418 local filteredTargets = {}
399419 local unitDefId = spGetUnitDefID (targetId )
400420 if not unitDefId then
401421 return nil
402422 end
403423
404- local isEnemyTarget = (spGetUnitAllyTeam (targetId ) ~= myAllyTeamID )
424+ local isEnemyTarget = spGetUnitAllyTeam (targetId ) ~= myAllyTeamID
425+ if isEnemyTarget and targetAllegiance ~= ALL_UNITS and targetAllegiance ~= ENEMY_UNITS then
426+ -- targeting enemy when only allies are allowed
427+ return nil
428+ end
405429
406- local unitsInArea
407430 if isEnemyTarget then
408- unitsInArea = spGetUnitsInCylinder (cmdX , cmdZ , radius , Spring .ENEMY_UNITS )
409- elseif not skipAlliedUnits then
410- local nearbyUnits = spGetUnitsInCylinder (cmdX , cmdZ , radius )
411- if not nearbyUnits then
412- return nil
413- end
431+ targetAllegiance = ENEMY_UNITS
432+ else
433+ targetAllegiance = spGetUnitTeam (targetId )
434+ end
414435
415- unitsInArea = {}
416- for i = 1 , # nearbyUnits do
417- local unitID = nearbyUnits [i ]
418- if spAreTeamsAllied (spGetUnitTeam (unitID ), myTeamID ) then
419- unitsInArea [# unitsInArea + 1 ] = unitID
420- end
421- end
436+ local unitsInArea = spGetUnitsInCylinder (cmdX , cmdZ , radius , targetAllegiance )
422437
423- if # unitsInArea == 0 then
424- return nil
425- end
426- end
427438 if not unitsInArea then
428439 return nil
429440 end
430441
431- if options . ctrl then
442+ if ctrl then
432443 return unitsInArea
433444 end
434445
@@ -438,14 +449,18 @@ local function filterUnits(targetId, cmdX, cmdZ, radius, options, skipAlliedUnit
438449 tableInsert (filteredTargets , unitID )
439450 end
440451 end
452+
441453 return filteredTargets
442454end
443455
444- local function filterFeatures ( targetId , cmdX , cmdZ , radius , options )
445- if not options . alt then
446- return nil
447- end
456+ local function getTechLevel ( unitDefName )
457+ local unitDef = UnitDefNames [ unitDefName ]
458+ return unitDef and unitDef . customParams . techlevel
459+ end
448460
461+ local function filterFeatures (targetId , cmdX , cmdZ , radius , options , targetUnitDefName )
462+ local alt = options .alt
463+ local ctrl = options .ctrl
449464 local filteredTargets = {}
450465 local featureDefId = spGetFeatureDefID (targetId )
451466 if not featureDefId then
@@ -457,13 +472,31 @@ local function filterFeatures(targetId, cmdX, cmdZ, radius, options)
457472 return nil
458473 end
459474
475+ local targetTechLevel
476+ if ctrl then
477+ targetTechLevel = getTechLevel (targetUnitDefName )
478+ end
479+
460480 for i = 1 , # featuresInArea do
461481 local featureId = featuresInArea [i ]
462- if spGetFeatureDefID (featureId ) == featureDefId then
463- -- featureId is normalised to Game.maxUnits + featureId because of:
464- -- https://springrts.com/wiki/Lua_CMDs#CMDTYPE.ICON_UNIT_FEATURE_OR_AREA
465- -- "expect 1 parameter in return (unitid or Game.maxUnits+featureid)"
466- featureId = Game .maxUnits + featureId
482+ local shouldInsert = false
483+ if alt and spGetFeatureDefID (featureId ) == featureDefId then
484+ shouldInsert = true
485+ elseif ctrl then
486+ local unitDefName = spGetFeatureResurrect (featureId )
487+ local unitTechLevel = getTechLevel (unitDefName )
488+ if unitTechLevel == targetTechLevel then
489+ shouldInsert = true
490+ end
491+ end
492+ if shouldInsert then
493+ if not Engine .FeatureSupport .noOffsetForFeatureID then
494+ -- featureId is normalised to Game.maxUnits + featureId because of:
495+ -- https://springrts.com/wiki/Lua_CMDs#CMDTYPE.ICON_UNIT_FEATURE_OR_AREA
496+ -- "expect 1 parameter in return (unitd or Game.maxUnits+featureid)"
497+ -- offset due to be removed in future engine version
498+ featureId = featureId + Game .maxUnits
499+ end
467500 tableInsert (filteredTargets , featureId )
468501 end
469502 end
@@ -500,14 +533,14 @@ function widget:CommandNotify(cmdId, params, options)
500533 local filteredTargets
501534
502535 if targetType == UNIT then
503- filteredTargets = filterUnits (targetId , cmdX , cmdZ , radius , options , currentCommand .skipAlliedUnits )
536+ filteredTargets = filterUnits (targetId , cmdX , cmdZ , radius , options , currentCommand .targetAllegiance )
504537 elseif targetType == FEATURE then
505- local featureDefName = spGetFeatureResurrect (targetId )
538+ local unitDefName = spGetFeatureResurrect (targetId )
506539 -- filter only wrecks which can be resurrected
507- if featureDefName == nil or featureDefName == " " then
540+ if unitDefName == nil or unitDefName == " " then
508541 return false
509542 end
510- filteredTargets = filterFeatures (targetId , cmdX , cmdZ , radius , options )
543+ filteredTargets = filterFeatures (targetId , cmdX , cmdZ , radius , options , unitDefName )
511544 end
512545
513546 if not filteredTargets or # filteredTargets == 0 then
@@ -522,7 +555,6 @@ local function initialize()
522555 if spGetSpectatingState () then
523556 widgetHandler :RemoveWidget ()
524557 end
525- myTeamID = spGetMyTeamID ()
526558 myAllyTeamID = spGetMyAllyTeamID ()
527559end
528560
0 commit comments