1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2020, 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/* Small member of the landscape class to handle the sky background */
18
19#include <C4Include.h>
20#include <C4Sky.h>
21
22#include <C4Game.h>
23#include <C4Random.h>
24#include <C4SurfaceFile.h>
25#include <C4Components.h>
26#include <C4Wrappers.h>
27
28static bool SurfaceEnsureSize(C4Surface **ppSfc, int iMinWdt, int iMinHgt)
29{
30 // safety
31 if (!ppSfc) return false; if (!*ppSfc) return false;
32 // get size
33 int iWdt = (*ppSfc)->Wdt, iHgt = (*ppSfc)->Hgt;
34 int iDstWdt = iWdt, iDstHgt = iHgt;
35 // check if it must be enlarged
36 while (iDstWdt < iMinWdt) iDstWdt += iWdt;
37 while (iDstHgt < iMinHgt) iDstHgt += iHgt;
38 if (iDstWdt == iWdt && iDstHgt == iHgt) return true;
39 // create new surface
40 C4Surface *pNewSfc = new C4Surface();
41 if (!pNewSfc->Create(iWdt: iDstWdt, iHgt: iDstHgt, fOwnPal: false))
42 {
43 delete pNewSfc;
44 return false;
45 }
46 // blit tiled into dest surface
47 lpDDraw->BlitSurfaceTile2(sfcSurface: *ppSfc, sfcTarget: pNewSfc, iToX: 0, iToY: 0, iToWdt: iDstWdt, iToHgt: iDstHgt, iOffsetX: 0, iOffsetY: 0, fSrcColKey: false);
48 // destroy old surface, assign new
49 delete *ppSfc; *ppSfc = pNewSfc;
50 // success
51 return true;
52}
53
54void C4Sky::SetFadePalette(int32_t *ipColors)
55{
56 // If colors all zero, use game palette default blue
57 if (ipColors[0] + ipColors[1] + ipColors[2] + ipColors[3] + ipColors[4] + ipColors[5] == 0)
58 {
59 uint8_t *pClr = Game.GraphicsResource.GamePalette + 3 * CSkyDef1;
60 FadeClr1 = C4RGB(pClr[0], pClr[1], pClr[2]);
61 FadeClr2 = C4RGB(pClr[3 * 19 + 0], pClr[3 * 19 + 1], pClr[3 * 19 + 2]);
62 }
63 else
64 {
65 // set colors
66 FadeClr1 = C4RGB(ipColors[0], ipColors[1], ipColors[2]);
67 FadeClr2 = C4RGB(ipColors[3], ipColors[4], ipColors[5]);
68 }
69}
70
71bool C4Sky::Init(bool fSavegame)
72{
73 int32_t skylistn;
74
75 // reset scrolling pos+speed
76 // not in savegame, because it will have been loaded from game data there
77 if (!fSavegame)
78 {
79 x = y = xdir = ydir = 0; ParX = ParY = 10; ParallaxMode = 0;
80 }
81
82 // Check for sky bitmap in scenario file
83 Surface = new C4Surface();
84 bool loaded = !!Surface->LoadAny(hGroup&: Game.ScenarioFile, C4CFN_Sky, fOwnPal: true, fNoErrIfNotFound: true);
85
86 // Else, evaluate scenario core landscape sky default list
87 if (!loaded)
88 {
89 // Scan list sections
90 SReplaceChar(str: Game.C4S.Landscape.SkyDef, fc: ',', tc: ';'); // modifying the C4S here...!
91 skylistn = SCharCount(cTarget: ';', szInStr: Game.C4S.Landscape.SkyDef) + 1;
92 StdStrBuf sky;
93 StdStrBuf::MakeRef(str: Game.C4S.Landscape.SkyDef).GetSection(idx: SeededRandom(iSeed: Game.Parameters.RandomSeed, iRange: skylistn), psOutSection: &sky, cSeparator: ';');
94 sky.TrimSpaces();
95 // Sky tile specified, try load
96 if (sky.getLength() && sky != "Default")
97 {
98 // Check for sky tile in scenario file
99 loaded = !!Surface->LoadAny(hGroup&: Game.ScenarioFile, szFilename: sky.getData(), fOwnPal: true, fNoErrIfNotFound: true);
100 if (!loaded)
101 {
102 loaded = !!Surface->LoadAny(hGroupset&: Game.GraphicsResource.Files, szFilename: sky.getData(), fOwnPal: true);
103 }
104 }
105 }
106
107 if (loaded)
108 {
109 FadeClr1 = FadeClr2 = 0xffffff;
110 // enlarge surface to avoid slow 1*1-px-skies
111 if (!SurfaceEnsureSize(ppSfc: &Surface, iMinWdt: 128, iMinHgt: 128)) return false;
112
113 // set parallax scroll mode
114 switch (Game.C4S.Landscape.SkyScrollMode)
115 {
116 case 0: // default: no scrolling
117 break;
118 case 1: // go with the wind in xdir, and do some parallax scrolling in ydir
119 ParallaxMode = C4SkyPM_Wind;
120 ParY = 20;
121 break;
122 case 2: // parallax in both directions
123 ParX = ParY = 20;
124 break;
125 }
126 }
127
128 // Else, try creating default Surface
129 if (!loaded)
130 {
131 SetFadePalette(Game.C4S.Landscape.SkyDefFade);
132 delete Surface;
133 Surface = nullptr;
134 }
135
136 // no sky - using fade in newgfx
137 if (!Surface)
138 return true;
139
140 // Store size
141 if (Surface)
142 {
143 int iWdt, iHgt;
144 if (Surface->GetSurfaceSize(irX&: iWdt, irY&: iHgt))
145 {
146 Width = iWdt; Height = iHgt;
147 }
148 }
149
150 // Success
151 return true;
152}
153
154void C4Sky::Default()
155{
156 Width = Height = 0;
157 Surface = nullptr;
158 x = y = xdir = ydir = 0;
159 Modulation = 0x00ffffff;
160 ParX = ParY = 10;
161 ParallaxMode = C4SkyPM_Fixed;
162 BackClr = 0;
163 BackClrEnabled = false;
164}
165
166C4Sky::~C4Sky()
167{
168 Clear();
169}
170
171void C4Sky::Clear()
172{
173 delete Surface; Surface = nullptr;
174 Modulation = 0x00ffffff;
175}
176
177bool C4Sky::Save(C4Group &hGroup)
178{
179 // Sky-saving disabled by scenario core
180 // (With this option enabled, script-defined changes to sky palette will not be saved!)
181 if (Game.C4S.Landscape.NoSky)
182 {
183 hGroup.Delete(C4CFN_Sky);
184 return true;
185 }
186 // no sky?
187 if (!Surface) return true;
188 // FIXME?
189 // Success
190 return true;
191}
192
193void C4Sky::Execute()
194{
195 // surface exists?
196 if (!Surface) return;
197 // advance pos
198 x += xdir; y += ydir;
199 // clip by bounds
200 if (x >= itofix(x: Width)) x -= itofix(x: Width);
201 if (y >= itofix(x: Height)) y -= itofix(x: Height);
202 // update speed
203 if (ParallaxMode == C4SkyPM_Wind) xdir = FIXED100(x: Game.Weather.Wind);
204}
205
206void C4Sky::Draw(C4FacetEx &cgo)
207{
208 // background color?
209 if (BackClrEnabled) Application.DDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: cgo.X, iY1: cgo.Y, iX2: cgo.X + cgo.Wdt, iY2: cgo.Y + cgo.Hgt, dwClr: BackClr);
210 // sky surface?
211 if (Modulation != 0xffffff) Application.DDraw->ActivateBlitModulation(dwWithClr: Modulation);
212 if (Surface)
213 {
214 // blit parallax sky
215 int iParX = cgo.TargetX * 10 / ParX - fixtoi(x);
216 int iParY = cgo.TargetY * 10 / ParY - fixtoi(x: y);
217 Application.DDraw->BlitSurfaceTile2(sfcSurface: Surface, sfcTarget: cgo.Surface, iToX: cgo.X, iToY: cgo.Y, iToWdt: cgo.Wdt, iToHgt: cgo.Hgt, iOffsetX: iParX, iOffsetY: iParY, fSrcColKey: false);
218 }
219 else
220 {
221 // no sky surface: blit sky fade
222 uint32_t dwClr1 = GetSkyFadeClr(iY: cgo.TargetY);
223 uint32_t dwClr2 = GetSkyFadeClr(iY: cgo.TargetY + cgo.Hgt);
224 Application.DDraw->DrawBoxFade(sfcDest: cgo.Surface, iX: cgo.X, iY: cgo.Y, iWdt: cgo.Wdt, iHgt: cgo.Hgt, dwClr1, dwClr2: dwClr1, dwClr3: dwClr2, dwClr4: dwClr2, iBoxOffX: cgo.TargetX, iBoxOffY: cgo.TargetY);
225 }
226 if (Modulation != 0xffffff) Application.DDraw->DeactivateBlitModulation();
227 // done
228}
229
230uint32_t C4Sky::GetSkyFadeClr(int32_t iY)
231{
232 int32_t iPos2 = (iY * 256) / GBackHgt; int32_t iPos1 = 256 - iPos2;
233 return (((((FadeClr1 & 0xff00ff) * iPos1 + (FadeClr2 & 0xff00ff) * iPos2) & 0xff00ff00)
234 | (((FadeClr1 & 0x00ff00) * iPos1 + (FadeClr2 & 0x00ff00) * iPos2) & 0x00ff0000)) >> 8)
235 | (FadeClr1 & 0xff000000);
236}
237
238bool C4Sky::SetModulation(uint32_t dwWithClr, uint32_t dwBackClr)
239{
240 Modulation = dwWithClr;
241 BackClr = dwBackClr;
242 BackClrEnabled = (Modulation >> 24) ? true : false;
243 return true;
244}
245
246void C4Sky::CompileFunc(StdCompiler *pComp)
247{
248 pComp->Value(rStruct: mkNamingAdapt(rValue: mkCastIntAdapt(rValue&: x), szName: "X", rDefault: Fix0));
249 pComp->Value(rStruct: mkNamingAdapt(rValue: mkCastIntAdapt(rValue&: y), szName: "Y", rDefault: Fix0));
250 pComp->Value(rStruct: mkNamingAdapt(rValue: mkCastIntAdapt(rValue&: xdir), szName: "XDir", rDefault: Fix0));
251 pComp->Value(rStruct: mkNamingAdapt(rValue: mkCastIntAdapt(rValue&: ydir), szName: "YDir", rDefault: Fix0));
252 pComp->Value(rStruct: mkNamingAdapt(rValue&: Modulation, szName: "Modulation", rDefault: 0x00ffffffU));
253 pComp->Value(rStruct: mkNamingAdapt(rValue&: ParX, szName: "ParX", rDefault: 10));
254 pComp->Value(rStruct: mkNamingAdapt(rValue&: ParY, szName: "ParY", rDefault: 10));
255 pComp->Value(rStruct: mkNamingAdapt(rValue&: ParallaxMode, szName: "ParMode", C4SkyPM_Fixed));
256 pComp->Value(rStruct: mkNamingAdapt(rValue&: BackClr, szName: "BackClr", rDefault: 0));
257 pComp->Value(rStruct: mkNamingAdapt(rValue&: BackClrEnabled, szName: "BackClrEnabled", rDefault: false));
258}
259