| 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 | |
| 28 | static 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 | |
| 54 | void 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 | |
| 71 | bool 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 | |
| 154 | void 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 | |
| 166 | C4Sky::~C4Sky() |
| 167 | { |
| 168 | Clear(); |
| 169 | } |
| 170 | |
| 171 | void C4Sky::Clear() |
| 172 | { |
| 173 | delete Surface; Surface = nullptr; |
| 174 | Modulation = 0x00ffffff; |
| 175 | } |
| 176 | |
| 177 | bool 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 | |
| 193 | void 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 | |
| 206 | void 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 | |
| 230 | uint32_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 | |
| 238 | bool 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 | |
| 246 | void 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 | |