Writing extensions for the game Balanced Annihilation based on the Spring Engine
The article is intended for all lovers of the good old Total Annihilation and its open implementation in the form of SpringRTS + Balanced Annihilation .
Despite the fact that the Air Screen Keeper widget turned out to be, by and large, a useless undertaking, with its example, due to its small size, the main ideas of building extensions to games based on the Spring engine can be reflected.
So, the essence of the widget (ie, extensions) is to inform the player in one form or another about the fact that the so-called. an air screen consisting of a plurality of airplanes performing the “patrol” command was attacked by the enemy from the ground. Usually such attacks at the height of the battle (8 by 8 players) are not very noticeable and can be easily swatted, as the enemy thus destroys up to 70% of the planes, if you take a break from something momentary. ')
Therefore, as a noticeable message, we will use a marker on the map, visible only to the player, and we will put it in the place where our plane was attacked. In addition, in order not to overrun everything in the world with markers, we will exhibit them with a delay of several seconds.
Unfortunately, there is no video where it could be viewed live. Instead, I will give an example of a more complex widget that manages designers for the automatic development of metal mining mines within the perimeter of the player’s base. By the way, the latter is calculated automatically due to the algorithm described in the wonderful article on the construction of minimal convex hulls .
To begin, initialize the widget and sort the available aircraft using the arsenal of functions provided by the engine:
localfunctiondispatchUnit(unitID, unitDefID)local udef = UnitDefs[unitDefID] -- id -- , if udef.isAirUnit then units[unitID] = true end end function widget:Initialize() local allunits = spGetTeamUnits(spGetMyTeamID()) for _, uid in ipairs(allunits) do dispatchUnit(uid, spGetUnitDefID(uid)) end end
In the table units we now have all the planes available to the player at the time of initialization of the widget. This, however, is not enough, because we need exactly the air screen, i.e. aircraft performing command patrol. Some may be chilling on the ground, such as bombers, or transport planes, but we just need patrols at the moment.
Let's go over the available planes and see their team queues for the presence of a “patrol”, then put the found planes into the airscreen table (there is no doubt that there is a more efficient way to implement this procedure, but at the time of creating the widget I didn’t think of anything better The 128th frame of the game does such sorting. Fortunately, this happens quickly enough so that the player does not notice anything).
localfunctionUnitHasPatrolOrder(unitID)local queue=spGetCommandQueue(unitID,2) for i,cmd inipairs(queue) doif cmd.id==CMD.PATROL thenreturntrueendendreturnfalseendfunctionupdateAirScreenUnits()for id, v inpairs(units) doif UnitHasPatrolOrder(id) then airscreen[id] = trueendendendfunctionwidget:GameFrame(frameNum)if (frameNum % 128 ) == 0then updateAirScreenUnits() endend
Perhaps a more efficient implementation would be to handle the UnitCommand event, a command that arrives for a unit. If this is a patrol and there are no other teams, then you can put the plane in the airscreen table. Anyway.
You also need to keep in mind that the planes are built, destroyed, in addition, someone from the team can give us a couple, or we ourselves can give them to someone, we have to follow this with the help of the relevant UnitFinished and UnitDestroyed events:
functionwidget:UnitFinished(unitID, unitDefID, unitTeam)if (unitTeam ~= spGetMyTeamID()) thenreturnend dispatchUnit(unitID, unitDefID) -- end function widget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam) if airscreen[unitID] then if attackerID then notify(attackerID) -- , , id else notify(unitID) -- , id end airscreen[unitID] = nil -- end units[unitID] = nil -- end
When receiving / transmitting units between players, from the point of view of the widget, approximately the same thing happens when creating / destroying, so just refer to the existing methods:
The notify function puts a marker on the map with a delay and is quite simple:
functionwidget:Update(dt) lastMarkTime = lastMarkTime - dt endfunctionnotify(unitID)if (lastMarkTime < 0) then lastMarkTime = MARK_DELAY local msg = "AA"local x, y, z = Spring.GetUnitPosition(unitID) spMarkerAddPoint(x, y, z, msg, true) endend
The full code of the widget can be found here: github.com/spike-spb/air-screen-keeper/blob/master/air-screen-keeper.lua To install this widget, you need to copy it to <PathOn-installationSpring> / LuaUI / Widgets. Unfortunately, many still face the problem of not installing widgets ... many simply cannot start the game itself, so a video guide on this, in general, simple process was compiled: springrts.ru/howto
In addition, there is a more detailed video guide on creating widgets, more than an hour long, created by Alexander Lipatov, which once helped me to quickly navigate the process of developing extensions for Spring, so I'll also bring it here: youtu.be/ eMEEa9imx3g
In general, it must be said that using such extensions you can create wonderful opportunities, sometimes significantly affecting the strategy and tactics of the game, so that old-school players with programming skills have a wonderful opportunity not only to play the good old game, but also to create it yourself.