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
26C4Weather::C4Weather()
27{
28 Default();
29}
30
31C4Weather::~C4Weather()
32{
33 Clear();
34}
35
36void 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
72void 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
151void C4Weather::Clear() {}
152
153bool 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
167int32_t C4Weather::GetWind(int32_t x, int32_t y)
168{
169 if (GBackIFT(x, y)) return 0;
170 return Wind;
171}
172
173int32_t C4Weather::GetTemperature()
174{
175 return Temperature;
176}
177
178bool 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
186void 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
196bool 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
205bool 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
217void 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
223void C4Weather::SetTemperature(int32_t iTemperature)
224{
225 Temperature = BoundBy<int32_t>(bval: iTemperature, lbound: -100, rbound: 100);
226 SetSeasonGamma();
227}
228
229void C4Weather::SetSeason(int32_t iSeason)
230{
231 Season = BoundBy<int32_t>(bval: iSeason, lbound: 0, rbound: 100);
232 SetSeasonGamma();
233}
234
235int32_t C4Weather::GetSeason()
236{
237 return Season;
238}
239
240void C4Weather::SetClimate(int32_t iClimate)
241{
242 Climate = BoundBy<int32_t>(bval: iClimate, lbound: -50, rbound: +50);
243 SetSeasonGamma();
244}
245
246int32_t C4Weather::GetClimate()
247{
248 return Climate;
249}
250
251static 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
259void 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
287void 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