| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2019, 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 | /* Special regions to extend the pathfinder */ |
| 18 | |
| 19 | #include <C4Include.h> |
| 20 | #include <C4TransferZone.h> |
| 21 | |
| 22 | #include <C4Game.h> |
| 23 | #include <C4FacetEx.h> |
| 24 | #include <C4Wrappers.h> |
| 25 | |
| 26 | C4TransferZone::C4TransferZone() |
| 27 | { |
| 28 | Default(); |
| 29 | } |
| 30 | |
| 31 | C4TransferZone::~C4TransferZone() |
| 32 | { |
| 33 | Clear(); |
| 34 | } |
| 35 | |
| 36 | void C4TransferZone::Default() |
| 37 | { |
| 38 | Object = nullptr; |
| 39 | X = Y = Wdt = Hgt = 0; |
| 40 | Next = nullptr; |
| 41 | Used = false; |
| 42 | } |
| 43 | |
| 44 | void C4TransferZone::Clear() {} |
| 45 | |
| 46 | C4TransferZones::C4TransferZones() |
| 47 | { |
| 48 | Default(); |
| 49 | } |
| 50 | |
| 51 | C4TransferZones::~C4TransferZones() |
| 52 | { |
| 53 | Clear(); |
| 54 | } |
| 55 | |
| 56 | void C4TransferZones::Default() |
| 57 | { |
| 58 | First = nullptr; |
| 59 | } |
| 60 | |
| 61 | void C4TransferZones::Clear() |
| 62 | { |
| 63 | C4TransferZone *pZone, *pNext; |
| 64 | for (pZone = First; pZone; pZone = pNext) { pNext = pZone->Next; delete pZone; } |
| 65 | First = nullptr; |
| 66 | } |
| 67 | |
| 68 | void C4TransferZones::ClearPointers(C4Object *pObj) |
| 69 | { |
| 70 | // Clear object pointers |
| 71 | for (C4TransferZone *pZone = First; pZone; pZone = pZone->Next) |
| 72 | if (pZone->Object == pObj) |
| 73 | pZone->Object = nullptr; |
| 74 | // Remove cleared zones immediately |
| 75 | RemoveNullZones(); |
| 76 | } |
| 77 | |
| 78 | bool C4TransferZones::Set(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, C4Object *pObj) |
| 79 | { |
| 80 | C4TransferZone *pZone; |
| 81 | // Empty zone: clear existing object zones |
| 82 | if (!iWdt || !iHgt) { ClearPointers(pObj); return true; } |
| 83 | // Update existing zone |
| 84 | if (pZone = Find(pObj)) |
| 85 | { |
| 86 | pZone->X = iX; pZone->Y = iY; |
| 87 | pZone->Wdt = iWdt; pZone->Hgt = iHgt; |
| 88 | } |
| 89 | // Allocate and add new zone |
| 90 | else |
| 91 | Add(iX, iY, iWdt, iHgt, pObj); |
| 92 | // Success |
| 93 | return true; |
| 94 | } |
| 95 | |
| 96 | bool C4TransferZones::Add(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, C4Object *pObj) |
| 97 | { |
| 98 | C4TransferZone *pZone; |
| 99 | // Allocate and add new zone |
| 100 | pZone = new C4TransferZone; |
| 101 | pZone->X = iX; pZone->Y = iY; |
| 102 | pZone->Wdt = iWdt; pZone->Hgt = iHgt; |
| 103 | pZone->Object = pObj; |
| 104 | pZone->Next = First; |
| 105 | First = pZone; |
| 106 | // Success |
| 107 | return true; |
| 108 | } |
| 109 | |
| 110 | void C4TransferZones::Synchronize() |
| 111 | { |
| 112 | Clear(); |
| 113 | Game.Objects.UpdateTransferZones(); |
| 114 | } |
| 115 | |
| 116 | C4TransferZone *C4TransferZones::Find(int32_t iX, int32_t iY) |
| 117 | { |
| 118 | for (C4TransferZone *pZone = First; pZone; pZone = pZone->Next) |
| 119 | if (Inside<int32_t>(ival: iX - pZone->X, lbound: 0, rbound: pZone->Wdt - 1)) |
| 120 | if (Inside<int32_t>(ival: iY - pZone->Y, lbound: 0, rbound: pZone->Hgt - 1)) |
| 121 | return pZone; |
| 122 | return nullptr; |
| 123 | } |
| 124 | |
| 125 | void C4TransferZones::Draw(C4FacetEx &cgo) |
| 126 | { |
| 127 | for (C4TransferZone *pZone = First; pZone; pZone = pZone->Next) |
| 128 | pZone->Draw(cgo); |
| 129 | } |
| 130 | |
| 131 | void C4TransferZone::Draw(C4FacetEx &cgo, bool fHighlight) |
| 132 | { |
| 133 | if (Used) fHighlight = true; |
| 134 | lpDDraw->DrawFrame(sfcDest: cgo.Surface, |
| 135 | x1: cgo.X + X - cgo.TargetX, y1: cgo.Y + Y - cgo.TargetY, |
| 136 | x2: cgo.X + X - cgo.TargetX + Wdt - 1, y2: cgo.Y + Y - cgo.TargetY + Hgt - 1, |
| 137 | col: fHighlight ? CGreen : CRed); |
| 138 | } |
| 139 | |
| 140 | bool C4TransferZone::At(int32_t iX, int32_t iY) |
| 141 | { |
| 142 | return (Inside<int32_t>(ival: iX - X, lbound: 0, rbound: Wdt - 1) && Inside<int32_t>(ival: iY - Y, lbound: 0, rbound: Hgt - 1)); |
| 143 | } |
| 144 | |
| 145 | int32_t C4TransferZones::RemoveNullZones() |
| 146 | { |
| 147 | int32_t iResult = 0; |
| 148 | C4TransferZone *pZone, *pNext, *pPrev = nullptr; |
| 149 | for (pZone = First; pZone; pZone = pNext) |
| 150 | { |
| 151 | pNext = pZone->Next; |
| 152 | if (!pZone->Object) |
| 153 | { |
| 154 | delete pZone; |
| 155 | if (pPrev) pPrev->Next = pNext; |
| 156 | else First = pNext; |
| 157 | iResult++; |
| 158 | } |
| 159 | else |
| 160 | pPrev = pZone; |
| 161 | } |
| 162 | return iResult; |
| 163 | } |
| 164 | |
| 165 | void AdjustMoveToTarget(int32_t &rX, int32_t &rY, bool fFreeMove, int32_t iShapeHgt); |
| 166 | |
| 167 | bool C4TransferZone::GetEntryPoint(int32_t &rX, int32_t &rY, int32_t iToX, int32_t iToY) |
| 168 | { |
| 169 | // Target inside zone: move outside horizontally |
| 170 | if (Inside<int32_t>(ival: iToX - X, lbound: 0, rbound: Wdt - 1) && Inside<int32_t>(ival: iToY - Y, lbound: 0, rbound: Hgt - 1)) |
| 171 | if (iToX < X + Wdt / 2) iToX = X - 1; else iToX = X + Wdt; |
| 172 | // Get closest adjacent point |
| 173 | rX = BoundBy<int32_t>(bval: iToX, lbound: X - 1, rbound: X + Wdt); |
| 174 | rY = BoundBy<int32_t>(bval: iToY, lbound: Y - 1, rbound: Y + Hgt); |
| 175 | // Search around zone for free |
| 176 | int32_t iX1 = rX, iY1 = rY, iX2 = rX, iY2 = rY; |
| 177 | int32_t iXIncr1 = 0, iYIncr1 = -1, iXIncr2 = 0, iYIncr2 = +1; |
| 178 | int32_t cnt; |
| 179 | for (cnt = 0; cnt < 2 * Wdt + 2 * Hgt; cnt++) |
| 180 | { |
| 181 | // Found free |
| 182 | if (!GBackSolid(x: iX1, y: iY1)) { rX = iX1; rY = iY1; break; } |
| 183 | if (!GBackSolid(x: iX2, y: iY2)) { rX = iX2; rY = iY2; break; } |
| 184 | // Advance |
| 185 | iX1 += iXIncr1; iY1 += iYIncr1; |
| 186 | iX2 += iXIncr2; iY2 += iYIncr2; |
| 187 | // Corners |
| 188 | if (iY1 < Y - 1) { iY1 = Y - 1; iXIncr1 = +1; iYIncr1 = 0; } |
| 189 | if (iX1 > X + Wdt) { iX1 = X + Wdt; iXIncr1 = 0; iYIncr1 = +1; } |
| 190 | if (iY1 > Y + Hgt) { iY1 = Y + Hgt; iXIncr1 = -1; iYIncr1 = 0; } |
| 191 | if (iX1 < X - 1) { iX1 = X - 1; iXIncr1 = 0; iYIncr1 = -1; } |
| 192 | if (iY2 < Y - 1) { iY2 = Y - 1; iXIncr2 = -1; iYIncr2 = 0; } |
| 193 | if (iX2 > X + Wdt) { iX2 = X + Wdt; iXIncr2 = 0; iYIncr2 = -1; } |
| 194 | if (iY2 > Y + Hgt) { iY2 = Y + Hgt; iXIncr2 = +1; iYIncr2 = 0; } |
| 195 | if (iX2 < X - 1) { iX2 = X - 1; iXIncr2 = 0; iYIncr2 = +1; } |
| 196 | } |
| 197 | // No free found |
| 198 | if (cnt >= 2 * Wdt + 2 * Hgt) return false; |
| 199 | // Vertical walk-to adjust (only if at the side of zone) |
| 200 | if (!Inside<int32_t>(ival: rX - X, lbound: 0, rbound: Wdt - 1)) |
| 201 | AdjustMoveToTarget(rX, rY, fFreeMove: false, iShapeHgt: 20); |
| 202 | // Success |
| 203 | return true; |
| 204 | } |
| 205 | |
| 206 | void C4TransferZones::ClearUsed() |
| 207 | { |
| 208 | for (C4TransferZone *pZone = First; pZone; pZone = pZone->Next) |
| 209 | pZone->Used = false; |
| 210 | } |
| 211 | |
| 212 | C4TransferZone *C4TransferZones::Find(C4Object *pObj) |
| 213 | { |
| 214 | for (C4TransferZone *pZone = First; pZone; pZone = pZone->Next) |
| 215 | if (pZone->Object == pObj) |
| 216 | return pZone; |
| 217 | return nullptr; |
| 218 | } |
| 219 | |