Several headers define macros that use ef_ptr() without including
"file.h". Fix that. Drop redundant inclusions elsewhere.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
Use a single array member instead of multiple scalar members. Only
the array elements that replace scalar members are can be non-zero for
now.
This is a first step to permitting more build materials.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
The work required for build and repairs is traditionally a function of
build materials: 20 + lcm + 2*hcm for ships, planes and land units,
and (lcm + 2*hcm + oil + rad)/5 for nukes. Make it independently
configurable instead, via new ship-chr, plane-chr, land-chr, nuke-chr
selector bwork, backed by new struct mchrstr member m_bwork, struct
plchrstr member pl_bwork, struct lchrstr member l_bwork, struct
nchrstr member n_bwork. Keep the required work exactly the same for
now.
Clients that compute work from materials need to be updated. Easy,
since build work is now exposed in xdump.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
Every piece is formatted either with pr(), or with sprintf() for later
sending with wu(). The output is actually identical. Format with
sprintf() always, and then either pr() or wu() the results.
While there, change the first parameter's type from int to natid.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
Much of the retreat code duplicates navigate and march code. Worse,
retreat's version is full of bugs:
* Land units can sometimes retreat when they couldn't march: while on
the trading block (forbidden with march since 4.0.9), crewless
(likewise since 4.0.0), kidnapped in a foreign sector (inconsistent
since land units were added in Chainsaw 3), loaded on a ship
(likewise) or a land unit (inconsistent since trains were added in
4.0.0).
* Ships can retreat while on the trading block (forbidden with
navigate since 4.0.9)
* Land units can't retreat into foreign sectors even though they could
march there, namely when sector is allied or the land unit is a spy.
They can march there since 4.0.0.
* Land units keep their fortification on retreat. Has been that way
since retreat was added in Chainsaw.
Then there's group retreat. It's basically crazy:
* It triggers retreat for everyone in the same fleet or army, one
after the other, regardless of retreat path, conditions (including
group retreat), or even location. The latter is quite abusable
since retreats aren't interdicted. Has been that way since retreat
was added in Chainsaw.
* Group retreat fails to trigger when the originally retreating ship
or land unit has no retreat path left when it's done. Broken in
commit b860123.
Finally, the reporting to the owner is sub-par:
* When a retreat is cut short by insufficient mobility or
obstructions, its end sector isn't reported, leaving the player
guessing.
* Non-retreats can be confusingly reported as retreat to the same
sector. Can happen when the retreat path starts with 'h' (obscure
feature to suppress a single retreat), or when a group retreat
includes a ship or land unit without retreat orders.
* Interaction with mines during retreat is reported before the retreat
itself, which can be quite confusing.
* Sweeping landmines isn't reported at all.
* Much code and much bulletin text is dedicated to reporting what
caused the retreat, even though it should be perfectly obvious.
Rewrite this on top of common navigate and march code. Reuse of
common code fixes the "can retreat when it couldn't navigate/march"
and the "can't retreat into sectors it could navigate or march into"
bugs, and improves the reporting.
One special case isn't a bug fix but a rule change: mountains. The
old code forbids that explicitly, and it's clearly intentional, if
undocumented. The new code allows it by not doing anything special.
Turn group retreat into an actual group retreat: everyone in the same
fleet and sector with the the same retreat path and group retreat
condition joins the group. The group retreats together, just like in
navigate and march.
Take care to always report the end sector. When retreat is
impossible, report "can't retreat". When retreat is partial, report
"and stays in X,Y". When it's complete, report "stopped at X,Y".
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
unit_move() is too big and has too many paths through its loop.
Maintenance of the (unspoken) loop invariant isn't obvious. In fact,
it isn't maintained on some paths. I found several bugs:
* We check prerequisite conditions for moving before the first move
and around prompts. When a condition becomes wrong on the move,
movement continues all the same until the next prompt. I believe
the only way this can happen is loss of crew due to hitting a mine.
* We cache ships and land units in a list of struct ulist. When a
ship or land unit gets left behind, its node is removed from the
list and freed.
We keep pointer flg pointing to the flagship in that list for
convenience. However, the pointer isn't updated until the next
prompt. It's referenced for automatic radar and all sub-commands
other than the six directions and 'h'. Use after free when such a
sub-command gets processed after a flagship change without a prompt.
Same for land units. For instance, navigating a pair of ships "jh"
where the flagship has no mobility leaves the flagship behind, then
attempts to radar automatically using the ship in the freed list
node. Likewise, marching a similar pair of land units "jl" examines
the land unit in the freed list node to figure out how to look.
* We cache mobility in the same list to support fractional mobility
during movement. Movement deducts from cached mobility and writes
the result back to the ship or land unit.
If something else charges it mobility while it's in this list, the
cache becomes stale. shp_nav() and lnd_nav() reload stale caches,
but don't run often enough. For instance, when a ship hits mines,
the mine damage makes the cache stale. If a direction or 'h'
follows directly, the stale mobility is written back, clobbering the
mine hit's mobility loss.
This mess dates back to Empire 2, where it replaced a different mess.
There may be more bugs.
unit_move()'s complex control flow makes reasoning about its loop
invariant too error-prone. Rewrite the mess instead, splitting off
sensible subroutines.
Also fixes a couple of minor annoyances:
* White-space can confuse the parser. For instance, "jg l" is
interpreted like "jgll". Fix to reject the space. Broken in commit
0c12d83, v4.3.7.
* The flagship uses radar automatically before any sub-command (since
Chainsaw), and all ships use it automatically after a move (since
4.2.2). Make them all use it before and after each sub-command,
whether it's a move or not.
* Land units don't use radar automatically. Make them use it just
like ships.
* Always report a flagship / leader change right when it happens, not
only before and after a prompt.
Left for another day, marked FIXME: BTU charging is unclean.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
lnd_may_mar() uses lp->lnd_own rather than actor, but that's okay,
lnd_mar() ensures they're the same.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
When ships enter a sector with sea mines, any minesweepers sweep, then
hit mines, and finally all ships (including the minesweepers) hit
mines. Sweeping in a sector (navigate sub-command 'm') works the same
without the final step.
When land units enter a sector with land mines, any engineers sweep,
and then all land units (including the engineers) hit mines. Sweeping
in a sector (march sub-command 'm') works the same, which means
non-engineers can hit mines then. Broken in Empire 2.
Actually broken for ships too then. 4.0.17 fixed ships, but neglected
to fix land units.
Change the land unit code to work like the ship code. Fixes march
sub-command 'm' not to expose non-engineers to mines. Changes march,
attack and assault with option INTERDICT_ATT enabled to expose
engineers twice.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
The capability to march land units spread over several sectors is
obscure and rarely useful. Accidental use is probably more frequent
than intentional use. Issues:
* Interactive prompts show only the leader's position, and give no
clue that some land units are actually elsewhere.
* Path finding is supported only when all marching land units are in
the same sector.
* In each step, the bmap is updated for the leader's radar. The bmap
is not updated around other marching land units. Already odd when
all units are in the leader's sector, and odder still when some are
elsewhere.
* Interdiction becomes rather complex. For each movement, every
sector entered is interdicted independently. This means the same
ship, land unit or plane can interdict multiple times. Interdiction
order depends on the order the code examines land units. which the
player can control. This is all pretty much undocumented.
* Complicates the code and its maintenance. Multiplies the number of
test cases needed to cover march.
I feel we're better off without this feature.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
Unlike the move command, march checks sector abandonment before every
step.
If the player declines, the last land unit stays put and is removed
from the march.
Except when sectors or land units change while we're waiting for the
player's reply. Then the last unit is not removed from the march.
This can scatter land units. Screwed up when checking for abandoning
the sector was added in 4.2.2.
Change march to work like move, and to avoid scattering land units: if
the player declines to abandon the sector, the command simply fails.
Put the check into new lnd_abandon_askyn().
Extend would_abandon() and want_to_abandon() from a single land unit
to many. Rename the latter to abandon_askyn() for consistency.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
With autonav and SAIL gone, shp_nav_put() isn't used externally
anymore. lnd_mar_put() never was; it got external linkage just for
symmetry.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
lnd_put() serves two masters: march, which wants it to report
"stopped" and write back struct ulist member mobility to
unit.land.lnd_mobil, and ground combat, which doesn't.
lnd_put() assumes march when actor is non-zero. Correct (but see
commit 8c502d4). Dates back to Empire 2.
Too ugly for my taste. Specialize for each master instead:
lnd_mar_put() for march, and lnd_put() for ground combat.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
Commit d94d269 combined them into unit_put(), but that has turned out
not to be useful. Split them again.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
... get_dlist(), att_reacting_units().
This loses malloc() error checking in ask_olist() and get_dlist(). No
great loss, because we don't check in so many other places, including
att_reacting_units(). We should use a wrapper that terminates on
error, though. Left for another day.
ask_olist(), get_dlist() and att_reacting_units() zero the struct
ulist with memset(). lnd_insque() doesn't, so these functions need to
zero any members not otherwise initialized explicitly now.
Signed-off-by: Markus Armbruster <armbru@pond.sub.org>
As long as symbol_by_value(), show_capab() and togg() support only
int, flags need to fit into int.
Not a problem in practice, because no machine capable of running
Empire has int narrower than 32 bits, and 32 bits suffice.
Some flags members are long instead of int: struct lchrstr member
l_flags, struct natstr member nat_flags, struct mchrstr member m_flags
are long. Waste of space on machines with long wider than int.
Change them to int.
Rearrange struct lchrstr and struct natstr to avoid holes.
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.
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.
Oops when a stale copy is written back, i.e. the processor was yielded
since the copy was made. Such bugs are difficult to spot. Sequence
numbers catch them when they do actual harm (they also catch different
bugs). Generation numbers catch them even when they don't.
New ef_generation to count generations. Call new ef_make_stale() to
increment it whenever the processor may be yielded.
New struct emptypedstr member generation. To conserve space, make it
a bit-field of twelve bits, i.e. generations are only recorded modulo
2^12. Make sure all members of unit empobj_storage share it. It is
only used in copies; its value on disk and in the cache is
meaningless. Copies with generation other than ef_generation are
stale. Stale copies that are a multiple of 2^12 generations old can't
be detected, but that is sufficiently improbable.
Set generation to ef_generation by calling new ef_mark_fresh() when
making copies in ef_read() and ef_blank(). nav_ship() and
fltp_to_list() make copies without going through ef_read(), and
therefore need to call ef_mark_fresh() as well. Also call it in
obj_changed() to make check_sect_ok() & friends freshen their argument
when it is unchanged.
New must_be_fresh() oopses when its argument is stale. Call it in
ef_write() to catch write back of stale copies.
Store them and ef_type in bit-fields.
Allocate eight bits for ef_type. Values range from 0 to EF_GAME
(currently 16), so this is plenty.
Allocate twelve bits for sequence numbers. Sequence number mismatches
are now missed when the numbers are equal modulo 2^12. Still
sufficiently improbable.
Common machines store the bit-fields in a 32 bit word. There are
twelve bits left in that word for future use. Total savings 16 bits,
which is exactly what the previous commit spent on wider uids on
common machines.
Before, they were stored as short. Wider uids use more space, but the
next commit will recover it by narrowing other members.
The use of short has always limited the number of ships, planes, land
units and nukes to SHRT_MAX (commonly 32768). Only the most extreme
games ever came close.
Commit 49780e2c (v4.3.12) added struct sctstr member sct_uid to make
struct empobj member uid work for sectors. This made the limit apply
to sectors as well. We've had games with more than 32768 sectors.
struct lndstr member lnd_flags is a leftover from Empire3's C_SYNC,
which was ripped out in 4.0.0.
struct lonstr member l_sel, struct nuk_str members nuk_ship, nuk_land,
and struct trtstr member trt_bond have been there basically forever
without any use.
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.
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.
Persistent game state encodes "who carries what" by storing the
carrier uid in the cargo. Cargo lists augment that: they store lists
of cargo for each carrier. They are not persistent.
New unit_cargo_init() to compute the cargo lists from game state.
Call it in ef_init_srv() and at the end of update_main().
New unit_onresize() to resize the cargo list data structure.
Installed as units' struct empfile callback onresize to make them
resize automatically with the unit files.
New unit_carrier_change() to update cargo lists when carriers change
in game state. Convenience wrappers pln_carrier_change(),
lnd_carrier_change() and nuk_carrier_change(). Call them from
prewrite callbacks to keep cargo lists in sync with game state.
To make that work, unused units must not point to a carrier. Add new
pln_oninit(), lnd_oninit() and nuk_oninit() take care of newly created
units. Change lnd_prewrite() and nuk_prewrite() to take dead land
units and nukes off their carrier. pln_prewrite() did that already.
New unit_cargo_first(), unit_cargo_next() to traverse cargo lists.
Convenience wrappers lnd_first_on_ship(), lnd_first_on_land(),
lnd_next_on_unit(), pln_first_on_ship(), pln_first_on_land(),
pln_next_on_unit() and nuk_on_plane(). The latter is disabled for now
because it clashes with an existing function.
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.
This oopses on output dependency violations, e.g. two threads doing a
read-modify-write without synchronization, or the one thread nesting
several read-modify-writes. Such bugs are difficult to spot, and tend
to be abusable. I figure we have quite a few of them.
New struct emptypedstr member seqno. Make sure all members of unit
empobj_storage share it. Initialize it in files: main() and
file_sct_init(). Set it in ef_blank() and new ef_set_uid() by calling
new get_seqno(). Use ef_set_uid() when copying objects: swaps(),
doland(), doship(), doplane(), dounit(), delete_old_news(). Step it
in ef_write() by calling new new_seqno().
Factor do_read() out of fillcache() to make it available for
get_seqno().
The abstract idea of tying ships and land units to a logistical tether
is sound, the concrete implementation as option FUEL is flawed. It
adds too much busy-work to the game to be enjoyable. It hasn't been
enabled in a public game for years. The code implementing it is ugly,
repetitive, and a burden to maintain.
Remove selector fuel from ship_ca[] and land_ca[], and selectors
fuelc, fuelu from mchr_ca[] and lchr_ca[]. Remove fields fuelc, fuelu
from ship.config and land.config.
Remove command fuel from player_coms[].
Deprecate edit key 'B' in doship(), dounit(), and don't show it in
pr_ship(), pr_land().
Drop opt_FUEL code from build_ship(), shi(), sdump(), ship_damage(),
show_ship_stats(), do_mob_ship(), nav_ship(), build_land(), land(),
ldump(), land_damage(), show_land_stats(), do_mob_land(),
resupply_all(), resupply_commod(), get_minimum(), has_supply(),
unit_list(), vers().
Remove opt_FUEL, fuel_mult, struct shpstr member shp_fuel, struct
mchrstr members m_fuelc and m_fuelu, M_OILER, struct lndstr member
lnd_fuel, struct lchrstr members l_fuelc and l_fuelu, fuel(), and
auto_fuel_ship().
Make sure all members of unit empobj_storage share it.
Add matching timestamp member to struct comstr, struct empobj, struct
gamestr, struct lonstr, struct natstr, struct nwsstr, struct trdstr,
struct trtstr. The timestamp isn't yet set for these. To be fixed.
Move the timestamp member to the right place in struct lndstr, struct
loststr, struct realmstr, struct nukstr, struct plnstr, struct sctstr,
struct shpstr.
struct lndstr members lnd_spy, lnd_rad, lnd_ammo, lnd_fuelc,
lnd_fuelu, lnd_maxlight, lnd_maxlight are mere copies of struct
lchrstr members l_spy, l_rad, l_ammo, l_fuelc, l_fuelu, l_nxlight,
l_nland. Remove them.
Make land unit selectors spy, rmax, ammo, fuelc, fuelu, maxlight
virtual.