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.
Replacing getrel(getnatp(US), THEM) by relations_with(US, THEM) makes
a difference only when US equals THEM. Replace in places where it's
obvious that they're not equal.
Note: getsect() sets player->owner to "player is god or owns this
sector". Thus, after getsect(..., §), sect.sct_own ==
player->cnum implies player->owner. Conversely, !player->owner
implies sect.sct_own != player->cnum. Similarly for getship(),
getplane() and nxtitem().
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)...".
Much code assumes that only the plane's owner can fly it.
pln_airbase_ok() oopses since commit 446f1991. Before, flying planes
from carriers failed with a bogus "not valid for" message, and flying
from sectors had output misdirected to the plane's owner.
It would be nice to let deities fly foreign planes, but the assumption
is not trivial to remove, so just satisfy it for now.
Historical note: it looks like deities used to be able to fly foreign
planes just fine until Chainsaw 3 added missions. The launch command
has always rejected foreign planes, even for deities.
Don't use multiple calls of mpr() to print a single line, because that
creates a separate bulletin for each part. The read command normally
merges the bulletins, but if the bulletins are more than five seconds
apart (clock jumped somehow), we get a bulletin header in the middle
of a line.
While there, wrap long "blam" lines. Can only happen for bomb loads
above 16. Stock game needs a tech 406 jhb for that.
The mpr() misuse was introduced in Empire 2.
When reporting sweeps, it reported the location in the plane owner's
coordinate system instead of the player's. Fortunately, they're the
same in normal usage. They can differ only when a deity flies foreign
planes.
These ships could only use their x-light slots for x-light planes, not
their plane slots. For instance, agc (30 x-light slots, 32 plane
slots) could load only 30 sams, and mb (0 x-light slots, 10 plane
slots) could not load any sams.
Culprit is could_be_on_ship(). Broken in commit 3e370da5, v4.3.17.
When d of n cargo items are discarded for want of space, pln_dropoff()
reported -d items discarded and -d items unloaded. Already broken in
BSD Empire 1.1.
Commit a269cdd7 (v4.3.23) removed the nuclear damage. But it left the
nuke on the missile, which made pln_damage() oops and return zero
damage.
Fix by destroying the nuke separately.
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.
msl_equip() used mission_pln_equip(), which is for automatic defenses,
and therefore silent. Its users launch_as(), launch_missile() and
launch_sat() printed failure diagnostics based on assumptions on what
could go wrong.
Switch to the appropriate function for commands, pln_equip(), and
remove the guesswork. Implement mission 'i' there. When launching
from a foreign base, its owner now gets informed. Unimportant at this
time, as players can't easily deploy missiles in foreign bases.
This change also switches off automatic supply for launch. Consistent
with bomb.
There are three ways to fly cargo: transport (fly command with a
commodity argument), cargo drops (drop command that isn't a sea mine
drop), and paradrops.
A cargo flight can be either an airlift or an airdrop. Airlifts carry
more cargo than airdrops. A cargo drop or paradrop with a non-VTOL
plane is an airdrop. Anything else is an airlift.
Before, paradrop always behaved like an airdrop, regardless of VTOL,
and drop always like an airlift. This made little sense.
Effect of the change on the stock game: paradrop with tc carries twice
the punch, and np/tr/jt can drop less than they can fly. In
particular, tr can't drop guns anymore, and jt can drop only one
instead of three.
Before, bomb selected any plane, but planes with zero load could not
be equipped. Cargo planes could be equipped fine, and they flew bombs
to the target, where they silently vanished.
Closes#1388263.
Initially, paradrop capability was implied both by capability cargo
and by capability VTOL. Chainsaw changed para() to require cargo, and
added compile-time option PARAFLAG to additionally require new
capability para. The optional PARAFLAG rule became mandatory in
Empire 2.
Chainsaw left the old tests for "cargo or VTOL" in place. Because
para() checked "cargo and para" first, the old tests for "cargo or
VTOL" always passed, so they had no effect.
Clean them up anyway.
These missions imply the cargo type, just like bombing missions. Use
the implied type instead of cargo type parameter ip there. Parameter
ip is now optional except for missions 't' (transport) and 'd' (drop).
Simplify para() not to pass the optional cargo type. Leave drop()
alone, because always passing the type is simpler there.
pln_arm(), pln_equip(), mission_pln_arm() mission_pln_equip() took a
mission parameter encoding the kind of sortie (strategic bomb,
pinpoint bomb, transport, ...), a flag parameter to further specify
the plane's role, and a parameter ip to specify the load.
The flags argument was always either P_F (intercept), P_F | P_ESC
(escort), or zero (any other role).
With non-zero flags, mission and ip argument were not used in any way.
Use mission 'e' and null load for escorts, and remove flags.
Intercept can still be identified by mission zero.
Also change pln_mobcost() to take a mission parameter instead of
flags, so that pln_arm() and mission_pln_arm() can simply pass on
their mission.
Check the true load instead of the ip parameter. Makes a difference
only when callers pass a bogus ip that isn't actually used. Happens
for escorts, but then the call can only be reached for deities,
because for mortals the transports fail before the escorts.
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.
Since the previous two commits, ac_encounter() checks its
mission_flags argument only for proper mission flags PM_R and PM_S,
not for plane flags P_A, P_S, P_I.
This makes the code to put plane flags into mission flags useless.
Remove it from bomb(), drop(), fly(), para(), reco(),
perform_mission(), mission_pln_arm(), air_defense(), pln_arm().
Much of that code was useless even before: P_X and P_H since Chainsaw
3 option STEALTHV became mandatory in Empire 2, and P_MINE since
commit cc0c3e4f (v4.3.0) cleaned up mine drops.
A reconnaissance patrol (recon and sweep) uses sonar when ASW planes
participate. ac_encounter() enabled sonar when P_A was in
mission_flags. These get computed by pln_arm() and callers. However,
they set P_A only when *all* planes were capable, including escorts.
Fix by checking actual plane capabilities instead. Closes#1389451.
Until commit 3e370da5, dead planes had to be explicitely taken off
their carrier to update load counters. This is no longer necessary;
simplify pln_put1() and scut(). scut() got it wrong, by the way: it
failed to take planes off land units.
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.
pln_nuktype is redundant; it can be computed from the nuke's
nuk_plane.
Make plane selector nuketype virtual and NSC_EXTRA. It should have
been NSC_EXTRA all along. This changes xdump plane.
Don't set it in arm(), disarm(), build_plane(), pln_damage() and
nuk_fixup(). The latter no longer does anything, remove it.
Deprecate edit key 'n' in doplane(), and don't show it in pr_plane().
The key never made much sense.
eff_bomb(), comm_bomb(), ship_bomb(), plane_bomb(), land_bomb(),
strat_bomb(), mission_pln_equip(), air_damage(), msl_hit(),
pln_equip() tested pln_nuketype to check whether a plane carries a
nuke. Test nuk_on_plane() instead.
pdump(), plan(), trade_desc() print whether and what kind of nuke a
plane carries. Adapt that to use nuk_on_plane().
The old code used getstarg() to get an argument with a different
prompt than snxtitem() uses, then passed the value to snxtitem()
unchecked. If the player aborts, getstarg() returns a null pointer,
and snxtitem() prompts again. Affected:
* load/lload plane/land third argument; load_plane_ship(),
load_land_ship(), load_plane_land(), load_land_land()
* bomb, drop, fly, paradrop, recon and sweep second argument;
get_planes()
* tend and ltend second and fourth argument; ltend(), tend(),
tend_land()
* mission second argument; mission()
Fix by making snxtitem() taking a prompt argument, null pointer
requests the old prompt.
Use that to simplify multifire() and torp(). Change the other callers
to pass NULL.
This affects commands bomb, drop, fly, paradrop, recon and sweep.
The failure to abort was harmless, because all callers get additional
arguments, and abort then.
The old code didn't honor command abortion at the following prompts:
* arm third argument
* deliver fourth argument (also simplify)
* fire third argument
* fly and recon prompt for carrier to land on: pln_onewaymission()
treated abort like empty input, which made planes attempt landing in
the sector.
* lmine second argument
* order d fourth argument
* power c nat(s) argument
* range second argument
* sail second argument
* shutdown both arguments (first one was broken in commit 84cfd670,
v4.3.10, second one never worked).
* tend third argument
Commit 7ca4f412 (v4.3.12) marked planes flying a sortie with
PLN_LAUNCHED, and made pln_arm() reject planes with that flag set.
This was designed to reject escorts that were already flying as
bombers. It didn't work, because the test for PLN_LAUNCHED used a
stale copy of the plane created by pln_sel(). Fix by getting a fresh
copy.
The bug always existed, but the botched fix in commit 7ca4f412 made it
worse. Before, ac_encounter() dropped escorts that were also bombers,
so the bug merely wasted plane fuel. After, such planes were
effectively duplicated, and damage to one of them, usually the bomber,
was wiped out. Abusable.
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.
Commit 7ca4f412 fixed tracking of planes flying a sortie by marking
them with flag PLN_LAUNCHED. It failed to write SAMs and planes
flying missions back to the plane file, in sam_intercept() and
mission_pln_arm(). The only known problem with that is fairly
harmless: when the mission damages planes on the ground, the planes
flying it get damaged as if they were still sitting in their bases,
but the damage gets wiped out when they land.
The same issue applies to missiles. So they need to be tracked as
well. Do that in msl_hit().
While there, remove a few redundant PLN_LAUNCHED sanity checks.
Planes normally sit in their base (sector or carrier), where they can
be spied, damaged, captured, loaded, unloaded, upgraded and so forth.
All this must not be possible while they fly. There are two kinds of
flying planes: satellites in orbit, and planes flying a sortie.
Satellites in orbit have always been marked with flag PLN_LAUNCHED.
Works. What didn't work was tracking planes flying a sortie.
If you look at one sortie in isolation, up to three groups of planes
can be flying at any point of time: the primary group, which carries
out the sortie's mission (bomb, transport, ...), their escorts, and a
group of hostile planes flying interception or air defense.
The old code attempted to track these planes by passing those groups
to the places that need to know whether a plane is flying. This was
complex and incomplete, and broke down completely for the pin-bombing
command.
It was complex, because the plane code needs to keep track of all the
call chains that can lead to a place that needs to know whether a
plane flies, and pass the groups down the call chains. This leads to
a rather ugly passing of plane groups all over the place.
It was incomplete, because it generally failed to pass the escorts.
And the whole scheme broke down for the pin-bombing command. That's
because pin-bombing asks the player for targets while his planes are
loitering above the target sector. This yields the processor and lets
other code run. Which does not get the flying planes passed.
The new code marks planes and SAMs (but not other missiles) flying a
sortie with flag PLN_LAUNCHED (the previous commit laid the groundwork
for that), and does away with passing around groups of flying planes.
This fixes the following bugs:
* Many commands could interact with foreign planes flying for a
pin-bombing command as if they were sitting in their base. This
includes spying, damaging, capturing, loading, or upgrading them,
and even getting intercepted by them. Any changes to those planes
were wiped out when they landed. Abusable.
* The bomb command could bomb its own escorts, directly (pin-bomb
planes) or through collateral damage, strategic sector damage,
collapsing bridges or nuke damage. The damage to the escorts was
wiped out when they landed.
* If you asked for a plane to fly both in the primary group and the
escort group, you got charged fuel for two sorties instead of one.
* pln_put1() and pln_put() now recognize planes that didn't take off,
and refrain from making them land. Intercept (since commit
c64e2149) and air defense can do that. Making them land had no
ill-effects, but it was still wrong.
There's one new problem: if PLN_LAUNCHED doesn't get reset properly,
due to game crash during flight or some other bug, the plane gets
stuck in the air. Catch and fix that on game start in ef_verify().