| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2022, The LegacyClonk Team and contributors |
| 6 | * |
| 7 | * Distributed under the terms of the ISC license; see accompanying file |
| 8 | * "COPYING" for details. |
| 9 | * |
| 10 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 11 | * See accompanying file "TRADEMARK" for details. |
| 12 | * |
| 13 | * To redistribute this file separately, substitute the full license texts |
| 14 | * for the above references. |
| 15 | */ |
| 16 | |
| 17 | /* Controls temperature, wind, and natural disasters */ |
| 18 | |
| 19 | #include <C4Include.h> |
| 20 | #include <C4Weather.h> |
| 21 | |
| 22 | #include <C4Object.h> |
| 23 | #include <C4Random.h> |
| 24 | #include <C4Wrappers.h> |
| 25 | |
| 26 | C4Weather::C4Weather() |
| 27 | { |
| 28 | Default(); |
| 29 | } |
| 30 | |
| 31 | C4Weather::~C4Weather() |
| 32 | { |
| 33 | Clear(); |
| 34 | } |
| 35 | |
| 36 | void C4Weather::Init(bool fScenario) |
| 37 | { |
| 38 | if (fScenario) |
| 39 | { |
| 40 | // Season |
| 41 | Season = Game.C4S.Weather.StartSeason.Evaluate(); |
| 42 | YearSpeed = Game.C4S.Weather.YearSpeed.Evaluate(); |
| 43 | // Temperature |
| 44 | Climate = 100 - Game.C4S.Weather.Climate.Evaluate() - 50; |
| 45 | Temperature = Climate; |
| 46 | // Wind |
| 47 | Wind = TargetWind = Game.C4S.Weather.Wind.Evaluate(); |
| 48 | // Precipitation |
| 49 | if (!Game.C4S.Head.NoInitialize) |
| 50 | if (Game.C4S.Weather.Rain.Evaluate()) |
| 51 | for (int32_t iClouds = (std::min)(GBackWdt / 500, b: 5); iClouds > 0; iClouds--) |
| 52 | { |
| 53 | volatile int iWidth = GBackWdt / 15 + Random(iRange: 320); |
| 54 | volatile int iX = Random(GBackWdt); |
| 55 | LaunchCloud(iX, iY: -1, iWidth, |
| 56 | iStrength: Game.C4S.Weather.Rain.Evaluate(), |
| 57 | szPrecipitation: Game.C4S.Weather.Precipitation); |
| 58 | } |
| 59 | // Lightning |
| 60 | LightningLevel = Game.C4S.Weather.Lightning.Evaluate(); |
| 61 | // Disasters |
| 62 | MeteoriteLevel = Game.C4S.Disasters.Meteorite.Evaluate(); |
| 63 | VolcanoLevel = Game.C4S.Disasters.Volcano.Evaluate(); |
| 64 | EarthquakeLevel = Game.C4S.Disasters.Earthquake.Evaluate(); |
| 65 | // gamma? |
| 66 | NoGamma = Game.C4S.Weather.NoGamma; |
| 67 | } |
| 68 | // set gamma |
| 69 | SetSeasonGamma(); |
| 70 | } |
| 71 | |
| 72 | void C4Weather::Execute() |
| 73 | { |
| 74 | // Season |
| 75 | if (!Tick35) |
| 76 | { |
| 77 | SeasonDelay += YearSpeed; |
| 78 | if (SeasonDelay >= 200) |
| 79 | { |
| 80 | SeasonDelay = 0; |
| 81 | Season++; |
| 82 | if (Season > Game.C4S.Weather.StartSeason.Max) |
| 83 | Season = Game.C4S.Weather.StartSeason.Min; |
| 84 | SetSeasonGamma(); |
| 85 | } |
| 86 | } |
| 87 | // Temperature |
| 88 | if (!Tick35) |
| 89 | { |
| 90 | int32_t iTemperature = Climate - static_cast<int32_t>(TemperatureRange * cos(x: 6.28 * static_cast<float>(Season) / 100.0)); |
| 91 | if (Temperature < iTemperature) Temperature++; |
| 92 | else if (Temperature > iTemperature) Temperature--; |
| 93 | } |
| 94 | // Wind |
| 95 | if (!Tick1000) |
| 96 | TargetWind = Game.C4S.Weather.Wind.Evaluate(); |
| 97 | if (!Tick10) |
| 98 | Wind = BoundBy<int32_t>(bval: Wind + Sign(val: TargetWind - Wind), |
| 99 | lbound: Game.C4S.Weather.Wind.Min, |
| 100 | rbound: Game.C4S.Weather.Wind.Max); |
| 101 | if (!Tick10) |
| 102 | SoundLevel(name: "Wind" , obj: nullptr, iLevel: (std::max)(a: Abs(val: Wind) - 30, b: 0) * 2); |
| 103 | // Disaster launch |
| 104 | if (!Tick10) |
| 105 | { |
| 106 | // Meteorite |
| 107 | if (!Random(iRange: 60)) |
| 108 | if (Random(iRange: 100) < MeteoriteLevel) |
| 109 | { |
| 110 | C4Object *meto; |
| 111 | // In cave landscapes, meteors must be created a bit lower so they don't hit the ceiling |
| 112 | // (who activates meteors in cave landscapes anyway?) |
| 113 | // force argument evaluation order |
| 114 | const auto r2 = Random(iRange: 100 + 1); |
| 115 | const auto r1 = Random(GBackWdt); |
| 116 | meto = Game.CreateObject(type: C4ID_Meteor, pCreator: nullptr, owner: NO_OWNER, |
| 117 | x: r1, y: Game.Landscape.TopOpen ? -20 : 5, r: 0, |
| 118 | xdir: itofix(x: r2 - 50) / 10, |
| 119 | ydir: Game.Landscape.TopOpen ? Fix0 : itofix(x: 2), rdir: itofix(x: 1) / 5); |
| 120 | } |
| 121 | // Lightning |
| 122 | if (!Random(iRange: 35)) |
| 123 | if (Random(iRange: 100) < LightningLevel) |
| 124 | { |
| 125 | LaunchLightning(x: Random(GBackWdt), y: 0, |
| 126 | xdir: -20, xrange: 41, ydir: +5, yrange: 15, fDoGamma: true); |
| 127 | } |
| 128 | // Earthquake |
| 129 | if (!Random(iRange: 50)) |
| 130 | if (Random(iRange: 100) < EarthquakeLevel) |
| 131 | { |
| 132 | // force argument evaluation order |
| 133 | const auto r2 = Random(GBackHgt); |
| 134 | const auto r1 = Random(GBackWdt); |
| 135 | LaunchEarthquake(iX: r1, iY: r2); |
| 136 | } |
| 137 | // Volcano |
| 138 | if (!Random(iRange: 60)) |
| 139 | if (Random(iRange: 100) < VolcanoLevel) |
| 140 | { |
| 141 | // force argument evaluation order |
| 142 | const auto r2 = Random(iRange: 10); |
| 143 | const auto r1 = Random(GBackWdt); |
| 144 | LaunchVolcano(mat: Game.Material.Get(szMaterial: "Lava" ), |
| 145 | x: r1, GBackHgt - 1, |
| 146 | size: BoundBy(bval: 15 * GBackHgt / 500 + r2, lbound: 10, rbound: 60)); |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | void C4Weather::Clear() {} |
| 152 | |
| 153 | bool C4Weather::LaunchLightning(int32_t x, int32_t y, int32_t xdir, int32_t xrange, int32_t ydir, int32_t yrange, bool fDoGamma) |
| 154 | { |
| 155 | C4Object *pObj; |
| 156 | if (pObj = Game.CreateObject(type: C4Id(str: "FXL1" ), pCreator: nullptr)) |
| 157 | pObj->Call(PSF_Activate, pPars: {C4VInt(iVal: x), |
| 158 | C4VInt(iVal: y), |
| 159 | C4VInt(iVal: xdir), |
| 160 | C4VInt(iVal: xrange), |
| 161 | C4VInt(iVal: ydir), |
| 162 | C4VInt(iVal: yrange), |
| 163 | C4VBool(fVal: !!fDoGamma)}); |
| 164 | return true; |
| 165 | } |
| 166 | |
| 167 | int32_t C4Weather::GetWind(int32_t x, int32_t y) |
| 168 | { |
| 169 | if (GBackIFT(x, y)) return 0; |
| 170 | return Wind; |
| 171 | } |
| 172 | |
| 173 | int32_t C4Weather::GetTemperature() |
| 174 | { |
| 175 | return Temperature; |
| 176 | } |
| 177 | |
| 178 | bool C4Weather::LaunchVolcano(int32_t mat, int32_t x, int32_t y, int32_t size) |
| 179 | { |
| 180 | C4Object *pObj; |
| 181 | if (pObj = Game.CreateObject(type: C4Id(str: "FXV1" ), pCreator: nullptr)) |
| 182 | pObj->Call(PSF_Activate, pPars: {C4VInt(iVal: x), C4VInt(iVal: y), C4VInt(iVal: size), C4VInt(iVal: mat)}); |
| 183 | return true; |
| 184 | } |
| 185 | |
| 186 | void C4Weather::Default() |
| 187 | { |
| 188 | Season = 0; YearSpeed = 0; SeasonDelay = 0; |
| 189 | Wind = TargetWind = 0; |
| 190 | Temperature = Climate = 0; |
| 191 | TemperatureRange = 30; |
| 192 | MeteoriteLevel = VolcanoLevel = EarthquakeLevel = LightningLevel = 0; |
| 193 | NoGamma = true; |
| 194 | } |
| 195 | |
| 196 | bool C4Weather::LaunchEarthquake(int32_t iX, int32_t iY) |
| 197 | { |
| 198 | C4Object *pObj; |
| 199 | if (pObj = Game.CreateObject(type: C4Id(str: "FXQ1" ), pCreator: nullptr, owner: NO_OWNER, x: iX, y: iY)) |
| 200 | if (pObj->Call(PSF_Activate)) |
| 201 | return true; |
| 202 | return false; |
| 203 | } |
| 204 | |
| 205 | bool C4Weather::LaunchCloud(int32_t iX, int32_t iY, int32_t iWidth, int32_t iStrength, const char *szPrecipitation) |
| 206 | { |
| 207 | if (Game.Material.Get(szMaterial: szPrecipitation) == MNone) return false; |
| 208 | C4Object *pObj; |
| 209 | if (pObj = Game.CreateObject(type: C4Id(str: "FXP1" ), pCreator: nullptr, owner: NO_OWNER, x: iX, y: iY)) |
| 210 | if (pObj->Call(PSF_Activate, pPars: {C4VInt(iVal: Game.Material.Get(szMaterial: szPrecipitation)), |
| 211 | C4VInt(iVal: iWidth), |
| 212 | C4VInt(iVal: iStrength)})) |
| 213 | return true; |
| 214 | return false; |
| 215 | } |
| 216 | |
| 217 | void C4Weather::SetWind(int32_t iWind) |
| 218 | { |
| 219 | Wind = BoundBy<int32_t>(bval: iWind, lbound: -100, rbound: +100); |
| 220 | TargetWind = BoundBy<int32_t>(bval: iWind, lbound: -100, rbound: +100); |
| 221 | } |
| 222 | |
| 223 | void C4Weather::SetTemperature(int32_t iTemperature) |
| 224 | { |
| 225 | Temperature = BoundBy<int32_t>(bval: iTemperature, lbound: -100, rbound: 100); |
| 226 | SetSeasonGamma(); |
| 227 | } |
| 228 | |
| 229 | void C4Weather::SetSeason(int32_t iSeason) |
| 230 | { |
| 231 | Season = BoundBy<int32_t>(bval: iSeason, lbound: 0, rbound: 100); |
| 232 | SetSeasonGamma(); |
| 233 | } |
| 234 | |
| 235 | int32_t C4Weather::GetSeason() |
| 236 | { |
| 237 | return Season; |
| 238 | } |
| 239 | |
| 240 | void C4Weather::SetClimate(int32_t iClimate) |
| 241 | { |
| 242 | Climate = BoundBy<int32_t>(bval: iClimate, lbound: -50, rbound: +50); |
| 243 | SetSeasonGamma(); |
| 244 | } |
| 245 | |
| 246 | int32_t C4Weather::GetClimate() |
| 247 | { |
| 248 | return Climate; |
| 249 | } |
| 250 | |
| 251 | static uint32_t SeasonColors[4][3] = |
| 252 | { |
| 253 | { 0x000000, 0x7f7f90, 0xefefff }, // winter: slightly blue; blued out by temperature |
| 254 | { 0x070f00, 0x90a07f, 0xffffdf }, // spring: green to yellow |
| 255 | { 0x000000, 0x808080, 0xffffff }, // summer: regular ramp |
| 256 | { 0x0f0700, 0xa08067, 0xffffdf } // fall: dark, brown ramp |
| 257 | }; |
| 258 | |
| 259 | void C4Weather::SetSeasonGamma() |
| 260 | { |
| 261 | if (NoGamma) return; |
| 262 | // get season num and offset |
| 263 | int32_t iSeason1 = (Season / 25) % 4; int32_t iSeason2 = (iSeason1 + 1) % 4; |
| 264 | int32_t iSeasonOff1 = BoundBy(bval: Season % 25, lbound: 5, rbound: 19) - 5; int32_t iSeasonOff2 = 15 - iSeasonOff1; |
| 265 | uint32_t dwClr[3]{}; |
| 266 | // interpolate between season colors |
| 267 | for (int32_t i = 0; i < 3; ++i) |
| 268 | for (int32_t iChan = 0; iChan < 24; iChan += 8) |
| 269 | { |
| 270 | uint8_t byC1 = uint8_t(SeasonColors[iSeason1][i] >> iChan); |
| 271 | uint8_t byC2 = uint8_t(SeasonColors[iSeason2][i] >> iChan); |
| 272 | int32_t iChanVal = (byC1 * iSeasonOff2 + byC2 * iSeasonOff1) / 15; |
| 273 | // red+green: reduce in winter |
| 274 | if (Temperature < 0) |
| 275 | if (iChan) |
| 276 | iChanVal += Temperature / 2; |
| 277 | else |
| 278 | // blue channel: emphasize in winter |
| 279 | iChanVal -= Temperature / 2; |
| 280 | // set channel |
| 281 | dwClr[i] |= BoundBy<int32_t>(bval: iChanVal, lbound: 0, rbound: 255) << iChan; |
| 282 | } |
| 283 | // apply gamma ramp |
| 284 | Game.GraphicsSystem.SetGamma(dwClr1: dwClr[0], dwClr2: dwClr[1], dwClr3: dwClr[2], C4GRI_SEASON); |
| 285 | } |
| 286 | |
| 287 | void C4Weather::CompileFunc(StdCompiler *pComp) |
| 288 | { |
| 289 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Season, szName: "Season" , rDefault: 0)); |
| 290 | pComp->Value(rStruct: mkNamingAdapt(rValue&: YearSpeed, szName: "YearSpeed" , rDefault: 0)); |
| 291 | pComp->Value(rStruct: mkNamingAdapt(rValue&: SeasonDelay, szName: "SeasonDelay" , rDefault: 0)); |
| 292 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Wind, szName: "Wind" , rDefault: 0)); |
| 293 | pComp->Value(rStruct: mkNamingAdapt(rValue&: TargetWind, szName: "TargetWind" , rDefault: 0)); |
| 294 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Temperature, szName: "Temperature" , rDefault: 0)); |
| 295 | pComp->Value(rStruct: mkNamingAdapt(rValue&: TemperatureRange, szName: "TemperatureRange" , rDefault: 30)); |
| 296 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Climate, szName: "Climate" , rDefault: 0)); |
| 297 | pComp->Value(rStruct: mkNamingAdapt(rValue&: MeteoriteLevel, szName: "MeteoriteLevel" , rDefault: 0)); |
| 298 | pComp->Value(rStruct: mkNamingAdapt(rValue&: VolcanoLevel, szName: "VolcanoLevel" , rDefault: 0)); |
| 299 | pComp->Value(rStruct: mkNamingAdapt(rValue&: EarthquakeLevel, szName: "EarthquakeLevel" , rDefault: 0)); |
| 300 | pComp->Value(rStruct: mkNamingAdapt(rValue&: LightningLevel, szName: "LightningLevel" , rDefault: 0)); |
| 301 | pComp->Value(rStruct: mkNamingAdapt(rValue&: NoGamma, szName: "NoGamma" , rDefault: false)); |
| 302 | uint32_t dwGammaDefaults[C4MaxGammaRamps * 3]; |
| 303 | for (int32_t i = 0; i < C4MaxGammaRamps; ++i) |
| 304 | { |
| 305 | dwGammaDefaults[i * 3 + 0] = 0x000000; |
| 306 | dwGammaDefaults[i * 3 + 1] = 0x808080; |
| 307 | dwGammaDefaults[i * 3 + 2] = 0xffffff; |
| 308 | } |
| 309 | pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: Game.GraphicsSystem.dwGamma), szName: "Gamma" , rDefault: dwGammaDefaults)); |
| 310 | } |
| 311 | |