Change s_commod() not to use the supply sink as source. As explained
in the message of the commit before the previous one, using the sink
as source makes it impossible for callers to safely keep a copy of the
sink across a supply call. All current users do that. Some were safe
anyway, some were not:
* fort_fire() was safe, because a fort draws shells only when it has
none.
* shp_fire() was unsafe for ships with capability supply and ammo use
greater than 1. No such ship exists in the stock game.
* shp_dchrg() was unsafe for ships with both capabilities dchrg and
supply. Same for shp_torp() and capability torp, and
shp_missile_defense() and capability anti-missile. No such ship
exists in the stock game.
* lnd_fire(), supp() and get_dlist() were safe, because they draw
shells only when they have less than their ammo need, and then they
don't supply any.
* mission_pln_equip() was unsafe when equipping planes with shells in
supply sources.
* landmine() was unsafe for land units with both capability engineer
and supply. No such land units exist in the stock game.
* load() and lload() were unsafe for loadable supply units, but the
supply use there was disabled in commit 65410d16 because of another
bug.
* ask_olist() and att_reacting_units() were safe, because
lnd_can_attack() excludes supply units.
In the stock game, planes flying interception or support missions,
abms intercepting ballistic missiles, launch of missiles or anti-sats
could conjure up shells, triggering a seqno mismatch oops.
In games with unusual customizations, this could also happen with
supply ships firing guns or torpedoes, dropping depth charges, or
shooting down marine missiles, and in the lmine command.
To implement the supply from self avoidance described in the previous
commit's message, s_commod() needs to be redesigned along the same
principles: take the sink as argument, update and put it. Also take
an item limit argument, and return whether supply request was fully
met.
Update sct_supply(), shp_supply(), lnd_supply() and
lnd_could_be_supplied(). The former three become straighforward
wrappers.
supply_commod() and try_supply_commod() are now unused, remove them.
The automatic supply interface has design flaws that make it hard to
use correctly. Its current uses are in fact all wrong (until commit
0179fd86, the update had a few uses that might have been correct).
Some of the bugs can only bite with land unit capability combinations
that don't exist in the stock game, though.
Automatic supply draws supplies from supply sources in range. Since
that can update any supply source in range, all copies of potential
supply sources a caller may keep can get invalidated. Writing back
such an invalid copy wipes out the deduction of supplies and mobility
from a source, triggering a seqno mismatch oops.
This commit redesigns the interface so that callers can safely keep a
copy of the object drawing the supplies (the "supply sink"). The idea
is to pass the sink to the supply code, so it can avoid using it as
source. The actual avoiding will be implemented in a later commit.
Copies other than the supply sink still need to be eliminated. See
commit 65410d16 for an example.
Other improvements to help avoid common errors:
* Supply functions are commonly used to ensure the sink has a certain
amount of supplies. A common error is to fetch that amount
regardless of how many the sink already has. It's more convenient
for such users to pass how many they need to have instead of how
many to get.
* A common use of supply functions is to get supplies for immediate
use. If that use turns out not to be possible after all, the
supplies need to be added somewhere, which is all too easy to
forget. Many bugs of this kind have been fixed over time, and there
are still some left. This class of bugs can be avoided by adding
the supplies to the sink automatically.
In fact, this commit fixes precisely such bugs in mission_pln_equip()
and shp_missile_defense(): plane interception and support missions,
missile interception (abms), launch of ballistic missiles and
anti-sats could all lose shells, or supply more than needed.
Replace supply_commod() by new sct_supply(), shp_supply(),
lnd_supply(), and resupply_all() by new lnd_supply_all(). Simplify
users accordingly.
There's just one use of resupply_commod() left, in landmine(). Use
lnd_supply_all() there, and remove resupply_commod().
Automatic supply always leaves enough food to avoid starvation in
supply sources, except for one case: when drawing supplies from the
sector containing the sink.
This behavior contradicted info supply. However, do_feed() used to
rely on it (it would have wiped out food without it). Supply use
there was removed in commit 7da69c92, so we can now fix this.
Affected by this is the automatic food supply of land units in combat,
and the food supply in commands supply, load and lload. Except supply
is disabled due to bugs in the last two.
When a supply request got served completely from a remote sector after
some other source had already provided some of it, the sector was
charged mobility for the complete amount instead of just the part it
actually provided.
Its implementation in s_commod() increases lnd_seqno even when
!actually_doit, which can cause spurious seqno oopses in callers of
lnd_could_be_supplied(). I can't be bothered to clean up this mess
right now, because recursive resupply is too dumb to be really useful
anyway: each step uses the first source it finds, without
consideration of mobility cost.
Being in supply is relevant for defending and reacting units. The
code used has_supply() to check that.
Contrary to its name, has_supply() does not check whether the land
unit has enough supplies to be in supply, but whether it has or could
draw enough. So, defending and reacting units did not actually draw
any missing supplies.
Fix that in get_dlist() and att_reacting_units() by calling
resupply_all(), then checking with new lnd_in_supply() instead of
has_supply(). The fix of att_reacting_units() is complicated by the
fact that it is also used in the strength command, and should keep not
drawing supplies there.
Rename has_supply() to lnd_could_be_supplied(). Replace its uses
immediately after resupply_all() by lnd_in_supply().
Running supply recursively here is problematic, because it can draw
supplies from the outer supply's destination, which can then end up
with less than it asked for.
Serving as supply source never puts a unit that is in supply out of
supply. So, the recursive supply here denies the sink its supplies to
put a supply unit somewhere else back in supply. That's robbing Peter
to pay Paul. Drop it.
The common supply code does not supply a land unit from the ship
carrying it, only resupply_all() does that, since 4.3.14. It would be
nice to fix the inconsistency by always supplying land units that way,
but that's relatively costly now, because of the supply code's design.
Just drop it for now.
Affects load and lload (except resupply is disabled there because it's
buggy), supply, assault and board.
4.0.9 changed flak not to use up shells, but they still had to be
present. Drop that, because it doesn't really provide any value.
Moreover, this gets rid of the buggy flak shell supply code (seqno
mismatch oopses, lost supplies).
The ancients designed interception dead simple: when you overfly a
sector, you get intercepted by the sector owner. Fine print
interception rules govern which planes intercept.
Then complexity got piled on top of it.
Chainsaw 2 added an extra interception by surface ship owners, in the
target sector only.
Chainsaw 3 added an extra interception by land unit owners, in the
target sector only (Empire 4 later merged this extra land unit
interception with the extra surface ship interception).
Chainsaw 3 added an entirely separate kind of interception: air
defense missions. When you enter a sector in some air defense op
area, you get intercepted. Fine print air defense rules govern which
planes intercept. These rules differ significantly from the
interception fine print.
Additional complexity comes from these facts:
* Air defense mission interception happens in addition to non-mission
interception. You can boost your total interception by setting up
air defense. Which means you must set it up, or forgo an advantage.
* Air defense planes are not available for non-mission interception
duty. You need to decide on a split.
* In contrast to non-mission interception, interceptors flying air
defense get intercepted.
Moreover, the air defense code breaks one of the plane code's design
assumptions, namely that just one plane sortie is active at a time.
The air defense sortie runs while the sortie it intercepts is in
progress. This leads to two interceptions being active at the same
time: the one intercepting the original sortie, and the one
intercepting the air defense sortie. The same plane can fly in both
interceptions, and damage received in the interception of the air
defense sortie is wiped out, triggering a seqno mismatch oops.
The previous commit already simplified non-mission interception: you
get intercepted by anyone who owns the sector, or a surface ship or a
land unit there, whether it's the target sector or not.
Now simplify mission interception, by merging air defense back into
ordinary interception: when you overfly a sector, you get intercepted
by anyone who owns the sector, or a surface ship or land unit there,
or has an air defense mission covering the sector. That's all. No
multiple interceptions, no separate air defense rules.
Remove air_defense(). Simplify ac_encounter() and sam_intercept()
accordingly; both lose their last parameter.
Change sam_intercept() and ac_intercept() to intercept in mission op
areas. New parameter only_mission to suppress non-mission
interception. Pass zero when the intercepting country owns the sector
or a surface ship or land unit in the sector.
ac_encounter() can't efficiently predict whether a country intercepts,
so it needs to call ac_intercept() unconditionally. This kills the
optimization to collect interceptors only when needed; simplify
accordingly, replacing getilist() by getilists().
In each sector, any country owning the sector, a surface ship or a
land unit gets to intercept.
Before, only the sector owner got to intercept, except for the target
sector. There, any country owning surface ships or land units got to
intercept in addition to the sector owner. Thus, a sector owner with
surface ships or land units there got to intercept twice.
Info Intercept claimed you get to intercept once for ships and once
again for land units, which was wrong since 4.0.9.
Info bomb suggested that flak fires only in the target sector, which
was wrong since 4.2.8. Drop that.
Sectors already spotted overflying planes in every sector along the
flight path, but ships and land units did that only in the target
sector, once if you got any ships there, in ac_encounter(), once if
you got any land units there, in ac_encounter(), once for ships firing
flak, in ac_shipflak(), and once for land units firing flak, in
ac_landflak(). Remove all that, and generalize ac_encounter()'s code
for sectors to spot planes to include ships and land units. Unlike
before, ships and land units don't spot allied planes.
Planes now spot ships and land units only when flying recon or sweep,
and along all of their flight path instead of just the target sector.
It still takes a spy plane to identify ships and land units.
Before, non-spy planes spotted ships and land units only in the target
sector, regardless of type of sortie, once for all ships and land
units, in ac_encounter(), once for ships firing flak, in
ac_shipflak(), and once for land units firing flak, in ac_landflak().
Remove all that.
Change ac_encounter() to start intercepting and running air defense
missions at the assembly point instead of the first sector entered
from there.
This also fixes a coding bug: when the flight path was empty, evaded
was used uninitialized when checking whether to intercept over the
target. The compiler even warned about that. Since the uninitialized
evaded typically read non-zero, interception triggered by ships and
land units didn't work. Abusable: if you managed to make your target
sector an assembly point, e.g. by placing an own or allied ship there,
you could bomb it without getting intercepted or taking flak.
A country's SAMs launched only in the first interception of a sortie.
That was because ac_intercept() made sam_intercept() delete all SAMs
from the list of available interceptors. sam_intercept() also deleted
any SAMs out of range. Don't do that, delete unused SAMs along with
other unused interceptors on return from ac_encounter().
Planes were not subject to air defense and flak over allied sectors.
Air defense was broken when Empire 2 changed it to happen after
spotting.
Flak was broken when 4.2.8 made ships and land units fire flak in
every sector, not just the target sector. Although an allied sector
doesn't fire flak, it may still contain hostile ships and land units.
Also makes use of ilist[] more robust: before, if relations somehow
went sour after unfriendly[] was initialized, the sector intercept
would run with an invalid interceptor list, and crash.
Chainsaw had missiles flying missions together with planes, and to
make that work, aircombat.c got code for coping with missiles. Since
Empire 2, missiles fly separately and don't go through aircombat.c
anymore. The missile code there has been useless for more than a
decade. Remove it.
This simplifies ac_encounter(), sam_intercept(), ac_intercept(),
ac_airtoair(), and gets rid of count_non_missiles(), all_missiles().
ac_encounter() passed MAXNOC instead of the sector owner to setcont(),
and setcont() does nothing for such an argument. Screwed up from the
start, in commit 144c7cb5, v4.2.22.
ac_encounter() lets all owners of ships and land units in the target
sector intercept, but not more than once.
Move the interception code behind reporting of both ships and land
units. Before, it was duplicated for ships and land units. Land
units didn't get reported when no bombers got through interception
triggered by ships.
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.
A reconnaissance patrol (recon and sweep) reports much more detail
when spy planes participate. However, ac_encounter() didn't stop
doing that after all spy planes were shot down or aborted.
Fix by recalculating plane capabilities for every sector.
Remove the KillIdle thread. Add timeout to struct iop, initialized in
io_open(). Obey it in io_input() by passing it to empth_select(). If
empth_select() times out, report that back through io_input() to
recvclient() and player_login(). If player_login() receives a timeout
indication, print a message and terminate the session. If
recvclient() receives a timeout indication, flash a message to the
player and initiate a shut down the player's session.
Create WIN32 sys/time.h to define struct timeval. This creates some
conflicts with WIN32 windows.h definitions. Including windows.h in
show.c and info.c creates conflicts, so remove that. Modify service.c
to include sys/socket.h instead of windows.h to remove the conflict
with sys/time.h.
Add check to ensure a country by that name does not exist.
Ensure the length is not too long. Note this is a change
behaviour for edit and change commands which used to silently
truncate long names. Enforce that a country name can not have
control characters in it. Ensure that a country name is not
blank or just spaces.
Land units were erroneously charged the much lower raw path cost,
except when attacking high-mobility terrain (mountains). Broken in
commit 2673a258, v4.3.6.
Conditions comparing strings behaved rather erratically: while wing=m
was smartly interpreted as wing='m' (because m is ambiguous as plane
selector), wing=e was not so smartly interpreted as wing=effic
(because e unambiguously identifies effic; this was then rejected due
to incompatible types), and wing=g was even less smartly interpreted
as wing=group.
Address this by a redesign of the identifier resolution rules in
nstr_comp(): If the condition contains just one identifier, it names a
selector. If it contains two, try to intepret either as selector or,
if the other can be interpreted as selector, as value for that.
String selectors accept any identifier as value, numeric selectors
only the ones listed in their table.
Interpret both identifiers as selectors only if their types are
compatible (makes rpath=gu work for ships) and their tables, if any,
match (makes type=spy work for land units).
If more than one interpretation makes sense, drop any value
interpretations of identifiers that are unabbreviated selector names,
and selector interpretations of those that are not (makes wing=w work
for planes).
Change nstr_match_val() to accept any identifier as value for a string
selector. Replace nstr_mkselval() by nstr_resolve_val(), to resolve
string values in addition to symbolic ones. Remove resolution to
string from nstr_resolve_id(), drop its last parameter. Remove unused
nstr_string_ok(). New nstr_is_name_of_ca(), nstr_ca_comparable().
Examples for conditions that are now interpreted differently:
condition old interpretation new interpretation
wing=g wing=group wing='g'
w=g wing=group rejected as ambiguous (1)
w=e wing=effic (2) wing='e'
(1) because both wing='g' and 'w'=group make sense
(2) rejected as incomparable later on
Change nstr_mkselval() to generate values with promoted types only,
and replace nstr_coerce_val() by new and simpler nstr_optype() in
nstr_comp().
Replace the only remaining use of nstr_coerce_val() in surv() by
nstr_promote(), and remove nstr_coerce_val().
This loses one half of the unimplemented sketch of coercions to
NSC_STRING. Drop the other half from nstr_exec_val().
Before, the escort mission didn't support an op-area, and planes on
escort mission escorted anywhere. Change mission() to define the
op-area for escort missions as well. Show it in mission() and
show_mission(). Check it in find_escorts().
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.
The mission command limits op area radius to the possible range.
That's okay, as it doesn't actually restrict possible op areas. When
the mission is executed, it was limited again. Don't do that; remove
the limiting code from build_mission_list_type() and show_mission().
The removed limiting had no effect, except when the range shrunk.
Then limiting reduced the op area more than necessary. For instance,
consider an object O with initial range 3 on a mission around M with
range 3:
- - - y - - -
- - z y - -
- - z x y - -
- O x x M -
- - z x y - -
- - z y - -
- - - y - - -
Initially, all sectors not marked - are in range and in the op area.
If the range drops to two, sectors marked O, x and z are still in
range of O. But only the x are still in range of M. The O and z got
excluded.
Range can currently shrink when plane range is reduced (range
command), or a ship, plane or land unit somehow loses tech (deity
intervention).
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.