March code reads the land units into a land unit list, and writes them
back when it changes them, e.g. when a land unit stops. If a land
unit changes in the land unit file while it is in such a land unit
list, the copy in the land unit list becomes stale, and must not be
used.
To that end, do_unit_move() calls lnd_mar() after prompting for path
or destination. lnd_mar() re-reads all the land units.
Unfortunately, it still writes back stale copies in certain
circumstances. Known ways to trigger such writes:
* Deity loads land unit onto a ship or land unit
* Land unit's crew killed just right, e.g. by collateral damage from
interdiction, followed by additional updates, such as shell fire
damage
* Sector no longer owned or allied, e.g. allied sector captured by an
enemy (own sector would kill or retreat the land unit)
Writing a stale copy wipes out the updates that made the copy stale,
and triggers a seqno mismatch oops. For instance, damage that follows
killing of all crew by collateral damage from interdiction is wiped
out. If no damage follows, we still get a generation oops.
lnd_take_casualty() uses uninitialized rsect to compute the mobility
cost of retreating a defending land unit. This can charge incorrect
mobility, prevent retreat, or, if the stars align just right, crash
the server when sector_mcost() subscripts dchr[] with it.
Broken in commit 4e7c993a, v4.3.6. Reported by Scott C. Zielinski.
POGO can navigate dead ships, and march dead land units. The ghosts
even get sighted and interdicted, and can hit mines (landmines only
until commit fe372539, v4.3.27). Noted for ships in commit 9100af0b.
Has always been broken. Fix by making shp_sel() and lnd_sel()
explicitly reject ghosts.
Same code pattern also exists in pln_sel, but dead plains fail the
efficiency test, so it's harmless there. Apply the same fix anyway.
Why upgrade? I'm not a lawyer, but here's my take on the differences
to version 2:
* Software patents: better protection against abuse of patents to
prevent users from exercising the rights under the GPL. I doubt
we'll get hit with a patent suit, but it's a good move just on
general principles.
* License compatibility: compatible with more free licenses, i.e. can
"steal" more free software for use in Empire. I don't expect to steal
much, but it's nice to have the option.
* Definition of "source code": modernization of some details for today's
networked world, to make it easier to distribute the software. Not
really relevant to us now, as we normally distribute full source code.
* Tivoization: this is about putting GPL-licensed software in hardware,
then make the hardware refuse to run modified software. "Neat" trick
to effectively deny its users their rights under the GPL. Abuse was
"pioneered" by TiVo (popular digital video recorders). GPLv3 forbids
it. Unlikely to become a problem for us.
* Internationalization: more careful wording, to harden the license
outside the US. The lawyers tell us it better be done that way.
* License violations: friendlier way to deal with license violations.
This has come out of past experience enforcing the GPL.
* Additional permissions: Probably not relevant to us.
Also include myself in the list of principal authors.
No functional change, because the value of rel only matters when
sect.sct_own != actor, and then it's the same as before.
The new value of rel permits simplifying sect.sct_own != actor && rel
!= ALLIED to just rel != ALLIED.
Replacing getrel(getnatp(US), THEM) by relations_with(US, THEM) makes
a difference only when US equals THEM.
Replace patterns like "us == them || getrel(getnatp(us), them)..." by
"relations_with(us, them)...".
feels_like_helping() case cn == foe is missing in the code it
replaces. No difference in behavior, because:
* cn == foe && cn == friend can't happen. Because you can't get into
ground combat against yourself (assault, attack and paradrop don't
let you), friend != foe for support.
* cn == foe && cn != friend behaves the same: no support.
feels_like_helping() returns 0 because of the explicit case. The
replaced code doesn't support because cn can't be at war with
itself.
lnd_mar() and lnd_mar_one_sector() take an actor argument.
Nevertheless, they sometimes used player->cnum. Fortunately, they are
the same: all callers pass current player for actor. Normalize to
actor for consistency.
SLOW_WAR has issues:
* The check whether the attacker old-owns the attacked sector is
broken, because att_abort() uses sect.sct_oldown uninitialized.
Spotted by the Clang Static Analyzer.
* Its implementation in setrel() is somewhat scary. It's actually
okay, because that part of setrel() only runs within decl(). Other
callers don't reach it: update_main() because player->god != 0
there, and the rest because they never pass a rel < HOSTILE.
* Documentation is a bit vague.
SLOW_WAR hasn't been used in a public game in years. Fixing it is not
worth it, so remove it instead.
lnd_mar(), lnd_sweep() and lnd_mar_one_sector() printed to the current
player, their actor argument, and to land unit owner.
lnd_mar_one_sector()'s use of xyas() looked particularly suspicious:
it passed actor, then printed the result to the current player or land
unit owner. Fortunately, all three are the same: all callers pass
current player for actor, and land unit owner is the same, since even
deities can't march foreign land units. Normalize to actor for
consistency.
While there, rename lnd_mess() to lnd_stays().
take_def() and ask_move_in() printed both to the current player and to
land unit owner. Their use of prcom() and xyas() looked particularly
suspicious: they used the current player, then printed the result to
the land unit owner. Fortunately, current player and land unit owner
are the same, since even even deities can't attack with foreign land
units. Normalize to current player for consistency.
Switch get_ototal(), get_oland(), kill_land() and move_in_land() to
current player as well.
Much of the code assumes that only the land unit's owner can march it.
The assumption is correct, because lnd_mar() leaves foreign land units
behind with a bogus "was disbanded at" message (suppressed for country
It would be nice to let deities march foreign land units, but the
assumption is not trivial to remove. For now, just avoid the bogus
message.
Historical note: it looks like deities used to be able to march
foreign land units just fine until Empire 2 factored common code out
of navigate, sail and autonav, and updated march to match navigate.
Likewise, it looks like they could board with foreign land units until
Empire 2 factored out common ground combat code. Commands attack and
assault have always rejected foreign land units, even for deities.
When refusing to march foreign land units, it reported the land unit's
location in the land unit's coordinate system instead of the player's.
Fortunately, they're the same, since even deities can't march foreign
land unit.
Movement stops when shp_interdict() or lnd_interdict() report
interdiction. However, they reported it only when there was
interdiction damage.
Zero interdiction damage commonly happens when interdicting missiles
miss, or all bombers abort. Stopping regardless of damage makes more
sense there.
Moreover, not stopping is buggy: do_unit_move() needs to take care not
to wipe out updates made by interdiction to the moving ships or land
units. It does so only when it stops. Updates made by interdiction
without interdiction damage could get wiped out, triggering a seqno
mismatch oops.
Known ways moving ships and land units can get updated by interdiction
despite there is no interdiction damage:
* Interdicting bombers get intercepted by planes based on a navigating
carrier, carrier gets charged petrol. The bug wipes out the petrol
use.
* Marching land units get interdicted by planes, but all planes miss.
Sufficiently large collateral damage to the sector can still damage
the land units. The bug wipes out the damage to land units.
To make shp_interdict() and lnd_interdict() report interdiction
regardless of damage, change lnd_missile_interdiction(),
lnd_fort_interdiction(), lnd_mission_interdiction(),
shp_missile_interdiction(), shp_fort_interdiction(),
shp_mission_interdiction() to return whether there was interdiction.
Before, they returned whether there was damage.
Change unit_interdict(), perform_mission(), perform_mission_land(),
perform_mission_ship(), perform_mission_msl(), and
perform_mission_bomb() to return -1 for no interdiction, so that
callers can distinguish no interdiction from interdiction with no
damage.
Collateral damage was disabled, because after msl_hit() reported a
miss, the missile may or may not have reached the target.
Fix by splitting msl_launch() off msl_hit().
Drop the disabled collateral damage code for sector targets, because
sectors can't be missed. Enable it for ships and land units.
Since msl_launch() returns whether the missile is sub-launched, drop
launch_missile() parameter sublaunch, and simplify its caller.
Before Empire 2, nukes could be delivered only with bomb (special
mission 'n', airburst only) and launch (targeting sectors or
satellites only).
Empire 2 made nukes available for any kind of bombing, and for any
missile strike on sectors or ships. This included interdiction and
support missions. Nuclear-tipped anti-sats and bomb mission n were
removed.
Unfortunately, this was done in a messy way, which led to
inconsistencies and bugs. The problem is that ordinary damage affects
just the target, while nuke damage affects an area. Code dealing with
plane damage was designed for the former. Instead of rewriting it to
cope with area damage cleanly, nuke damage got shoehorned into
pln_damage(), the function to compute conventional plane damage, as a
side effect: computing damage blasted sectors in the area.
If the plane carried a nuke, pln_damage() returned zero (conventional)
damage. Without further logic, this simply bypassed the code to apply
damage to the target. This worked out okay when the target already
got damaged correctly by the side effect.
However, some targets are immune to the side effect: when interdicting
a move or explore command, the commodities being moved are not in any
sector.
For other targets, damage has effects other than damaging the target:
offensive and defensive support don't apply the (conventional) damage
to the target sector. Instead, they turn it into a combat bonus.
Without further logic, nuclear damage doesn't contribute to that.
To make all that work, pln_damage() returned the nuclear damage for
ground zero as well. Because a plane does either conventional or
nuclear damage, one of them is always zero.
Most callers simply ignored the nuclear damage, and applied only the
conventional damage.
Bug: land units and ships failed to retreat when pin-bombed or
missiled with a nuke. That's because they received zero conventional
damage.
The mission code flies planes and missiles and tallies their damage.
This mission damage included nuclear damage at ground zero (except for
missiles sometimes, see below), to make support and commodity
interdiction work. Unfortunately, this broke other things.
Bug: when bombers interdicted ships or land units, nukes first damaged
the ships or land units by the side effect, then again through mission
damage. Interdicting missiles had a special case to avoid this.
Bug: when interdicting move, explore or transport, nukes first damaged
the sector by the side effect, then again through mission damage's
collateral damage.
There may well be more bugs hiding in this mess.
The mess is not worth fixing. While the idea of interdicting and
supporting with nukes sounds kind of cool, I believe it's pretty
irrelevant in actual play.
Instead, go back to a variation of the original rules: nukes can be
delivered only through bomb mission 's' and launch at sectors.
Make arm reject marine missiles in addition to satellites, ABMs and
SAMs, and clear the mission. Make mission reject planes armed with
nukes. Oops when they show up in mission_pln_equip() anyway.
Make pln_equip() allow planes with nukes only for missions 's' and
't'.
Clean up pln_damage() to just compute damage, without side effect.
Change strat_bomb() and launch_missile() to detonate nukes. Simplify
the other callers. Parameter mission of msl_launch_mindam() is now
unused, remove it.
Missiles exploding on launch no longer set off their nukes. That was
pretty ridiculous anyway.
Seamines and landmines share storage. Sea and bridge span sectors can
hold only sea mines, other sector types only landmines. Sector type
checks were missing or incorrect in several places:
* Seamines under bridge spans were mistaken for landmines in several
places:
- ground combat mine defense bonus, in get_mine_dsupport() and
stre(),
- land units retreating from bombs, in retreat_land1(),
- non-land unit ground movement (commands explore, move, transport,
and INTERDICT_ATT of military), in check_lmines(),
Fix them to check the sector type with new SCT_MINES_ARE_SEAMINES(),
SCT_LANDMINES().
* plane_sweep() mistook landmines for seamines in harbors. Bug could
not bite, because it's only called for sea sectors. Drop the bogus
check for harbor.
* Collapsing a bridge tower magically converted landmines into
seamines. Make knockdown() clear landmines.
Also use SCT_MINES_ARE_SEAMINES() and SCT_LANDMINES() in mine(),
landmine(), lnd_sweep() and lnd_check_mines(). No functional change
there.
Keep checking only for sea in pln_mine(), plane_sweep(),
retreat_ship1(), shp_sweep() and shp_check_one_mines(). This means
seamines continue not to work under bridges. Making them work there
is tempting, but as long as finding seamines clobbers the sector
designation in the bmap, it's better to have them in sea sectors only.
Historical notes:
Mines started out simple enough: you could mine sea and bridge spans,
and ships hit and swept mines in foreign sectors.
Chainsaw 2 introduced aerial mining and sweeping. Unlike ships,
planes could not mine bridge spans. plane_sweep() could sweep
harbors, which was wrong, but it was never called there, so the bug
could not bite.
Chainsaw 3 introduced landmines. The idea was to permit only seamines
in some sector types, and only landmines in the others, so they can
share storage. To figure out whether a sector has a particular kind
of mines, you need to check the sector type. Such checks already
existed in mine, drop and sweep, and they were kept unchanged. The
new lmine command also got the check. Everything else did not.
Ground movement and combat could hit and sweep seamines in bridge
spans. Ships could hit and sweep landmines in harbors.
Empire 2 fixed land unit movement (march, INTERDICT_ATT) not to
mistake seamines for landmines on bridge spans. It fixed ships not to
mistake landmines for seamines. The fix also neutered seamines under
bridge spans: ships could neither hit nor sweep them anymore. Both
fixes missed retreat.
Commit 5663713b (v4.3.1) made ship retreat consistent with other ship
movement.
Land unit reactions are overly complex because we have two different
concepts controlling them: reaction radius (set with lrange) and
reserve mission (set with mission). You need to deal with both to set
up or query reactions.
Commit 8d0e1af5 "fixed" this by making reserve missions meaningless.
The previous commit made reserve missions meaningful again: they
support an op-area now. This brought back the problem of having to
deal with two separate commands to accomplish one thing.
Fix this for good by removing non-mission land unit reaction
alltogether. The only feature we lose by that is the ability to order
land units to react until the order is explicitely cancelled. That's
because missions are implicitely cleared by many commands and events,
while non-mission reaction wasn't. Closes#858121 and #858122.
Remove the non-mission reaction case from att_reacting_units().
Don't limit reserve missions to the land unit's reaction radius: make
lnd_reaction_range() return the type's maximum radius instead of
lnd_rad_max.
The reaction radius is now useless. Remove the lrange command, and
struct lndstr member lnd_rad_max along with its selector react.
Remove land command's column rd. Make ldump show column react as
zero. Deprecate edit key 'P' in dounit(), and don't show it in
pr_land().
Before, they always reacted to their maximum range, and the op-area
was unused. Change mission() to define the op-area for reserve
missions as well. Remove the special-case for showing reserve
missions from mission() and show_mission(). New lnd_reaction_range()
factored out of att_reacting_units(). Use it in oprange() to cover
reserve missions. Pass the mission as separate parameter to oprange()
for now, because miss() doesn't set it in the object until later.
Land units on reserve missions used to pay only half the usual
mobility for combat. This bonus was commented out in the code in
4.0.0, but not in info. Remove it from both.
With RAILWAYS, highway-like sectors double as rail. They need to be
at least 5% efficient to be operational, and then they additionally
extend rail into adjacent sectors that are at least 60% efficient.
New opt_RAILWAYS, SCT_HAS_RAIL(), sct_rail_track(). Update
sector_mcost(), bp_neighbors(), lnd_mar_one_sector() for RAILWAYS
mobility rules. Update sinfra(), spyline(), satdisp_sect() to show
rail track instead of rail infrastructure for RAILWAYS.
New virtual sector selector track, implemented by nsc_sct_track().
Load counters are redundant; they can be computed from the carrier
uids. Keeping them up-to-date as the carriers change is a pain, and
we never got that quite complete.
Computing load counters straight from the carrier uids every time we
need them would be rather inefficient, but computing them from cargo
lists is not. So do that.
Remove the load counters: struct shpstr members shp_nplane,
shp_nchoppers, shp_nxlight, shp_nland, and struct lndstr members
lnd_nxlight and lnd_nland.
Don't compute/update load counters in build_ship(), build_land(),
land(), ldump(), load_plane_ship(), load_land_ship(),
load_plane_land(), load_land_land(), lstat(), sdump(), shi(), sstat(),
tend_land(), check_trade(), put_combat(), pln_oneway_to_carrier_ok),
pln_newlanding(), fit_plane_on_ship(), fit_plane_on_land(),
unit_list().
Nothing left in fit_plane_off_ship(), fit_plane_off_land(), so remove
them.
load_land_ship(), load_land_land(), check_trade(), pln_newlanding(),
put_plane_on_ship(), take_plane_off_ship(), put_plane_on_land(),
take_plane_off_land() no longer change the carrier, so don't put it.
Remove functions to recompute the load counters from carrier uids:
count_units(), lnd_count_units(), count_planes(), count_land_planes(),
pln_fixup() and lnd_fixup(), along with the latter two's private
copies of fit_plane_on_ship() and fit_plane_on_land().
New cargo list functions to compute load counts: unit_cargo_count()
and unit_nplane(), with convenience wrappers shp_nplane(),
shp_nland(), lnd_nxlight(), lnd_nland().
Use them to make ship selectors nplane, nchoppers, nxlight, nland
virtual. They now reflect what is loaded, not how the load uses the
available slots. This makes a difference when x-light planes or
choppers use plane slots.
Use them to make land unit selectors nxlight and nland virtual.
Use them to get load counts in land(), ldump(), load_plane_ship(),
load_land_ship(), load_plane_land(), load_land_land(), sdump(), shi(),
tend_land(), fit_plane_on_land(), trade_desc(), unit_list().
Rewrite fit_plane_on_ship() and could_be_on_ship() to use
shp_nplane(). could_be_on_ship() now takes load count arguments, as
computed by shp_nplane(), so it can be used for checking against an
existing load as well.
The values in these columns were computed by count_sect_units() and
count_sect_planes(), which included land units and planes in the count
that aren't shown by prunits() and prplanes(), namely own and embarked
units. Confusing. Moreover, count_sect_planes() and prunits() rolled
dice separately for spy units. This could leak the presence of spies
even when prunits() didn't show them.
All fixable, but not worth the trouble; just remove the counts.