1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2004, Sven2
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// graphics used by object definitions (object and portraits)
19
20#include <C4Include.h>
21#include <C4DefGraphics.h>
22
23#include <C4SurfaceFile.h>
24#include <C4Object.h>
25#include <C4ObjectInfo.h>
26#include <C4Config.h>
27#include <C4Components.h>
28#include <C4Application.h>
29#include <C4Game.h>
30#include <C4Menu.h>
31#include <C4ObjectMenu.h>
32#include <C4Player.h>
33#include <C4Log.h>
34
35// C4DefGraphics
36
37C4DefGraphics::C4DefGraphics(C4Def *pOwnDef)
38{
39 // store def
40 pDef = pOwnDef;
41 // zero fields
42 Bitmap = BitmapClr = nullptr;
43 pNext = nullptr;
44 fColorBitmapAutoCreated = false;
45}
46
47void C4DefGraphics::Clear()
48{
49 // zero own fields
50 delete BitmapClr; BitmapClr = nullptr;
51 delete Bitmap; Bitmap = nullptr;
52 // delete additonal graphics
53 C4AdditionalDefGraphics *pGrp2N = pNext, *pGrp2;
54 while (pGrp2 = pGrp2N) { pGrp2N = pGrp2->pNext; pGrp2->pNext = nullptr; delete pGrp2; }
55 pNext = nullptr; fColorBitmapAutoCreated = false;
56}
57
58bool C4DefGraphics::LoadGraphics(C4Group &hGroup, const char *szFilename, const char *szFilenamePNG, const char *szOverlayPNG, bool fColorByOwner)
59{
60 // try png
61 if (szFilenamePNG && hGroup.AccessEntry(szWildCard: szFilenamePNG))
62 {
63 Bitmap = new C4Surface();
64 if (!Bitmap->ReadPNG(hGroup)) return false;
65 }
66 else
67 {
68 if (szFilename)
69 if (!hGroup.AccessEntry(szWildCard: szFilename)
70 || !(Bitmap = GroupReadSurface(hGroup)))
71 return false;
72 }
73 // Create owner color bitmaps
74 if (fColorByOwner)
75 {
76 // Create additionmal bitmap
77 BitmapClr = new C4Surface();
78 // if overlay-surface is present, load from that
79 if (szOverlayPNG && hGroup.AccessEntry(szWildCard: szOverlayPNG))
80 {
81 if (!BitmapClr->ReadPNG(hGroup))
82 return false;
83 // set as Clr-surface, also checking size
84 if (!BitmapClr->SetAsClrByOwnerOf(Bitmap))
85 {
86 const char *szFn = szFilenamePNG ? szFilenamePNG : szFilename;
87 if (!szFn) szFn = "???";
88 DebugLog(level: spdlog::level::err, fmt: "Gfx loading error in {}: {} ({} x {}) doesn't match overlay {} ({} x {}) - invalid file or size mismatch",
89 args: hGroup.GetFullName().getData(), args&: szFn, args: Bitmap ? Bitmap->Wdt : -1, args: Bitmap ? Bitmap->Hgt : -1,
90 args&: szOverlayPNG, args&: BitmapClr->Wdt, args&: BitmapClr->Hgt);
91 delete BitmapClr; BitmapClr = nullptr;
92 return false;
93 }
94 }
95 else
96 // otherwise, create by all blue shades
97 if (!BitmapClr->CreateColorByOwner(pBySurface: Bitmap)) return false;
98 fColorBitmapAutoCreated = true;
99 }
100 // success
101 return true;
102}
103
104bool C4DefGraphics::LoadAllGraphics(C4Group &hGroup, bool fColorByOwner)
105{
106 // load basic graphics
107 if (!LoadGraphics(hGroup, C4CFN_DefGraphics, C4CFN_DefGraphicsPNG, C4CFN_ClrByOwnerPNG, fColorByOwner)) return false;
108 // load additional graphics
109 // first, search all png-graphics in NewGfx
110 char Filename[_MAX_PATH + 1]; *Filename = 0;
111 C4DefGraphics *pLastGraphics = this;
112 int32_t iWildcardPos;
113 iWildcardPos = SCharPos(cTarget: '*', C4CFN_DefGraphicsExPNG);
114 int32_t iOverlayWildcardPos = SCharPos(cTarget: '*', C4CFN_ClrByOwnerExPNG);
115 hGroup.ResetSearch();
116 while (hGroup.FindNextEntry(C4CFN_DefGraphicsExPNG, sFileName: Filename, iSize: nullptr, fChild: nullptr, fStartAtFilename: !!*Filename))
117 {
118 // skip def graphics
119 if (SEqualNoCase(szStr1: Filename, C4CFN_DefGraphicsPNG)) continue;
120 // get name
121 char GrpName[_MAX_PATH + 1];
122 SCopy(szSource: Filename + iWildcardPos, sTarget: GrpName, _MAX_PATH);
123 RemoveExtension(szFileName: GrpName);
124 // clip to max length
125 GrpName[C4MaxName] = 0;
126 // create new graphics
127 pLastGraphics->pNext = new C4AdditionalDefGraphics(pDef, GrpName);
128 pLastGraphics = pLastGraphics->pNext;
129 // create overlay-filename
130 char OverlayFn[_MAX_PATH + 1];
131 if (fColorByOwner)
132 {
133 // GraphicsX.png -> OverlayX.png
134 SCopy(C4CFN_ClrByOwnerExPNG, sTarget: OverlayFn, _MAX_PATH);
135 OverlayFn[iOverlayWildcardPos] = 0;
136 SAppend(szSource: Filename + iWildcardPos, szTarget: OverlayFn);
137 EnforceExtension(szFileName: OverlayFn, szExtension: GetExtension(C4CFN_ClrByOwnerExPNG));
138 }
139 // load them
140 if (!pLastGraphics->LoadGraphics(hGroup, szFilename: nullptr, szFilenamePNG: Filename, szOverlayPNG: fColorByOwner ? OverlayFn : nullptr, fColorByOwner))
141 return false;
142 }
143 // load bitmap-graphics
144 iWildcardPos = SCharPos(cTarget: '*', C4CFN_DefGraphicsEx);
145 hGroup.ResetSearch();
146 *Filename = 0;
147 while (hGroup.FindNextEntry(C4CFN_DefGraphicsEx, sFileName: Filename, iSize: nullptr, fChild: nullptr, fStartAtFilename: !!*Filename))
148 {
149 // skip def graphics
150 if (SEqualNoCase(szStr1: Filename, C4CFN_DefGraphics)) continue;
151 // get graphics name
152 char GrpName[_MAX_PATH + 1];
153 SCopy(szSource: Filename + iWildcardPos, sTarget: GrpName, _MAX_PATH);
154 RemoveExtension(szFileName: GrpName);
155 // clip to max length
156 GrpName[C4MaxName] = 0;
157 // check if graphics already exist (-> loaded as PNG)
158 if (Get(szGrpName: GrpName)) continue;
159 // create new graphics
160 pLastGraphics->pNext = new C4AdditionalDefGraphics(pDef, GrpName);
161 pLastGraphics = pLastGraphics->pNext;
162 // load them
163 if (!pLastGraphics->LoadGraphics(hGroup, szFilename: Filename, szFilenamePNG: nullptr, szOverlayPNG: nullptr, fColorByOwner))
164 return false;
165 }
166 // load portrait graphics
167 iWildcardPos = SCharPos(cTarget: '*', C4CFN_Portraits);
168 hGroup.ResetSearch();
169 *Filename = 0;
170 while (hGroup.FindNextEntry(C4CFN_Portraits, sFileName: Filename, iSize: nullptr, fChild: nullptr, fStartAtFilename: !!*Filename))
171 {
172 // get graphics name
173 char GrpName[_MAX_PATH + 1];
174 SCopy(szSource: Filename + iWildcardPos, sTarget: GrpName, _MAX_PATH);
175 RemoveExtension(szFileName: GrpName);
176 // clip to max length
177 GrpName[C4MaxName] = 0;
178 // determine file type (bmp or png)
179 char OverlayFn[_MAX_PATH + 1]; bool fBMP; *OverlayFn = 0;
180 if (SEqualNoCase(szStr1: GetExtension(fname: Filename), szStr2: "bmp"))
181 fBMP = true;
182 else
183 {
184 fBMP = false;
185 if (!SEqualNoCase(szStr1: GetExtension(fname: Filename), szStr2: "png")) continue;
186 // create overlay-filename for PNGs
187 if (fColorByOwner)
188 {
189 // PortraitX.png -> OverlayX.png
190 SCopy(C4CFN_ClrByOwnerExPNG, sTarget: OverlayFn, _MAX_PATH);
191 OverlayFn[iOverlayWildcardPos] = 0;
192 SAppend(szSource: Filename + iWildcardPos, szTarget: OverlayFn);
193 EnforceExtension(szFileName: OverlayFn, szExtension: GetExtension(C4CFN_ClrByOwnerExPNG));
194 }
195 }
196 // create new graphics
197 pLastGraphics->pNext = new C4PortraitGraphics(pDef, GrpName);
198 pLastGraphics = pLastGraphics->pNext;
199 // load them
200 if (!pLastGraphics->LoadGraphics(hGroup, szFilename: fBMP ? Filename : nullptr, szFilenamePNG: fBMP ? nullptr : Filename, szOverlayPNG: *OverlayFn ? OverlayFn : nullptr, fColorByOwner))
201 return false;
202 }
203 // done, success
204 return true;
205}
206
207bool C4DefGraphics::ColorizeByMaterial(int32_t iMat, C4MaterialMap &rMats, uint8_t bGBM)
208{
209 C4Surface *sfcBitmap = GetBitmap(); // first bitmap only
210 if (sfcBitmap)
211 {
212 uint32_t dwMatColors[C4M_ColsPerMat];
213 for (int32_t i = 0; i < C4M_ColsPerMat; ++i)
214 dwMatColors[i] = rMats.Map[iMat].GetDWordColor(iIndex: i);
215 Application.DDraw->SurfaceAllowColor(sfcSfc: sfcBitmap, pdwColors: dwMatColors, iNumColors: C4M_ColsPerMat, fAllowZero: true);
216 }
217 // colorize other graphics
218 if (pNext) return pNext->ColorizeByMaterial(iMat, rMats, bGBM); else return true;
219}
220
221C4DefGraphics *C4DefGraphics::Get(const char *szGrpName)
222{
223 // no group or empty string: base graphics
224 if (!szGrpName || !szGrpName[0]) return this;
225 // search additional graphics
226 for (C4AdditionalDefGraphics *pGrp = pNext; pGrp; pGrp = pGrp->pNext)
227 if (SEqualNoCase(szStr1: pGrp->GetName(), szStr2: szGrpName)) return pGrp;
228 // nothing found
229 return nullptr;
230}
231
232C4PortraitGraphics *C4PortraitGraphics::Get(const char *szGrpName)
233{
234 // no group or empty string: no gfx
235 if (!szGrpName || !szGrpName[0]) return nullptr;
236 // search self and additional graphics
237 for (C4AdditionalDefGraphics *pGrp = this; pGrp; pGrp = pGrp->GetNext())
238 if (SEqualNoCase(szStr1: pGrp->GetName(), szStr2: szGrpName)) return pGrp->IsPortrait();
239 // nothing found
240 return nullptr;
241}
242
243bool C4DefGraphics::CopyGraphicsFrom(C4DefGraphics &rSource)
244{
245 // clear previous
246 delete BitmapClr; BitmapClr = nullptr;
247 delete Bitmap; Bitmap = nullptr;
248 // copy from source
249 if (rSource.Bitmap)
250 {
251 Bitmap = new C4Surface();
252 if (!Bitmap->Copy(fromSfc&: *rSource.Bitmap))
253 {
254 delete Bitmap; Bitmap = nullptr; return false;
255 }
256 }
257 if (rSource.BitmapClr)
258 {
259 BitmapClr = new C4Surface();
260 if (!BitmapClr->Copy(fromSfc&: *rSource.BitmapClr))
261 {
262 delete Bitmap; Bitmap = nullptr;
263 delete BitmapClr; BitmapClr = nullptr; return false;
264 }
265 if (Bitmap) BitmapClr->SetAsClrByOwnerOf(Bitmap);
266 }
267 // done, success
268 return true;
269}
270
271void C4DefGraphics::DrawClr(C4Facet &cgo, bool fAspect, uint32_t dwClr)
272{
273 // create facet and draw it
274 C4Surface *pSfc = BitmapClr ? BitmapClr : Bitmap; if (!pSfc) return;
275 C4Facet fct(pSfc, 0, 0, pSfc->Wdt, pSfc->Hgt);
276 fct.DrawClr(cgo, fAspect, dwClr);
277}
278
279void C4DefGraphicsAdapt::CompileFunc(StdCompiler *pComp)
280{
281 bool fCompiler = pComp->isCompiler();
282 // nothing?
283 if (!fCompiler && !pDefGraphics) return;
284 // definition
285 C4ID id; if (!fCompiler) id = pDefGraphics->pDef->id;
286 pComp->Value(rStruct: mkC4IDAdapt(rValue&: id));
287 // go over two separators ("::"). Expect them if an id was found.
288 if (!pComp->Separator(eSep: StdCompiler::SEP_PART2) || !pComp->Separator(eSep: StdCompiler::SEP_PART2))
289 pComp->excCorrupt(message: "DefGraphics: expected \"::\"");
290 // compile name
291 StdStrBuf Name; if (!fCompiler) Name.Ref(pnData: pDefGraphics->GetName());
292 pComp->Value(rStruct: mkDefaultAdapt(rValue: mkParAdapt(rObj&: Name, rPar: StdCompiler::RCT_Idtf), rDefault: ""));
293 // reading: search def-graphics
294 if (fCompiler)
295 {
296 // search definition, throw expection if not found
297 C4Def *pDef = Game.Defs.ID2Def(id);
298 // search def-graphics
299 if (!pDef || !(pDefGraphics = pDef->Graphics.Get(szGrpName: Name.getData())))
300 pComp->excCorrupt(message: "DefGraphics: could not find graphics \"{}\" in {}({})!", args: Name.getData(), args: C4IdText(id), args: pDef ? pDef->Name.getData() : "def not found");
301 }
302}
303
304C4AdditionalDefGraphics::C4AdditionalDefGraphics(C4Def *pOwnDef, const char *szName) : C4DefGraphics(pOwnDef)
305{
306 // store name
307 SCopy(szSource: szName, sTarget: Name, iMaxL: C4MaxName);
308}
309
310C4PortraitGraphics *C4PortraitGraphics::GetByIndex(int32_t iIndex)
311{
312 // start from this portrait
313 C4DefGraphics *pResult = this;
314 while (iIndex--)
315 {
316 // get next indexed
317 pResult = pResult->GetNext(); if (!pResult) return nullptr;
318 // skip non-portraits
319 if (!pResult->IsPortrait()) ++iIndex;
320 }
321 // return portrait
322 return pResult->IsPortrait();
323}
324
325C4DefGraphicsPtrBackup::C4DefGraphicsPtrBackup(C4DefGraphics *pSourceGraphics)
326{
327 // assign graphics + def
328 pGraphicsPtr = pSourceGraphics;
329 BitmapPtr = pSourceGraphics->GetBitmap();
330 pDef = pSourceGraphics->pDef;
331 // assign name
332 const char *szName = pGraphicsPtr->GetName();
333 if (szName) SCopy(szSource: szName, sTarget: Name, iMaxL: C4MaxName); else *Name = 0;
334 // create next graphics recursively
335 C4DefGraphics *pNextGfx = pGraphicsPtr->pNext;
336 if (pNextGfx)
337 pNext = new C4DefGraphicsPtrBackup(pNextGfx);
338 else
339 pNext = nullptr;
340}
341
342C4DefGraphicsPtrBackup::~C4DefGraphicsPtrBackup()
343{
344 // graphics ptr still assigned? then remove dead graphics pointers from objects
345 if (pGraphicsPtr) AssignRemoval();
346 // delete following graphics recursively
347 delete pNext;
348}
349
350void C4DefGraphicsPtrBackup::AssignUpdate(C4DefGraphics *pNewGraphics)
351{
352 // only if graphics are assigned
353 if (pGraphicsPtr)
354 {
355 const auto newSameGraphics = pNewGraphics->Get(szGrpName: Name);
356 // check all objects
357 C4Object *pObj;
358 for (C4ObjectLink *pLnk = Game.Objects.First; pLnk; pLnk = pLnk->Next)
359 if (pObj = pLnk->Obj) if (pObj->Status)
360 {
361 if (pObj->pGraphics == pGraphicsPtr)
362 {
363 // same graphics found: try to set them
364 if (!pObj->SetGraphics(szGraphicsName: Name, pSourceDef: pDef))
365 if (!pObj->SetGraphics(szGraphicsName: Name, pSourceDef: pObj->Def))
366 {
367 // shouldn't happen
368 pObj->AssignRemoval(); pObj->pGraphics = nullptr;
369 }
370 }
371 // remove any overlay graphics
372 for (;;)
373 {
374 C4GraphicsOverlay *pGfxOverlay;
375 for (pGfxOverlay = pObj->pGfxOverlay; pGfxOverlay; pGfxOverlay = pGfxOverlay->GetNext())
376 {
377 if (pGfxOverlay->GetGfx() == pGraphicsPtr)
378 {
379 if (!newSameGraphics)
380 {
381 // then remove this overlay and redo the loop, because iterator has become invalid
382 pObj->RemoveGraphicsOverlay(iOverlayID: pGfxOverlay->GetID());
383 break;
384 }
385
386 pGfxOverlay->UpdateSourceGraphics(newSource: newSameGraphics);
387 }
388 }
389 // looped through w/o removal?
390 if (!pGfxOverlay) break;
391 }
392 if (pDef && pObj->Menu)
393 {
394 // update menu frame decorations - may do multiple updates to the same deco if multiple menus share it...
395 if (C4GUI::FrameDecoration *pDeco = pObj->Menu->GetFrameDecoration(); pDeco && pDeco->idSourceDef == pDef->id)
396 {
397 if (!pDeco->UpdateGfx())
398 {
399 pObj->Menu->SetFrameDeco(nullptr);
400 }
401 }
402
403 // update menu item icons
404 for (std::int32_t count = pObj->Menu->GetItemCount(), i = 0; i < count; ++i)
405 {
406 const auto item = pObj->Menu->GetItem(iIndex: i);
407 if (item->Symbol.Surface == BitmapPtr)
408 {
409 if (newSameGraphics)
410 {
411 item->Symbol.Surface = pDef->Graphics.GetBitmap();
412 }
413 else
414 {
415 item->Symbol.Clear();
416 }
417 }
418 }
419 }
420 }
421 // check all object infos for portraits
422 for (C4Player *pPlr = Game.Players.First; pPlr; pPlr = pPlr->Next)
423 for (C4ObjectInfo *pInfo = pPlr->CrewInfoList.GetFirst(); pInfo; pInfo = pInfo->Next)
424 {
425 if (pInfo->Portrait.GetGfx() == pGraphicsPtr)
426 {
427 // portrait found: try to re-set by new name
428 if (!pInfo->SetPortrait(szPortraitName: Name, pSourceDef: pDef, fAssignPermanently: false, fCopyFile: false))
429 // not found: no portrait then
430 pInfo->ClearPortrait(fPermanently: false);
431 }
432 if (pInfo->pNewPortrait && pInfo->pNewPortrait->GetGfx() == pGraphicsPtr)
433 {
434 // portrait found as new portrait: simply reset (no complex handling for EM crew changes necessary)
435 delete pInfo->pNewPortrait;
436 pInfo->pNewPortrait = nullptr;
437 }
438 }
439 // done; reset field to indicate finished update
440 pGraphicsPtr = nullptr;
441 }
442 // check next graphics
443 if (pNext) pNext->AssignUpdate(pNewGraphics);
444}
445
446void C4DefGraphicsPtrBackup::AssignRemoval()
447{
448 // only if graphics are assigned
449 if (pGraphicsPtr)
450 {
451 // check all objects
452 C4Object *pObj;
453 for (C4ObjectLink *pLnk = Game.Objects.First; pLnk; pLnk = pLnk->Next)
454 if (pObj = pLnk->Obj) if (pObj->Status)
455 {
456 if (pObj->pGraphics == pGraphicsPtr)
457 // same graphics found: reset them
458 if (!pObj->SetGraphics()) { pObj->AssignRemoval(); pObj->pGraphics = nullptr; }
459 // remove any overlay graphics
460 for (;;)
461 {
462 C4GraphicsOverlay *pGfxOverlay;
463 for (pGfxOverlay = pObj->pGfxOverlay; pGfxOverlay; pGfxOverlay = pGfxOverlay->GetNext())
464 if (pGfxOverlay->GetGfx() == pGraphicsPtr)
465 {
466 // then remove this overlay and redo the loop, because iterator has become invalid
467 pObj->RemoveGraphicsOverlay(iOverlayID: pGfxOverlay->GetID());
468 break;
469 }
470 // looped through w/o removal?
471 if (!pGfxOverlay) break;
472 }
473 // remove menu frame decorations
474 C4GUI::FrameDecoration *pDeco;
475 if (pDef && pObj->Menu && (pDeco = pObj->Menu->GetFrameDecoration()))
476 if (pDeco->idSourceDef == pDef->id)
477 pObj->Menu->SetFrameDeco(nullptr);
478 }
479 // done; reset field to indicate finished update
480 pGraphicsPtr = nullptr;
481 }
482 // check next graphics
483 if (pNext) pNext->AssignRemoval();
484}
485
486bool C4Portrait::Load(C4Group &rGrp, const char *szFilename, const char *szFilenamePNG, const char *szOverlayPNG)
487{
488 // clear previous
489 Clear();
490 // create new gfx
491 pGfxPortrait = new C4DefGraphics();
492 // load
493 if (!pGfxPortrait->LoadGraphics(hGroup&: rGrp, szFilename, szFilenamePNG, szOverlayPNG, fColorByOwner: true))
494 {
495 // load failure
496 delete pGfxPortrait; pGfxPortrait = nullptr;
497 return false;
498 }
499 // assign owned gfx
500 fGraphicsOwned = true;
501 // done, success
502 return true;
503}
504
505bool C4Portrait::Link(C4DefGraphics *pGfxPortrait)
506{
507 // clear previous
508 Clear();
509 // simply assign ptr
510 this->pGfxPortrait = pGfxPortrait;
511 // done, success
512 return true;
513}
514
515bool C4Portrait::SavePNG(C4Group &rGroup, const char *szFilename, const char *szOverlayFN)
516{
517 // safety
518 if (!pGfxPortrait || !szFilename || !pGfxPortrait->Bitmap) return false;
519 // save files
520 if (pGfxPortrait->fColorBitmapAutoCreated)
521 {
522 // auto-created ColorByOwner: save file with blue shades to be read by frontend
523 if (!pGfxPortrait->GetBitmap(dwClr: 0xff)->SavePNG(hGroup&: rGroup, szFilename)) return false;
524 }
525 else
526 {
527 // save regular baseface
528 if (!pGfxPortrait->Bitmap->SavePNG(hGroup&: rGroup, szFilename)) return false;
529 // save Overlay
530 if (pGfxPortrait->BitmapClr && szOverlayFN)
531 if (!pGfxPortrait->BitmapClr->SavePNG(hGroup&: rGroup, szFilename: szOverlayFN, fSaveAlpha: true, fApplyGamma: false, fSaveOverlayOnly: true)) return false;
532 }
533 // done, success
534 return true;
535}
536
537bool C4Portrait::CopyFrom(C4DefGraphics &rCopyGfx)
538{
539 // clear previous
540 Clear();
541 // gfx copy
542 pGfxPortrait = new C4DefGraphics();
543 if (!pGfxPortrait->CopyGraphicsFrom(rSource&: rCopyGfx))
544 {
545 Clear(); return false;
546 }
547 // mark as own gfx
548 fGraphicsOwned = true;
549 // done, success
550 return true;
551}
552
553bool C4Portrait::CopyFrom(C4Portrait &rCopy)
554{
555 // clear previous
556 Clear();
557 if (fGraphicsOwned = rCopy.fGraphicsOwned)
558 {
559 // gfx copy
560 pGfxPortrait = new C4DefGraphics();
561 if (!pGfxPortrait->CopyGraphicsFrom(rSource&: *rCopy.GetGfx()))
562 {
563 Clear(); return false;
564 }
565 }
566 else
567 {
568 // simple link
569 pGfxPortrait = rCopy.GetGfx();
570 }
571 // done, success
572 return true;
573}
574
575const char *C4Portrait::EvaluatePortraitString(const char *szPortrait, C4ID &rIDOut, C4ID idDefaultID, uint32_t *pdwClrOut)
576{
577 // examine portrait string
578 if (SLen(sptr: szPortrait) > 6 && szPortrait[4] == ':' && szPortrait[5] == ':')
579 {
580 // C4ID::PortraitName or C4ID::dwClr::PortraitName
581 rIDOut = C4Id(str: szPortrait);
582 // color specified?
583 szPortrait += 6;
584 const char *szAfterQColon = SSearch(szString: szPortrait, szIndex: "::");
585 if (szAfterQColon)
586 {
587 char buf[7];
588
589 // szAfterQColon-szPortrait-2 results in long on 64bit,
590 // so the template needs to be specialised
591 SCopy(szSource: szPortrait, sTarget: buf, iMaxL: std::min<ptrdiff_t>(a: 6, b: szAfterQColon - szPortrait - 2));
592 if (pdwClrOut) sscanf(s: buf, format: "%x", pdwClrOut);
593 szPortrait = szAfterQColon;
594 }
595 // return last part of string
596 return szPortrait;
597 }
598 else
599 {
600 // PortraitName. ID is info ID
601 rIDOut = idDefaultID;
602 return szPortrait;
603 }
604}
605
606// C4GraphicsOverlay: graphics overlay used to attach additional graphics to objects
607
608C4GraphicsOverlay::~C4GraphicsOverlay()
609{
610 // free any additional overlays
611 C4GraphicsOverlay *pNextOther = pNext, *pOther;
612 while (pOther = pNextOther)
613 {
614 pNextOther = pOther->pNext;
615 pOther->pNext = nullptr;
616 delete pOther;
617 }
618}
619
620void C4GraphicsOverlay::UpdateFacet()
621{
622 // special: Nothing to update for object and pSourceGfx may be nullptr
623 // If there will ever be something to init here, UpdateFacet() will also need to be called when objects have been loaded
624 if (eMode == MODE_Object) return;
625 // otherwise, source graphics must be specified
626 if (!pSourceGfx) return;
627 C4Def *pDef = pSourceGfx->pDef;
628 assert(pDef);
629 fZoomToShape = false;
630 // update by mode
631 switch (eMode)
632 {
633 case MODE_None:
634 break;
635
636 case MODE_Base: // def base graphics
637 fctBlit.Set(nsfc: pSourceGfx->GetBitmap(), nx: 0, ny: 0, nwdt: pDef->Shape.Wdt, nhgt: pDef->Shape.Hgt, ntx: pDef->Shape.x + pDef->Shape.Wdt / 2, nty: pDef->Shape.y + pDef->Shape.Hgt / 2);
638 break;
639
640 case MODE_Action: // graphics of specified action
641 {
642 // Find act in ActMap of object
643 int32_t cnt;
644 for (cnt = 0; cnt < pDef->ActNum; cnt++)
645 if (SEqual(szStr1: Action, szStr2: pDef->ActMap[cnt].Name))
646 break;
647 if (cnt == pDef->ActNum) { fctBlit.Default(); break; }
648 // assign base gfx of action
649 // doesn't catch any special action parameters (FacetBase, etc.)...
650 C4ActionDef *pAct = pDef->ActMap + cnt;
651 fctBlit.Set(nsfc: pSourceGfx->GetBitmap(), nx: pAct->Facet.x, ny: pAct->Facet.y, nwdt: pAct->Facet.Wdt, nhgt: pAct->Facet.Hgt);
652 }
653 break;
654
655 case MODE_IngamePicture:
656 case MODE_Picture: // def picture
657 fZoomToShape = true;
658 fctBlit.Set(nsfc: pSourceGfx->GetBitmap(), nx: pDef->PictureRect.x, ny: pDef->PictureRect.y, nwdt: pDef->PictureRect.Wdt, nhgt: pDef->PictureRect.Hgt);
659 break;
660
661 case MODE_ExtraGraphics: // like ColorByOwner-sfc
662 // calculated at runtime
663 break;
664
665 case MODE_Object:
666 // nothing to do
667 break;
668 }
669}
670
671void C4GraphicsOverlay::UpdateSourceGraphics(C4DefGraphics *newSource)
672{
673 pSourceGfx = newSource;
674 UpdateFacet();
675}
676
677void C4GraphicsOverlay::Set(Mode aMode, C4DefGraphics *pGfx, const char *szAction, uint32_t dwBMode, C4Object *pOvrlObj)
678{
679 // set values
680 eMode = aMode;
681 pSourceGfx = pGfx;
682 if (szAction) SCopy(szSource: szAction, sTarget: Action, iMaxL: C4MaxName); else *Action = 0;
683 dwBlitMode = dwBMode;
684 pOverlayObj = pOvrlObj;
685 // (keep transform)
686 // reset phase
687 iPhase = 0;
688 // update used facet
689 UpdateFacet();
690}
691
692bool C4GraphicsOverlay::IsValid(const C4Object *pForObj) const
693{
694 assert(pForObj);
695 if (eMode == MODE_Object)
696 {
697 if (!pOverlayObj || !pOverlayObj->Status) return false;
698 return !pOverlayObj->HasGraphicsOverlayRecursion(pCheckObj: pForObj);
699 }
700 else if (eMode == MODE_ExtraGraphics)
701 {
702 return !!pSourceGfx;
703 }
704 else
705 return pSourceGfx && fctBlit.Surface;
706}
707
708void C4GraphicsOverlay::CompileFunc(StdCompiler *pComp)
709{
710 // read ID
711 pComp->Value(rInt&: iID); pComp->Separator();
712 // read def-graphics
713 pComp->Value(rStruct: mkDefaultAdapt(rValue: C4DefGraphicsAdapt(pSourceGfx), rDefault: nullptr));
714 pComp->Separator();
715 // read mode
716 pComp->Value(rStruct: mkIntAdapt(rValue&: eMode)); pComp->Separator();
717 // read action (identifier)
718 pComp->Value(mkStringAdaptMIE(Action)); pComp->Separator();
719 // read blit mode
720 pComp->Value(rInt&: dwBlitMode); pComp->Separator();
721 // read phase
722 pComp->Value(rInt&: iPhase); pComp->Separator();
723 // read transform
724 pComp->Separator(eSep: StdCompiler::SEP_START);
725 pComp->Value(rStruct&: Transform);
726 pComp->Separator(eSep: StdCompiler::SEP_END);
727 // read color-modulation
728 if (pComp->Separator())
729 pComp->Value(rStruct: mkIntAdapt(rValue&: dwClrModulation));
730 else
731 // default
732 if (pComp->isCompiler()) dwClrModulation = 0xffffff;
733 // read overlay target object
734 if (pComp->Separator())
735 pComp->Value(rStruct&: pOverlayObj);
736 else
737 // default
738 if (pComp->isCompiler()) pOverlayObj.Reset();
739 // update used facet according to read data
740 if (pComp->isCompiler()) UpdateFacet();
741}
742
743void C4GraphicsOverlay::EnumeratePointers()
744{
745 pOverlayObj.Enumerate();
746}
747
748void C4GraphicsOverlay::DenumeratePointers()
749{
750 pOverlayObj.Denumerate();
751}
752
753void C4GraphicsOverlay::Draw(C4FacetEx &cgo, C4Object *pForObj, int32_t iByPlayer)
754{
755 assert(!IsPicture());
756 assert(pForObj);
757 // get target pos
758 int32_t cotx = cgo.TargetX, coty = cgo.TargetY; pForObj->TargetPos(riTx&: cotx, riTy&: coty, fctViewport: cgo);
759 int32_t iTx = pForObj->x - cotx + cgo.X,
760 iTy = pForObj->y - coty + cgo.Y;
761 // special blit mode
762 if (dwBlitMode == C4GFXBLIT_PARENT)
763 (pOverlayObj ? pOverlayObj : pForObj)->PrepareDrawing();
764 else
765 {
766 Application.DDraw->SetBlitMode(dwBlitMode);
767 if (dwClrModulation != 0xffffff) Application.DDraw->ActivateBlitModulation(dwWithClr: dwClrModulation);
768 }
769 // drawing specific object?
770 if (pOverlayObj)
771 {
772 // Draw specified object at target pos of this object; offset by transform.
773 // This ignores any other transform than offset, and it doesn't work with parallax overlay objects yet
774 // (But any parallaxity of pForObj is regarded in calculation of cotx/y!)
775 int32_t oldTx = cgo.TargetX, oldTy = cgo.TargetY;
776 cgo.TargetX = cotx - pForObj->x + pOverlayObj->x - Transform.GetXOffset();
777 cgo.TargetY = coty - pForObj->y + pOverlayObj->y - Transform.GetYOffset();
778 pOverlayObj->Draw(cgo, iByPlayer, eDrawMode: C4Object::ODM_Overlay);
779 pOverlayObj->DrawTopFace(cgo, iByPlayer, eDrawMode: C4Object::ODM_Overlay);
780 cgo.TargetX = oldTx;
781 cgo.TargetY = oldTy;
782 }
783 else if (eMode == MODE_ExtraGraphics)
784 {
785 // draw self with specified gfx
786 if (pSourceGfx)
787 {
788 C4DefGraphics *pPrevGfx = pForObj->GetGraphics();
789 C4DrawTransform *pPrevTrf = pForObj->pDrawTransform;
790 C4DrawTransform trf;
791 if (pPrevTrf)
792 {
793 trf = *pPrevTrf;
794 trf *= Transform;
795 }
796 else
797 {
798 trf = Transform;
799 }
800 pForObj->SetGraphics(pNewGfx: pSourceGfx, fUpdateData: true);
801 pForObj->pDrawTransform = &trf;
802 pForObj->Draw(cgo, iByPlayer, eDrawMode: C4Object::ODM_BaseOnly);
803 pForObj->DrawTopFace(cgo, iByPlayer, eDrawMode: C4Object::ODM_BaseOnly);
804 pForObj->SetGraphics(pNewGfx: pPrevGfx, fUpdateData: true);
805 pForObj->pDrawTransform = pPrevTrf;
806 }
807 }
808 else
809 {
810 // no object specified: Draw from fctBlit
811 // update by object color
812 if (fctBlit.Surface) fctBlit.Surface->SetClr(pForObj->Color);
813
814 // draw there
815 C4DrawTransform trf(Transform, float(iTx), float(iTy));
816 if (fZoomToShape)
817 {
818 float fZoom = std::min<float>(a: static_cast<float>(pForObj->Shape.Wdt) / std::max<int>(a: fctBlit.Wdt, b: 1), b: static_cast<float>(pForObj->Shape.Hgt) / std::max<int>(a: fctBlit.Hgt, b: 1));
819 trf.ScaleAt(sx: fZoom, sy: fZoom, tx: float(iTx), ty: float(iTy));
820 }
821 fctBlit.DrawT(sfcTarget: cgo.Surface, iX: iTx - fctBlit.Wdt / 2 + fctBlit.TargetX, iY: iTy - fctBlit.Hgt / 2 + fctBlit.TargetY, iPhaseX: iPhase, iPhaseY: 0, pTransform: &trf, noScalingCorrection: false, scale: pSourceGfx->pDef->Scale);
822 }
823
824 // cleanup
825 if (dwBlitMode == C4GFXBLIT_PARENT)
826 (pOverlayObj ? pOverlayObj : pForObj)->FinishedDrawing();
827 else
828 {
829 Application.DDraw->ResetBlitMode();
830 Application.DDraw->DeactivateBlitModulation();
831 }
832}
833
834void C4GraphicsOverlay::DrawPicture(C4Facet &cgo, C4Object *pForObj)
835{
836 assert(IsPicture());
837 // update object color
838 if (pForObj && fctBlit.Surface) fctBlit.Surface->SetClr(pForObj->Color);
839 // special blit mode
840 if (dwBlitMode == C4GFXBLIT_PARENT)
841 {
842 if (pForObj) pForObj->PrepareDrawing();
843 }
844 else
845 {
846 Application.DDraw->SetBlitMode(dwBlitMode);
847 if (dwClrModulation != 0xffffff) Application.DDraw->ActivateBlitModulation(dwWithClr: dwClrModulation);
848 }
849 // the translation has to be adjusted, because positioning on the picture assumes that cgo is quadratic with side length C4SymbolSize
850 C4DrawTransform adjustedTransform{Transform, 0, 0};
851 float scaleFactor = static_cast<float>(cgo.Wdt) / float{C4SymbolSize};
852 adjustedTransform.mat[2] *= scaleFactor;
853 adjustedTransform.mat[5] *= scaleFactor;
854 // draw at given rect
855 fctBlit.DrawT(cgo, fAspect: true, iPhaseX: iPhase, iPhaseY: 0, pTransform: &C4DrawTransform(adjustedTransform, cgo.X + float(cgo.Wdt) / 2, cgo.Y + float(cgo.Hgt) / 2), noScalingCorrection: pSourceGfx->pDef->Scale);
856 // cleanup
857 if (dwBlitMode == C4GFXBLIT_PARENT)
858 {
859 if (pForObj) pForObj->FinishedDrawing();
860 }
861 else
862 {
863 Application.DDraw->ResetBlitMode();
864 Application.DDraw->DeactivateBlitModulation();
865 }
866}
867
868bool C4GraphicsOverlay::operator==(const C4GraphicsOverlay &rCmp) const
869{
870 // compare relevant fields
871 // ignoring phase, because different animation state may be concatenated in graphics display
872 return (eMode == rCmp.eMode)
873 && (pSourceGfx == rCmp.pSourceGfx)
874 && SEqual(szStr1: Action, szStr2: rCmp.Action)
875 && (dwBlitMode == rCmp.dwBlitMode)
876 && (dwClrModulation == rCmp.dwClrModulation)
877 && (Transform == rCmp.Transform)
878 && (pOverlayObj == rCmp.pOverlayObj);
879}
880
881void C4GraphicsOverlayListAdapt::CompileFunc(StdCompiler *pComp)
882{
883 bool fNaming = pComp->hasNaming();
884 if (pComp->isCompiler())
885 {
886 // clear list
887 delete[] pOverlay; pOverlay = nullptr;
888 // read the whole list
889 C4GraphicsOverlay *pLast = nullptr;
890 bool fContinue;
891 do
892 {
893 C4GraphicsOverlay *pNext = new C4GraphicsOverlay();
894 try
895 {
896 // read an overlay
897 pComp->Value(rStruct&: *pNext);
898 }
899 catch (const StdCompiler::NotFoundException &)
900 {
901 // delete unused overlay
902 delete pNext; pNext = nullptr;
903 // clear up
904 if (!pLast) pOverlay = nullptr;
905 // done
906 return;
907 }
908 // link it
909 if (pLast)
910 pLast->SetNext(pNext);
911 else
912 pOverlay = pNext;
913 // step
914 pLast = pNext;
915 // continue?
916 if (fNaming)
917 fContinue = pComp->Separator(eSep: StdCompiler::SEP_SEP2) || pComp->Separator(eSep: StdCompiler::SEP_SEP);
918 else
919 pComp->Value(rBool&: fContinue);
920 } while (fContinue);
921 }
922 else
923 {
924 // write everything
925 bool fContinue = true;
926 for (C4GraphicsOverlay *pPos = pOverlay; pPos; pPos = pPos->GetNext())
927 {
928 // separate
929 if (pPos != pOverlay)
930 if (fNaming)
931 pComp->Separator(eSep: StdCompiler::SEP_SEP2);
932 else
933 pComp->Value(rBool&: fContinue);
934 // write
935 pComp->Value(rStruct&: *pPos);
936 }
937 // terminate
938 if (!fNaming)
939 {
940 fContinue = false;
941 pComp->Value(rBool&: fContinue);
942 }
943 }
944}
945