![]() |
Formulas
|
This document describes all formulas and processes used in PHost. It is intended for players who do not leave anything to chance. If you believe that some aspect of PHost operation should be documented in this file but is not, send mail to the PHost group and bring this to our attention.
This document is roughly grouped by the major object of an action. For example, Pillage is a mission that affects planets, and is therefore documented in the Planets section. If possible, formulas are arranged in the order they are needed during host processing. The exact sequence of events is documented on the Host Sequence page.
Variable names (Hull_mass, Native_race, ...) should be self-explanatory. We use common arithmetic notation. In particular, / is a fractional division.
Configuration options are specified verbatim, with links to their description. Eff_ConfigOption denotes the value of the ConfigOption modified by experience modifiers EModConfigOption.
Internally, PHost often uses people counts for populations, which were also used in the older documentation. For this document, numbers have most of the time been converted to the the more familiar clans. Since VGA Planets does not support fractional clans, PHost truncates after each step anyway. Some formulas still use people counts. For those, Colonist_clans is the clan count (e.g. 32), Colonists is the people count (e.g. 3200).
When a series of "if..." sentences is given, take the first applicable one.
Abs(X) | Absolute value of X |
ArcTan(X,Y) | Arc-tangent of X/Y (four-quadrant), angle in radiant |
Ceil(X) | Next-larger integer, preserving the sign (round up if there is any fractional part). For example, Ceil(10.0) = 10, Ceil(10.1) = 11, Ceil(-5.2) = -6 |
Cos(X) | Cosine of X, argument is in radiants |
ERnd(X) | Round X to integer (round towards nearest integer; round towards nearest even number if ends in .5 exactly) |
Exp(X) | Exponential of X (2.7182^X) |
Ln(X) | Natural logarithm of X |
Max(X,Y,...) | Maximum |
Min(X,Y,...) | Minimum |
PI | The number π = 3.14159265358979323846 |
Random(X) | Random integer, between 0 (inclusive) up to X (not inclusive). For example, Random(3) yields one of 0, 1, 2. |
Round(X) | Round X to integer (round towards nearest integer; round up if ends in .5 exactly) |
Sgn(X) | Sign of X (+1 if positive, -1 if negative, 0 if zero) |
Sin(X) | Sine of X, argument is in radiant |
Sqrt(X) | Square-root of X |
Sqrt(X) | Square-root of X |
Trunc(X) | Truncate X to integer (remove fractional part). For example, Trunc(10.0) = 10, Trunc(10.1) = 10, Trunc(-5.2) = -5 |
X^N | X to the Nth power |
PHost creates a file, score.log, which logs the standard score. This is a very simple score computed as follows:
Score = Num_planets * 10 + Num_capital_ships * 10 + Num_freighters + Num_starbases * 120
A capital ship is one having at least one weapon (could be an Outrider with a Laser, or a Death Star armed up to the teeth).
Many client programs can also display this score. They compute it from the Num_planets etc. values reported within the result files. If ScoringMethod is set to None, PHost does not send all these values, causing clients to report incorrect scores. The following values will still be sent if ScoringMethod=None:
This table shows all actions involving planets. The left column shows the PControl stages, in chronological order. The middle column contains all actions affecting happiness or population (like "taxation happens before growth"), the right-most column contains all actions affecting minerals and buildings (like "beam-up-multiple happens before lfm" (hint, hint)). The "Details" column contains a link to the relevant section of this document, if there is one.
Stage | Happiness/Population | Minerals/Industry | Details |
---|---|---|---|
LargeMeteors, MeteorShowers | Meteors might bring down happiness and kill people. | Meteors and meteor showers add minerals to planet core. | Formulas |
DeluxeSuperSpy | Ionic pulse triggered by Super Spy Deluxe may kill defenses | ||
CargoDump | Ground combat | Formulas | |
Training | Ships gather supplies for training | ||
GatherMission | Ships gather stuff | ||
SpecialMissions_1 | Lizards Hiss | Formulas | |
BuildFighters, BuildTorpedoes | Ships gather stuff | ||
ShipBuilding_1 | Cloning might consume minerals. | ||
DumpOldBaseParts | Starbases recycle old starship parts | ||
BaseMissions_1 | Starbases first recycle ships, then maximize defense or load torps onto ships | ||
FreeFighters | Starbases build "free" fighters | ||
GloryDevices | Glory devices kill people and toast Amorphous natives | Glory devices damage planet | Formulas |
ColonizeMission | Colonize ships bring clans | Colonize ships bring minerals | Formulas |
BaseMissions_2 | Starbases refuel ships or unload them | ||
Combat | Population might be killed in combat. Happiness change if planet is captured. |
Defense might be destroyed | Effects on planet, Combat |
Terraforming | Terraforming makes environment more pleasant for inhabitants. | ||
SensorSweep | Sensor Sweep might discover planets according to their industry | Formulas | |
SpecialMissions_2 | Pillage / RGA might make people unhappy | Pillage / RGA will destroy stuff, Dark Sense reports amounts afterwards | Formulas |
PlanetaryProduction | Mining, supply production, then trans-uranium decay | Formulas | |
PlanetaryHappiness | if planet is unowned or happiness is 29 or lower, taxes are set to 0. Then, happiness changes | Formulas | |
PlanetaryTaxation | Tax collection | Formulas | |
PlanetaryGrowth | Growth | Formulas | |
PlanetaryLosses | Climate deaths, then losses through riots | Overpopulation eat supplies, then structure decay, then losses through riots, then Amorphs eating colonists | Formulas |
ShipBuilding_2 | Cloning might consume minerals. | ||
Assimilation | Cyborgs assimilate natives | Formulas |
Maximum Number of Buildings: The maximum number of structures you can build on a planet depends on the population of the planet.
Maximum_mines = Colonist_clans ...if Colonist_clans < 200 Round(200 + Sqrt(Colonist_clans - 200)) ...if Colonist_clans >= 200 Maximum_factories = Colonist_clans ...if Colonist_clans < 100 Round(100 + Sqrt(Colonist_clans - 100)) ...if Colonist_clans >= 100 Maximum_defense_posts = Colonist_clans ...if Colonist_clans < 50 Round(50 + Sqrt(Colonist_clans - 50)) ...if Colonist_clans >= 50
Note that you need not permanently fulfill these conditions. You can build factories and beam off clans to a ship. PHost will consider this possibility when checking turn files. However, buildings over the limit as determined from the planetary population will decay at a rate of StructureDecayPerTurn.
Large meteors kill people and make them unhappy. Meteor showers have no effect on the population.
Colonists_survived = Colonists * (Random(91)+10) / 100 Colonist_happiness_change = -(Random(31) + 50) Natives_survived = Natives * (Random(100)+1) / 100 Native_happiness_change = -(Random(31) + 50)
In simple words, up to 90% of the colonists and up to 99% of the natives die, and they lose 50 to 80 happiness points. The mineral amounts added to the planet's core (not surface!) are configurable (LargeMeteorOreRanges, MeteorShowerOreRanges).
Ground combat happens when a hostile ship drops colonists on a foreign planet, using the ship-to-planet cargo transporters.
During ground combat, attacking colonists and defenders annihilate each other, subject to their attack and defense ratios, until one party remains.
Ground_attack_result = "attacker wins with Trunc(Result/Attacker_rate) clans remaining" ...if Result >= 0 "defender wins with Trunc(-Result/Defender_rate) clans remaining" ...if Result < 0 ...with Attacker_rate = GroundKillFactor[Ship_owner] Defender_rate = GroundDefenseFactor[Planet_owner] + Planet_defense / 20 Result = (Attacking_clans * Attacker_rate) - (Defending_clans * Defender_rate)
Note that if the result yields 0 clans remaining (maybe due to rounding), the planet gets unowned.
Imperial Assault: When a successful Imperial Assault is performed, the result is always "attacker wins with Attacking_clans clans remaining", independant of the number of Defending_clans and their attack/defense ratios. Imperial Assault is successful if the preconditions for the Imperial Assault device are fulfilled.
Hiss increases planetary happiness, by the same amount for natives and colonists. This formula used to read
Hiss_happiness_change = Min(Number_of_hissing_ships, MaxShipsHissing) * HissEffectRate
However, since PHost 4.1, HissEffectRate is arrayized. Therefore, in the general case, the Hiss_happiness_change is the sum of the HissEffectRate of the first MaxShipsHissing ships hissing at the planet (which can possibly belong to different players with different rates).
Note that hissing happens before taxation; after the hiss effect increases happiness, taxation will it bring down again.
Buildings affect your visibility to Sensor Sweep.
Industry_detection_chance = 0% ...if Mines < MinesForDetectable and Factories < FactoriesForDetectable 0% ...if Defense >= DefenseForUndetectable 100% - (Defense / DefenseForUndetectable * 100%) ...in all other cases Industry_level_reported = "minimal" ...if (Mines + Factories) < 30 "light" ...if (Mines + Factories) < 60 "moderate" ...if (Mines + Factories) < 90 "substantial" ...if (Mines + Factories) < 120 "heavy" ...if (Mines + Factories) >= 120 Bioscan_detection_chance = 0% ...if Defense >= 20 0% ...if ship does not have bioscanner (of course :-) 100% ...if ship has full bioscanner 20% ...otherwise
Pillage: Ships that do Pillage will plunder planetary households to get cash and supplies. They will then kill people which makes the population unhappy.
Supplies_made = Trunc((Colonist_clans + Native_clans) / 100) Money_made = Trunc((Colonist_clans + Native_clans) / 100) Colonist_clans_survived = Trunc(Colonist_clans * 0.8 - 20) Native_clans_survived = Trunc(Native_clans * 0.8 - 120) Colonist_happy_change = -10 Native_happy_change = -10
RGA: Ships that do Rebel Ground Attack will destroy planetary resources. They will then kill colonists, which makes them unhappy. Sarcastic natives will laugh at unhappy colonists.
Money_remaining = Trunc(Money * 0.7) Supplies_remaining = Trunc(Supplies * 0.6) Defense_remaining = Trunc(Defense_posts * 0.8) Mines_remaining = Trunc(Mines * 0.4) Factories_remaining = Trunc(Factories * 0.7) Colonist_clans_survived = Trunc(Colonist_clans * 0.8) Colonist_happy_change = -60 Native_happy_change = +40
Mining: The maximum amount of minerals extracted from a planet's core depends on the mineral density.
Max_minerals_mined = Round(Mining_rate * Mine_count / 100) ...where Mining_rate = Round(RaceMiningRate * Mineral_density / 100) * RF ...with RF = 2 for Reptilian natives, 1 otherwise Minerals_mined = Min(Max_minerals_mined, Minerals_in_core)
(v4.1:) PHost before version 4.1 used Trunc instead of Round here. The new formula always yields a little more minerals.
Supplies: Supplies are produced by factories, or by Bovinoid natives.
Supplies_made = Trunc((Factory_contribution + Bovinoid_contribution) * ProductionRate) / 100) ...where Factory_contribution = Factories Bovinoid_contribution = Min(Trunc(Native_clans / 100), Colonist_clans) ...if natives are Bovinoid 0 ...otherwise
Trans-Uranium Decay: New minerals appear in the planet's core. Note that this happens after mining, so you cannot extract the new minerals the same turn. Trans-uranium decay happens separately for all four minerals.
New_minerals_in_core = Trunc((TransuraniumDecayRate * Mineral_density + 50) / 100)
Your demand for taxes changes happiness. Note that these happiness changes occur before actual taxation. If the happiness before this stage is below 30, taxes are set to zero (and new happiness is computed according to this rate). If the happiness after this stage is below 30, no taxes are collected either.
Colonist_happiness_change = Trunc(10 - Sqrt(Colonist_clans / 10000) - Abs(Temperature - Target_temperature) / Temp_divisor - (Mines + Factories) / 300 - Colonist_tax * 0.8) ...where Target_temperature = 100 and Temp_divisor = 66 if owner is Crystal and CrystalsPreferDeserts is on ...where Target_temperature = 50 and Temp_divisor = 33 otherwise Native_happiness_change = Trunc(5 + Native_gov_number / 2 - Sqrt(Native_clans / 10000) - (Mines + Factories) / 200 - Native_tax * 0.85) ...plus 10 if natives are Avian
The Native_gov_number is tabulated in the Planet Rules section (e.g. 6 for Monarchy, yielding a value of 6/2 = 3 for the first term).
Happiness is summarized as follows:
Happiness_level = "happy" ...if Happiness >= 90 "calm" ...if Happiness >= 70 "unhappy" ...if Happiness >= 50 "very angry" ...if Happiness >= 40 "rioting" ...if Happiness >= 20 "fighting" ...if Happiness < 20 Happiness_change_level = "hate you" ...if Happiness_change < -5 "angry about you" ...if Happiness_change < 0 "undecided" ...if Happiness_change = 0 "like your leadership" ...if Happiness_change <= 4 "love you" ...if Happiness_change >= 5
Taxes are collected separately from colonists and natives.
Taxes_collected = Taxes_from_colonists + Taxes_from_natives ...at most MaxPlanetaryIncome Taxes_from_colonists = Round(Round(Colonist_clans * Colonist_tax / 1000) * ColonistTaxRate / 100) ...if Colonist_happiness >= 30 0 ...if Colonist_happiness < 30 Taxes_from_natives = 0 ...if Native_happiness < 30 0 ...if natives are Amorphous Round(Min(Colonist_clans, Natives_due) * IF * NativeTaxRate / 100) ...where Natives_due = Round(Native_clans * Native_gov_number * Native_tax / 5000) ...where IF = 2 if natives are insectoid, IF = 1 otherwise
The Native_gov_number is tabulated in the Planet Rules section (e.g. 6 for Monarchy).
Maximum Population: The maximum population for a planet is a fixed number which depends on the planet's climate. With ClimateLimitsPopulation enabled, the maximum population on an ideally-tempered world is 100000 clans (10 million colonists). If configured, supplies help you support more colonists on a planet.
Eff_max_colonist_clans = Trunc(Eff_max_colonists / 100) Eff_max_colonists = Max_colonists + Supply_bonus ...if ClimateLimitsPopulation is enabled ...at least 1 25000000 ...otherwise Max_colonists = Trunc(10000000 * Sin(Temperature * PI / 200)) ...if colonists are Crystalline and CrystalsPreferDeserts is enabled and CrystalSinTempBehavior is enabled and Temperature >= 15 300 + MaxColTempSlope * Temperature ...if colonists are Crystalline and CrystalsPreferDeserts is enabled and CrystalSinTempBehavior is enabled and Temperature < 15 100000 * Temperature ...if colonists are Crystalline and CrystalsPreferDeserts is enabled and CrystalSinTempBehavior is disabled 9000000 ...if colonists are Rebel and Temperature < 20 300 + MaxColTempSlope * Temperature ...if Temperature < 15 100 + (100-Temperature) * MaxColTempSlope ...if Temperature > 84 ...at least 6000 if colonists are Klingon, Robot, Rebel or Colony Trunc(10000000 * Sin(Temperature * PI / 100)) ...in all remaining cases Supply_bonus = Trunc(100 * Supplies / 40) ...if AllowEatingSupplies is enabled 0 ...otherwise
Note that these formulas use colonists, not
clans. A future PHost version may simplify that to clans,
which would get slightly different rounding behaviour.
Native populations have simpler formulas. The absolute maximum is 15.6 million natives (156000 clans) for most races, 10 million for Siliconoids when they like it hot. Supplies do not help you to increase the limit.
Max_native_clans = 156000 ...if ClimateLimitsPopulation is disabled 1000 * Temperature ...if natives are Siliconoid and CrystalsPreferDeserts is enabled and PHost is version 3.3c or newer Trunc(Sin(Temperature * PI / 100) * 156000) ...in all remaining cases
It might be considered a bug that Siliconoids cannot reach the 15.6 million mark with CrystalsPreferDeserts enabled, but changing the formula would again complicate matters even more.
Growth: Growth depends on taxation and climate. Populations will never grow over the limit. The maximum growth rate is 5% per turn.
Colonist_growth_in_clans = Trunc(Round(Colonist_growth_rate * Colonist_clans / 100) * RaceGrowthRate / 100) ...at most Eff_max_colonist_clans - Colonist_clans Colonist_growth_rate = 0 ...if Colonist_clans >= Eff_max_colonist_clans 0 ...if Colonist_happiness < 70 0 ...if colonists are Crystalline and CrystalsPreferDeserts is enabled and CrystalSinTempBehavior is enabled and Temperature < 15 5 * Sin(Temperature * PI / 200) / (1 + Colonist_tax / 5) ...if colonists are Crystalline and CrystalsPreferDeserts is enabled and CrystalSinTempBehavior is enabled and Temperature >= 15 5 * (Temperature / 100) / (1 + Colonist_tax / 5) ...if colonists are Crystalline and CrystalsPreferDeserts is enabled and CrystalSinTempBehavior is disabled 0 ...if Temperature < 15 0 ...if Temperature > 84 5 * Sin(Temperature * PI / 100) / (1 + Colonist_tax / 5) ...in all other cases
Native growth is similar, but here the maximum growth rate is 4%. Natives do not grow on unowned planets(!).
Native_growth_in_clans = Trunc(Round(Native_growth_rate * Native_clans) / 100) ...at most Max_native_clans - Native_clans Native_growth_rate = 0 ...if Native_clans >= Max_native_clans 0 ...if Native_happiness < 70 4 * (Temperature / 100) / (1 + Native_tax / 5) ...if natives are Siliconoids and CrystalsPreferDeserts is enabled and PHost is version 3.3c or newer 4 * Sin(Temperature * PI / 100) / (1 + Native_tax / 5) ...in all other cases
Overpopulation eats supplies: If enabled, colonist overpopulation will eat supplies to survive.
Supply_loss = 0 ...if AllowEatingSupplies is off 0 ...if ClimateLimitsPopulation is off 0 ...if Colonists <= Max_colonists Trunc((Colonists - Max_colonists) / 4000) + 1 ...otherwise
If there are fewer supplies than needed, they'll all be used up with no negative consequences.
Climate Deaths: If enabled, colonists and natives over the population limit will die.
Colonist_clans_dying = 0 ...if ClimateLimitsPopulation is off 0 ...if Colonist_clans <= Eff_max_colonist_clans Min(Trunc(Colonist_clans * ClimateDeathRate / 100), Eff_max_colonist_clans - Colonist_clans) ...otherwise Native_clans_dying = 0 ...if ClimateLimitsPopulation is off 0 ...if planet is unowned 0 ...if Native_clans <= Max_native_clans Min(Trunc(Native_clans * NativeClimateDeathRate / 100), Max_native_clans - Native_clans) ...otherwise
If the population exceeds the absolute maximum of 250000
clans (25 million people), the excess clans will die and be
reported as climate deaths, even if
ClimateLimitsPopulation is off.
Eff_max_colonist_clans is computed
after overpopulation-eats-supplies, so it uses the new
(lower) supply amount. It uses the same formula as in the
Growth step, but not the same
value.
Structure Losses: Planetary buildings can be lost through riots or lack of maintenance (i.e. fewer colonists than needed to support them).
Mine_loss = Mine_decay + Riot_loss Factory_loss = Factory_decay + Riot_loss Defense_loss = Defense_decay ...where Riot_loss = 10 ...if planet has colonists, and Colonist_happiness < 40 10 ...if planet has natives, and Native_happiness < 40 0 ...otherwise Mine_decay = Max(Min(Mines - Maximum_mines, Eff_StructureDecay), 0) Factory_decay = Max(Min(Factories - Maximum_factories, Eff_StructureDecay), 0) Defense_decay = Max(Min(Defense_posts - Maximum_defense_posts, Eff_StructureDecay), 0) Eff_StructureDecay = StructureDecayOnUnowned ...if planet is unowned StructureDecayPerTurn[Planet_owner] ...otherwise
Population loss through riots: Fighting colonists and/or natives will lead to loss of population. Even if just one population group fights, the other one will suffer as well.
Colonist_clans_dying = Ceil(Colonist_clans * (40 - Colonist_happiness) / 500) ...if Colonist_happiness < 20 Ceil(Colonist_clans * (40 - Native_happiness) / 2500) ...if planet has natives, and Native_happiness < 20 0 ...otherwise Native_clans_dying = Ceil(Native_clans * (40 - Native_happiness) / 500) ...if Native_happiness < 20 Ceil(Native_clans * (40 - Colonist_happiness) / 2500) ...if planet has colonists, and Colonist_happiness < 20 0 ...otherwise
Population loss from amorphous natives: Amorphous natives eat colonists after all other losses.
Colonist_clans_eaten = 0 ...if natives are not Amorphous 5 ...if Native_happiness >= 70 20 ...if Native_happiness >= 50 40 ...otherwise
If the Amorphous natives eat all your colonists or more, you'll lose the planet.
Cyborgs assimilate natives, and turn them into colonists. One assimilated native clan yields one new cyborg colonist clan.
Native_clans_assimilated =
Trunc(Colonist_clans * BorgAssimilationRate / 100)
...at most Native_clans
PHost versions before 4.1 / 3.5 would have removed
Ceil(...) native clans, but only added
Trunc(...) clans.
This table lists all events that affect ships, in the order in which they happen. Effects have been roughly grouped into two categories, although the distinction is not as sharp as for the planet table.
Stage | Cargo, Fuel, Cloak | Damage, Crew, Mission | Details |
---|---|---|---|
TransferOwner | Ships de-cloak after owner change | Mission and waypoint reset after owner change | |
AntiCloak_1 | Lokis de-cloak ships | ||
DeluxeSuperSpy | Ionic pulse may de-cloak ships | ||
RobMission | Ships rob or get robbed | ||
GamblingShips | Gambling makes money | ||
CargoDump, CargoTransfer | Ships can receive cargo Excess cargo may be given back |
Ground Combat | |
TrimShipCargo | Excess cargo removed | ||
CrewExchange | Crew Exchange mission | ||
Training | Training mission | Formulas | |
BeamTransfers | Ships give away/receive cargo | ||
GatherMission, BeamUpCredits | Ships gather stuff | ||
MineLaying | Ships turn torpedoes into minefields | Formulas | |
MineSweeping | Ships receive torpedoes from scooping | Formulas | |
WebDraining | Ships lose fuel to web minefields | ||
IonStormEffects | Ion storms de-cloak | Ships take damage and lose crew | Formulas |
SpecialMissions_1 | Super Refit, Repair Ship, Self Repair | ||
BuildFighters | Build Fighters | ||
BuildTorpedoes | mkt etc. | ||
Alchemy | Alchemy functions | ||
ShipBuildOrders | Ships trying to clone are set to Warp 0 | ||
BaseMissions_1 | Ships receive torps | Ships are fixed or recycled, or forced to surrender | |
SupplyRepair_1 | Supplies are used... | ...for self-repair | Formulas |
BoardingParties | Ships are boarded | Formulas | |
TowResolution | Tow conflicts resolved and tows canceled | Formulas | |
Movement | Fuel burned for movement and cloaking Mine hits can de-cloak ship |
Mine hits damage ship |
Movement Formulas General fuel burn |
GloryDevices | Damage taken from exploding Glory Devices | Formulas | |
Chunneling | Fuel consumed by chunneling | Chunneling resets waypoints | |
AntiCloak_2 | Lokis de-cloak ships | ||
ColonizeMission | Ships land on planets and are recycled | Formulas | |
SupplyRepair_2 | Supplies are used... | ...for self-repair | Formulas |
BaseMissions_2 | Starbases refuel ships or unload them | ||
Combat | Fuel burned De-cloak if damaged Borgs gather debris |
Ships take damage, lose crew, change ownership | Formulas |
SupplyRepair_3 | Supplies are used... | ...for self-repair | Formulas |
SensorSweep | Formulas | ||
SpecialMissions_2 | Pillaging ships gather cash/supplies | Formulas | |
Experience | Experience upgrades |
Ships burn fuel or need to have fuel available to perform a variety of operations. In addition to the operations listed here, many other maneuvers require the ship to have at least one kiloton of fuel (but do not consume that). Having fuel is also a prerequisite for being able to take part in combat.
Many formulas depend on the mass of a ship. There are three different masses which are of interest: hull mass, total mass, and combat mass.
Total_ship_mass = Hull_mass + Fuel + Supplies + Colonists (cargo) + Tritanium + Molybdenum + Duranium (cargo) + Torpedoes + Fighters (ammunition) + Num_torp_launchers * Torp_launcher_mass (weight of torpedo launchers) + Num_beams * Beam_mass (weight of beam weapons)
Note that fighter bays do not have weight (they are built into the hull, and are included in its weight). Money, engines and crew don't weigh anything as well.
Masses are recomputed from the ship's data whenever they are needed. If your ship's cargo changes, its mass changes as well.
Ships that cloak need to burn fuel.
Fuel_burned_by_cloak = 0 ...if ship has Advanced Cloaking Trunc(CloakFuelBurn * Max(Hull_mass, 100) / 100) ...otherwise Fuel_needed_before_movement = 1 + Fuel_burned_by_cloak Fuel_needed_after_movement = 1
Ships need to decloak if their fuel drops below the Fuel_needed value. After the normal cloak fuel was burnt, one kt must remain; fuelless ships cannot cloak.
Ships burn fuel after movement. If the ship has less fuel, nothing bad will happen, but the ship will end up without fuel afterwards.
Fuel_burned_per_turn = Ceil(Hull_mass * FuelUsagePerTurnFor100KT / 100)
Ships burn fuel after each fight. If the ship has less fuel, nothing bad will happen, but the ship will end up without fuel afterwards (and must thus stop fighting).
Fuel_burned_per_fight = Ceil(Hull_mass * FuelUsagePerFightFor100KT / 100)
Ships with the Training mission use supplies from the planet they orbit to gain experience.
Experience_added = EPTrainingScale * Points / (Sqrt(Hull_Crew) + 1) ...where Points = Supplies if Supplies <= 25 Points = 25 + Sqrt(8*(Supplies-25)) otherwise
(v4.0i:) Academy ships receive four times as much experience from training.
See Experience Training for a more detailed explanation of training.
Supply repair happens several times in host processing. This process is automatic, and there is no way to stop or force it. Ships do not need fuel to repair themselves.
Damage_repaired = Min(Damage, Trunc(Supplies_on_ship / 5)) Supplies_used = 5 * Damage_repaired
Ships with the Boarding ability (generally, all Privateer and Tholian ships) can board fuel-less enemy ships by towing them, and thus obtain control over them. Uncooperating enemy crew members are jetissoned and replaced by own crew.
New_crew_on_boarded_ship = Traitors + Crew_beamed_over New_crew_on_boarding_ship = Crew_on_boarding_ship - Crew_beamed_over ...where Traitors = Crew_on_boarded_ship ...if boarded ship is Privateer Trunc(0.9 * Crew_on_boarded_ship) ...if boarded ship is Fed Trunc(0.7 * Crew_on_boarded_ship) ...if boarded ship is Colonial Trunc(0.4 * Crew_on_boarded_ship) ...if boarded ship is Empire 0 ...otherwise Crew_beamed_over = Min(Boarded_ship_full_crew - Traitors, Trunc(Boarding_ship_crew / 2))
As a special exception, if these formulas would reduce New_crew_on_boarding_ship below 2, PHost uses Traitors = 2 and Crew_beamed_over = 0.
For more details about towing, see Tow Conflict Resolution.
Tow_strength =
Engine_contribution + Movement_contribution
Engine_contribution =
Engine_tech^2 * Eff_engines * Warp_factor * TowStrengthEngineScale
Movement_contribution =
Movement_distance * TowStrengthDistanceScale
...where
Movement_distance = Min(Waypoint_distance,
Max_dist,
Max_allowed_by_fuel)
Before a ship starts moving, its waypoint needs be known (in form of a X/Y pair, or, actually, a dX/dY displacement). Intercept resolution attempts to ensure that for intercepting ships.
If a wraparound universe is being used, ships move the shortest possible path to reach their waypoint (i.e. "100 ly to the east", not "1900 ly to the west"). After moving across a map seam, they are moved into the map again: if a ship moves 50 ly east of the right seam (outside the map), it will end up 50 ly east of the left seam (inside the map).
Speed Limit: Damage may force a ship to slow down. Note that the maximum speed is Warp 9, even if the formula yields a higher value.
Max_speed = 9 ...if ship has Hardened Engines 15 - Trunc(Damage/10) ...if ship owner is Lizards 10 - Trunc(Damage/10) ...otherwise
Basics: First, we compute the maximum distance permitted by our warp factor. This is the permitted movement distance for our ship. Both values may change during movement.
Max_dist = Warp^2 ...for normal engines Warp^2 * 2 ...for gravitonic engines Permitted_dist = Max_dist ...if ship has fuel 1 ...if ship has no fuel, and AllowNoFuelMovement is on 0 ...if ship has no fuel, and AllowNoFuelMovement is off
PHost uses a time-based reasoning to move ships. Normally, a ship has one time unit to move. It doesn't need to use that unit completely. A ship moving towards a 32 ly waypoint at Warp 8 will usually reach that waypoint after one-half time unit. Mine hits can cause the ship to stop or change speed after a particular time. If a towed ship breaks free after its tower hit a mine, for example, it can use the remaining time not used by its tower to reach its own waypoint.
The distance a ship is allowed to move is computed using the identity
Movement_distance = Max_dist * Movement_time
Fuel_usage = Trunc((ERnd(Total_ship_mass/10) * Distance * Engine_fuel_usage) / (10000 * Max_dist)) ...if UseAccurateFuelModel is off Total_ship_mass * (1.0 - Exp(-(Engine_fuel_usage * Distance) / (Max_dist * 100000))) ...if UseAccurateFuelModel is on
Note that the UseAccurateFuelModel formula can yield fractional values. In case ship movement occurs in multiple phases (because the ship hits a mine), these fractional values are added up, and arithmetically rounded at the end. In contrast, non-accurate movement rounds after every step.
If the ship successfully finishes its journey (i.e. does not hit a mine), Distance is the same as Movement_distance. This is what client programs assume to predict your fuel usage.
The Engine_fuel_usage is part of the engine definition (engspec.dat), and differs for all engines and speeds. Most reference sheets tabulate Engine_fuel_usage / (Warp^2) because that yields nicer numbers.
Ship movement is computed in exact numbers. PHost computes the angle to the ship's waypoint, and moves it there using double-precision floating point. Thus, it is possible that a ship hits a mine after 17.3 ly at coordinates (2351.56, 1792.35). For reporting, and for actually placing the ship in the game universe, PHost has to round.
New_position = (Waypoint_X, Waypoint_Y) ...if Distance_moved = Distance_to_waypoint (i.e. waypoint reached exactly) (Position_X + Ceil(Distance_moved * Cos(Heading)), Position_Y + Ceil(Distance_moved * Sin(Heading))) ...with Heading = ArcTan(Waypoint_X - Position_X, Waypoint_Y - Position_Y)
Two noteworthy properties of this formula:
For example, if you set a 90 ly waypoint, your ship will most likely move, say, 81.4 ly, but will only burn fuel for 81 ly, and its ramscoop will only make fuel for 81 ly.
Ships with Ramscoop gather up thin intersteller matter during movement, and convert that into fuel. This fuel is available after movement. It can not be used for movement. If the ship hits a web mine, it will first lose fuel to the webs, and then receive its ramscoop fuel (i.e. it will end the turn with fuel, even if the webs sucked it dry before).
Fuel_made_by_ramscoop = Trunc(RamScoopFuelPerLY * (Distance_moved_uncloaked + 0.5*Distance_moved_cloaked))
A ship can move a part of its journey cloaked and another part uncloaked if it hits a mine that brings it above the DamageLevelForCloakFail limit.
Only regular movement produces fuel. Hyperjumping, chunneling, and all movement not produced by the ship's engines (i.e. being towed, ion storm, etc.) do not cause fuel to be made. The ship can, however, tow another ship or intercept a ship. Reportedly, ships do not make fuel while intercepting in some HOST versions; hence in PHost up to 4.0e, they don't make fuel either. In current versions, this restriction no longer exists.
A planet's mass will pull every ship in a certain distance towards the planet (default: 3 ly). This can be used to save fuel: move just to the edge of the warp well and let the planet pull you. To move inside a warp well, you must go at Warp 1; Warp 1 ships are not pulled.
Preconditions:
A ship is pulled towards the planet for which the following holds:
If more than one planet is in range, the ship is pulled towards the one with the highest Id. If no planet is in range, the ship is not affected.
While the ship is moved by gravity wells, it does not need fuel, and is not in danger of hitting mines.
PHost's movement method requires all waypoints to be known before movement starts. For intercept, it therefore attempts to sort ships into an order such that all intercept targets move before their respective interceptors. In case of circular intercept, this cannot be done, so it is treated specially:
Waypoint_X = (Sum(Position_X) + Adjust_X) / Num_ships_in_loop Waypoint_Y = (Sum(Position_Y) + Adjust_Y) / Num_ships_in_loop Adjust_X = 0 ...if AllowWraparoundMap is off i * Map_dimension_X ...where 0 <= i < Num_ships_in_loop such that Sum((Position_X - Waypoint_X)^2) is minimal Adjust_Y = 0 ...if AllowWraparoundMap is off j * Map_dimension_Y ...where 0 <= j < Num_ships_in_loop such that Sum((Position_Y - Waypoint_Y)^2) is minimal
The Adjust_X and Adjust_Y parameters are used only in wraparound-map games, and attempt to make sure that ships move the minimum possible distance. They are found by iterating all possible i and j values and picking the best fit. In PHost below 4.0k/3.4m, they are always zero.
Hyperjump ships can use their hyperdrive instead of the normal engines to move. See the HYP friendly code for preconditions for hyperjumping.
Fuel_used = 50 New_position = (Waypoint_X, Waypoint_Y) ...if Distance_to_waypoint >= 340 and Distance_to_waypoint <= 360 (Position_X + Sgn(DX) * Trunc(350 * Abs(DX) / Distance_to_waypoint) + 0.4999999, Position_Y + Sgn(DY) * Trunc(350 * Abs(DY) / Distance_to_waypoint) + 0.4999999) ...with DX = Waypoint_X - Position_X DY = Waypoint_Y - Position_Y
Glory Devices, when set off, do damage to all ships and planets at the same location. The Glory Device ship itself will explode and thus be destroyed.
Damage_done_to_ship = Trunc(Trunc(100 * MineHitDamageFor100KT / Hull_mass) / DF) ...with DF = 10 ...if it's a GloryDeviceLowDamage (the advanced version), and ship owned by same player as glory device or players are allied, and GD player offers ship and combat level DF = 5 ...if it's a GloryDeviceHighDamage (the simple version) and ship owned by same player as glory device or players are allied, and GD player offers ship and combat level DF = 1 ...otherwise Supplies_added_to_planet = Trunc(Native_clans / 10) ...if natives are Amorphous 0 ...otherwise Native_clans_survived = 0 ...if natives are Amorphous Native_clans ...if natives are Bovinoid or Reptilian Trunc(0.6 * Native_clans) ...otherwise Colonist_clans_survived = Colonist_clans ...if colonists are Lizard or Crystal Trunc(0.6 * Colonist_clans) ...otherwise Mines_remaining = Trunc(0.75 * Mines) Factories_remaining = Trunc(0.75 * Factories) Defense_posts_remaining = Trunc(0.75 * Defense_posts)
Using the colonize mission, ships can land on planets, be unloaded and disassembled, to start a new colony. You use that only if you want to get rid of a ship, normally, it suffices to unload colonists to start a colony.
Colonist_clans_on_planet =
Colonist_clans_on_ship + Trunc(Ship_crew / 100) + 1
Neutronium_added_to_planet =
Neutronium_on_ship
Money_added_to_planet =
Money_on_ship
Tritanium_added_to_planet =
Tritanium_on_ship + Trunc(RecycleRate * Tritanium_built_into_ship / 100)
...with
Tritanium_built_into_ship =
Hull_tritanium_cost
+ Engine_tritanium_cost * Num_engines
+ Beam_tritanium_cost * Num_beams
+ Torp_launcher_tritanium_cost * Num_torp_launchers
Duranium and Molybdenum are recycled according to the same formula as Tritanium.
Note that ammunition on the ship is not recycled, and does not yield minerals for the colony.
Trunc(100 - 2*Damage/3) ...if ship is Lizard Trunc(100 - Damage) ...otherwise
Hull_mass + Trunc(ESB_Rate * Engine_cost / 100)
...plus 50 if ship is Fed
The Engine_cost is the monetary cost of one engine of
the ship. The ESB_Rate is the sum of the following
components:
Beam_count = Ship_beams ...if ship has Full Weaponry Min(Ship_beams, Max_beams - Trunc(Max_beams * Damage / Divisor)) ...otherwise Tube_count = Ship_tubes ...if ship has Full Weaponry Min(Ship_tubes, Max_tubes - Trunc(Max_tubes * Damage / Divisor)) ...otherwise Bay_count = Basis_bays + Bonus_bays ...with Basis_bays = Ship_bays ...if ship has Full Weaponry Max_bays - Trunc(Max_bays * Ship_damage / Divisor) ...otherwise Bonus_bays = ExtraFighterBays + EModExtraFighterBays ...if Basis_bays > 0 0 ...otherwise Divisor = 150 ...if ship is Lizard 100 ...otherwiseMax_beams, Max_tubes and Max_bays are the maximum amounts allowed by the ship's hull.
A planet's defense is reduced if the planet gets damaged.
Eff_Planet_Defense = Trunc(Planet_Defense * (100 - Damage) / 100) Eff_Base_Defense = Trunc(Base_Defense * (100 - Damage) / 100) Eff_Total_Defense = Trunc((Base_Defense + Planet_Defense) * (100 - Damage) / 100)
Note that Eff_Total_Defense can differ from Eff_Planet_Defense + Eff_Base_Defense due to rounding.
A base's tech level is reduced if the base gets damaged. Here's the formula for beam tech, it applies to the other areas as well.
Eff_Beam_Tech = Beam_Tech ...at most Trunc((100 - Base_damage) / 10) ...at least 1
Planet's equipment in combat:
Combat_mass = 100 + Eff_Planet_Defense ...plus Eff_Base_Defense if base exists Beam_type = Round(Sqrt(Eff_Planet_Defense / 2)) ...if no base Max(above, Eff_Beam_Tech) ...if base present Beam_count = Round(Sqrt(Eff_Total_Defense / 3)) ...at most 10 if AllowAlternativeCombat is off ...at most 20 if AllowAlternativeCombat is on Torp_type = Round(Sqrt(Eff_Planet_Defense / 2)) ...if no base Max(above, Eff_Torp_Tech) ...if base present Tube_count = Round(Sqrt(Eff_Total_Defense / 4)) ...at most 20 Torp_count = Tube_count * PlanetaryTorpsPerTube ...plus Trunc(Base_torp_cost / Torp_money_cost(Torp_type)) if base present ...at most 255 Base_torp_cost = Torp_money_cost(1) * Torps_in_storage(1) + Torp_money_cost(2) * Torps_in_storage(2) ... + Torp_money_cost(10) * Torps_in_storage(10) Bay_count = Round(Sqrt(Eff_Planet_Defense)) ...plus 5 if base present Fighter_count = Round(Sqrt(Eff_Planet_Defense)) + Base_fighters
Beam_hit_odds = Eff_BeamHitOdds + Trunc((Beam_Kill_Power + Beam_Expl_Power) * Eff_BeamHitBonus / 100) Beam_recharge_rate = BeamRechargeRate + Trunc((Beam_Kill_Power + Beam_Expl_Power) * Eff_BeamRechargeBonus / 100) Beam_recharge_per_tick = Random(Beam_recharge_rate) Torp_hit_odds = Eff_TorpHitOdds + Trunc((Torp_Kill_Power + Torp_Expl_Power) * Eff_TorpHitBonus / 100) Torp_recharge_rate = Eff_TubeRechargeRate + Trunc((Torp_Kill_Power + Torp_Expl_Power) * Eff_TubeRechargeBonus / 100) Torp_recharge_per_tick = Random(Torp_recharge_rate) Bay_recharge_rate = Eff_BayRechargeRate + Eff_BayRechargeBonus * Num_Bays Bay_recharge_per_tick = Random(Bay_recharge_rate)
Eff_XXX means the effective value of a configuration option, which consists of the actual configuration value plus the experience modifier of the unit using the weapon.
Every weapon hit is defined by three values:
Beams: The Expl_Power and Kill_Power are taken from the weapon definition. A beam emits death rays if its Expl_Power is zero. If the owner of the weapon is Privateer, the Kill_Power is tripled. The beam's charge affects its power; a beam fired at charge 600 has only 60% of its normal power.
Expl_Power = Trunc(Beam_expl_power * Beam_charge / 1000) Kill_Power = Trunc(Beam_kill_power * Beam_charge / 1000) * PF ...with PF=3 if beam owner is privateer, PF=1 otherwise
Torpedoes: The Expl_Power and Kill_Power are taken from the weapon definition. If AllowAlternativeCombat is off, the values are doubled (note that some clients already display the doubled values). A torpedo emits death rays if its Expl_Power is zero.
Fighters: The Expl_Power and Kill_Power are defined by FighterBeamExplosive and FighterBeamKill, respectively. Fighters never fire death rays.
The following formulas are used if AllowAlternativeCombat is off. In this case, all results are integers, weapon hits do at least one point of damage.
Shield_damage =
Trunc(1.5 + ((Expl_Power * Eff_ShieldDamageScaling
+ Kill_Power * Eff_ShieldKillScaling) / (Mass + 1)))
Hull_damage =
Trunc(1.5 + ((Shield_damage * Eff_HullDamageScaling / (Mass + 1)))
Crew_killed =
Trunc(0.5 + ((Kill_Power * Eff_CrewKillScaling) / (Mass + 1)))
...at least 1 if the weapon emits Death Rays.
The following formulas are used if AllowAlternativeCombat is on. In this case, results can be fractional numbers, and damages are tracked in fractions. Therefore, weapon hits can do less than one point of damage.
Shield_damage = (Expl_Power * Eff_ShieldDamageScaling + Kill_Power * Eff_ShieldKillScaling) / (Mass + 1) Hull_damage = (Expl_Power * Eff_HullDamageScaling) / (Mass + 1) Crew_killed = (Kill_Power * Eff_CrewKillScaling) / (Mass + 1)
Eff_XXX means the effective value of a configuration option, which consists of the actual configuration value plus any experience modifier of the unit being hit (these are the defensive bonuses).
Ship captured: If a ship was captured in combat (because it lost its crew), it changes ownership. It receives a small emergency crew (the capturing ship doesn't lose any crew beyond that killed in combat, though).
Crew_after_combat = Min(10, Hull_crew)
If a Borg ship (PlayerRace = 6) destroys another ship, it will attempt to beam up debris of the destroyed ship.
Neutronium_gathered = Neutronium_on_ship Tritanium_gathered = Tritanium_on_ship + Hull_tritanium_cost
Duranium and Molybdenum are gathered using the same formula as Tritanium. The order is Tritanium, Duranium, Molybdenum; if the cargo room is full before all minerals are gathered, the excess is not loaded up.
Planet lost: If a planet was lost in combat, it changes ownership. If the planet had a starbase, that one is destroyed.
Defense_after_combat = 0 Colonists_after_combat = Trunc(Colonists * ColonistCombatSurvivalRate / 100) Colonist_happiness_change = -Trunc(ColonistCombatSurvivalRate / 2) Experience_points_after_combat = Trunc(Experience_points * ColonistCombatSurvivalRate / 100) Natives_after_combat = Trunc(Natives * NativeCombatSurvivalRate / 100) Native_happiness_change = -20
Planet won: Ammunition that was fired in combat is used from the starbase's storage first. The base also takes damage first.
Fighters_lost = Min(Fighters_launched_in_combat, Base_fighters)
Torpedoes used in combat are made up of all types' storage, so after combat, the base loses torpedoes of all types. PHost uses the following algorithm:
Torpedo_costs = Torps_fired * Torp_money_cost(Torp_type) Repeat For i:=1 To 10 (iterate through all torpedo types) If (Torps_in_storage(i) > 0) And (Torpedo_costs > Torp_money_cost(i)) Remove one torpedo of type i Decrease Torpedo_costs by Torp_money_cost(i) EndIf EndFor Until the above loop didn't find any more torpedoes to remove
This attempts to make sure losses are distributed equally among all types.
Ending Status: The planet can take part in several fights during a turn. After all these fights are completed, damage effects are computed.
Base_damage_after_combat =
Base_damage + Damage_taken
...if that is 100 or more, the base is destroyed
Base_defense_after_combat =
Trunc(Base_defense * Damage_taken / 100)
Max_base_tech =
Max(1, Trunc((100 - Base_damage_after_combat) / 10))
Defense_after_combat =
Trunc(Planet_defense * Damage_taken / 100)
The Max_base_tech is the maximum tech level the base can have after combat. Tech levels above that limit are reduced.
Minefields consist of an integer number of minefield units. From that, the minefield's radius is computed:
Minefield_radius = Sqrt(Mine_units)
Internally, PHost uses that exact value. In messages, the radius is reported as Trunc(Minefield_radius), the diameter is reported as 2 * Trunc(Minefield_radius).
Some clients evaluate the Mine_units directly to plot minefields a little more exact. For example, PCC plots Round(Minefield_radius).
Each turn, minefields decay and shrink a little.
Mine_units_after_decay = Trunc(Mine_units * (100 - MineDecayRate) / 100) ...if minefield contains normal mines Trunc(Mine_units * (100 - WebMineDecayRate) / 100) ...if minefield contains web mines
Torpedo ships can lay mines. The same formulas apply to web laying.
Torps_laid = Min(Torps_available, Trunc((Max_units - Existing_units) / Conversion_rate)) ...0 if Conversion_rate is zero or Existing_units >= Max_units ...where Conversion_rate = Trunc(Torp_type^2 * Min(UnitsPerTorpRate[Ship_owner], UnitsPerTorpRate[Minefield_owner]) / 100) Max_units = MaximumMinefieldRadius[Minefield_owner] ^ 2 ...if it is a normal mine field MaximumWebMinefieldRadius[Minefield_owner] ^ 2 ...if it is a web field Units_laid = Torps_laid * Conversion_rate
Existing_units is the number of mine units in the field we're adding to, 0 if it is a new field. Torps_available is the number of Torps allowed for laying using mdX or an extended mission like Lay Minefield. Note that Ship_owner and Minefield_owner can differ if miX or an extended mission is used.
PHost refuses adding torpedoes to a minefield that is close to its maximum size if that would exceed the maximum. For example, Robots cannot add a Mark 8 torpedo (type 10) to a 99 ly (=9801 units) minefield if the maximum radius is 100 ly, because 9801+400 = 10201 which is more than the allowed 10000. In pathological configurations, PHost may even refuse to lay a new minefield if laying a single torpedo would already exceed the limit.
Preconditions: If two minefields overlap, i.e. Radius_1 + Radius_2 > Sqrt((X_1-X_2)^2 + (Y_1-Y_2)^2), they interact unless any of the following holds:
In the 1:1 case, i.e. two hostile minefields overlapping, the following formula is used:
Mines_exploding = Min(Units_1, Units_2) ...if Dist = 0 Units_1 ...if A < 0 Units_2 ...if A > D Units_1 - A^2 ...otherwise ...with D = Distance between minefield centers A = (Units_1 - Units_2 + D^2) / (2*D) Units_1, Units_2 = Number of mine units contained in both fields
This formula is used with AlternativeMinesDestroyMines enabled when a minefield is laid or enlarged to overlap an old one. In this case, PHost iterates through all other minefields in Id order, checks whether there is a hostile overlap, and applies the above formula.
During the Mines destroy Mines stage of host processing, PHost uses more complicated formulas. The basic ideas are the following: a minefield that overlaps N hostile minefields shrinks at a speed of N minefield units per time unit. For each minefield pair, we can therefore compute the time until the overlap is gone. PHost now computes the minimum such time, and removes N * Time units from all minefields. At least one overlap is gone now. All the speeds are now recomputed, and the algorithm restarts if overlaps remain.
Time_until_overlap_gone = Min(U1 / S1, U2 / S2) ...if D=0 (i.e. concentric minefields) U1 / S1 ...if U2*S1 - U1*S2 >= D^2 * S1 (i.e. field 2 eliminates field 1 completely) U2 / S2 ...if U1*S2 - U2*S1 >= D^2 * S2 (i.e. field 1 eliminates field 2 completely) (U1 - A^2) / S1 ...if S1 = S2 ((U1-U2-D^2)*S1 - (U1-U2+D^2)*S2 + 2*Sqrt(D^2 * (U2*S1^2 - (U1+U2-D^2)*S1*S2 + U1*S2^2))) / (S1 - S2)^2 ...otherwise ...with D = Distance between minefield centers A = (U1 - U2 + D^2) / (2*D) U1,U2 = Mine units in both fields (Units_1, Units_2) S1,S2 = Shrink speeds, i.e. overlap counts for both fields
The frightening last formula is the solution to Sqrt(U1 - S1*x) + Sqrt(U2 - S2*x) = D obtained using Mathematica.
PHost's actual implementation is heavily optimized for speed. It does not consider all minefields at once; instead it groups them into possibly interacting partitions first (i.e. start by picking an arbitrary minefield, and add all that may interact with it considering owner, type and location, add all that may interact with the fields just added, and so on). In addition, to guarantee reasonable progress, it always advances time by a minimum amount Epsilon, which can make a minefield lose a handful more units than required to remove all overlaps. Epsilon currently is 1.0 divided by the maximum overlap occuring in the current partition, but at least 1/16, empirically chosen by testing as a compromise between precision and speed.
To predict mines-destroy-mines at client-side, it probably suffices to use an Epsilon of 1/16 all the time.
After computing all losses, PHost attempts to sort them in a way that they can be reasonably incorporated into the game data, and reported to players. The above formulas may yield results such as "minefield pair #17/#233 loses 0.12 units", but since the unit count must be an integer, PHost has to round again. Let Loss(A,B) be the loss of minefield pair A/B as computed according to the above algorithm. The amount ultimately reported using util.dat record 29 and the message will be
Rounded_loss(A,B) = Min(Ceil(Loss(A,B)), Units_A, Units_B)
New v4.1a: In case this formula causes a minefield to lose less mines than needed to resolve all overlaps, PHost will finally remove the excess using util.dat record 53. This generally happens only when many overlaps exist with a single field.
Mine sweeping comes in two flavours: normal sweeping and scooping. Scooping is selected using the msc friendly code or the Scoop Torpedoes extended mission. Mine sweeping scans all minefields in range, and destroys hostile minefields if possible. Mine scooping, in addition, gathers up torpedoes from a minefield owned by the same player as the ship, and turns it back into torpedoes.
Units_swept = Beam_contribution + Fighter_contribution Beam_contribution = Beam_count * Beam_type^2 * Sweep_rate ...where Sweep_rate is WebMineSweepRate or MineSweepRate ...if ship has beams, is within range, and field not hidden by ion storm 0 ...otherwise Fighter_contribution = Fighter_count * FighterSweepRate ...if ship has bays, and is within FighterSweepRange, and minefield is a normal minefield or ship is owned by colonies and AllowColoniesSweepWebs is enabled 0 ...otherwise Torps_scooped = Min(Trunc(Mine_units / Conversion_rate), Free_cargo_room, Mission_limit) ...where Conversion_rate = Trunc(Torp_type^2 * UnitsPerTorpRate[Ship_owner] / 100) Units_scooped = Mine_units ...if Trunc(MineUnits / Conversion_rate) = Torps_scooped Torps_scooped * Conversion_rate ...otherwise
The Units_scooped formula put in other words: If, after scooping the requested number of torpedoes, the equivalent of a fractional torpedo remains (i.e. less than Conversion_rate mine units), the minefield is completely removed.
If the Scoop Torpedoes mission is used, the parameter specifies the maximum number of torpedoes to scoop from each applicable minefield, not the total amount. This is denoted by the Mission_limit variable above. If the regular Mine Sweep mission together with the msc friendly code is used, there is no Mission_limit.
The probability that a ship hits a mine depends on the type of the minefield and the ship's speed.
Mine_hit_odds = 0 ...if Speed <= Safe_warp Max(0, Base - (9 - Speed) * Bonus / 100) ...with (Base, Bonus) = WebMineHitOdds, WebMineOddsWarpBonusX100 ...if web minefield MineHitOdds, MineOddsWarpBonusX100 ...if ship is not cloaked, normal minefield MineHitOddsWhenCloakedX10/10, CloakMineOddsWarpBonusX100 ...if ship is cloaked, normal minefield
If you put the normal integer option values into this formula, you will get out a (possibly fractional) percentage, 0 to 100. This is the probability that the ship hits a mine when moving through 1 ly of the minefield.
All relevant options are arrayized and can therefore be different for each player. PHost consults the ship owner's value when moving a particular ship. It does not matter who owns the minefield.
PHost uses standard probability calculus to determine whether a ship successfully passes a minefield and, if it hits a mine, where that happens. For your convenience, here is the formula that a ship successfully passes a minefield of a given size (which is not in this form used in PHost):
Percentage = 100 * (((100 - Mine_hit_odds)/100) ^ Distance)
Damage_taken = Trunc(100 * MineHitDamageFor100KT / Hull_mass) ...for normal mines Trunc(100 * WebHitDamageFor100KT / Hull_mass) ...for web mines
Other effects of a mine hit (all of them are checked, in this order):
The size of the wormhole entry point depends on its mass.
Wormhole_radius = (Wormhole_mass ^ (WrmEntryPowerX100 / 100)) / 2
Note that PHost uses the exact fractional value internally. Ufos will report Trunc(Wormhole_radius) (versions up to 3.3d always report 5).
Endpoint Movement: Wormhole endpoints move each turn. They move towards their waypoint up to WrmDisplacement lightyears in X/Y direction, and add some random jitter of WrmRandDisplacement.
Endpoint_displacement = Waypoint_displacement + Random_displacement ...where Random_displacement = Random(1 + 2*WrmRandDisplacement) - WrmRandDisplacement Waypoint_displacement = Waypoint_position - Endpoint_position ...at most WrmDisplacement ...at least -WrmDisplacement
Size changes: Wormholes change their mass and instability each turn.
Mass_change = WrmMassAdd + Random(1 + 2*WrmRandMass) - WrmRandMass Instability_change = -WrmStabilityAddX10 / 10 + Random(1 + 2*WrmRandStability) - WrmRandStability
The mass is an integer in the range 1..32767, the instability is a possibly fractional value in range 0..100. Note that if the mass drops to zero by this process, the wormhole dies, and PHost will set the start and end coordinates to (0,0).
Let Ship_dist be the distance between the scanning ship and the center of the wormhole entry point (the X,Y from wormhole.txt).
Deterministic Scanning: A ship sees all wormholes for which Ship_dist <= WrmScanRange. A ship with the ScansAllWormholes function sees all wormholes for which Ship_dist <= 2 * WrmScanRange. These rules are only applied if WrmScanRange is not 0.
Probabilistic Scanning: The chance to see a wormhole depends on its mass.
Detection_radius = 10 * Wormhole_mass ^ (1/3) Detection_chance = 100% ...if Ship_dist <= Detection_radius (4 - (Ship_dist/Detection_radius)^2) * 33.3% ...if Detection_radius < Ship_dist <= 2*Detection_radius 0% ...if Ship_dist > 2*Detection_radius
For every wormhole, a dice is rolled.
Ships must be within Wormhole_radius lightyears from the endpoint's center to enter a wormhole.
Wormhole travel imposes some stress on the wormhole. The higher the stress, the higher the chance of a travel failure.
Wormhole_stress = 0 ...if Ship_mass < Wormhole_mass ((Ship_mass / Wormhole_mass) - 1)^2 ...otherwise Travel_failure_odds = Wormhole_stress + Wormhole_instability + (Wormhole_stress*Wormhole_instability / 10) Travel_failure_figure = Random(100) Travel_fuel_usage = Fuel_usage(Equiv_distance, WrmTravelWarpSpeed) ...where Equiv_distance = Wormhole_distance / WrmTravelDistDivisor
Note that PHost up to version 4.0j/3.4l truncates the
Travel_failure_odds value to an integral number. Later
versions use the exact value.
Possible outcomes of wormhole travel:
Travel_fuel_usage > Fuel Travel fails: New_ship_X = 1000 + Random(2001) New_ship_Y = 1000 + Random(2001) Damage_taken = 25 + Random(75) Travel_fuel_usage <= Fuel and Travel_failure_figure >= Travel_failure_odds Safe travel: New_ship_X = Endpoint_X - 10 + Random(21) New_ship_Y = Endpoint_Y - 10 + Random(21) Damage_taken = 0 Travel_fuel_usage <= Fuel and Travel_failure_figure < Travel_failure_odds Successful travel with damage taken: New_ship_X = Endpoint_X - 10 + Random(21) New_ship_Y = Endpoint_Y - 10 + Random(21) Damage_taken = (Travel_failure_odds - Travel_failure_figure) ^ 2
The wormhole instability provides a rating for the safety of this wormhole. It is summarized and reported to players using the following levels:
Wormhole_stability_rating = "very stable" ...if Wormhole_instability <= 5 "stable" ...if Wormhole_instability <= 15 "mostly stable" ...if Wormhole_instability <= 30 "unstable" ...if Wormhole_instability <= 50 "very unstable" ...if Wormhole_instability <= 80 "completely unstable" ...otherwise
Ion storm classes are described in the Rules section.
Speed = 8 ...if Voltage > 250 6 ...if Radius < 200 2 + Random(3) ...otherwise, i.e. random between 2 and 4 Heading = Old_heading - 10 + Random(21) ...i.e. change by +/- 10 degrees
Voltage_change = 2 * Random(6) ...i.e. 0 to 10 in steps of two Radius_change = Random(4) ...i.e. 0 to 3 New_voltage = Old_voltage + Voltage_change + Additional_gain New_radius = Old_radius - Radius_change ...if Old_radius > Radius_change 1 - (Old_radius - Radius_change) ...otherwise
The Additional_gain consists of four bonuses that are given to each storm. Note that, because growing storms always have odd voltages, adding one will turn the storm into a weakening one. All four conditions are checked, so the bonus may be anything between zero and four.
Additional_gain = +1 ...if second rule was used for New_radius +1 ...with a 1% chance +1 ...with a 2.5% chance if New_voltage > 320 +1 ...with a 10% chance if New_voltage > 500
Voltage_change = 4 + 2 * Random(6) ...i.e. 4 to 14 in steps of two Radius_change = Random(11) ...i.e. 0 to 10 New_voltage = Old_voltage - Voltage_change ...Storm disappears if this is zero or less New_radius = Old_radius + Radius_change
Pancaking Effect: The pancaking effect has a probability of 1:33 to occur, and happens after normal radius and voltage changes. This may change the storm into a growing one.
Voltage_after_pancaking = Round(Sqrt(New_voltage)) Radius_after_pancaking = 2 * New_radius
Storms Id_1 and Id_2 merge if they partially overlap: Distance_of_centers^2 <= Radius_1^2 + Radius_2^2.
New_X = Trunc((X_1 * Voltage_1 + X_2 * Voltage_2) / (Voltage_1 + Voltage_2)) New_Y = Trunc((Y_1 * Voltage_1 + Y_2 * Voltage_2) / (Voltage_1 + Voltage_2)) ...i.e. weighed average; closer to center of stronger storm New_Radius = Trunc(Sqrt(Radius_1^2 + Radius_2^2)) ...i.e. the storm is larger than each of the original ones New_voltage = Trunc((Voltage_1 * Radius_1^2 + Voltage_2 * Radius_2^2) / (Radius_1^2 + Radius_2^2)) ...i.e. weighed average
The Id of the new storm is the same as the Id of the stronger storm; if they are equally strong, it is the higher of the two. The new heading and speed are taken from that storm.
Whether the new storm is growing or weakening depends upon whether the computed New_voltage is odd or even.
Ships: All storms de-cloak ships that don't have advanced cloaking. Storms stronger than 150 MeV affect all ships that do not cloak or were thus uncloaked, and which do not have an Ion Shield.
Essentially, this means that ships with advanced cloak are safe from ion storms while they are cloaked. However, this means they cannot do anything else while inside the storm. Ships with Ion Shield, in contrast, can do any mission without being in danger of getting damaged. The Ion Shield alone will not help them stay cloaked, though.
Ship_new_location_X = Round(Ship_location_X + Sin(Heading) * Distance) Ship_new_location_Y = Round(Ship_location_Y + Cos(Heading) * Distance) ...with Distance = 0.75 * Storm_warp^2 Heading = Storm_heading * PI/180 (i.e. converted to radiant) Ship_damage_taken = Trunc((Random(200) + Storm_voltage - Ship_mass - 20 * Ship_engine) * Exp_modificator) ...with Exp_modificator = 1 ...if NumExperienceLevels = 0 1 - (Ship_experience_level / NumExperienceLevels) ...otherwise ...plus 50 if ship does not have fuel ...at least zero, of course Ship_damage = Ship_damage + Ship_damage_taken Ship_crew = Trunc(Ship_crew * (100 - Ship_damage_taken) / 100) Ship_experience_gain = Trunc(EPShipIonStorm100MEV * (Storm_voltage - 100) / 100)
Minefields: Ion storms can hide minefields from scanning ships. A minefield is hidden if all of the following conditions apply:
A hidden minefield cannot be swept or seen using beams alone. If the ship can use fighters to sweep (FighterSweepRate nonzero), those will see and sweep the minefield, though.
Last updated 15 July 2007.