When called from the scuttle command, scuttle_tradeship() asks for
confirmation when scuttling doesn't pay. When called from the autonav
code, it can't ask. Change it to fail then, and use that in
nav_ship() to avoid scuttle where it doesn't pay. Also simplify some.
Fix scuttle to ask for confirmation when scuttling a tradeship in an
unsuitable sector even when the tradeship is pirated. Broken when
commit a99bc3be (v4.2.13) suppressed that for pirated tradeships
wholesale because it let pirates ferret out where the ship was built.
You can now scrap ships in own or friendly, efficient harbors, planes
in own or allied, efficient airfields, and land units in any own or
allied sector.
When something can't be scrapped because of these rules, print a
suitable message.
Before, you could scrap ships regardless of relations to sector owner
(info claimed friendly was required), land units regardless of
relations, but not while on ships, and planes even in friendly
airfields (info claimed allied was required).
When scrapping in a deity sector, scrap claimed it gave the cargo to
POGO, which is somewhat bogus, as POGO can't own such stuff.
scra() and scut() printed their "scrapped in" / "scuttled in" message
in two parts. Messages for scrapped / scuttled cargo were printed
between the parts. Fix by printing in one go, after the cargo
messages.
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.
Change snxtitem_all() loops that skip everything but a carrier's cargo
to use snxtitem_cargo() in scra(), scuttle_ship(), scuttle_land(),
takeover_ship(), takeover_land(), trade_desc(), feed_ship().
Instead of counting the load with lnd_nland() / lnd_nxlight(), check
whether there's at least one loaded with lnd_first_on_land() /
pln_first_on_land().
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().
Losses of sectors, ships, planes, land units and nukes are tracked in
the lost file. To keep it current, makelost() and makenotlost() were
called whenever one of these changed owners. Cumbersome and
error-prone. In fact, the lost file was never perfectly accurate.
Detect the ownership change in the prewrite callback and call
makelost() / makenotlost() from there. Remove lost file updates from
where they're no longer needed: right before a put. takeover() is a
bit more involved: it doesn't put the sectors, but all callers do,
except for guerrilla(). So remove the lost file update from
takeover(), but add it to guerrilla().
This takes care of lost file update for all ownership changes that go
through ef_write(). It can't take care of any missing updates for
changes that don't go through it.
Change checksect() not to abandon occupied sectors to the old owner
when there is no military and no land units. This effectively
restores pre-Chainsaw 3 behavior. Matching change to would_abandon().
Rationale. Traditional ways to change sector owner:
(1) Attack, assault, paradrop can transfer a sector to the attacker,
in take_def().
(2) Guerrilla warfare at the update can transfer a sector to the old
owner, in guerilla(). This happens when che kill all military and
the sector is sufficiently disloyal to the owner.
(3) Whenever all civilians, military and land units are removed from a
sector, no matter how, it silently reverts to the deity, in
checksect().
Chainsaw 3 added:
(4) Whenever all military and land units are removed from an occupied
sector, no matter how, it silently reverts to the old owner, in
checksect().
This addition isn't seamless. Funnies include:
* When che kill all military and land units, but the sector is loyal,
(3) doesn't transfer to the old owner. But since there's no
military and land units left, (4) transfers it anyway, only without
telling the lucky old owner. The latter transfer is buggy:
checksect() runs only on ef_read() and ef_write(), not the update
(bug#1010856), so the silent transfer is delayed until the next
ef_write(). But code using ef_read() sees it right away. For
instance, the path finder, which doesn't use ef_read(), can route a
path through a sector lost that way. The actual move, which does
use ef_read(), then chokes on that path.
* When you attack a sector, and get defeated with the help of reacting
land units, but succeed in killing the *local* defenders, (4) makes
the sector silently revert to the old owner. Which might be
somebody who wasn't involved in the fight, and gets no notification
whatsoever of his windfall.
* You can abandon a sector to the old-owner by removing all military
and land units from it, in a myriad of ways. Some ways ask you for
confirmation (move, march, load), many don't (navigate, plane
construction, delivery; arguably bugs), and others simply don't let
you (paradrop, fly, distribution).
This problem also exists for abandoning to deity, i.e. through (3)
instead of (4). Some ways to move out civilians don't let you do
that (distribute), but most do. However, accidentally abandoning an
empty sector to deity is less serious than a populated one to
another player.
In my opinion, (4) doesn't add much to the game, and fixing the
funnies isn't worth the effort.
spy() stored coordinates of sectors successfully spied in an array.
Since it didn't bother to normalize coordinates, it could spy sectors
near the "seams" of the world more than once. The array was also
wastefully large.
Fix by using a sector bitmap instead, like do_look().
launch_sat() failed to normalize the satellite's coordinates when it
added a random course deviation. Satellites with screwed up
coordinates were missed by skywatch, and possibly in other places.
path() failed to normalize coordinate argument for deltx() and
delty(). This could subscript map[] and mapbuf[] out of bounds.
Broken in commit 3ca88271.
MAPWIDTH(3) used to be one too small, which made rout() clobber the
map row's terminating zero when the map spans the whole world in x.
Since Chainsaw 2, rout() copied rows into an additional buffer to add
a terminating zero. Remove that junk.
Change struct range from exclusive to inclusive upper bounds, for
consistency with struct realmstr and the area syntax. Also fix many
bugs.
real()'s conversion from struct range's exclusive upper bounds to
struct realmstr's inclusive upper bounds could underflow and store -1
in the realms file. Harmless, because its users didn't mind:
list_realm() and nstr_exec_val() convert back to relative coordinates,
and sarg_getrange() is only used by sarg_area(), which happened to
undo the damage. The change to inclusive upper bounds gets rid of the
broken conversion.
xyinrange() incorrectly treated the upper bound as inclusive, unless
the bounds were equal. Impact:
* nxtitem() and nxtitemp() cases NS_AREA and NS_DIST attempted to hack
around xyinrange()'s lossage(!), but screwed up: sectors on the
lower bound of of a range spanning the the whole world were skipped.
This affected all command arguments that support area or distance
syntax for items. In sufficiently small worlds, it could also make
radar miss satellites and ships, sonar miss ships, satellite miss
ships and land units, nuclear detonations miss ships, planes, land
units and nukes, automatic supply miss ship and land unit supply
sources, ships and land units fail to return fire, ships fail to
fire support.
* draw_map() could draw units sitting just right or just below of the
mapped area. No effect, as these parts of the map weren't actually
shown.
xydist_range() produced an inclusive upper bound when it decided that
the range covers everything in that dimension (which it didn't get
quite right either). This could make snxtsct_dist() and
snxtitem_dist() initialize the iterator with an incorrect upper bound.
Similar impact as the xyinrange() / nxtitem() lossage.
border() could print the hundreds line unnecessarily.
snxtsct() and snxtsct_all() screwed up for odd WORLD_Y: they failed to
include (WORLD_Y - 1) / 2 in the y-range. This affected all command
arguments that support "*" syntax for sectors, plus add ... c, power
n, and break.
snxtsct_all() failed to normalize the bounds (presumed harmless).
There were a few correct, but somewhat unclean uses of struct range
with inclusive upper bounds:
* nat_reset() used one internally.
* pathrange() worked with inclusive upper bounds internally, but
corrected to exclusive upper bounds before passing the range out.
* sarg_getrange() worked with inclusive upper bounds. Its only caller
sarg_area() corrected that to exclusive upper bounds.
The change to inclusive upper bounds cleans this up.
unit_map() and xysize_range() had no issues (isn't that amazing?), but
need to be updated for the changed struct range semantics.
The correct method to compute indexes into a map buffer for a struct
range is deltx(), delty().
path() used deltax(), deltay() instead, which yield correct results
only for indexes up to half the world size. Pathes spanning larger
areas were screwed up.
sona(), radmap2(), satmap() also used deltax(), deltay(), but only
with arguments where those yield correct results.
draw_map() used xnorm(), ynorm() instead, which is correct, but less
clear and less efficient.
Failing a command with code RET_SYN prints help and doesn't charge
BTUs. Failing with code RET_FAIL doesn't print help and charges BTUs.
A couple of command failures were changed or added recently to fail
with RET_SYN, because they're due to invalid player input. Some of
them, however, can happen after the command already did something, so
BTUs must be charged, or else players can deliberately fail the
command to save BTUs:
* Commit 9eda5f87 adds RET_SYN failures when getting player input
fails for:
- arm third argument
- deliver fourth argument
- fire third argument
- lmine second argument
- order d fourth argument
- range second argument
- sail second argument
- tend third argument
* Commit be41e70f likewise for:
- designate second argument
- morale second argument
- set third argument
- tend fourth argument
* Commit d000bf92 likewise (with a bogus commit message) for bdes
second argument.
* Commit 9f4ce71a likewise for ltend third and fourth argument.
* Commit 9031b03b changes failure code from RET_FAIL when getting
player input fails for threshold third argument. It adds RET_SYN
failure when the argument is bad. Some bad arguments already failed
that way before.
* Commit a7cf69af changes it from RET_FAIL when designate second
argument is bad.
Change them all to fail with RET_FAIL.
Many other places have the same bug, but those are left for another
day.
Item production is limited to 999 units, level production is
unlimited.
Commit 0e721173 (v4.2.15) changed prod() from no limit to 999 units,
which fixed it for items, and broke it for levels. Undo the change
for levels.
The cheesy test for repeated messages broke down when working on more
than one sector. Reword the message so that repetition is fine, and
drop the test.
improve() attempted not to spend the last dollar, but screwed up when
improving more than one sector. This could bankrupt the player.
Replace the flawed code by the same simple method that is used
elsewhere: break the loop when there's not enough money left for the
current sector.
Complicated by the fact that conv() ran the conversion code twice,
first for adding up the cost for chkmoney(), then for actually
converting. chkmoney() asks the player to confirm when he's about to
spend more than half his cash. Get rid of that, not worth the
complexity. This merges do_conv() back into conv().
Complicated by the fact that demo() ran the demobilization code twice,
first for adding up the cost for chkmoney(), then for actually
demobilizing. chkmoney() asks the player to confirm when he's about
to spend more than half his cash. Get rid of that, not worth the
complexity. This merges do_demo() back into demo().
It also removes the command's virtually undocumented fourth argument.
Update player_coms[] accordingly. While there, make it require money;
it won't do anything useful without money anyway.
RET_SYS was used for commands failing due to internal or environmental
errors, but not really systematically. The difference to RET_FAIL is
how dispatch() treats them: RET_SYS got logged, and cost no BTUs.
More specific logging is possible at the point of failure than in
dispatch(). Make sure that's done for all failures that used to
return RET_SYS.
The change in BTU charging affects commands consider, offer, repay,
trade failing due to internal errors. It also affects deity commands
reload and turn (irrelevant because deities get unlimited BTUs), and
commands apropos, info and motd (irrelevant because they cost no
BTUs).
Behave like plane_bomb() and land_bomb(): deal with leading whitespace
and signs in the input, print a message when asked to bomb a ship that
is not there.
land_bomb() failed to reduce flak proportional to efficiency. Missed
in commit c7f68f2e, v4.3.6.
Also change it to round randomly instead of down, to match
ac_landflak().
Change planesatxy() not to list embarked planes, plane_bomb() not to
bomb them, and land_bomb() not to bomb embarked land units.
Curiously, embarked land units were not listed as targets before, but
could be bombed all the same.
This is the human-readable buddy of xdump product, which dumps pchr[].
It duplicates much of the information in show sect c, but in more
accessible form. It's in show_product().
Remove product information from info Quick-ref. show reflects the
actual game, is more complete, and should be just as readable.
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.
The old code didn't return RET_SYN when aborting at the prompts for
the third and fourth argument.
While there, return RET_SYN instead of RET_OK when the tender can't
hold the commodity to be tended.