| @@ -0,0 +1,624 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #ifndef CLIENT_CONST_H | |
| 24 | + | #define CLIENT_CONST_H | |
| 25 | + | ||
| 26 | + | #include <cstdint> | |
| 27 | + | ||
| 28 | + | namespace Otc { | |
| 29 | + | enum : int { | |
| 30 | + | MAX_ELEVATION = 24, | |
| 31 | + | ||
| 32 | + | SEA_FLOOR = 7, | |
| 33 | + | MAX_Z = 15, | |
| 34 | + | UNDERGROUND_FLOOR = SEA_FLOOR + 1, | |
| 35 | + | AWARE_UNDEGROUND_FLOOR_RANGE = 5, | |
| 36 | + | ||
| 37 | + | INVISIBLE_TICKS_PER_FRAME = 500, | |
| 38 | + | INVISIBLE_TICKS_PER_FRAME_FAST = 100, | |
| 39 | + | ITEM_TICKS_PER_FRAME = 500, | |
| 40 | + | ITEM_TICKS_PER_FRAME_FAST = 100, | |
| 41 | + | ANIMATED_TEXT_DURATION = 1000, | |
| 42 | + | STATIC_DURATION_PER_CHARACTER = 60, | |
| 43 | + | MIN_STATIC_TEXT_DURATION = 3000, | |
| 44 | + | MAX_STATIC_TEXT_WIDTH = 200, | |
| 45 | + | MAX_AUTOWALK_STEPS_RETRY = 10, | |
| 46 | + | MAX_AUTOWALK_DIST = 127 | |
| 47 | + | }; | |
| 48 | + | ||
| 49 | + | enum DepthConst { MAX_DEPTH = 16384 - 2048 }; | |
| 50 | + | ||
| 51 | + | enum DrawFlags { | |
| 52 | + | DrawGround = 1, | |
| 53 | + | DrawGroundBorders = 2, | |
| 54 | + | DrawOnBottom = 4, | |
| 55 | + | DrawOnTop = 8, | |
| 56 | + | DrawItems = 16, | |
| 57 | + | DrawCreatures = 32, | |
| 58 | + | DrawEffects = 64, | |
| 59 | + | DrawMissiles = 128, | |
| 60 | + | DrawCreaturesInformation = 256, | |
| 61 | + | DrawStaticTexts = 512, | |
| 62 | + | DrawAnimatedTexts = 1024, | |
| 63 | + | DrawAnimations = 2048, | |
| 64 | + | DrawBars = 4096, | |
| 65 | + | DrawNames = 8192, | |
| 66 | + | DrawLights = 16384, | |
| 67 | + | DrawManaBar = 32768, | |
| 68 | + | DontDrawLocalPlayer = 65536, | |
| 69 | + | DrawIcons = 131072, | |
| 70 | + | DrawWalls = DrawOnBottom | DrawOnTop, | |
| 71 | + | DrawEverything = DrawGround | DrawGroundBorders | DrawWalls | DrawItems | | |
| 72 | + | DrawCreatures | DrawEffects | DrawMissiles | | |
| 73 | + | DrawCreaturesInformation | DrawStaticTexts | | |
| 74 | + | DrawAnimatedTexts | DrawAnimations | DrawBars | DrawNames | | |
| 75 | + | DrawLights | DrawManaBar | DrawIcons | |
| 76 | + | }; | |
| 77 | + | ||
| 78 | + | enum DatOpts { | |
| 79 | + | DatGround = 0, | |
| 80 | + | DatGroundClip, | |
| 81 | + | DatOnBottom, | |
| 82 | + | DatOnTop, | |
| 83 | + | DatContainer, | |
| 84 | + | DatStackable, | |
| 85 | + | DatForceUse, | |
| 86 | + | DatMultiUse, | |
| 87 | + | DatWritable, | |
| 88 | + | DatWritableOnce, | |
| 89 | + | DatFluidContainer, | |
| 90 | + | DatSplash, | |
| 91 | + | DatBlockWalk, | |
| 92 | + | DatNotMoveable, | |
| 93 | + | DatBlockProjectile, | |
| 94 | + | DatBlockPathFind, | |
| 95 | + | DatPickupable, | |
| 96 | + | DatHangable, | |
| 97 | + | DatHookSouth, | |
| 98 | + | DatHookEast, | |
| 99 | + | DatRotable, | |
| 100 | + | DatLight, | |
| 101 | + | DatDontHide, | |
| 102 | + | DatTranslucent, | |
| 103 | + | DatDisplacement, | |
| 104 | + | DatElevation, | |
| 105 | + | DatLyingCorpse, | |
| 106 | + | DatAnimateAlways, | |
| 107 | + | DatMinimapColor, | |
| 108 | + | DatLensHelp, | |
| 109 | + | DatFullGround, | |
| 110 | + | DatIgnoreLook, | |
| 111 | + | DatCloth, | |
| 112 | + | DatAnimation, // lastest tibia | |
| 113 | + | DatLastOpt = 255 | |
| 114 | + | }; | |
| 115 | + | ||
| 116 | + | enum InventorySlot { | |
| 117 | + | InventorySlotHead = 1, | |
| 118 | + | InventorySlotNecklace, | |
| 119 | + | InventorySlotBackpack, | |
| 120 | + | InventorySlotArmor, | |
| 121 | + | InventorySlotRight, | |
| 122 | + | InventorySlotLeft, | |
| 123 | + | InventorySlotLegs, | |
| 124 | + | InventorySlotFeet, | |
| 125 | + | InventorySlotRing, | |
| 126 | + | InventorySlotAmmo, | |
| 127 | + | InventorySlotPurse, | |
| 128 | + | InventorySlotExt1, | |
| 129 | + | InventorySlotExt2, | |
| 130 | + | InventorySlotExt3, | |
| 131 | + | InventorySlotExt4, | |
| 132 | + | LastInventorySlot | |
| 133 | + | }; | |
| 134 | + | ||
| 135 | + | enum Statistic { | |
| 136 | + | Health = 0, | |
| 137 | + | MaxHealth, | |
| 138 | + | FreeCapacity, | |
| 139 | + | Experience, | |
| 140 | + | Level, | |
| 141 | + | LevelPercent, | |
| 142 | + | Mana, | |
| 143 | + | MaxMana, | |
| 144 | + | MagicLevel, | |
| 145 | + | MagicLevelPercent, | |
| 146 | + | Soul, | |
| 147 | + | Stamina, | |
| 148 | + | LastStatistic | |
| 149 | + | }; | |
| 150 | + | ||
| 151 | + | enum Skill { | |
| 152 | + | Fist = 0, | |
| 153 | + | Club, | |
| 154 | + | Sword, | |
| 155 | + | Axe, | |
| 156 | + | Distance, | |
| 157 | + | Shielding, | |
| 158 | + | Fishing, | |
| 159 | + | CriticalChance, | |
| 160 | + | CriticalDamage, | |
| 161 | + | LifeLeechChance, | |
| 162 | + | LifeLeechAmount, | |
| 163 | + | ManaLeechChance, | |
| 164 | + | ManaLeechAmount, | |
| 165 | + | LastSkill | |
| 166 | + | }; | |
| 167 | + | ||
| 168 | + | enum Direction { | |
| 169 | + | North = 0, | |
| 170 | + | East, | |
| 171 | + | South, | |
| 172 | + | West, | |
| 173 | + | NorthEast, | |
| 174 | + | SouthEast, | |
| 175 | + | SouthWest, | |
| 176 | + | NorthWest, | |
| 177 | + | InvalidDirection | |
| 178 | + | }; | |
| 179 | + | ||
| 180 | + | enum FluidsColor { | |
| 181 | + | FluidTransparent = 0, | |
| 182 | + | FluidBlue, | |
| 183 | + | FluidRed, | |
| 184 | + | FluidBrown, | |
| 185 | + | FluidGreen, | |
| 186 | + | FluidYellow, | |
| 187 | + | FluidWhite, | |
| 188 | + | FluidPurple | |
| 189 | + | }; | |
| 190 | + | ||
| 191 | + | enum FluidsType { | |
| 192 | + | FluidNone = 0, | |
| 193 | + | FluidWater, | |
| 194 | + | FluidMana, | |
| 195 | + | FluidBeer, | |
| 196 | + | FluidOil, | |
| 197 | + | FluidBlood, | |
| 198 | + | FluidSlime, | |
| 199 | + | FluidMud, | |
| 200 | + | FluidLemonade, | |
| 201 | + | FluidMilk, | |
| 202 | + | FluidWine, | |
| 203 | + | FluidHealth, | |
| 204 | + | FluidUrine, | |
| 205 | + | FluidRum, | |
| 206 | + | FluidFruidJuice, | |
| 207 | + | FluidCoconutMilk, | |
| 208 | + | FluidTea, | |
| 209 | + | FluidMead | |
| 210 | + | }; | |
| 211 | + | ||
| 212 | + | enum FightModes { FightOffensive = 1, FightBalanced = 2, FightDefensive = 3 }; | |
| 213 | + | ||
| 214 | + | enum ChaseModes { DontChase = 0, ChaseOpponent = 1 }; | |
| 215 | + | ||
| 216 | + | enum PVPModes { WhiteDove = 0, WhiteHand = 1, YellowHand = 2, RedFist = 3 }; | |
| 217 | + | ||
| 218 | + | enum PlayerSkulls { | |
| 219 | + | SkullNone = 0, | |
| 220 | + | SkullYellow, | |
| 221 | + | SkullGreen, | |
| 222 | + | SkullWhite, | |
| 223 | + | SkullRed, | |
| 224 | + | SkullBlack, | |
| 225 | + | SkullOrange | |
| 226 | + | }; | |
| 227 | + | ||
| 228 | + | enum PlayerShields { | |
| 229 | + | ShieldNone = 0, | |
| 230 | + | ShieldWhiteYellow, // 1 party leader | |
| 231 | + | ShieldWhiteBlue, // 2 party member | |
| 232 | + | ShieldBlue, // 3 party member sexp off | |
| 233 | + | ShieldYellow, // 4 party leader sexp off | |
| 234 | + | ShieldBlueSharedExp, // 5 party member sexp on | |
| 235 | + | ShieldYellowSharedExp, // 6 // party leader sexp on | |
| 236 | + | ShieldBlueNoSharedExpBlink, // 7 party member sexp inactive guilty | |
| 237 | + | ShieldYellowNoSharedExpBlink, // 8 // party leader sexp inactive guilty | |
| 238 | + | ShieldBlueNoSharedExp, // 9 party member sexp inactive innocent | |
| 239 | + | ShieldYellowNoSharedExp, // 10 party leader sexp inactive innocent | |
| 240 | + | ShieldGray // 11 member of another party | |
| 241 | + | }; | |
| 242 | + | ||
| 243 | + | enum PlayerEmblems { | |
| 244 | + | EmblemNone = 0, | |
| 245 | + | EmblemGreen, | |
| 246 | + | EmblemRed, | |
| 247 | + | EmblemBlue, | |
| 248 | + | EmblemMember, | |
| 249 | + | EmblemOther | |
| 250 | + | }; | |
| 251 | + | ||
| 252 | + | enum CreatureIcons { | |
| 253 | + | NpcIconNone = 0, | |
| 254 | + | NpcIconChat, | |
| 255 | + | NpcIconTrade, | |
| 256 | + | NpcIconQuest, | |
| 257 | + | NpcIconTradeQuest | |
| 258 | + | }; | |
| 259 | + | ||
| 260 | + | enum PlayerStates { | |
| 261 | + | IconNone = 0, | |
| 262 | + | IconPoison = 1, | |
| 263 | + | IconBurn = 2, | |
| 264 | + | IconEnergy = 4, | |
| 265 | + | IconDrunk = 8, | |
| 266 | + | IconManaShield = 16, | |
| 267 | + | IconParalyze = 32, | |
| 268 | + | IconHaste = 64, | |
| 269 | + | IconSwords = 128, | |
| 270 | + | IconDrowning = 256, | |
| 271 | + | IconFreezing = 512, | |
| 272 | + | IconDazzled = 1024, | |
| 273 | + | IconCursed = 2048, | |
| 274 | + | IconPartyBuff = 4096, | |
| 275 | + | IconPzBlock = 8192, | |
| 276 | + | IconPz = 16384, | |
| 277 | + | IconBleeding = 32768, | |
| 278 | + | IconHungry = 65536 | |
| 279 | + | }; | |
| 280 | + | ||
| 281 | + | enum MessageMode { | |
| 282 | + | MessageNone = 0, | |
| 283 | + | MessageSay = 1, | |
| 284 | + | MessageWhisper = 2, | |
| 285 | + | MessageYell = 3, | |
| 286 | + | MessagePrivateFrom = 4, | |
| 287 | + | MessagePrivateTo = 5, | |
| 288 | + | MessageChannelManagement = 6, | |
| 289 | + | MessageChannel = 7, | |
| 290 | + | MessageChannelHighlight = 8, | |
| 291 | + | MessageSpell = 9, | |
| 292 | + | MessageNpcFrom = 10, | |
| 293 | + | MessageNpcTo = 11, | |
| 294 | + | MessageGamemasterBroadcast = 12, | |
| 295 | + | MessageGamemasterChannel = 13, | |
| 296 | + | MessageGamemasterPrivateFrom = 14, | |
| 297 | + | MessageGamemasterPrivateTo = 15, | |
| 298 | + | MessageLogin = 16, | |
| 299 | + | MessageWarning = 17, | |
| 300 | + | MessageGame = 18, | |
| 301 | + | MessageFailure = 19, | |
| 302 | + | MessageLook = 20, | |
| 303 | + | MessageDamageDealed = 21, | |
| 304 | + | MessageDamageReceived = 22, | |
| 305 | + | MessageHeal = 23, | |
| 306 | + | MessageExp = 24, | |
| 307 | + | MessageDamageOthers = 25, | |
| 308 | + | MessageHealOthers = 26, | |
| 309 | + | MessageExpOthers = 27, | |
| 310 | + | MessageStatus = 28, | |
| 311 | + | MessageLoot = 29, | |
| 312 | + | MessageTradeNpc = 30, | |
| 313 | + | MessageGuild = 31, | |
| 314 | + | MessagePartyManagement = 32, | |
| 315 | + | MessageParty = 33, | |
| 316 | + | MessageBarkLow = 34, | |
| 317 | + | MessageBarkLoud = 35, | |
| 318 | + | MessageReport = 36, | |
| 319 | + | MessageHotkeyUse = 37, | |
| 320 | + | MessageTutorialHint = 38, | |
| 321 | + | MessageThankyou = 39, | |
| 322 | + | MessageMarket = 40, | |
| 323 | + | MessageMana = 41, | |
| 324 | + | MessageBeyondLast = 42, | |
| 325 | + | ||
| 326 | + | // deprecated | |
| 327 | + | MessageMonsterYell = 43, | |
| 328 | + | MessageMonsterSay = 44, | |
| 329 | + | MessageRed = 45, | |
| 330 | + | MessageBlue = 46, | |
| 331 | + | MessageRVRChannel = 47, | |
| 332 | + | MessageRVRAnswer = 48, | |
| 333 | + | MessageRVRContinue = 49, | |
| 334 | + | MessageGameHighlight = 50, | |
| 335 | + | MessageNpcFromStartBlock = 51, | |
| 336 | + | LastMessage = 52, | |
| 337 | + | MessageInvalid = 255 | |
| 338 | + | }; | |
| 339 | + | ||
| 340 | + | enum GameFeature { | |
| 341 | + | GameProtocolChecksum = 1, | |
| 342 | + | GameAccountNames = 2, | |
| 343 | + | GameChallengeOnLogin = 3, | |
| 344 | + | GamePenalityOnDeath = 4, | |
| 345 | + | GameNameOnNpcTrade = 5, | |
| 346 | + | GameDoubleFreeCapacity = 6, | |
| 347 | + | GameDoubleExperience = 7, | |
| 348 | + | GameTotalCapacity = 8, | |
| 349 | + | GameSkillsBase = 9, | |
| 350 | + | GamePlayerRegenerationTime = 10, | |
| 351 | + | GameChannelPlayerList = 11, | |
| 352 | + | GamePlayerMounts = 12, | |
| 353 | + | GameEnvironmentEffect = 13, | |
| 354 | + | GameCreatureEmblems = 14, | |
| 355 | + | GameItemAnimationPhase = 15, | |
| 356 | + | GameMagicEffectU16 = 16, | |
| 357 | + | GamePlayerMarket = 17, | |
| 358 | + | GameSpritesU32 = 18, | |
| 359 | + | GameTileAddThingWithStackpos = 19, | |
| 360 | + | GameOfflineTrainingTime = 20, | |
| 361 | + | GamePurseSlot = 21, | |
| 362 | + | GameFormatCreatureName = 22, | |
| 363 | + | GameSpellList = 23, | |
| 364 | + | GameClientPing = 24, | |
| 365 | + | GameExtendedClientPing = 25, | |
| 366 | + | GameDoubleHealth = 28, | |
| 367 | + | GameDoubleSkills = 29, | |
| 368 | + | GameChangeMapAwareRange = 30, | |
| 369 | + | GameMapMovePosition = 31, | |
| 370 | + | GameAttackSeq = 32, | |
| 371 | + | GameBlueNpcNameColor = 33, | |
| 372 | + | GameDiagonalAnimatedText = 34, | |
| 373 | + | GameLoginPending = 35, | |
| 374 | + | GameNewSpeedLaw = 36, | |
| 375 | + | GameForceFirstAutoWalkStep = 37, | |
| 376 | + | GameMinimapRemove = 38, | |
| 377 | + | GameDoubleShopSellAmount = 39, | |
| 378 | + | GameContainerPagination = 40, | |
| 379 | + | GameThingMarks = 41, | |
| 380 | + | GameLooktypeU16 = 42, | |
| 381 | + | GamePlayerStamina = 43, | |
| 382 | + | GamePlayerAddons = 44, | |
| 383 | + | GameMessageStatements = 45, | |
| 384 | + | GameMessageLevel = 46, | |
| 385 | + | GameNewFluids = 47, | |
| 386 | + | GamePlayerStateU16 = 48, | |
| 387 | + | GameNewOutfitProtocol = 49, | |
| 388 | + | GamePVPMode = 50, | |
| 389 | + | GameWritableDate = 51, | |
| 390 | + | GameAdditionalVipInfo = 52, | |
| 391 | + | GameBaseSkillU16 = 53, | |
| 392 | + | GameCreatureIcons = 54, | |
| 393 | + | GameHideNpcNames = 55, | |
| 394 | + | GameSpritesAlphaChannel = 56, | |
| 395 | + | GamePremiumExpiration = 57, | |
| 396 | + | GameBrowseField = 58, | |
| 397 | + | GameEnhancedAnimations = 59, | |
| 398 | + | GameOGLInformation = 60, | |
| 399 | + | GameMessageSizeCheck = 61, | |
| 400 | + | GamePreviewState = 62, | |
| 401 | + | GameLoginPacketEncryption = 63, | |
| 402 | + | GameClientVersion = 64, | |
| 403 | + | GameContentRevision = 65, | |
| 404 | + | GameExperienceBonus = 66, | |
| 405 | + | GameAuthenticator = 67, | |
| 406 | + | GameUnjustifiedPoints = 68, | |
| 407 | + | GameSessionKey = 69, | |
| 408 | + | GameDeathType = 70, | |
| 409 | + | GameIdleAnimations = 71, | |
| 410 | + | GameKeepUnawareTiles = 72, | |
| 411 | + | GameIngameStore = 73, | |
| 412 | + | GameIngameStoreHighlights = 74, | |
| 413 | + | GameIngameStoreServiceType = 75, | |
| 414 | + | GameAdditionalSkills = 76, | |
| 415 | + | GameDistanceEffectU16 = 77, | |
| 416 | + | GamePrey = 78, | |
| 417 | + | GameDoubleMagicLevel = 79, | |
| 418 | + | ||
| 419 | + | GameExtendedOpcode = 80, | |
| 420 | + | GameMinimapLimitedToSingleFloor = 81, | |
| 421 | + | GameSendWorldName = 82, | |
| 422 | + | ||
| 423 | + | GameDoubleLevel = 83, | |
| 424 | + | GameDoubleSoul = 84, | |
| 425 | + | GameDoublePlayerGoodsMoney = 85, | |
| 426 | + | GameCreatureWalkthrough = 86, | |
| 427 | + | GameDoubleTradeMoney = 87, | |
| 428 | + | GameSequencedPackets = 88, | |
| 429 | + | GameTibia12Protocol = 89, | |
| 430 | + | ||
| 431 | + | // 90-99 otclientv8 features | |
| 432 | + | GameNewWalking = 90, | |
| 433 | + | GameSlowerManualWalking = 91, | |
| 434 | + | ||
| 435 | + | GameItemTooltip = 93, | |
| 436 | + | ||
| 437 | + | GameBot = 95, | |
| 438 | + | GameBiggerMapCache = 96, | |
| 439 | + | GameForceLight = 97, | |
| 440 | + | GameNoDebug = 98, | |
| 441 | + | GameBotProtection = 99, | |
| 442 | + | ||
| 443 | + | // Custom features for customer | |
| 444 | + | GameCreatureDirectionPassable = 100, | |
| 445 | + | GameFasterAnimations = 101, | |
| 446 | + | GameCenteredOutfits = 102, | |
| 447 | + | GameSendIdentifiers = 103, | |
| 448 | + | GameWingsAndAura = 104, | |
| 449 | + | GamePlayerStateU32 = 105, | |
| 450 | + | GameOutfitShaders = 106, | |
| 451 | + | GameForceAllowItemHotkeys = 107, | |
| 452 | + | GameCountU16 = 108, | |
| 453 | + | GameDrawAuraOnTop = 109, | |
| 454 | + | ||
| 455 | + | // advanced features | |
| 456 | + | GamePacketSizeU32 = 110, | |
| 457 | + | GamePacketCompression = 111, | |
| 458 | + | ||
| 459 | + | // OTCv8-dev features | |
| 460 | + | GameOldInformationBar = 112, | |
| 461 | + | GameHealthInfoBackground = 113, | |
| 462 | + | GameWingOffset = 114, | |
| 463 | + | GameAuraFrontAndBack = 115, | |
| 464 | + | ||
| 465 | + | GameMapDrawGroundFirst = 116, | |
| 466 | + | GameMapIgnoreCorpseCorrection = 117, | |
| 467 | + | GameDontCacheFiles = 118, | |
| 468 | + | GameBigAurasCenter = 119, | |
| 469 | + | GameNewUpdateWalk = 120, | |
| 470 | + | GameNewCreatureStacking = 121, | |
| 471 | + | GameCreaturesMana = 122, | |
| 472 | + | GameQuickLootFlags = 123, | |
| 473 | + | GameDontMergeAnimatedText = 124, | |
| 474 | + | GameMissionId = 125, | |
| 475 | + | GameItemCustomAttributes = 126, | |
| 476 | + | GameAnimatedTextCustomFont = 127, | |
| 477 | + | GameOutfitAttackData = 128, | |
| 478 | + | ||
| 479 | + | LastGameFeature = 130 | |
| 480 | + | }; | |
| 481 | + | ||
| 482 | + | enum PathFindResult { | |
| 483 | + | PathFindResultOk = 0, | |
| 484 | + | PathFindResultSamePosition, | |
| 485 | + | PathFindResultImpossible, | |
| 486 | + | PathFindResultTooFar, | |
| 487 | + | PathFindResultNoWay | |
| 488 | + | }; | |
| 489 | + | ||
| 490 | + | enum PathFindFlags { | |
| 491 | + | PathFindAllowNotSeenTiles = 1, | |
| 492 | + | PathFindAllowCreatures = 2, | |
| 493 | + | PathFindAllowNonPathable = 4, | |
| 494 | + | PathFindAllowNonWalkable = 8, | |
| 495 | + | PathFindIgnoreCreatures = 16 | |
| 496 | + | }; | |
| 497 | + | ||
| 498 | + | enum AutomapFlags { | |
| 499 | + | MapMarkTick = 0, | |
| 500 | + | MapMarkQuestion, | |
| 501 | + | MapMarkExclamation, | |
| 502 | + | MapMarkStar, | |
| 503 | + | MapMarkCross, | |
| 504 | + | MapMarkTemple, | |
| 505 | + | MapMarkKiss, | |
| 506 | + | MapMarkShovel, | |
| 507 | + | MapMarkSword, | |
| 508 | + | MapMarkFlag, | |
| 509 | + | MapMarkLock, | |
| 510 | + | MapMarkBag, | |
| 511 | + | MapMarkSkull, | |
| 512 | + | MapMarkDollar, | |
| 513 | + | MapMarkRedNorth, | |
| 514 | + | MapMarkRedSouth, | |
| 515 | + | MapMarkRedEast, | |
| 516 | + | MapMarkRedWest, | |
| 517 | + | MapMarkGreenNorth, | |
| 518 | + | MapMarkGreenSouth | |
| 519 | + | }; | |
| 520 | + | ||
| 521 | + | enum VipState { VipStateOffline = 0, VipStateOnline = 1, VipStatePending = 2 }; | |
| 522 | + | ||
| 523 | + | enum SpeedFormula { | |
| 524 | + | SpeedFormulaA = 0, | |
| 525 | + | SpeedFormulaB, | |
| 526 | + | SpeedFormulaC, | |
| 527 | + | LastSpeedFormula | |
| 528 | + | }; | |
| 529 | + | ||
| 530 | + | enum Blessings { | |
| 531 | + | BlessingNone = 0, | |
| 532 | + | BlessingAdventurer = 1, | |
| 533 | + | BlessingSpiritualShielding = 1 << 1, | |
| 534 | + | BlessingEmbraceOfTibia = 1 << 2, | |
| 535 | + | BlessingFireOfSuns = 1 << 3, | |
| 536 | + | BlessingWisdomOfSolitude = 1 << 4, | |
| 537 | + | BlessingSparkOfPhoenix = 1 << 5 | |
| 538 | + | }; | |
| 539 | + | ||
| 540 | + | enum DeathType { DeathRegular = 0, DeathBlessed = 1 }; | |
| 541 | + | ||
| 542 | + | enum StoreProductTypes { ProductTypeOther = 0, ProductTypeNameChange = 1 }; | |
| 543 | + | ||
| 544 | + | enum StoreErrorTypes { | |
| 545 | + | StoreNoError = -1, | |
| 546 | + | StorePurchaseError = 0, | |
| 547 | + | StoreNetworkError = 1, | |
| 548 | + | StoreHistoryError = 2, | |
| 549 | + | StoreTransferError = 3, | |
| 550 | + | StoreInformation = 4 | |
| 551 | + | }; | |
| 552 | + | ||
| 553 | + | enum StoreStates { StateNone = 0, StateNew = 1, StateSale = 2, StateTimed = 3 }; | |
| 554 | + | ||
| 555 | + | enum PreySlotNum_t : uint8_t { | |
| 556 | + | PREY_SLOTNUM_FIRST = 0, | |
| 557 | + | PREY_SLOTNUM_SECOND = 1, | |
| 558 | + | PREY_SLOTNUM_THIRD = 2, | |
| 559 | + | PREY_SLOTNUM_LAST = PREY_SLOTNUM_THIRD | |
| 560 | + | }; | |
| 561 | + | enum PreyState_t : uint8_t { | |
| 562 | + | PREY_STATE_LOCKED = 0, | |
| 563 | + | PREY_STATE_INACTIVE = 1, | |
| 564 | + | PREY_STATE_ACTIVE = 2, | |
| 565 | + | PREY_STATE_SELECTION = 3, | |
| 566 | + | PREY_STATE_SELECTION_CHANGE_MONSTER = 4, | |
| 567 | + | PREY_STATE_SELECTION_FROMALL = 5, | |
| 568 | + | PREY_STATE_CHANGE_FROMALL = 6, | |
| 569 | + | }; | |
| 570 | + | enum PreyMessageDialog_t : uint8_t { | |
| 571 | + | // PREY_MESSAGEDIALOG_IMBUEMENT_SUCCESS = 0, | |
| 572 | + | // PREY_MESSAGEDIALOG_IMBUEMENT_ERROR = 1, | |
| 573 | + | // PREY_MESSAGEDIALOG_IMBUEMENT_ROLL_FAILED = 2, | |
| 574 | + | // PREY_MESSAGEDIALOG_IMBUEMENT_STATION_NOT_FOUND = 3, | |
| 575 | + | // PREY_MESSAGEDIALOG_IMBUEMENT_CHARM_SUCCESS = 10, | |
| 576 | + | // PREY_MESSAGEDIALOG_IMBUEMENT_CHARM_ERROR = 11, | |
| 577 | + | PREY_MESSAGEDIALOG_PREY_MESSAGE = 20, | |
| 578 | + | PREY_MESSAGEDIALOG_PREY_ERROR = 21, | |
| 579 | + | }; | |
| 580 | + | enum PreyResourceType_t : uint8_t { | |
| 581 | + | PREY_RESOURCETYPE_BANK_GOLD = 0, | |
| 582 | + | PREY_RESOURCETYPE_INVENTORY_GOLD = 1, | |
| 583 | + | PREY_RESOURCETYPE_PREY_BONUS_REROLLS = 10 | |
| 584 | + | }; | |
| 585 | + | enum PreyBonusType_t : uint8_t { | |
| 586 | + | PREY_BONUS_DAMAGE_BOOST = 0, | |
| 587 | + | PREY_BONUS_DAMAGE_REDUCTION = 1, | |
| 588 | + | PREY_BONUS_XP_BONUS = 2, | |
| 589 | + | PREY_BONUS_IMPROVED_LOOT = 3, | |
| 590 | + | PREY_BONUS_NONE = 4, // internal usage but still added to client; | |
| 591 | + | PREY_BONUS_FIRST = PREY_BONUS_DAMAGE_BOOST, | |
| 592 | + | PREY_BONUS_LAST = PREY_BONUS_IMPROVED_LOOT, | |
| 593 | + | }; | |
| 594 | + | enum PreyAction_t : uint8_t { | |
| 595 | + | PREY_ACTION_LISTREROLL = 0, | |
| 596 | + | PREY_ACTION_BONUSREROLL = 1, | |
| 597 | + | PREY_ACTION_MONSTERSELECTION = 2, | |
| 598 | + | PREY_ACTION_REQUEST_ALL_MONSTERS = 3, | |
| 599 | + | PREY_ACTION_CHANGE_FROM_ALL = 4, | |
| 600 | + | PREY_ACTION_LOCK_PREY = 5, | |
| 601 | + | ||
| 602 | + | }; | |
| 603 | + | enum PreyConfigState { | |
| 604 | + | PREY_CONFIG_STATE_FREE, | |
| 605 | + | PREY_CONFIG_STATE_PREMIUM, | |
| 606 | + | PREY_CONFIG_STATE_TIBIACOINS | |
| 607 | + | }; | |
| 608 | + | enum PreyUnlockState_t : uint8_t { | |
| 609 | + | PREY_UNLOCK_STORE_AND_PREMIUM = 0, | |
| 610 | + | PREY_UNLOCK_STORE = 1, | |
| 611 | + | PREY_UNLOCK_NONE = 2, | |
| 612 | + | }; | |
| 613 | + | ||
| 614 | + | enum MagicEffectsType_t : uint16_t { | |
| 615 | + | MAGIC_EFFECTS_END_LOOP = 0, | |
| 616 | + | MAGIC_EFFECTS_DELTA = 1, | |
| 617 | + | MAGIC_EFFECTS_DELAY = 2, | |
| 618 | + | MAGIC_EFFECTS_CREATE_EFFECT = 3, | |
| 619 | + | MAGIC_EFFECTS_CREATE_DISTANCEEFFECT = 4, | |
| 620 | + | MAGIC_EFFECTS_CREATE_DISTANCEEFFECT_REVERSED = 5, | |
| 621 | + | }; | |
| 622 | + | } // namespace Otc | |
| 623 | + | ||
| 624 | + | #endif |
| @@ -0,0 +1,143 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #include "container.h" | |
| 24 | + | #include "item.h" | |
| 25 | + | ||
| 26 | + | Container::Container(int id, int capacity, const std::string& name, const ItemPtr& containerItem, bool hasParent, bool isUnlocked, bool hasPages, int containerSize, int firstIndex) | |
| 27 | + | { | |
| 28 | + | m_id = id; | |
| 29 | + | m_capacity = capacity; | |
| 30 | + | m_name = name; | |
| 31 | + | m_containerItem = containerItem; | |
| 32 | + | m_hasParent = hasParent; | |
| 33 | + | m_closed = false; | |
| 34 | + | m_unlocked = isUnlocked; | |
| 35 | + | m_hasPages = hasPages; | |
| 36 | + | m_size = containerSize; | |
| 37 | + | m_firstIndex = firstIndex; | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | ItemPtr Container::getItem(int slot) | |
| 41 | + | { | |
| 42 | + | if(slot < 0 || slot >= (int)m_items.size()) | |
| 43 | + | return nullptr; | |
| 44 | + | return m_items[slot]; | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | void Container::onOpen(const ContainerPtr& previousContainer) | |
| 48 | + | { | |
| 49 | + | callLuaField("onOpen", previousContainer); | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | void Container::onClose() | |
| 53 | + | { | |
| 54 | + | m_closed = true; | |
| 55 | + | callLuaField("onClose"); | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | void Container::onAddItem(const ItemPtr& item, int slot) | |
| 59 | + | { | |
| 60 | + | slot -= m_firstIndex; | |
| 61 | + | ||
| 62 | + | m_size++; | |
| 63 | + | // indicates that there is a new item on next page | |
| 64 | + | if(m_hasPages && slot > m_capacity) { | |
| 65 | + | callLuaField("onSizeChange", m_size); | |
| 66 | + | return; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | if(slot == 0) | |
| 70 | + | m_items.push_front(item); | |
| 71 | + | else | |
| 72 | + | m_items.push_back(item); | |
| 73 | + | updateItemsPositions(); | |
| 74 | + | ||
| 75 | + | callLuaField("onSizeChange", m_size); | |
| 76 | + | callLuaField("onAddItem", slot, item); | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | ItemPtr Container::findItemById(uint itemId, int subType) | |
| 80 | + | { | |
| 81 | + | for(const ItemPtr& item : m_items) | |
| 82 | + | if(item->getId() == itemId && (subType == -1 || item->getSubType() == subType)) | |
| 83 | + | return item; | |
| 84 | + | return nullptr; | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | void Container::onAddItems(const std::vector<ItemPtr>& items) | |
| 88 | + | { | |
| 89 | + | for(const ItemPtr& item : items) | |
| 90 | + | m_items.push_back(item); | |
| 91 | + | updateItemsPositions(); | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | void Container::onUpdateItem(int slot, const ItemPtr& item) | |
| 95 | + | { | |
| 96 | + | slot -= m_firstIndex; | |
| 97 | + | if(slot < 0 || slot >= (int)m_items.size()) { | |
| 98 | + | g_logger.traceError("slot not found"); | |
| 99 | + | return; | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | ItemPtr oldItem = m_items[slot]; | |
| 103 | + | m_items[slot] = item; | |
| 104 | + | item->setPosition(getSlotPosition(slot)); | |
| 105 | + | ||
| 106 | + | callLuaField("onUpdateItem", slot, item, oldItem); | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | void Container::onRemoveItem(int slot, const ItemPtr& lastItem) | |
| 110 | + | { | |
| 111 | + | slot -= m_firstIndex; | |
| 112 | + | if(m_hasPages && slot >= (int)m_items.size()) { | |
| 113 | + | m_size--; | |
| 114 | + | callLuaField("onSizeChange", m_size); | |
| 115 | + | return; | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | if(slot < 0 || slot >= (int)m_items.size()) { | |
| 119 | + | g_logger.traceError("slot not found"); | |
| 120 | + | return; | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | ItemPtr item = m_items[slot]; | |
| 124 | + | m_items.erase(m_items.begin() + slot); | |
| 125 | + | ||
| 126 | + | ||
| 127 | + | if(lastItem) { | |
| 128 | + | onAddItem(lastItem, m_firstIndex + m_capacity - 1); | |
| 129 | + | m_size--; | |
| 130 | + | } | |
| 131 | + | m_size--; | |
| 132 | + | ||
| 133 | + | updateItemsPositions(); | |
| 134 | + | ||
| 135 | + | callLuaField("onSizeChange", m_size); | |
| 136 | + | callLuaField("onRemoveItem", slot, item); | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | void Container::updateItemsPositions() | |
| 140 | + | { | |
| 141 | + | for(int slot = 0; slot < (int)m_items.size(); ++slot) | |
| 142 | + | m_items[slot]->setPosition(getSlotPosition(slot)); | |
| 143 | + | } |
| @@ -0,0 +1,80 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #ifndef CONTAINER_H | |
| 24 | + | #define CONTAINER_H | |
| 25 | + | ||
| 26 | + | #include "declarations.h" | |
| 27 | + | #include "item.h" | |
| 28 | + | ||
| 29 | + | #include <framework/luaengine/luaobject.h> | |
| 30 | + | ||
| 31 | + | // @bindclass | |
| 32 | + | class Container : public LuaObject | |
| 33 | + | { | |
| 34 | + | protected: | |
| 35 | + | Container(int id, int capacity, const std::string& name, const ItemPtr& containerItem, bool hasParent, bool isUnlocked, bool hasPages, int containerSize, int firstIndex); | |
| 36 | + | ||
| 37 | + | public: | |
| 38 | + | ItemPtr getItem(int slot); | |
| 39 | + | std::deque<ItemPtr> getItems() { return m_items; } | |
| 40 | + | int getItemsCount() { return m_items.size(); } | |
| 41 | + | Position getSlotPosition(int slot) { return Position(0xffff, m_id | 0x40, slot); } | |
| 42 | + | int getId() { return m_id; } | |
| 43 | + | int getCapacity() { return m_capacity; } | |
| 44 | + | ItemPtr getContainerItem() { return m_containerItem; } | |
| 45 | + | std::string getName() { return m_name; } | |
| 46 | + | bool hasParent() { return m_hasParent; } | |
| 47 | + | bool isClosed() { return m_closed; } | |
| 48 | + | bool isUnlocked() { return m_unlocked; } | |
| 49 | + | bool hasPages() { return m_hasPages; } | |
| 50 | + | int getSize() { return m_size; } | |
| 51 | + | int getFirstIndex() { return m_firstIndex; } | |
| 52 | + | ItemPtr findItemById(uint itemId, int subType); | |
| 53 | + | ||
| 54 | + | protected: | |
| 55 | + | void onOpen(const ContainerPtr& previousContainer); | |
| 56 | + | void onClose(); | |
| 57 | + | void onAddItem(const ItemPtr& item, int slot); | |
| 58 | + | void onAddItems(const std::vector<ItemPtr>& items); | |
| 59 | + | void onUpdateItem(int slot, const ItemPtr& item); | |
| 60 | + | void onRemoveItem(int slot, const ItemPtr& lastItem); | |
| 61 | + | ||
| 62 | + | friend class Game; | |
| 63 | + | ||
| 64 | + | private: | |
| 65 | + | void updateItemsPositions(); | |
| 66 | + | ||
| 67 | + | int m_id; | |
| 68 | + | int m_capacity; | |
| 69 | + | ItemPtr m_containerItem; | |
| 70 | + | std::string m_name; | |
| 71 | + | bool m_hasParent; | |
| 72 | + | bool m_closed; | |
| 73 | + | bool m_unlocked; | |
| 74 | + | bool m_hasPages; | |
| 75 | + | int m_size; | |
| 76 | + | int m_firstIndex; | |
| 77 | + | std::deque<ItemPtr> m_items; | |
| 78 | + | }; | |
| 79 | + | ||
| 80 | + | #endif |
| @@ -0,0 +1,1247 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #include "creature.h" | |
| 24 | + | #include "thingtypemanager.h" | |
| 25 | + | #include "localplayer.h" | |
| 26 | + | #include "map.h" | |
| 27 | + | #include "tile.h" | |
| 28 | + | #include "item.h" | |
| 29 | + | #include "game.h" | |
| 30 | + | #include "effect.h" | |
| 31 | + | #include "luavaluecasts_client.h" | |
| 32 | + | #include "lightview.h" | |
| 33 | + | #include "healthbars.h" | |
| 34 | + | ||
| 35 | + | #include <framework/graphics/graphics.h> | |
| 36 | + | #include <framework/core/eventdispatcher.h> | |
| 37 | + | #include <framework/core/clock.h> | |
| 38 | + | #include <framework/core/graphicalapplication.h> | |
| 39 | + | ||
| 40 | + | #include <framework/graphics/paintershaderprogram.h> | |
| 41 | + | #include <framework/graphics/texturemanager.h> | |
| 42 | + | #include <framework/graphics/framebuffermanager.h> | |
| 43 | + | #include "spritemanager.h" | |
| 44 | + | ||
| 45 | + | #include <framework/util/stats.h> | |
| 46 | + | #include <framework/util/extras.h> | |
| 47 | + | ||
| 48 | + | std::array<double, Otc::LastSpeedFormula> Creature::m_speedFormula = { -1,-1,-1 }; | |
| 49 | + | ||
| 50 | + | Creature::Creature() : Thing() | |
| 51 | + | { | |
| 52 | + | m_id = 0; | |
| 53 | + | m_healthPercent = 100; | |
| 54 | + | m_manaPercent = -1; | |
| 55 | + | m_speed = 200; | |
| 56 | + | m_direction = Otc::South; | |
| 57 | + | m_walkDirection = Otc::South; | |
| 58 | + | m_walkAnimationPhase = 0; | |
| 59 | + | m_walkedPixels = 0; | |
| 60 | + | m_skull = Otc::SkullNone; | |
| 61 | + | m_shield = Otc::ShieldNone; | |
| 62 | + | m_emblem = Otc::EmblemNone; | |
| 63 | + | m_type = Proto::CreatureTypeUnknown; | |
| 64 | + | m_icon = Otc::NpcIconNone; | |
| 65 | + | m_lastStepDirection = Otc::InvalidDirection; | |
| 66 | + | m_footLastStep = 0; | |
| 67 | + | m_nameCache.setFont(g_fonts.getFont("verdana-11px-rounded")); | |
| 68 | + | m_nameCache.setAlign(Fw::AlignTopCenter); | |
| 69 | + | m_footStep = 0; | |
| 70 | + | //m_speedFormula.fill(-1); | |
| 71 | + | m_outfitColor = Color::white; | |
| 72 | + | m_progressBarPercent = 0; | |
| 73 | + | m_progressBarUpdateEvent = nullptr; | |
| 74 | + | g_stats.addCreature(); | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | Creature::~Creature() | |
| 78 | + | { | |
| 79 | + | g_stats.removeCreature(); | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | void Creature::draw(const Point& dest, bool animate, LightView* lightView) | |
| 83 | + | { | |
| 84 | + | if (!canBeSeen()) | |
| 85 | + | return; | |
| 86 | + | ||
| 87 | + | const int sprSize = g_sprites.spriteSize(); | |
| 88 | + | Point jumpOffset = Point(m_jumpOffset.x, m_jumpOffset.y); | |
| 89 | + | Point creatureCenter = dest - jumpOffset + m_walkOffset - getDisplacement() + Point(sprSize / 2, sprSize / 2); | |
| 90 | + | drawBottomWidgets(creatureCenter, m_walking ? m_walkDirection : m_direction); | |
| 91 | + | ||
| 92 | + | Point animationOffset = animate ? m_walkOffset : Point(0, 0); | |
| 93 | + | ||
| 94 | + | if (m_showTimedSquare && animate) { | |
| 95 | + | g_drawQueue->addBoundingRect(Rect(dest - jumpOffset + (animationOffset - getDisplacement() + 2 * g_sprites.getOffsetFactor()), Size(sprSize - 4 * g_sprites.getOffsetFactor(), sprSize - 4 * g_sprites.getOffsetFactor())), 2 * g_sprites.getOffsetFactor(), m_timedSquareColor); | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | if (m_showStaticSquare && animate) { | |
| 99 | + | g_drawQueue->addBoundingRect(Rect(dest - jumpOffset + (animationOffset - getDisplacement()), Size(sprSize, sprSize)), 2 * g_sprites.getOffsetFactor(), m_staticSquareColor); | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | if (m_outfit.getCategory() != ThingCategoryCreature) | |
| 103 | + | animationOffset -= getDisplacement(); | |
| 104 | + | ||
| 105 | + | size_t drawQueueSize = g_drawQueue->size(); | |
| 106 | + | m_outfit.setWingsOffset(getWingsOffset()); | |
| 107 | + | m_outfit.draw(dest - jumpOffset + animationOffset, m_walking ? m_walkDirection : m_direction, getAttackPulsePhase(), true, lightView); | |
| 108 | + | if (m_marked) { | |
| 109 | + | g_drawQueue->setMark(drawQueueSize, updatedMarkedColor()); | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | drawTopWidgets(creatureCenter, m_walking ? m_walkDirection : m_direction); | |
| 113 | + | ||
| 114 | + | Light light = rawGetThingType()->getLight(); | |
| 115 | + | if (m_light.intensity != light.intensity || m_light.color != light.color) | |
| 116 | + | light = m_light; | |
| 117 | + | ||
| 118 | + | // local player always have a minimum light in complete darkness | |
| 119 | + | if (isLocalPlayer()) { | |
| 120 | + | light.intensity = std::max<uint8>(light.intensity, 2); | |
| 121 | + | if (light.color == 0 || light.color > 215) | |
| 122 | + | light.color = 215; | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | if(lightView) | |
| 126 | + | lightView->addLight(creatureCenter, light); | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | void Creature::drawOutfit(const Rect& destRect, Otc::Direction direction, const Color& color, bool animate, bool ui, bool oldScaling) | |
| 130 | + | { | |
| 131 | + | if (direction == Otc::InvalidDirection) | |
| 132 | + | direction = m_direction; | |
| 133 | + | ||
| 134 | + | m_outfit.draw(destRect, direction, 0, animate, ui, oldScaling); | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | void Creature::drawInformation(const Point& point, bool useGray, const Rect& parentRect, int drawFlags) | |
| 138 | + | { | |
| 139 | + | if (!g_game.getFeature(Otc::GameOldInformationBar) && g_game.getClientVersion() >= 760) { | |
| 140 | + | if (m_healthPercent < 1) // creature is dead, we get rid of its information bar | |
| 141 | + | return; | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | Color fillColor = Color(96, 96, 96); | |
| 145 | + | ||
| 146 | + | if (!useGray) | |
| 147 | + | fillColor = m_informationColor; | |
| 148 | + | ||
| 149 | + | // calculate main rects - hp/mana | |
| 150 | + | Rect backgroundRect = Rect(point.x + m_informationOffset.x - (13.5), point.y + m_informationOffset.y, 27, 4); | |
| 151 | + | backgroundRect.bind(parentRect); | |
| 152 | + | ||
| 153 | + | //debug | |
| 154 | + | if (g_extras.debugWalking) { | |
| 155 | + | int footDelay = (getStepDuration(true)) / 3; | |
| 156 | + | int footAnimPhases = getWalkAnimationPhases() - 1; | |
| 157 | + | m_nameCache.setText(stdext::format("%i %i %i %i %i\n %i %i\n%i %i %i\n%i %i %i %i %i", | |
| 158 | + | (int)m_stepDuration, (int)getStepDuration(true), getStepDuration(false), (int)m_walkedPixels, (int)m_walkTimer.ticksElapsed(), | |
| 159 | + | (int)m_walkOffset.x, (int)m_walkOffset.y, | |
| 160 | + | (int)m_speed, (int)getTile()->getGroundSpeed(), (int)g_game.getWalkId(), | |
| 161 | + | (int)(g_clock.millis() - m_footLastStep), (int)footDelay, (int)footAnimPhases, (int)m_walkAnimationPhase, (int)stdext::millis())); | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | Size nameSize = m_nameCache.getTextSize(); | |
| 165 | + | Rect textRect = Rect(point.x + m_informationOffset.x - nameSize.width() / 2.0, point.y + m_informationOffset.y - 12, nameSize); | |
| 166 | + | textRect.bind(parentRect); | |
| 167 | + | ||
| 168 | + | // distance them | |
| 169 | + | uint32 offset = 12; | |
| 170 | + | if (isLocalPlayer()) { | |
| 171 | + | offset *= 2; | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | if (textRect.top() == parentRect.top()) | |
| 175 | + | backgroundRect.moveTop(textRect.top() + offset); | |
| 176 | + | if (backgroundRect.bottom() == parentRect.bottom()) | |
| 177 | + | textRect.moveTop(backgroundRect.top() - offset); | |
| 178 | + | ||
| 179 | + | HealthBarPtr healthBar = nullptr; | |
| 180 | + | HealthBarPtr manaBar = nullptr; | |
| 181 | + | if (g_game.getFeature(Otc::GameHealthInfoBackground)) { | |
| 182 | + | if (m_outfit.getHealthBar() > 0) { | |
| 183 | + | healthBar = g_healthBars.getHealthBar(m_outfit.getHealthBar()); | |
| 184 | + | } | |
| 185 | + | if (m_outfit.getManaBar() > 0) { | |
| 186 | + | manaBar = g_healthBars.getManaBar(m_outfit.getManaBar()); | |
| 187 | + | } | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | if (healthBar) { | |
| 191 | + | backgroundRect.setHeight(healthBar->getHeight()); | |
| 192 | + | backgroundRect.moveTop(backgroundRect.top() + healthBar->getBarOffset().y); | |
| 193 | + | backgroundRect.moveLeft(backgroundRect.left() + healthBar->getBarOffset().x); | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | // health rect is based on background rect, so no worries | |
| 197 | + | Rect healthRect = backgroundRect.expanded(-1); | |
| 198 | + | healthRect.setWidth((m_healthPercent / 100.0) * 25); | |
| 199 | + | ||
| 200 | + | // draw | |
| 201 | + | if (g_game.getFeature(Otc::GameBlueNpcNameColor) && isNpc() && m_healthPercent == 100 && !useGray) | |
| 202 | + | fillColor = Color(0x66, 0xcc, 0xff); | |
| 203 | + | ||
| 204 | + | if (drawFlags & Otc::DrawBars && (!isNpc() || !g_game.getFeature(Otc::GameHideNpcNames))) { | |
| 205 | + | if (healthBar) { | |
| 206 | + | TexturePtr barTexture = healthBar->getTexture(); | |
| 207 | + | Rect barRect = Rect(backgroundRect.x() + healthBar->getOffset().x, backgroundRect.y() + healthBar->getOffset().y, barTexture->getSize()); | |
| 208 | + | g_drawQueue->addTexturedRect(barRect, barTexture, Rect(0, 0, barTexture->getSize())); | |
| 209 | + | } | |
| 210 | + | g_drawQueue->addFilledRect(backgroundRect, Color::black); | |
| 211 | + | g_drawQueue->addFilledRect(healthRect, fillColor); | |
| 212 | + | ||
| 213 | + | if (drawFlags & Otc::DrawManaBar) { | |
| 214 | + | int8 manaPercent = m_manaPercent; | |
| 215 | + | if (isLocalPlayer()) { | |
| 216 | + | LocalPlayerPtr player = g_game.getLocalPlayer(); | |
| 217 | + | if (player) { | |
| 218 | + | double maxMana = player->getMaxMana(); | |
| 219 | + | if (maxMana == 0) { | |
| 220 | + | manaPercent = 100; | |
| 221 | + | } else { | |
| 222 | + | manaPercent = (player->getMana() * 100) / maxMana; | |
| 223 | + | } | |
| 224 | + | } | |
| 225 | + | } | |
| 226 | + | if (manaPercent >= 0) { | |
| 227 | + | backgroundRect.moveTop(backgroundRect.bottom()); | |
| 228 | + | if (healthBar) { | |
| 229 | + | backgroundRect.moveTop(backgroundRect.top() + healthBar->getBarOffset().y + 1); | |
| 230 | + | } | |
| 231 | + | if (manaBar) { | |
| 232 | + | if (!healthBar) { | |
| 233 | + | backgroundRect.moveTop(backgroundRect.top() + 1); | |
| 234 | + | } | |
| 235 | + | backgroundRect.setHeight(manaBar->getHeight()); | |
| 236 | + | backgroundRect.moveTop(backgroundRect.top() + manaBar->getBarOffset().y); | |
| 237 | + | backgroundRect.moveLeft(backgroundRect.left() + manaBar->getBarOffset().x); | |
| 238 | + | ||
| 239 | + | TexturePtr barTexture = manaBar->getTexture(); | |
| 240 | + | Rect barRect = Rect(backgroundRect.x() + manaBar->getOffset().x, backgroundRect.y() + manaBar->getOffset().y, barTexture->getSize()); | |
| 241 | + | g_drawQueue->addTexturedRect(barRect, barTexture, Rect(0, 0, barTexture->getSize())); | |
| 242 | + | } | |
| 243 | + | g_drawQueue->addFilledRect(backgroundRect, Color::black); | |
| 244 | + | ||
| 245 | + | Rect manaRect = backgroundRect.expanded(-1); | |
| 246 | + | manaRect.setWidth(((float)manaPercent / 100.f) * 25); | |
| 247 | + | g_drawQueue->addFilledRect(manaRect, Color::blue); | |
| 248 | + | } | |
| 249 | + | } | |
| 250 | + | ||
| 251 | + | if (getProgressBarPercent()) { | |
| 252 | + | backgroundRect.moveTop(backgroundRect.bottom()); | |
| 253 | + | ||
| 254 | + | g_drawQueue->addFilledRect(backgroundRect, Color::black); | |
| 255 | + | ||
| 256 | + | Rect progressBarRect = backgroundRect.expanded(-1); | |
| 257 | + | double maxBar = 100; | |
| 258 | + | progressBarRect.setWidth(getProgressBarPercent() / (maxBar * 1.0) * 25); | |
| 259 | + | ||
| 260 | + | g_drawQueue->addFilledRect(progressBarRect, Color::white); | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | if (drawFlags & Otc::DrawNames) { | |
| 265 | + | m_nameCache.draw(textRect, fillColor); | |
| 266 | + | ||
| 267 | + | if (m_titleCache.hasText()) { | |
| 268 | + | Size titleSize = m_titleCache.getTextSize(); | |
| 269 | + | Point textCenter = textRect.topCenter(); | |
| 270 | + | textRect.setSize(titleSize); | |
| 271 | + | textRect.moveBottomCenter(textCenter); | |
| 272 | + | m_titleCache.draw(textRect, m_titleColor); | |
| 273 | + | } | |
| 274 | + | ||
| 275 | + | if (m_text) { | |
| 276 | + | auto extraTextSize = m_text->getCachedText().getTextSize(); | |
| 277 | + | Rect extraTextRect = Rect(point.x + m_informationOffset.x - extraTextSize.width() / 2.0, point.y + m_informationOffset.y + 15, extraTextSize); | |
| 278 | + | m_text->drawText(extraTextRect.center(), extraTextRect); | |
| 279 | + | } | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | if (!(drawFlags & Otc::DrawIcons)) | |
| 283 | + | return; | |
| 284 | + | ||
| 285 | + | if (m_skull != Otc::SkullNone && m_skullTexture) { | |
| 286 | + | Rect skullRect = Rect(backgroundRect.x() + 13.5 + 12, backgroundRect.y() + 5, m_skullTexture->getSize()); | |
| 287 | + | g_drawQueue->addTexturedRect(skullRect, m_skullTexture, Rect(0, 0, m_skullTexture->getSize())); | |
| 288 | + | } | |
| 289 | + | if (m_shield != Otc::ShieldNone && m_shieldTexture && m_showShieldTexture) { | |
| 290 | + | Rect shieldRect = Rect(backgroundRect.x() + 13.5, backgroundRect.y() + 5, m_shieldTexture->getSize()); | |
| 291 | + | g_drawQueue->addTexturedRect(shieldRect, m_shieldTexture, Rect(0, 0, m_shieldTexture->getSize())); | |
| 292 | + | } | |
| 293 | + | if (m_emblem != Otc::EmblemNone && m_emblemTexture) { | |
| 294 | + | Rect emblemRect = Rect(backgroundRect.x() + 13.5 + 12, backgroundRect.y() + 16, m_emblemTexture->getSize()); | |
| 295 | + | g_drawQueue->addTexturedRect(emblemRect, m_emblemTexture, Rect(0, 0, m_emblemTexture->getSize())); | |
| 296 | + | } | |
| 297 | + | if (m_type != Proto::CreatureTypeUnknown && m_typeTexture) { | |
| 298 | + | Rect typeRect = Rect(backgroundRect.x() + 13.5 + 12 + 12, backgroundRect.y() + 16, m_typeTexture->getSize()); | |
| 299 | + | g_drawQueue->addTexturedRect(typeRect, m_typeTexture, Rect(0, 0, m_typeTexture->getSize())); | |
| 300 | + | } | |
| 301 | + | if (m_icon != Otc::NpcIconNone && m_iconTexture) { | |
| 302 | + | Rect iconRect = Rect(backgroundRect.x() + 13.5 + 12, backgroundRect.y() + 5, m_iconTexture->getSize()); | |
| 303 | + | g_drawQueue->addTexturedRect(iconRect, m_iconTexture, Rect(0, 0, m_iconTexture->getSize())); | |
| 304 | + | } | |
| 305 | + | } | |
| 306 | + | ||
| 307 | + | bool Creature::isInsideOffset(Point offset) | |
| 308 | + | { | |
| 309 | + | // for worse precision: | |
| 310 | + | // Rect rect(getDrawOffset() - (m_walking ? m_walkOffset : Point(0,0)), Size(Otc::TILE_PIXELS - getDisplacementY(), Otc::TILE_PIXELS - getDisplacementX())); | |
| 311 | + | Rect rect(getDrawOffset() - getDisplacement(), Size(g_sprites.spriteSize(), g_sprites.spriteSize())); | |
| 312 | + | return rect.contains(offset); | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | bool Creature::canShoot(int distance) | |
| 316 | + | { | |
| 317 | + | return getTile() ? getTile()->canShoot(distance) : false; | |
| 318 | + | } | |
| 319 | + | ||
| 320 | + | void Creature::turn(Otc::Direction direction) | |
| 321 | + | { | |
| 322 | + | setDirection(direction); | |
| 323 | + | callLuaField("onTurn", direction); | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | void Creature::walk(const Position& oldPos, const Position& newPos) | |
| 327 | + | { | |
| 328 | + | if (oldPos == newPos) | |
| 329 | + | return; | |
| 330 | + | ||
| 331 | + | // get walk direction | |
| 332 | + | m_lastStepDirection = oldPos.getDirectionFromPosition(newPos); | |
| 333 | + | m_lastStepFromPosition = oldPos; | |
| 334 | + | m_lastStepToPosition = newPos; | |
| 335 | + | ||
| 336 | + | // set current walking direction | |
| 337 | + | setDirection(m_lastStepDirection); | |
| 338 | + | m_walkDirection = m_direction; | |
| 339 | + | ||
| 340 | + | // starts counting walk | |
| 341 | + | m_walking = true; | |
| 342 | + | m_walkTimer.restart(); | |
| 343 | + | m_walkedPixels = 0; | |
| 344 | + | ||
| 345 | + | if (m_walkFinishAnimEvent) { | |
| 346 | + | m_walkFinishAnimEvent->cancel(); | |
| 347 | + | m_walkFinishAnimEvent = nullptr; | |
| 348 | + | } | |
| 349 | + | ||
| 350 | + | // starts updating walk | |
| 351 | + | nextWalkUpdate(); | |
| 352 | + | } | |
| 353 | + | ||
| 354 | + | void Creature::stopWalk() | |
| 355 | + | { | |
| 356 | + | if (!m_walking) | |
| 357 | + | return; | |
| 358 | + | ||
| 359 | + | // stops the walk right away | |
| 360 | + | terminateWalk(); | |
| 361 | + | } | |
| 362 | + | ||
| 363 | + | void Creature::jump(int height, int duration) | |
| 364 | + | { | |
| 365 | + | if (!m_jumpOffset.isNull()) | |
| 366 | + | return; | |
| 367 | + | ||
| 368 | + | m_jumpTimer.restart(); | |
| 369 | + | m_jumpHeight = height; | |
| 370 | + | m_jumpDuration = duration; | |
| 371 | + | ||
| 372 | + | updateJump(); | |
| 373 | + | } | |
| 374 | + | ||
| 375 | + | void Creature::updateJump() | |
| 376 | + | { | |
| 377 | + | int t = m_jumpTimer.ticksElapsed(); | |
| 378 | + | double a = -4 * m_jumpHeight / (m_jumpDuration * m_jumpDuration); | |
| 379 | + | double b = +4 * m_jumpHeight / (m_jumpDuration); | |
| 380 | + | ||
| 381 | + | double height = a * t * t + b * t; | |
| 382 | + | int roundHeight = stdext::round(height); | |
| 383 | + | int halfJumpDuration = m_jumpDuration / 2; | |
| 384 | + | ||
| 385 | + | // schedules next update | |
| 386 | + | if (m_jumpTimer.ticksElapsed() < m_jumpDuration) { | |
| 387 | + | m_jumpOffset = PointF(height, height); | |
| 388 | + | ||
| 389 | + | int diff = 0; | |
| 390 | + | if (m_jumpTimer.ticksElapsed() < halfJumpDuration) | |
| 391 | + | diff = 1; | |
| 392 | + | else if (m_jumpTimer.ticksElapsed() > halfJumpDuration) | |
| 393 | + | diff = -1; | |
| 394 | + | ||
| 395 | + | int nextT, i = 1; | |
| 396 | + | do { | |
| 397 | + | nextT = stdext::round((-b + std::sqrt(std::max<double>(b * b + 4 * a * (roundHeight + diff * i), 0.0)) * diff) / (2 * a)); | |
| 398 | + | ++i; | |
| 399 | + | ||
| 400 | + | if (nextT < halfJumpDuration) | |
| 401 | + | diff = 1; | |
| 402 | + | else if (nextT > halfJumpDuration) | |
| 403 | + | diff = -1; | |
| 404 | + | } while (nextT - m_jumpTimer.ticksElapsed() == 0 && i < 3); | |
| 405 | + | ||
| 406 | + | auto self = static_self_cast<Creature>(); | |
| 407 | + | g_dispatcher.scheduleEvent([self] { | |
| 408 | + | self->updateJump(); | |
| 409 | + | }, nextT - m_jumpTimer.ticksElapsed()); | |
| 410 | + | } else | |
| 411 | + | m_jumpOffset = PointF(0, 0); | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | void Creature::onPositionChange(const Position& newPos, const Position& oldPos) | |
| 415 | + | { | |
| 416 | + | callLuaField("onPositionChange", newPos, oldPos); | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | void Creature::onAppear() | |
| 420 | + | { | |
| 421 | + | // cancel any disappear event | |
| 422 | + | if (m_disappearEvent) { | |
| 423 | + | m_disappearEvent->cancel(); | |
| 424 | + | m_disappearEvent = nullptr; | |
| 425 | + | } | |
| 426 | + | ||
| 427 | + | // creature appeared the first time or wasn't seen for a long time | |
| 428 | + | if (m_removed) { | |
| 429 | + | stopWalk(); | |
| 430 | + | m_removed = false; | |
| 431 | + | callLuaField("onAppear"); | |
| 432 | + | // walk | |
| 433 | + | } else if (m_oldPosition != m_position && m_oldPosition.isInRange(m_position, 1, 1) && m_allowAppearWalk) { | |
| 434 | + | m_allowAppearWalk = false; | |
| 435 | + | walk(m_oldPosition, m_position); | |
| 436 | + | callLuaField("onWalk", m_oldPosition, m_position); | |
| 437 | + | // teleport | |
| 438 | + | } else if (m_oldPosition != m_position) { | |
| 439 | + | stopWalk(); | |
| 440 | + | callLuaField("onDisappear"); | |
| 441 | + | callLuaField("onAppear"); | |
| 442 | + | } // else turn | |
| 443 | + | } | |
| 444 | + | ||
| 445 | + | void Creature::onDisappear() | |
| 446 | + | { | |
| 447 | + | if (m_disappearEvent) | |
| 448 | + | m_disappearEvent->cancel(); | |
| 449 | + | ||
| 450 | + | m_oldPosition = m_position; | |
| 451 | + | ||
| 452 | + | // a pair onDisappear and onAppear events are fired even when creatures walks or turns, | |
| 453 | + | // so we must filter | |
| 454 | + | auto self = static_self_cast<Creature>(); | |
| 455 | + | m_disappearEvent = g_dispatcher.addEvent([self] { | |
| 456 | + | self->m_removed = true; | |
| 457 | + | self->stopWalk(); | |
| 458 | + | ||
| 459 | + | self->callLuaField("onDisappear"); | |
| 460 | + | ||
| 461 | + | // invalidate this creature position | |
| 462 | + | if (!self->isLocalPlayer()) | |
| 463 | + | self->setPosition(Position()); | |
| 464 | + | self->m_oldPosition = Position(); | |
| 465 | + | self->m_disappearEvent = nullptr; | |
| 466 | + | self->clearWidgets(); | |
| 467 | + | }); | |
| 468 | + | } | |
| 469 | + | ||
| 470 | + | void Creature::onDeath() | |
| 471 | + | { | |
| 472 | + | callLuaField("onDeath"); | |
| 473 | + | } | |
| 474 | + | ||
| 475 | + | int Creature::getWalkAnimationPhases() | |
| 476 | + | { | |
| 477 | + | if (!getAnimator()) | |
| 478 | + | return getAnimationPhases(); | |
| 479 | + | return getAnimator()->getAnimationPhases() + (g_game.getFeature(Otc::GameIdleAnimations) ? 1 : 0); | |
| 480 | + | } | |
| 481 | + | ||
| 482 | + | void Creature::updateWalkAnimation(uint8 totalPixelsWalked) | |
| 483 | + | { | |
| 484 | + | // update outfit animation | |
| 485 | + | if (m_outfit.getCategory() != ThingCategoryCreature) | |
| 486 | + | return; | |
| 487 | + | ||
| 488 | + | int footAnimPhases = getWalkAnimationPhases() - 1; | |
| 489 | + | // TODO, should be /2 for <= 810 | |
| 490 | + | uint16 footDelay = getStepDuration(true); | |
| 491 | + | if (footAnimPhases > 0) { | |
| 492 | + | footDelay = ((getStepDuration(true) + 20) / (g_game.getFeature(Otc::GameFasterAnimations) ? footAnimPhases * 2 : footAnimPhases)); | |
| 493 | + | } | |
| 494 | + | if (!g_game.getFeature(Otc::GameFasterAnimations)) | |
| 495 | + | footDelay += 10; | |
| 496 | + | if (footDelay < 20) | |
| 497 | + | footDelay = 20; | |
| 498 | + | ||
| 499 | + | // Since mount is a different outfit we need to get the mount animation phases | |
| 500 | + | if (m_outfit.getMount() != 0) { | |
| 501 | + | ThingType* type = g_things.rawGetThingType(m_outfit.getMount(), m_outfit.getCategory()); | |
| 502 | + | footAnimPhases = std::min<int>(footAnimPhases, type->getAnimationPhases() - 1); | |
| 503 | + | } | |
| 504 | + | ||
| 505 | + | if (footAnimPhases == 0) { | |
| 506 | + | m_walkAnimationPhase = 0; | |
| 507 | + | } else if (g_clock.millis() >= m_footLastStep + footDelay && totalPixelsWalked < g_sprites.spriteSize()) { | |
| 508 | + | m_footStep++; | |
| 509 | + | m_walkAnimationPhase = 1 + (m_footStep % footAnimPhases); | |
| 510 | + | m_footLastStep = (g_clock.millis() - m_footLastStep) > footDelay * 1.5 ? g_clock.millis() : m_footLastStep + footDelay; | |
| 511 | + | } else if (m_walkAnimationPhase == 0 && totalPixelsWalked < g_sprites.spriteSize()) { | |
| 512 | + | m_walkAnimationPhase = 1 + (m_footStep % footAnimPhases); | |
| 513 | + | } | |
| 514 | + | ||
| 515 | + | if (totalPixelsWalked == g_sprites.spriteSize() && !m_walkFinishAnimEvent) { | |
| 516 | + | auto self = static_self_cast<Creature>(); | |
| 517 | + | m_walkFinishAnimEvent = g_dispatcher.scheduleEvent([self] { | |
| 518 | + | self->m_footStep = 0; | |
| 519 | + | self->m_walkAnimationPhase = 0; | |
| 520 | + | self->m_walkFinishAnimEvent = nullptr; | |
| 521 | + | }, 50); | |
| 522 | + | } | |
| 523 | + | ||
| 524 | + | } | |
| 525 | + | ||
| 526 | + | void Creature::updateWalkOffset(uint8 totalPixelsWalked, bool inNextFrame) | |
| 527 | + | { | |
| 528 | + | Point& walkOffset = inNextFrame ? m_walkOffsetInNextFrame : m_walkOffset; | |
| 529 | + | walkOffset = Point(0, 0); | |
| 530 | + | if (m_walkDirection == Otc::North || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::NorthWest) | |
| 531 | + | walkOffset.y = g_sprites.spriteSize() - totalPixelsWalked; | |
| 532 | + | else if (m_walkDirection == Otc::South || m_walkDirection == Otc::SouthEast || m_walkDirection == Otc::SouthWest) | |
| 533 | + | walkOffset.y = totalPixelsWalked - g_sprites.spriteSize(); | |
| 534 | + | ||
| 535 | + | if (m_walkDirection == Otc::East || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::SouthEast) | |
| 536 | + | walkOffset.x = totalPixelsWalked - g_sprites.spriteSize(); | |
| 537 | + | else if (m_walkDirection == Otc::West || m_walkDirection == Otc::NorthWest || m_walkDirection == Otc::SouthWest) | |
| 538 | + | walkOffset.x = g_sprites.spriteSize() - totalPixelsWalked; | |
| 539 | + | } | |
| 540 | + | ||
| 541 | + | void Creature::updateWalkingTile() | |
| 542 | + | { | |
| 543 | + | // determine new walking tile | |
| 544 | + | TilePtr newWalkingTile; | |
| 545 | + | Rect virtualCreatureRect(g_sprites.spriteSize() + (m_walkOffset.x - getDisplacementX()), | |
| 546 | + | g_sprites.spriteSize() + (m_walkOffset.y - getDisplacementY()), | |
| 547 | + | g_sprites.spriteSize(), g_sprites.spriteSize()); | |
| 548 | + | for (int xi = -1; xi <= 1 && !newWalkingTile; ++xi) { | |
| 549 | + | for (int yi = -1; yi <= 1 && !newWalkingTile; ++yi) { | |
| 550 | + | Rect virtualTileRect((xi + 1) * g_sprites.spriteSize(), (yi + 1) * g_sprites.spriteSize(), g_sprites.spriteSize(), g_sprites.spriteSize()); | |
| 551 | + | ||
| 552 | + | // only render creatures where bottom right is inside tile rect | |
| 553 | + | if (virtualTileRect.contains(virtualCreatureRect.bottomRight())) { | |
| 554 | + | newWalkingTile = g_map.getOrCreateTile(getPrewalkingPosition().translated(xi, yi, 0)); | |
| 555 | + | } | |
| 556 | + | } | |
| 557 | + | } | |
| 558 | + | ||
| 559 | + | if (newWalkingTile != m_walkingTile) { | |
| 560 | + | if (m_walkingTile) | |
| 561 | + | m_walkingTile->removeWalkingCreature(static_self_cast<Creature>()); | |
| 562 | + | if (newWalkingTile) { | |
| 563 | + | newWalkingTile->addWalkingCreature(static_self_cast<Creature>()); | |
| 564 | + | ||
| 565 | + | // recache visible tiles in map views | |
| 566 | + | if (newWalkingTile->isEmpty()) | |
| 567 | + | g_map.notificateTileUpdate(newWalkingTile->getPosition()); | |
| 568 | + | } | |
| 569 | + | m_walkingTile = newWalkingTile; | |
| 570 | + | } | |
| 571 | + | } | |
| 572 | + | ||
| 573 | + | void Creature::nextWalkUpdate() | |
| 574 | + | { | |
| 575 | + | // remove any previous scheduled walk updates | |
| 576 | + | if (m_walkUpdateEvent) | |
| 577 | + | m_walkUpdateEvent->cancel(); | |
| 578 | + | ||
| 579 | + | // do the update | |
| 580 | + | updateWalk(); | |
| 581 | + | ||
| 582 | + | // schedules next update | |
| 583 | + | if (!m_walking) { | |
| 584 | + | return; | |
| 585 | + | } | |
| 586 | + | ||
| 587 | + | auto self = static_self_cast<Creature>(); | |
| 588 | + | m_walkUpdateEvent = g_dispatcher.scheduleEvent([self]{ | |
| 589 | + | self->m_walkUpdateEvent = nullptr; | |
| 590 | + | self->nextWalkUpdate(); | |
| 591 | + | }, g_game.getFeature(Otc::GameNewUpdateWalk) ? | |
| 592 | + | std::max(getStepDuration(true) / std::max(g_app.getFps(), 1), 1) : (float)getStepDuration() / g_sprites.spriteSize() | |
| 593 | + | ); | |
| 594 | + | } | |
| 595 | + | ||
| 596 | + | void Creature::updateWalk() | |
| 597 | + | { | |
| 598 | + | float walkTicksPerPixel = ((float)(getStepDuration(true) + (g_game.getFeature(Otc::GameNewUpdateWalk) ? 0 : 10))) / (float)g_sprites.spriteSize(); | |
| 599 | + | uint8 totalPixelsWalked = std::min<uint8>(m_walkTimer.ticksElapsed() / walkTicksPerPixel, g_sprites.spriteSize()); | |
| 600 | + | uint8 totalPixelsWalkedInNextFrame = std::min<uint8>((m_walkTimer.ticksElapsed() + (g_game.getFeature(Otc::GameNewUpdateWalk) ? std::max(1000.f / g_app.getFps(), 1.0f) : 15)) / walkTicksPerPixel, g_sprites.spriteSize()); | |
| 601 | + | ||
| 602 | + | // needed for paralyze effect | |
| 603 | + | m_walkedPixels = std::max<uint8>(m_walkedPixels, totalPixelsWalked); | |
| 604 | + | uint8 walkedPixelsInNextFrame = std::max<uint8>(m_walkedPixels, totalPixelsWalkedInNextFrame); | |
| 605 | + | ||
| 606 | + | // update walk animation and offsets | |
| 607 | + | updateWalkAnimation(totalPixelsWalked); | |
| 608 | + | updateWalkOffset(m_walkedPixels); | |
| 609 | + | updateWalkOffset(walkedPixelsInNextFrame, true); | |
| 610 | + | updateWalkingTile(); | |
| 611 | + | ||
| 612 | + | // terminate walk | |
| 613 | + | if (!isLocalPlayer() && m_walking && m_walkTimer.ticksElapsed() >= getStepDuration()) | |
| 614 | + | terminateWalk(); | |
| 615 | + | } | |
| 616 | + | ||
| 617 | + | void Creature::terminateWalk() | |
| 618 | + | { | |
| 619 | + | // remove any scheduled walk update | |
| 620 | + | if (m_walkUpdateEvent) { | |
| 621 | + | m_walkUpdateEvent->cancel(); | |
| 622 | + | m_walkUpdateEvent = nullptr; | |
| 623 | + | } | |
| 624 | + | ||
| 625 | + | if (m_walkingTile) { | |
| 626 | + | m_walkingTile->removeWalkingCreature(static_self_cast<Creature>()); | |
| 627 | + | m_walkingTile = nullptr; | |
| 628 | + | } | |
| 629 | + | ||
| 630 | + | m_walking = false; | |
| 631 | + | m_walkedPixels = 0; | |
| 632 | + | m_walkOffset = Point(0, 0); | |
| 633 | + | m_walkOffsetInNextFrame = Point(0, 0); | |
| 634 | + | ||
| 635 | + | // reset walk animation states | |
| 636 | + | if (!m_walkFinishAnimEvent) { | |
| 637 | + | auto self = static_self_cast<Creature>(); | |
| 638 | + | m_walkFinishAnimEvent = g_dispatcher.scheduleEvent([self] { | |
| 639 | + | self->m_footStep = 0; | |
| 640 | + | self->m_walkAnimationPhase = 0; | |
| 641 | + | self->m_walkFinishAnimEvent = nullptr; | |
| 642 | + | }, 50); | |
| 643 | + | } | |
| 644 | + | } | |
| 645 | + | ||
| 646 | + | void Creature::setName(const std::string& name) | |
| 647 | + | { | |
| 648 | + | m_nameCache.setText(name); | |
| 649 | + | m_name = name; | |
| 650 | + | } | |
| 651 | + | ||
| 652 | + | void Creature::setHealthPercent(uint8 healthPercent) | |
| 653 | + | { | |
| 654 | + | if (healthPercent > 100) | |
| 655 | + | healthPercent = 100; | |
| 656 | + | ||
| 657 | + | if (!m_useCustomInformationColor) { | |
| 658 | + | if (healthPercent > 92) | |
| 659 | + | m_informationColor = Color(0x00, 0xBC, 0x00); | |
| 660 | + | else if (healthPercent > 60) | |
| 661 | + | m_informationColor = Color(0x50, 0xA1, 0x50); | |
| 662 | + | else if (healthPercent > 30) | |
| 663 | + | m_informationColor = Color(0xA1, 0xA1, 0x00); | |
| 664 | + | else if (healthPercent > 8) | |
| 665 | + | m_informationColor = Color(0xBF, 0x0A, 0x0A); | |
| 666 | + | else if (healthPercent > 3) | |
| 667 | + | m_informationColor = Color(0x91, 0x0F, 0x0F); | |
| 668 | + | else | |
| 669 | + | m_informationColor = Color(0x2C, 0x0F, 0x0F); | |
| 670 | + | } | |
| 671 | + | ||
| 672 | + | bool changed = m_healthPercent != healthPercent; | |
| 673 | + | m_healthPercent = healthPercent; | |
| 674 | + | if (changed) { | |
| 675 | + | callLuaField("onHealthPercentChange", healthPercent); | |
| 676 | + | } | |
| 677 | + | ||
| 678 | + | if (healthPercent <= 0) | |
| 679 | + | onDeath(); | |
| 680 | + | } | |
| 681 | + | ||
| 682 | + | void Creature::setDirection(Otc::Direction direction) | |
| 683 | + | { | |
| 684 | + | VALIDATE(direction != Otc::InvalidDirection); | |
| 685 | + | m_direction = direction; | |
| 686 | + | } | |
| 687 | + | ||
| 688 | + | void Creature::setOutfit(const Outfit& outfit) | |
| 689 | + | { | |
| 690 | + | Outfit oldOutfit = m_outfit; | |
| 691 | + | if (outfit.getCategory() != ThingCategoryCreature) { | |
| 692 | + | if (!g_things.isValidDatId(outfit.getAuxId(), outfit.getCategory())) | |
| 693 | + | return; | |
| 694 | + | m_outfit.setAuxId(outfit.getAuxId()); | |
| 695 | + | m_outfit.setCategory(outfit.getCategory()); | |
| 696 | + | m_outfit.setWings(0); | |
| 697 | + | m_outfit.setAura(0); | |
| 698 | + | } else { | |
| 699 | + | if (outfit.getId() > 0 && !g_things.isValidDatId(outfit.getId(), ThingCategoryCreature)) | |
| 700 | + | return; | |
| 701 | + | m_outfit = outfit; | |
| 702 | + | } | |
| 703 | + | m_walkAnimationPhase = 0; // might happen when player is walking and outfit is changed. | |
| 704 | + | m_attackPulseDuration = 0; | |
| 705 | + | ||
| 706 | + | callLuaField("onOutfitChange", m_outfit, oldOutfit); | |
| 707 | + | } | |
| 708 | + | ||
| 709 | + | void Creature::attackPulse(const Outfit& outfit, uint16 duration) | |
| 710 | + | { | |
| 711 | + | setOutfit(outfit); | |
| 712 | + | m_attackPulseDuration = std::max<uint16>(1, duration); | |
| 713 | + | m_attackPulseTimer.restart(); | |
| 714 | + | } | |
| 715 | + | ||
| 716 | + | uint Creature::getAttackPulsePhase() | |
| 717 | + | { | |
| 718 | + | if (m_walking || m_attackPulseDuration == 0 || m_outfit.getCategory() != ThingCategoryCreature) | |
| 719 | + | return m_walkAnimationPhase; | |
| 720 | + | ||
| 721 | + | const ticks_t elapsed = m_attackPulseTimer.ticksElapsed(); | |
| 722 | + | if (elapsed >= m_attackPulseDuration) { | |
| 723 | + | m_attackPulseDuration = 0; | |
| 724 | + | return m_walkAnimationPhase; | |
| 725 | + | } | |
| 726 | + | ||
| 727 | + | const ThingTypePtr& type = g_things.getThingType(m_outfit.getId(), ThingCategoryCreature); | |
| 728 | + | if (!type || type->isNull()) | |
| 729 | + | return m_walkAnimationPhase; | |
| 730 | + | ||
| 731 | + | const AnimatorPtr idleAnimator = type->getIdleAnimator(); | |
| 732 | + | const int idlePhases = idleAnimator ? idleAnimator->getAnimationPhases() : 0; | |
| 733 | + | const int phases = std::max<int>(1, type->getAnimationPhases() - idlePhases); | |
| 734 | + | const int phase = std::min<int>(phases - 1, static_cast<int>(elapsed * phases / m_attackPulseDuration)); | |
| 735 | + | return idleAnimator ? phase + 1 : phase; | |
| 736 | + | } | |
| 737 | + | ||
| 738 | + | void Creature::setOutfitColor(const Color& color, int duration) | |
| 739 | + | { | |
| 740 | + | if (m_outfitColorUpdateEvent) { | |
| 741 | + | m_outfitColorUpdateEvent->cancel(); | |
| 742 | + | m_outfitColorUpdateEvent = nullptr; | |
| 743 | + | } | |
| 744 | + | ||
| 745 | + | if (duration > 0) { | |
| 746 | + | Color delta = (color - m_outfitColor) / (float)duration; | |
| 747 | + | m_outfitColorTimer.restart(); | |
| 748 | + | updateOutfitColor(m_outfitColor, color, delta, duration); | |
| 749 | + | } else | |
| 750 | + | m_outfitColor = color; | |
| 751 | + | } | |
| 752 | + | ||
| 753 | + | void Creature::updateOutfitColor(Color color, Color finalColor, Color delta, int duration) | |
| 754 | + | { | |
| 755 | + | if (m_outfitColorTimer.ticksElapsed() < duration) { | |
| 756 | + | m_outfitColor = color + delta * m_outfitColorTimer.ticksElapsed(); | |
| 757 | + | ||
| 758 | + | auto self = static_self_cast<Creature>(); | |
| 759 | + | m_outfitColorUpdateEvent = g_dispatcher.scheduleEvent([=] { | |
| 760 | + | self->updateOutfitColor(color, finalColor, delta, duration); | |
| 761 | + | }, 100); | |
| 762 | + | } else { | |
| 763 | + | m_outfitColor = finalColor; | |
| 764 | + | } | |
| 765 | + | } | |
| 766 | + | ||
| 767 | + | void Creature::setSpeed(uint16 speed) | |
| 768 | + | { | |
| 769 | + | uint16 oldSpeed = m_speed; | |
| 770 | + | m_speed = speed; | |
| 771 | + | ||
| 772 | + | // speed can change while walking (utani hur, paralyze, etc..) | |
| 773 | + | if (m_walking) | |
| 774 | + | nextWalkUpdate(); | |
| 775 | + | ||
| 776 | + | callLuaField("onSpeedChange", m_speed, oldSpeed); | |
| 777 | + | } | |
| 778 | + | ||
| 779 | + | void Creature::setBaseSpeed(double baseSpeed) | |
| 780 | + | { | |
| 781 | + | if (m_baseSpeed != baseSpeed) { | |
| 782 | + | double oldBaseSpeed = m_baseSpeed; | |
| 783 | + | m_baseSpeed = baseSpeed; | |
| 784 | + | ||
| 785 | + | callLuaField("onBaseSpeedChange", baseSpeed, oldBaseSpeed); | |
| 786 | + | } | |
| 787 | + | } | |
| 788 | + | ||
| 789 | + | void Creature::setSkull(uint8 skull) | |
| 790 | + | { | |
| 791 | + | m_skull = skull; | |
| 792 | + | callLuaField("onSkullChange", m_skull); | |
| 793 | + | } | |
| 794 | + | ||
| 795 | + | void Creature::setShield(uint8 shield) | |
| 796 | + | { | |
| 797 | + | m_shield = shield; | |
| 798 | + | callLuaField("onShieldChange", m_shield); | |
| 799 | + | } | |
| 800 | + | ||
| 801 | + | void Creature::setEmblem(uint8 emblem) | |
| 802 | + | { | |
| 803 | + | m_emblem = emblem; | |
| 804 | + | callLuaField("onEmblemChange", m_emblem); | |
| 805 | + | } | |
| 806 | + | ||
| 807 | + | void Creature::setType(uint8 type) | |
| 808 | + | { | |
| 809 | + | m_type = type; | |
| 810 | + | callLuaField("onTypeChange", m_type); | |
| 811 | + | } | |
| 812 | + | ||
| 813 | + | void Creature::setIcon(uint8 icon) | |
| 814 | + | { | |
| 815 | + | m_icon = icon; | |
| 816 | + | callLuaField("onIconChange", m_icon); | |
| 817 | + | } | |
| 818 | + | ||
| 819 | + | void Creature::setSkullTexture(const std::string& filename) | |
| 820 | + | { | |
| 821 | + | m_skullTexture = g_textures.getTexture(filename); | |
| 822 | + | } | |
| 823 | + | ||
| 824 | + | void Creature::setShieldTexture(const std::string& filename, bool blink) | |
| 825 | + | { | |
| 826 | + | m_shieldTexture = g_textures.getTexture(filename); | |
| 827 | + | m_showShieldTexture = true; | |
| 828 | + | ||
| 829 | + | if (blink && !m_shieldBlink) { | |
| 830 | + | auto self = static_self_cast<Creature>(); | |
| 831 | + | g_dispatcher.scheduleEvent([self]() { | |
| 832 | + | self->updateShield(); | |
| 833 | + | }, SHIELD_BLINK_TICKS); | |
| 834 | + | } | |
| 835 | + | ||
| 836 | + | m_shieldBlink = blink; | |
| 837 | + | } | |
| 838 | + | ||
| 839 | + | void Creature::setEmblemTexture(const std::string& filename) | |
| 840 | + | { | |
| 841 | + | m_emblemTexture = g_textures.getTexture(filename); | |
| 842 | + | } | |
| 843 | + | ||
| 844 | + | void Creature::setTypeTexture(const std::string& filename) | |
| 845 | + | { | |
| 846 | + | m_typeTexture = g_textures.getTexture(filename); | |
| 847 | + | } | |
| 848 | + | ||
| 849 | + | void Creature::setIconTexture(const std::string& filename) | |
| 850 | + | { | |
| 851 | + | m_iconTexture = g_textures.getTexture(filename); | |
| 852 | + | } | |
| 853 | + | ||
| 854 | + | void Creature::setSpeedFormula(double speedA, double speedB, double speedC) | |
| 855 | + | { | |
| 856 | + | m_speedFormula[Otc::SpeedFormulaA] = speedA; | |
| 857 | + | m_speedFormula[Otc::SpeedFormulaB] = speedB; | |
| 858 | + | m_speedFormula[Otc::SpeedFormulaC] = speedC; | |
| 859 | + | } | |
| 860 | + | ||
| 861 | + | bool Creature::hasSpeedFormula() | |
| 862 | + | { | |
| 863 | + | return m_speedFormula[Otc::SpeedFormulaA] != -1 && m_speedFormula[Otc::SpeedFormulaB] != -1 | |
| 864 | + | && m_speedFormula[Otc::SpeedFormulaC] != -1; | |
| 865 | + | } | |
| 866 | + | ||
| 867 | + | void Creature::addTimedSquare(uint8 color) | |
| 868 | + | { | |
| 869 | + | m_showTimedSquare = true; | |
| 870 | + | m_timedSquareColor = Color::from8bit(color); | |
| 871 | + | ||
| 872 | + | // schedule removal | |
| 873 | + | auto self = static_self_cast<Creature>(); | |
| 874 | + | g_dispatcher.scheduleEvent([self]() { | |
| 875 | + | self->removeTimedSquare(); | |
| 876 | + | }, VOLATILE_SQUARE_DURATION); | |
| 877 | + | } | |
| 878 | + | ||
| 879 | + | ||
| 880 | + | void Creature::updateShield() | |
| 881 | + | { | |
| 882 | + | m_showShieldTexture = !m_showShieldTexture; | |
| 883 | + | ||
| 884 | + | if (m_shield != Otc::ShieldNone && m_shieldBlink) { | |
| 885 | + | auto self = static_self_cast<Creature>(); | |
| 886 | + | g_dispatcher.scheduleEvent([self]() { | |
| 887 | + | self->updateShield(); | |
| 888 | + | }, SHIELD_BLINK_TICKS); | |
| 889 | + | } else if (!m_shieldBlink) | |
| 890 | + | m_showShieldTexture = true; | |
| 891 | + | } | |
| 892 | + | ||
| 893 | + | Point Creature::getDrawOffset() | |
| 894 | + | { | |
| 895 | + | Point drawOffset; | |
| 896 | + | if (m_walking) { | |
| 897 | + | if (m_walkingTile) | |
| 898 | + | drawOffset -= Point(1, 1) * m_walkingTile->getDrawElevation() * g_sprites.getOffsetFactor(); | |
| 899 | + | drawOffset += m_walkOffset; | |
| 900 | + | } else { | |
| 901 | + | const TilePtr& tile = getTile(); | |
| 902 | + | if (tile) | |
| 903 | + | drawOffset -= Point(1, 1) * tile->getDrawElevation() * g_sprites.getOffsetFactor(); | |
| 904 | + | } | |
| 905 | + | return drawOffset; | |
| 906 | + | } | |
| 907 | + | ||
| 908 | + | uint16 Creature::getStepDuration(bool ignoreDiagonal, Otc::Direction dir) | |
| 909 | + | { | |
| 910 | + | uint16 speed = m_speed; | |
| 911 | + | if (speed < 1) | |
| 912 | + | return 0; | |
| 913 | + | ||
| 914 | + | if (g_game.getFeature(Otc::GameNewSpeedLaw)) | |
| 915 | + | speed *= 2; | |
| 916 | + | ||
| 917 | + | uint16 groundSpeed = 0; | |
| 918 | + | Position tilePos; | |
| 919 | + | ||
| 920 | + | if (dir == Otc::InvalidDirection) | |
| 921 | + | tilePos = m_lastStepToPosition; | |
| 922 | + | else | |
| 923 | + | tilePos = getPrewalkingPosition(true).translatedToDirection(dir); | |
| 924 | + | ||
| 925 | + | if (!tilePos.isValid()) | |
| 926 | + | tilePos = getPrewalkingPosition(true); | |
| 927 | + | ||
| 928 | + | const TilePtr& tile = g_map.getTile(tilePos); | |
| 929 | + | if (tile) { | |
| 930 | + | groundSpeed = tile->getGroundSpeed(); | |
| 931 | + | if (groundSpeed == 0) | |
| 932 | + | groundSpeed = 150; | |
| 933 | + | } | |
| 934 | + | ||
| 935 | + | int interval = 1000; | |
| 936 | + | if (groundSpeed > 0 && speed > 0) | |
| 937 | + | interval = 1000 * groundSpeed; | |
| 938 | + | ||
| 939 | + | if (g_game.getFeature(Otc::GameNewSpeedLaw) && hasSpeedFormula()) { | |
| 940 | + | int formulatedSpeed = 1; | |
| 941 | + | if (speed > -m_speedFormula[Otc::SpeedFormulaB]) { | |
| 942 | + | formulatedSpeed = std::max<int>(1, (int)floor((m_speedFormula[Otc::SpeedFormulaA] * log((speed / 2) | |
| 943 | + | + m_speedFormula[Otc::SpeedFormulaB]) + m_speedFormula[Otc::SpeedFormulaC]) + 0.5)); | |
| 944 | + | } | |
| 945 | + | interval = std::floor(interval / (double)formulatedSpeed); | |
| 946 | + | } else | |
| 947 | + | interval /= speed; | |
| 948 | + | ||
| 949 | + | if (g_game.getClientVersion() >= 900 && !g_game.getFeature(Otc::GameNewWalking)) | |
| 950 | + | interval = std::ceil((float)interval / (float)g_game.getServerBeat()) * g_game.getServerBeat(); | |
| 951 | + | ||
| 952 | + | float factor = 3; | |
| 953 | + | if (g_game.getClientVersion() <= 810) | |
| 954 | + | factor = 2; | |
| 955 | + | ||
| 956 | + | interval = std::max<int>(interval, g_game.getServerBeat()); | |
| 957 | + | ||
| 958 | + | if (!ignoreDiagonal && (m_lastStepDirection == Otc::NorthWest || m_lastStepDirection == Otc::NorthEast || | |
| 959 | + | m_lastStepDirection == Otc::SouthWest || m_lastStepDirection == Otc::SouthEast)) | |
| 960 | + | interval *= factor; | |
| 961 | + | ||
| 962 | + | if (!isServerWalking() && g_game.getFeature(Otc::GameSlowerManualWalking)) { | |
| 963 | + | interval += 25; | |
| 964 | + | } | |
| 965 | + | if (isServerWalking() && g_game.getFeature(Otc::GameNewWalking) && m_stepDuration > 0) // just use server value | |
| 966 | + | { | |
| 967 | + | interval = m_stepDuration; | |
| 968 | + | } | |
| 969 | + | ||
| 970 | + | return interval; | |
| 971 | + | } | |
| 972 | + | ||
| 973 | + | Point Creature::getDisplacement() | |
| 974 | + | { | |
| 975 | + | if (m_outfit.getCategory() == ThingCategoryEffect) | |
| 976 | + | return Point(8, 8) * g_sprites.getOffsetFactor(); | |
| 977 | + | else if (m_outfit.getCategory() == ThingCategoryItem) | |
| 978 | + | return Point(0, 0); | |
| 979 | + | ||
| 980 | + | if (m_outfit.getMount() != 0) { | |
| 981 | + | auto datType = g_things.rawGetThingType(m_outfit.getMount(), ThingCategoryCreature); | |
| 982 | + | return datType->getDisplacement() * g_sprites.getOffsetFactor(); | |
| 983 | + | } | |
| 984 | + | ||
| 985 | + | return Thing::getDisplacement() * g_sprites.getOffsetFactor(); | |
| 986 | + | } | |
| 987 | + | ||
| 988 | + | int Creature::getDisplacementX() | |
| 989 | + | { | |
| 990 | + | if (m_outfit.getCategory() == ThingCategoryEffect) | |
| 991 | + | return 8 * g_sprites.getOffsetFactor(); | |
| 992 | + | else if (m_outfit.getCategory() == ThingCategoryItem) | |
| 993 | + | return 0; | |
| 994 | + | ||
| 995 | + | if (m_outfit.getMount() != 0) { | |
| 996 | + | auto datType = g_things.rawGetThingType(m_outfit.getMount(), ThingCategoryCreature); | |
| 997 | + | return datType->getDisplacementX() * g_sprites.getOffsetFactor(); | |
| 998 | + | } | |
| 999 | + | ||
| 1000 | + | return Thing::getDisplacementX() * g_sprites.getOffsetFactor(); | |
| 1001 | + | } | |
| 1002 | + | ||
| 1003 | + | int Creature::getDisplacementY() | |
| 1004 | + | { | |
| 1005 | + | if (m_outfit.getCategory() == ThingCategoryEffect) | |
| 1006 | + | return 8 * g_sprites.getOffsetFactor(); | |
| 1007 | + | else if (m_outfit.getCategory() == ThingCategoryItem) | |
| 1008 | + | return 0; | |
| 1009 | + | ||
| 1010 | + | if (m_outfit.getMount() != 0) { | |
| 1011 | + | auto datType = g_things.rawGetThingType(m_outfit.getMount(), ThingCategoryCreature); | |
| 1012 | + | if (datType) { | |
| 1013 | + | return datType->getDisplacementY() * g_sprites.getOffsetFactor(); | |
| 1014 | + | } | |
| 1015 | + | } | |
| 1016 | + | ||
| 1017 | + | return Thing::getDisplacementY() * g_sprites.getOffsetFactor(); | |
| 1018 | + | } | |
| 1019 | + | ||
| 1020 | + | int Creature::getExactSize(int layer, int xPattern, int yPattern, int zPattern, int animationPhase) | |
| 1021 | + | { | |
| 1022 | + | int exactSize = 0; | |
| 1023 | + | ||
| 1024 | + | animationPhase = 0; | |
| 1025 | + | xPattern = Otc::South; | |
| 1026 | + | ||
| 1027 | + | zPattern = 0; | |
| 1028 | + | if (m_outfit.getMount() != 0) | |
| 1029 | + | zPattern = 1; | |
| 1030 | + | ||
| 1031 | + | for (yPattern = 0; yPattern < getNumPatternY(); yPattern++) { | |
| 1032 | + | if (yPattern > 0 && !(m_outfit.getAddons() & (1 << (yPattern - 1)))) | |
| 1033 | + | continue; | |
| 1034 | + | ||
| 1035 | + | for (layer = 0; layer < getLayers(); ++layer) | |
| 1036 | + | exactSize = std::max<int>(exactSize, Thing::getExactSize(layer, xPattern, yPattern, zPattern, animationPhase)); | |
| 1037 | + | } | |
| 1038 | + | ||
| 1039 | + | return exactSize; | |
| 1040 | + | } | |
| 1041 | + | ||
| 1042 | + | const ThingTypePtr& Creature::getThingType() | |
| 1043 | + | { | |
| 1044 | + | return g_things.getThingType(m_outfit.getId(), ThingCategoryCreature); | |
| 1045 | + | } | |
| 1046 | + | ||
| 1047 | + | ThingType* Creature::rawGetThingType() | |
| 1048 | + | { | |
| 1049 | + | return g_things.rawGetThingType(m_outfit.getId(), ThingCategoryCreature); | |
| 1050 | + | } | |
| 1051 | + | ||
| 1052 | + | void Creature::setText(const std::string& text, const Color& color) | |
| 1053 | + | { | |
| 1054 | + | if (!m_text) { | |
| 1055 | + | m_text = StaticTextPtr(new StaticText()); | |
| 1056 | + | } | |
| 1057 | + | m_text->setText(text); | |
| 1058 | + | m_text->setColor(color); | |
| 1059 | + | } | |
| 1060 | + | ||
| 1061 | + | std::string Creature::getText() | |
| 1062 | + | { | |
| 1063 | + | if (!m_text) { | |
| 1064 | + | return ""; | |
| 1065 | + | } | |
| 1066 | + | return m_text->getText(); | |
| 1067 | + | } | |
| 1068 | + | ||
| 1069 | + | ||
| 1070 | + | // widgets | |
| 1071 | + | void Creature::addTopWidget(const UIWidgetPtr& widget) | |
| 1072 | + | { | |
| 1073 | + | if (!widget) return; | |
| 1074 | + | if (std::find(m_topWidgets.begin(), m_topWidgets.end(), widget) == m_topWidgets.end()) { | |
| 1075 | + | m_topWidgets.push_back(widget); | |
| 1076 | + | } | |
| 1077 | + | } | |
| 1078 | + | ||
| 1079 | + | void Creature::addBottomWidget(const UIWidgetPtr& widget) | |
| 1080 | + | { | |
| 1081 | + | if (!widget) return; | |
| 1082 | + | if (std::find(m_bottomWidgets.begin(), m_bottomWidgets.end(), widget) == m_bottomWidgets.end()) { | |
| 1083 | + | m_bottomWidgets.push_back(widget); | |
| 1084 | + | } | |
| 1085 | + | } | |
| 1086 | + | ||
| 1087 | + | void Creature::addDirectionalWidget(const UIWidgetPtr& widget) | |
| 1088 | + | { | |
| 1089 | + | if (!widget) return; | |
| 1090 | + | if (std::find(m_directionalWidgets.begin(), m_directionalWidgets.end(), widget) == m_directionalWidgets.end()) { | |
| 1091 | + | m_directionalWidgets.push_back(widget); | |
| 1092 | + | } | |
| 1093 | + | } | |
| 1094 | + | ||
| 1095 | + | void Creature::removeTopWidget(const UIWidgetPtr& widget) | |
| 1096 | + | { | |
| 1097 | + | auto it = std::remove(m_topWidgets.begin(), m_topWidgets.end(), widget); | |
| 1098 | + | while(it != m_topWidgets.end()) { | |
| 1099 | + | (*it)->destroy(); | |
| 1100 | + | it = m_topWidgets.erase(it); | |
| 1101 | + | } | |
| 1102 | + | } | |
| 1103 | + | ||
| 1104 | + | void Creature::removeBottomWidget(const UIWidgetPtr& widget) | |
| 1105 | + | { | |
| 1106 | + | auto it = std::remove(m_bottomWidgets.begin(), m_bottomWidgets.end(), widget); | |
| 1107 | + | while (it != m_topWidgets.end()) { | |
| 1108 | + | (*it)->destroy(); | |
| 1109 | + | it = m_bottomWidgets.erase(it); | |
| 1110 | + | } | |
| 1111 | + | } | |
| 1112 | + | ||
| 1113 | + | void Creature::removeDirectionalWidget(const UIWidgetPtr& widget) | |
| 1114 | + | { | |
| 1115 | + | auto it = m_directionalWidgets.erase(std::remove(m_directionalWidgets.begin(), m_directionalWidgets.end(), widget)); | |
| 1116 | + | while (it != m_topWidgets.end()) { | |
| 1117 | + | (*it)->destroy(); | |
| 1118 | + | it = m_directionalWidgets.erase(it); | |
| 1119 | + | } | |
| 1120 | + | ||
| 1121 | + | } | |
| 1122 | + | ||
| 1123 | + | std::list<UIWidgetPtr> Creature::getTopWidgets() | |
| 1124 | + | { | |
| 1125 | + | return m_topWidgets; | |
| 1126 | + | } | |
| 1127 | + | ||
| 1128 | + | std::list<UIWidgetPtr> Creature::getBottomWidgets() | |
| 1129 | + | { | |
| 1130 | + | return m_bottomWidgets; | |
| 1131 | + | } | |
| 1132 | + | ||
| 1133 | + | std::list<UIWidgetPtr> Creature::getDirectionalWdigets() | |
| 1134 | + | { | |
| 1135 | + | return m_directionalWidgets; | |
| 1136 | + | } | |
| 1137 | + | ||
| 1138 | + | void Creature::clearWidgets() | |
| 1139 | + | { | |
| 1140 | + | clearTopWidgets(); | |
| 1141 | + | clearBottomWidgets(); | |
| 1142 | + | clearDirectionalWidgets(); | |
| 1143 | + | } | |
| 1144 | + | ||
| 1145 | + | void Creature::clearTopWidgets() | |
| 1146 | + | { | |
| 1147 | + | for (auto& widget : m_topWidgets) { | |
| 1148 | + | widget->destroy(); | |
| 1149 | + | } | |
| 1150 | + | m_topWidgets.clear(); | |
| 1151 | + | } | |
| 1152 | + | ||
| 1153 | + | void Creature::clearBottomWidgets() | |
| 1154 | + | { | |
| 1155 | + | for (auto& widget : m_bottomWidgets) { | |
| 1156 | + | widget->destroy(); | |
| 1157 | + | } | |
| 1158 | + | m_bottomWidgets.clear(); | |
| 1159 | + | } | |
| 1160 | + | ||
| 1161 | + | void Creature::clearDirectionalWidgets() | |
| 1162 | + | { | |
| 1163 | + | for (auto& widget : m_directionalWidgets) { | |
| 1164 | + | widget->destroy(); | |
| 1165 | + | } | |
| 1166 | + | m_directionalWidgets.clear(); | |
| 1167 | + | } | |
| 1168 | + | ||
| 1169 | + | void Creature::drawTopWidgets(const Point& dest, const Otc::Direction direction) | |
| 1170 | + | { | |
| 1171 | + | if (direction == Otc::North || direction == Otc::West) { | |
| 1172 | + | for (auto& widget : m_directionalWidgets) { | |
| 1173 | + | Rect dest_rect = widget->getRect(); | |
| 1174 | + | dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); | |
| 1175 | + | widget->setRect(dest_rect); | |
| 1176 | + | widget->draw(dest_rect, Fw::ForegroundPane); | |
| 1177 | + | } | |
| 1178 | + | } | |
| 1179 | + | for (auto& widget : m_topWidgets) { | |
| 1180 | + | Rect dest_rect = widget->getRect(); | |
| 1181 | + | dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); | |
| 1182 | + | widget->setRect(dest_rect); | |
| 1183 | + | widget->draw(dest_rect, Fw::ForegroundPane); | |
| 1184 | + | } | |
| 1185 | + | } | |
| 1186 | + | ||
| 1187 | + | void Creature::drawBottomWidgets(const Point& dest, const Otc::Direction direction) | |
| 1188 | + | { | |
| 1189 | + | for (auto& widget : m_bottomWidgets) { | |
| 1190 | + | Rect dest_rect = widget->getRect(); | |
| 1191 | + | dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); | |
| 1192 | + | widget->setRect(dest_rect); | |
| 1193 | + | widget->draw(dest_rect, Fw::ForegroundPane); | |
| 1194 | + | } | |
| 1195 | + | ||
| 1196 | + | if (direction == Otc::South || direction == Otc::East) { | |
| 1197 | + | for (auto& widget : m_directionalWidgets) { | |
| 1198 | + | Rect dest_rect = widget->getRect(); | |
| 1199 | + | dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); | |
| 1200 | + | widget->setRect(dest_rect); | |
| 1201 | + | widget->draw(dest_rect, Fw::ForegroundPane); | |
| 1202 | + | } | |
| 1203 | + | } | |
| 1204 | + | } | |
| 1205 | + | ||
| 1206 | + | void Creature::setProgressBar(uint32 duration, bool ltr) | |
| 1207 | + | { | |
| 1208 | + | if (m_progressBarUpdateEvent) { | |
| 1209 | + | m_progressBarUpdateEvent->cancel(); | |
| 1210 | + | m_progressBarUpdateEvent = nullptr; | |
| 1211 | + | } | |
| 1212 | + | ||
| 1213 | + | if (duration > 0) { | |
| 1214 | + | m_progressBarTimer.restart(); | |
| 1215 | + | updateProgressBar(duration, ltr); | |
| 1216 | + | } else | |
| 1217 | + | m_progressBarPercent = 0; | |
| 1218 | + | ||
| 1219 | + | callLuaField("onProgressBarStart", duration, ltr); | |
| 1220 | + | } | |
| 1221 | + | ||
| 1222 | + | void Creature::updateProgressBar(uint32 duration, bool ltr) | |
| 1223 | + | { | |
| 1224 | + | if (m_progressBarTimer.ticksElapsed() < duration) { | |
| 1225 | + | if (ltr) | |
| 1226 | + | m_progressBarPercent = abs(m_progressBarTimer.ticksElapsed() / static_cast<double>(duration) * 100); | |
| 1227 | + | else | |
| 1228 | + | m_progressBarPercent = abs((m_progressBarTimer.ticksElapsed() / static_cast<double>(duration) * 100) - 100); | |
| 1229 | + | ||
| 1230 | + | auto self = static_self_cast<Creature>(); | |
| 1231 | + | m_progressBarUpdateEvent = g_dispatcher.scheduleEvent([=] { | |
| 1232 | + | self->updateProgressBar(duration, ltr); | |
| 1233 | + | }, 50); | |
| 1234 | + | } else { | |
| 1235 | + | m_progressBarPercent = 0; | |
| 1236 | + | } | |
| 1237 | + | callLuaField("onProgressBarUpdate", m_progressBarPercent, duration, ltr); | |
| 1238 | + | } | |
| 1239 | + | ||
| 1240 | + | void Creature::setTitle(const std::string& title, const std::string& font, const Color& color) | |
| 1241 | + | { | |
| 1242 | + | m_titleCache.setText(title); | |
| 1243 | + | if (!font.empty()) { | |
| 1244 | + | m_titleCache.setFont(g_fonts.getFont(font)); | |
| 1245 | + | } | |
| 1246 | + | m_titleColor = color; | |
| 1247 | + | } |
| @@ -0,0 +1,320 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #ifndef CREATURE_H | |
| 24 | + | #define CREATURE_H | |
| 25 | + | ||
| 26 | + | #include "thing.h" | |
| 27 | + | #include "outfit.h" | |
| 28 | + | #include "tile.h" | |
| 29 | + | #include "mapview.h" | |
| 30 | + | #include <framework/core/scheduledevent.h> | |
| 31 | + | #include <framework/core/declarations.h> | |
| 32 | + | #include <framework/core/timer.h> | |
| 33 | + | #include <framework/graphics/fontmanager.h> | |
| 34 | + | #include <framework/graphics/cachedtext.h> | |
| 35 | + | #include <framework/ui/uiwidget.h> | |
| 36 | + | ||
| 37 | + | // @bindclass | |
| 38 | + | class Creature : public Thing | |
| 39 | + | { | |
| 40 | + | public: | |
| 41 | + | enum { | |
| 42 | + | SHIELD_BLINK_TICKS = 500, | |
| 43 | + | VOLATILE_SQUARE_DURATION = 1000 | |
| 44 | + | }; | |
| 45 | + | ||
| 46 | + | Creature(); | |
| 47 | + | virtual ~Creature(); | |
| 48 | + | ||
| 49 | + | virtual void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr); | |
| 50 | + | virtual void drawOutfit(const Rect& destRect, Otc::Direction direction = Otc::InvalidDirection, const Color& color = Color::white, bool animate = false, bool ui = false, bool oldScaling = false); | |
| 51 | + | ||
| 52 | + | void drawInformation(const Point& point, bool useGray, const Rect& parentRect, int drawFlags); | |
| 53 | + | ||
| 54 | + | bool isInsideOffset(Point offset); | |
| 55 | + | ||
| 56 | + | void setId(uint32 id) { m_id = id; } | |
| 57 | + | void setName(const std::string& name); | |
| 58 | + | void setManaPercent(int8 value) { m_manaPercent = value; } | |
| 59 | + | void setHealthPercent(uint8 healthPercent); | |
| 60 | + | void setDirection(Otc::Direction direction); | |
| 61 | + | void setOutfit(const Outfit& outfit); | |
| 62 | + | void attackPulse(const Outfit& outfit, uint16 duration); | |
| 63 | + | void setOutfitColor(const Color& color, int duration); | |
| 64 | + | void setLight(const Light& light) { m_light = light; } | |
| 65 | + | void setSpeed(uint16 speed); | |
| 66 | + | void setBaseSpeed(double baseSpeed); | |
| 67 | + | void setSkull(uint8 skull); | |
| 68 | + | void setShield(uint8 shield); | |
| 69 | + | void setEmblem(uint8 emblem); | |
| 70 | + | void setType(uint8 type); | |
| 71 | + | void setIcon(uint8 icon); | |
| 72 | + | void setSkullTexture(const std::string& filename); | |
| 73 | + | void setShieldTexture(const std::string& filename, bool blink); | |
| 74 | + | void setEmblemTexture(const std::string& filename); | |
| 75 | + | void setTypeTexture(const std::string& filename); | |
| 76 | + | void setIconTexture(const std::string& filename); | |
| 77 | + | void setPassable(bool passable) { m_passable = passable; } | |
| 78 | + | void setSpeedFormula(double speedA, double speedB, double speedC); | |
| 79 | + | ||
| 80 | + | void addTimedSquare(uint8 color); | |
| 81 | + | void removeTimedSquare() { m_showTimedSquare = false; } | |
| 82 | + | ||
| 83 | + | void showStaticSquare(const Color& color) { m_showStaticSquare = true; m_staticSquareColor = color; } | |
| 84 | + | void hideStaticSquare() { m_showStaticSquare = false; } | |
| 85 | + | ||
| 86 | + | void setInformationColor(const Color& color) { m_useCustomInformationColor = true; m_informationColor = color; } | |
| 87 | + | void resetInformationColor() { m_useCustomInformationColor = false; setHealthPercent(getHealthPercent()); } | |
| 88 | + | ||
| 89 | + | Point getInformationOffset() { return m_informationOffset; } | |
| 90 | + | void setInformationOffset(int x, int y) { m_informationOffset = Point(x, y); } | |
| 91 | + | ||
| 92 | + | void setText(const std::string& text, const Color& color); | |
| 93 | + | std::string getText(); | |
| 94 | + | void clearText() { setText("", Color::white); } | |
| 95 | + | ||
| 96 | + | void setTitle(const std::string& title, const std::string& font, const Color& color); | |
| 97 | + | void clearTitle() { setTitle("", "", Color::white); } | |
| 98 | + | std::string getTitle() { return m_titleCache.getText(); } | |
| 99 | + | ||
| 100 | + | uint32 getId() { return m_id; } | |
| 101 | + | std::string getName() { return m_name; } | |
| 102 | + | Point getWingsOffset() { return m_wingsOffset; } | |
| 103 | + | void setWingsOffset(int x, int y) { | |
| 104 | + | m_wingsOffset.x = x; | |
| 105 | + | m_wingsOffset.y = y; | |
| 106 | + | } | |
| 107 | + | uint8 getHealthPercent() { return m_healthPercent; } | |
| 108 | + | int8 getManaPercent() { return m_manaPercent; } | |
| 109 | + | Otc::Direction getDirection() { return m_direction; } | |
| 110 | + | Otc::Direction getWalkDirection() { return m_walkDirection; } | |
| 111 | + | Outfit getOutfit() { return m_outfit; } | |
| 112 | + | int getWings() { return m_outfit.getWings(); } | |
| 113 | + | Light getLight() { return m_light; } | |
| 114 | + | uint16 getSpeed() { return m_speed; } | |
| 115 | + | double getBaseSpeed() { return m_baseSpeed; } | |
| 116 | + | uint8 getSkull() { return m_skull; } | |
| 117 | + | uint8 getShield() { return m_shield; } | |
| 118 | + | uint8 getEmblem() { return m_emblem; } | |
| 119 | + | uint8 getType() { return m_type; } | |
| 120 | + | uint8 getIcon() { return m_icon; } | |
| 121 | + | bool isPassable() { return m_passable; } | |
| 122 | + | Point getDrawOffset(); | |
| 123 | + | uint16 getStepDuration(bool ignoreDiagonal = false, Otc::Direction dir = Otc::InvalidDirection); | |
| 124 | + | Point getWalkOffset(bool inNextFrame = false) { return inNextFrame ? m_walkOffsetInNextFrame : m_walkOffset; } | |
| 125 | + | Position getLastStepFromPosition() { return m_lastStepFromPosition; } | |
| 126 | + | Position getLastStepToPosition() { return m_lastStepToPosition; } | |
| 127 | + | float getStepProgress() { return m_walkTimer.ticksElapsed() / getStepDuration(); } | |
| 128 | + | int getStepTicksLeft() { return getStepDuration() - m_walkTimer.ticksElapsed(); } | |
| 129 | + | ticks_t getWalkTicksElapsed() { return m_walkTimer.ticksElapsed(); } | |
| 130 | + | double getSpeedFormula(Otc::SpeedFormula formula) { return m_speedFormula[formula]; } | |
| 131 | + | bool hasSpeedFormula(); | |
| 132 | + | std::array<double, Otc::LastSpeedFormula> getSpeedFormulaArray() { return m_speedFormula; } | |
| 133 | + | virtual Point getDisplacement(); | |
| 134 | + | virtual int getDisplacementX(); | |
| 135 | + | virtual int getDisplacementY(); | |
| 136 | + | virtual int getExactSize(int layer = 0, int xPattern = 0, int yPattern = 0, int zPattern = 0, int animationPhase = 0); | |
| 137 | + | PointF getJumpOffset() { return m_jumpOffset; } | |
| 138 | + | bool isTimedSquareVisible() { return m_showTimedSquare; } | |
| 139 | + | Color getTimedSquareColor() { return m_timedSquareColor; } | |
| 140 | + | bool isStaticSquareVisible() { return m_showStaticSquare; } | |
| 141 | + | Color getStaticSquareColor() { return m_staticSquareColor; } | |
| 142 | + | ||
| 143 | + | void updateShield(); | |
| 144 | + | ||
| 145 | + | // walk related | |
| 146 | + | int getWalkAnimationPhases(); | |
| 147 | + | virtual void turn(Otc::Direction direction); | |
| 148 | + | void jump(int height, int duration); | |
| 149 | + | virtual void walk(const Position& oldPos, const Position& newPos); | |
| 150 | + | virtual void stopWalk(); | |
| 151 | + | void allowAppearWalk(uint16_t stepSpeed) { m_allowAppearWalk = true; m_stepDuration = stepSpeed; } | |
| 152 | + | ||
| 153 | + | bool isWalking() { return m_walking; } | |
| 154 | + | bool isRemoved() { return m_removed; } | |
| 155 | + | bool isInvisible() { return m_outfit.getCategory() == ThingCategoryEffect && m_outfit.getAuxId() == 13; } | |
| 156 | + | bool isDead() { return m_healthPercent <= 0; } | |
| 157 | + | bool canBeSeen() { return !isInvisible() || isPlayer(); } | |
| 158 | + | ||
| 159 | + | bool isCreature() { return true; } | |
| 160 | + | bool canShoot(int distance); | |
| 161 | + | ||
| 162 | + | const ThingTypePtr& getThingType(); | |
| 163 | + | ThingType *rawGetThingType(); | |
| 164 | + | ||
| 165 | + | virtual void onPositionChange(const Position& newPos, const Position& oldPos); | |
| 166 | + | virtual void onAppear(); | |
| 167 | + | virtual void onDisappear(); | |
| 168 | + | virtual void onDeath(); | |
| 169 | + | ||
| 170 | + | virtual bool isPreWalking() { return false; } | |
| 171 | + | virtual Position getPrewalkingPosition(bool beforePrewalk = false) { return m_position; } | |
| 172 | + | ||
| 173 | + | TilePtr getWalkingTileOrTile() { | |
| 174 | + | return m_walkingTile ? m_walkingTile : getTile(); | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | virtual bool isServerWalking() { return true; } | |
| 178 | + | ||
| 179 | + | void setElevation(uint8 elevation) { | |
| 180 | + | m_elevation = elevation; | |
| 181 | + | } | |
| 182 | + | uint8 getElevation() { | |
| 183 | + | return m_elevation; | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | // widgets | |
| 187 | + | void addTopWidget(const UIWidgetPtr& widget); | |
| 188 | + | void addBottomWidget(const UIWidgetPtr& widget); | |
| 189 | + | void addDirectionalWidget(const UIWidgetPtr& widget); | |
| 190 | + | void removeTopWidget(const UIWidgetPtr& widget); | |
| 191 | + | void removeBottomWidget(const UIWidgetPtr& widget); | |
| 192 | + | void removeDirectionalWidget(const UIWidgetPtr& widget); | |
| 193 | + | std::list<UIWidgetPtr> getTopWidgets(); | |
| 194 | + | std::list<UIWidgetPtr> getBottomWidgets(); | |
| 195 | + | std::list<UIWidgetPtr> getDirectionalWdigets(); | |
| 196 | + | void clearWidgets(); | |
| 197 | + | void clearTopWidgets(); | |
| 198 | + | void clearBottomWidgets(); | |
| 199 | + | void clearDirectionalWidgets(); | |
| 200 | + | void drawTopWidgets(const Point& rect, const Otc::Direction direction); | |
| 201 | + | void drawBottomWidgets(const Point& rect, const Otc::Direction direction); | |
| 202 | + | ||
| 203 | + | // progress bar | |
| 204 | + | uint8 getProgressBarPercent() { return m_progressBarPercent; } | |
| 205 | + | void setProgressBar(uint32 duration, bool ltr); | |
| 206 | + | void updateProgressBar(uint32 duration, bool ltr); | |
| 207 | + | ||
| 208 | + | protected: | |
| 209 | + | virtual void updateWalkAnimation(uint8 totalPixelsWalked); | |
| 210 | + | virtual void updateWalkOffset(uint8 totalPixelsWalked, bool inNextFrame = false); | |
| 211 | + | void updateWalkingTile(); | |
| 212 | + | virtual void nextWalkUpdate(); | |
| 213 | + | virtual void updateWalk(); | |
| 214 | + | virtual void terminateWalk(); | |
| 215 | + | ||
| 216 | + | void updateOutfitColor(Color color, Color finalColor, Color delta, int duration); | |
| 217 | + | void updateJump(); | |
| 218 | + | ||
| 219 | + | uint32 m_id; | |
| 220 | + | std::string m_name; | |
| 221 | + | uint8 m_healthPercent; | |
| 222 | + | int8 m_manaPercent; | |
| 223 | + | Otc::Direction m_direction; | |
| 224 | + | Otc::Direction m_walkDirection; | |
| 225 | + | Outfit m_outfit; | |
| 226 | + | Light m_light; | |
| 227 | + | uint16 m_speed; | |
| 228 | + | double m_baseSpeed; | |
| 229 | + | uint8 m_skull; | |
| 230 | + | uint8 m_shield; | |
| 231 | + | uint8 m_emblem; | |
| 232 | + | uint8 m_type; | |
| 233 | + | uint8 m_icon; | |
| 234 | + | TexturePtr m_skullTexture; | |
| 235 | + | TexturePtr m_shieldTexture; | |
| 236 | + | TexturePtr m_emblemTexture; | |
| 237 | + | TexturePtr m_typeTexture; | |
| 238 | + | TexturePtr m_iconTexture; | |
| 239 | + | stdext::boolean<true> m_showShieldTexture; | |
| 240 | + | stdext::boolean<false> m_shieldBlink; | |
| 241 | + | stdext::boolean<false> m_passable; | |
| 242 | + | Color m_timedSquareColor; | |
| 243 | + | Color m_staticSquareColor; | |
| 244 | + | Color m_nameColor; | |
| 245 | + | stdext::boolean<false> m_showTimedSquare; | |
| 246 | + | stdext::boolean<false> m_showStaticSquare; | |
| 247 | + | stdext::boolean<true> m_removed; | |
| 248 | + | CachedText m_nameCache; | |
| 249 | + | Color m_informationColor; | |
| 250 | + | bool m_useCustomInformationColor = false; | |
| 251 | + | Point m_informationOffset; | |
| 252 | + | Point m_wingsOffset; | |
| 253 | + | Color m_outfitColor; | |
| 254 | + | ScheduledEventPtr m_outfitColorUpdateEvent; | |
| 255 | + | Timer m_outfitColorTimer; | |
| 256 | + | CachedText m_titleCache; | |
| 257 | + | Color m_titleColor; | |
| 258 | + | ||
| 259 | + | static std::array<double, Otc::LastSpeedFormula> m_speedFormula; | |
| 260 | + | ||
| 261 | + | // walk related | |
| 262 | + | int m_walkAnimationPhase; | |
| 263 | + | uint8 m_walkedPixels; | |
| 264 | + | uint m_footStep; | |
| 265 | + | Timer m_walkTimer; | |
| 266 | + | ticks_t m_footLastStep; | |
| 267 | + | TilePtr m_walkingTile; | |
| 268 | + | stdext::boolean<false> m_walking; | |
| 269 | + | stdext::boolean<false> m_allowAppearWalk; | |
| 270 | + | ScheduledEventPtr m_walkUpdateEvent; | |
| 271 | + | ScheduledEventPtr m_walkFinishAnimEvent; | |
| 272 | + | EventPtr m_disappearEvent; | |
| 273 | + | Point m_walkOffset; | |
| 274 | + | Point m_walkOffsetInNextFrame; | |
| 275 | + | Otc::Direction m_lastStepDirection; | |
| 276 | + | Position m_lastStepFromPosition; | |
| 277 | + | Position m_lastStepToPosition; | |
| 278 | + | Position m_oldPosition; | |
| 279 | + | uint8 m_elevation = 0; | |
| 280 | + | uint16 m_stepDuration = 0; | |
| 281 | + | ||
| 282 | + | Timer m_attackPulseTimer; | |
| 283 | + | uint16 m_attackPulseDuration = 0; | |
| 284 | + | uint getAttackPulsePhase(); | |
| 285 | + | ||
| 286 | + | // jump related | |
| 287 | + | float m_jumpHeight = 0; | |
| 288 | + | float m_jumpDuration = 0; | |
| 289 | + | PointF m_jumpOffset; | |
| 290 | + | Timer m_jumpTimer; | |
| 291 | + | ||
| 292 | + | // for bot | |
| 293 | + | StaticTextPtr m_text; | |
| 294 | + | ||
| 295 | + | // widgets | |
| 296 | + | std::list<UIWidgetPtr> m_bottomWidgets; | |
| 297 | + | std::list<UIWidgetPtr> m_directionalWidgets; | |
| 298 | + | std::list<UIWidgetPtr> m_topWidgets; | |
| 299 | + | ||
| 300 | + | // progress bar | |
| 301 | + | uint8 m_progressBarPercent; | |
| 302 | + | ScheduledEventPtr m_progressBarUpdateEvent; | |
| 303 | + | Timer m_progressBarTimer; | |
| 304 | + | }; | |
| 305 | + | ||
| 306 | + | // @bindclass | |
| 307 | + | class Npc : public Creature | |
| 308 | + | { | |
| 309 | + | public: | |
| 310 | + | bool isNpc() { return true; } | |
| 311 | + | }; | |
| 312 | + | ||
| 313 | + | // @bindclass | |
| 314 | + | class Monster : public Creature | |
| 315 | + | { | |
| 316 | + | public: | |
| 317 | + | bool isMonster() { return true; } | |
| 318 | + | }; | |
| 319 | + | ||
| 320 | + | #endif |
| @@ -0,0 +1,426 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #include "creatures.h" | |
| 24 | + | #include "creature.h" | |
| 25 | + | #include "map.h" | |
| 26 | + | ||
| 27 | + | #include <framework/xml/tinyxml.h> | |
| 28 | + | #include <framework/core/resourcemanager.h> | |
| 29 | + | ||
| 30 | + | CreatureManager g_creatures; | |
| 31 | + | ||
| 32 | + | static bool isInZone(const Position& pos/* placePos*/, | |
| 33 | + | const Position& centerPos, | |
| 34 | + | int radius) | |
| 35 | + | { | |
| 36 | + | if(radius == -1) | |
| 37 | + | return true; | |
| 38 | + | return ((pos.x >= centerPos.x - radius) && (pos.x <= centerPos.x + radius) && | |
| 39 | + | (pos.y >= centerPos.y - radius) && (pos.y <= centerPos.y + radius) | |
| 40 | + | ); | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | void CreatureManager::terminate() | |
| 44 | + | { | |
| 45 | + | clearSpawns(); | |
| 46 | + | clear(); | |
| 47 | + | m_nullCreature = nullptr; | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | void Spawn::load(TiXmlElement* node) | |
| 51 | + | { | |
| 52 | + | Position centerPos; | |
| 53 | + | centerPos.x = node->readType<int>("centerx"); | |
| 54 | + | centerPos.y = node->readType<int>("centery"); | |
| 55 | + | centerPos.z = node->readType<int>("centerz"); | |
| 56 | + | ||
| 57 | + | setCenterPos(centerPos); | |
| 58 | + | setRadius(node->readType<int32>("radius")); | |
| 59 | + | ||
| 60 | + | CreatureTypePtr cType(nullptr); | |
| 61 | + | for(TiXmlElement* cNode = node->FirstChildElement(); cNode; cNode = cNode->NextSiblingElement()) { | |
| 62 | + | if(cNode->ValueStr() != "monster" && cNode->ValueStr() != "npc") | |
| 63 | + | stdext::throw_exception(stdext::format("invalid spawn-subnode %s", cNode->ValueStr())); | |
| 64 | + | ||
| 65 | + | std::string cName = cNode->Attribute("name"); | |
| 66 | + | stdext::tolower(cName); | |
| 67 | + | stdext::trim(cName); | |
| 68 | + | stdext::ucwords(cName); | |
| 69 | + | ||
| 70 | + | if (!(cType = g_creatures.getCreatureByName(cName))) | |
| 71 | + | continue; | |
| 72 | + | ||
| 73 | + | cType->setSpawnTime(cNode->readType<int>("spawntime")); | |
| 74 | + | Otc::Direction dir = Otc::North; | |
| 75 | + | int16 dir_ = cNode->readType<int16>("direction"); | |
| 76 | + | if(dir_ >= Otc::East && dir_ <= Otc::West) | |
| 77 | + | dir = (Otc::Direction)dir_; | |
| 78 | + | cType->setDirection(dir); | |
| 79 | + | ||
| 80 | + | Position placePos; | |
| 81 | + | placePos.x = centerPos.x + cNode->readType<int>("x"); | |
| 82 | + | placePos.y = centerPos.y + cNode->readType<int>("y"); | |
| 83 | + | placePos.z = cNode->readType<int>("z"); | |
| 84 | + | ||
| 85 | + | cType->setRace(cNode->ValueStr() == "npc" ? CreatureRaceNpc : CreatureRaceMonster); | |
| 86 | + | addCreature(placePos, cType); | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | void Spawn::save(TiXmlElement* node) | |
| 91 | + | { | |
| 92 | + | const Position& c = getCenterPos(); | |
| 93 | + | node->SetAttribute("centerx", c.x); | |
| 94 | + | node->SetAttribute("centery", c.y); | |
| 95 | + | node->SetAttribute("centerz", c.z); | |
| 96 | + | ||
| 97 | + | node->SetAttribute("radius", getRadius()); | |
| 98 | + | ||
| 99 | + | TiXmlElement* creatureNode = nullptr; | |
| 100 | + | ||
| 101 | + | for(const auto& pair : m_creatures) { | |
| 102 | + | const CreatureTypePtr& creature = pair.second; | |
| 103 | + | if(!(creatureNode = new TiXmlElement(creature->getRace() == CreatureRaceNpc ? "npc" : "monster"))) | |
| 104 | + | stdext::throw_exception("Spawn::save: Ran out of memory while allocating XML element! Terminating now."); | |
| 105 | + | ||
| 106 | + | creatureNode->SetAttribute("name", creature->getName()); | |
| 107 | + | creatureNode->SetAttribute("spawntime", creature->getSpawnTime()); | |
| 108 | + | creatureNode->SetAttribute("direction", creature->getDirection()); | |
| 109 | + | ||
| 110 | + | const Position& placePos = pair.first; | |
| 111 | + | VALIDATE(placePos.isValid()); | |
| 112 | + | ||
| 113 | + | creatureNode->SetAttribute("x", placePos.x - c.x); | |
| 114 | + | creatureNode->SetAttribute("y", placePos.y - c.y); | |
| 115 | + | creatureNode->SetAttribute("z", placePos.z); | |
| 116 | + | ||
| 117 | + | node->LinkEndChild(creatureNode); | |
| 118 | + | } | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | void Spawn::addCreature(const Position& placePos, const CreatureTypePtr& cType) | |
| 122 | + | { | |
| 123 | + | const Position& centerPos = getCenterPos(); | |
| 124 | + | int m_radius = getRadius(); | |
| 125 | + | if(!isInZone(placePos, centerPos, m_radius)) { | |
| 126 | + | g_logger.warning(stdext::format("cannot place creature at %s (spawn's center position: %s, spawn radius: %d) (increment radius)", | |
| 127 | + | stdext::to_string(placePos), stdext::to_string(centerPos), | |
| 128 | + | m_radius | |
| 129 | + | )); | |
| 130 | + | return; | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | g_map.addThing(cType->cast(), placePos, 4); | |
| 134 | + | m_creatures.insert(std::make_pair(placePos, cType)); | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | void Spawn::removeCreature(const Position& pos) | |
| 138 | + | { | |
| 139 | + | auto iterator = m_creatures.find(pos); | |
| 140 | + | if(iterator != m_creatures.end()) { | |
| 141 | + | VALIDATE(iterator->first.isValid()); | |
| 142 | + | VALIDATE(g_map.removeThingByPos(iterator->first, 4)); | |
| 143 | + | m_creatures.erase(iterator); | |
| 144 | + | } | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | std::vector<CreatureTypePtr> Spawn::getCreatures() | |
| 148 | + | { | |
| 149 | + | std::vector<CreatureTypePtr> creatures; | |
| 150 | + | for (auto p : m_creatures) | |
| 151 | + | creatures.push_back(p.second); | |
| 152 | + | return creatures; | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | CreaturePtr CreatureType::cast() | |
| 156 | + | { | |
| 157 | + | CreaturePtr ret(new Creature); | |
| 158 | + | ||
| 159 | + | std::string cName = getName(); | |
| 160 | + | stdext::tolower(cName); | |
| 161 | + | stdext::trim(cName); | |
| 162 | + | stdext::ucwords(cName); | |
| 163 | + | ret->setName(cName); | |
| 164 | + | ||
| 165 | + | ret->setDirection(getDirection()); | |
| 166 | + | ret->setOutfit(getOutfit()); | |
| 167 | + | return ret; | |
| 168 | + | } | |
| 169 | + | ||
| 170 | + | CreatureManager::CreatureManager() | |
| 171 | + | { | |
| 172 | + | m_nullCreature = CreatureTypePtr(new CreatureType); | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | void CreatureManager::clearSpawns() | |
| 176 | + | { | |
| 177 | + | for(auto pair : m_spawns) | |
| 178 | + | pair.second->clear(); | |
| 179 | + | m_spawns.clear(); | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | void CreatureManager::loadMonsters(const std::string& file) | |
| 183 | + | { | |
| 184 | + | TiXmlDocument doc; | |
| 185 | + | doc.Parse(g_resources.readFileContents(file).c_str()); | |
| 186 | + | if(doc.Error()) | |
| 187 | + | stdext::throw_exception(stdext::format("cannot open monsters file '%s': '%s'", file, doc.ErrorDesc())); | |
| 188 | + | ||
| 189 | + | TiXmlElement* root = doc.FirstChildElement(); | |
| 190 | + | if(!root || root->ValueStr() != "monsters") | |
| 191 | + | stdext::throw_exception("malformed monsters xml file"); | |
| 192 | + | ||
| 193 | + | for(TiXmlElement* monster = root->FirstChildElement(); monster; monster = monster->NextSiblingElement()) { | |
| 194 | + | std::string fname = file.substr(0, file.find_last_of('/')) + '/' + monster->Attribute("file"); | |
| 195 | + | if(fname.substr(fname.length() - 4) != ".xml") | |
| 196 | + | fname += ".xml"; | |
| 197 | + | ||
| 198 | + | loadSingleCreature(fname); | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | doc.Clear(); | |
| 202 | + | m_loaded = true; | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | void CreatureManager::loadSingleCreature(const std::string& file) | |
| 206 | + | { | |
| 207 | + | loadCreatureBuffer(g_resources.readFileContents(file)); | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | void CreatureManager::loadNpcs(const std::string& folder) | |
| 211 | + | { | |
| 212 | + | std::string tmp = folder; | |
| 213 | + | if(!stdext::ends_with(tmp, "/")) | |
| 214 | + | tmp += "/"; | |
| 215 | + | ||
| 216 | + | if(!g_resources.directoryExists(tmp)) | |
| 217 | + | stdext::throw_exception(stdext::format("NPCs folder '%s' was not found.", folder)); | |
| 218 | + | ||
| 219 | + | const auto& fileList = g_resources.listDirectoryFiles(tmp); | |
| 220 | + | for(const std::string& file : fileList) | |
| 221 | + | loadCreatureBuffer(g_resources.readFileContents(tmp + file)); | |
| 222 | + | } | |
| 223 | + | ||
| 224 | + | void CreatureManager::loadSpawns(const std::string& fileName) | |
| 225 | + | { | |
| 226 | + | if(!isLoaded()) { | |
| 227 | + | g_logger.warning("creatures aren't loaded yet to load spawns."); | |
| 228 | + | return; | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | if(m_spawnLoaded) { | |
| 232 | + | g_logger.warning("attempt to reload spawns."); | |
| 233 | + | return; | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | try { | |
| 237 | + | TiXmlDocument doc; | |
| 238 | + | doc.Parse(g_resources.readFileContents(fileName).c_str()); | |
| 239 | + | if(doc.Error()) | |
| 240 | + | stdext::throw_exception(stdext::format("cannot load spawns xml file '%s: '%s'", fileName, doc.ErrorDesc())); | |
| 241 | + | ||
| 242 | + | TiXmlElement* root = doc.FirstChildElement(); | |
| 243 | + | if(!root || root->ValueStr() != "spawns") | |
| 244 | + | stdext::throw_exception("malformed spawns file"); | |
| 245 | + | ||
| 246 | + | for(TiXmlElement* node = root->FirstChildElement(); node; node = node->NextSiblingElement()) { | |
| 247 | + | if(node->ValueTStr() != "spawn") | |
| 248 | + | stdext::throw_exception("invalid spawn node"); | |
| 249 | + | ||
| 250 | + | SpawnPtr spawn(new Spawn); | |
| 251 | + | spawn->load(node); | |
| 252 | + | m_spawns.insert(std::make_pair(spawn->getCenterPos(), spawn)); | |
| 253 | + | } | |
| 254 | + | doc.Clear(); | |
| 255 | + | m_spawnLoaded = true; | |
| 256 | + | } catch(std::exception& e) { | |
| 257 | + | g_logger.error(stdext::format("Failed to load '%s': %s", fileName, e.what())); | |
| 258 | + | } | |
| 259 | + | } | |
| 260 | + | ||
| 261 | + | void CreatureManager::saveSpawns(const std::string& fileName) | |
| 262 | + | { | |
| 263 | + | try { | |
| 264 | + | TiXmlDocument doc; | |
| 265 | + | doc.SetTabSize(2); | |
| 266 | + | ||
| 267 | + | TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "UTF-8", ""); | |
| 268 | + | doc.LinkEndChild(decl); | |
| 269 | + | ||
| 270 | + | TiXmlElement* root = new TiXmlElement("spawns"); | |
| 271 | + | doc.LinkEndChild(root); | |
| 272 | + | ||
| 273 | + | for(auto pair : m_spawns) { | |
| 274 | + | TiXmlElement* elem = new TiXmlElement("spawn"); | |
| 275 | + | pair.second->save(elem); | |
| 276 | + | root->LinkEndChild(elem); | |
| 277 | + | } | |
| 278 | + | ||
| 279 | + | if(!doc.SaveFile("data"+fileName)) | |
| 280 | + | stdext::throw_exception(stdext::format("failed to save spawns XML %s: %s", fileName, doc.ErrorDesc())); | |
| 281 | + | } catch(std::exception& e) { | |
| 282 | + | g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); | |
| 283 | + | } | |
| 284 | + | } | |
| 285 | + | ||
| 286 | + | void CreatureManager::loadCreatureBuffer(const std::string& buffer) | |
| 287 | + | { | |
| 288 | + | TiXmlDocument doc; | |
| 289 | + | doc.Parse(buffer.c_str()); | |
| 290 | + | if(doc.Error()) | |
| 291 | + | stdext::throw_exception(stdext::format("cannot load creature buffer: %s", doc.ErrorDesc())); | |
| 292 | + | ||
| 293 | + | TiXmlElement* root = doc.FirstChildElement(); | |
| 294 | + | if(!root || (root->ValueStr() != "monster" && root->ValueStr() != "npc")) | |
| 295 | + | stdext::throw_exception("invalid root tag name"); | |
| 296 | + | ||
| 297 | + | std::string cName = root->Attribute("name"); | |
| 298 | + | stdext::tolower(cName); | |
| 299 | + | stdext::trim(cName); | |
| 300 | + | stdext::ucwords(cName); | |
| 301 | + | ||
| 302 | + | CreatureTypePtr newType(new CreatureType(cName)); | |
| 303 | + | for(TiXmlElement* attrib = root->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) { | |
| 304 | + | if(attrib->ValueStr() != "look") | |
| 305 | + | continue; | |
| 306 | + | ||
| 307 | + | internalLoadCreatureBuffer(attrib, newType); | |
| 308 | + | break; | |
| 309 | + | } | |
| 310 | + | ||
| 311 | + | doc.Clear(); | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | void CreatureManager::internalLoadCreatureBuffer(TiXmlElement* attrib, const CreatureTypePtr& m) | |
| 315 | + | { | |
| 316 | + | if(std::find(m_creatures.begin(), m_creatures.end(), m) != m_creatures.end()) | |
| 317 | + | return; | |
| 318 | + | ||
| 319 | + | Outfit out; | |
| 320 | + | ||
| 321 | + | int32 type = attrib->readType<int32>("type"); | |
| 322 | + | if(type > 0) { | |
| 323 | + | out.setCategory(ThingCategoryCreature); | |
| 324 | + | out.setId(type); | |
| 325 | + | } else { | |
| 326 | + | out.setCategory(ThingCategoryItem); | |
| 327 | + | out.setAuxId(attrib->readType<int32>("typeex")); | |
| 328 | + | } | |
| 329 | + | ||
| 330 | + | { | |
| 331 | + | out.setHead(attrib->readType<int>(("head"))); | |
| 332 | + | out.setBody(attrib->readType<int>(("body"))); | |
| 333 | + | out.setLegs(attrib->readType<int>(("legs"))); | |
| 334 | + | out.setFeet(attrib->readType<int>(("feet"))); | |
| 335 | + | out.setAddons(attrib->readType<int>(("addons"))); | |
| 336 | + | out.setMount(attrib->readType<int>(("mount"))); | |
| 337 | + | } | |
| 338 | + | ||
| 339 | + | m->setOutfit(out); | |
| 340 | + | m_creatures.push_back(m); | |
| 341 | + | } | |
| 342 | + | ||
| 343 | + | const CreatureTypePtr& CreatureManager::getCreatureByName(std::string name) | |
| 344 | + | { | |
| 345 | + | stdext::tolower(name); | |
| 346 | + | stdext::trim(name); | |
| 347 | + | stdext::ucwords(name); | |
| 348 | + | auto it = std::find_if(m_creatures.begin(), m_creatures.end(), | |
| 349 | + | [=] (const CreatureTypePtr& m) -> bool { return m->getName() == name; }); | |
| 350 | + | if(it != m_creatures.end()) | |
| 351 | + | return *it; | |
| 352 | + | g_logger.warning(stdext::format("could not find creature with name: %s", name)); | |
| 353 | + | return m_nullCreature; | |
| 354 | + | } | |
| 355 | + | ||
| 356 | + | const CreatureTypePtr& CreatureManager::getCreatureByLook(int look) | |
| 357 | + | { | |
| 358 | + | auto findFun = [=] (const CreatureTypePtr& c) -> bool | |
| 359 | + | { | |
| 360 | + | const Outfit& o = c->getOutfit(); | |
| 361 | + | return o.getId() == look || o.getAuxId() == look; | |
| 362 | + | }; | |
| 363 | + | auto it = std::find_if(m_creatures.begin(), m_creatures.end(), findFun); | |
| 364 | + | if(it != m_creatures.end()) | |
| 365 | + | return *it; | |
| 366 | + | g_logger.warning(stdext::format("could not find creature with looktype: %d", look)); | |
| 367 | + | return m_nullCreature; | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | SpawnPtr CreatureManager::getSpawn(const Position& centerPos) | |
| 371 | + | { | |
| 372 | + | auto it = m_spawns.find(centerPos); | |
| 373 | + | if(it != m_spawns.end()) | |
| 374 | + | return it->second; | |
| 375 | + | g_logger.debug(stdext::format("failed to find spawn at center %s",stdext::to_string(centerPos))); | |
| 376 | + | return nullptr; | |
| 377 | + | } | |
| 378 | + | ||
| 379 | + | SpawnPtr CreatureManager::getSpawnForPlacePos(const Position& pos) | |
| 380 | + | { | |
| 381 | + | for (const auto& pair : m_spawns) { | |
| 382 | + | const Position& centerPos = pair.first; | |
| 383 | + | const SpawnPtr& spawn = pair.second; | |
| 384 | + | ||
| 385 | + | if (isInZone(pos, centerPos, spawn->getRadius())) | |
| 386 | + | return spawn; | |
| 387 | + | } | |
| 388 | + | ||
| 389 | + | return nullptr; | |
| 390 | + | } | |
| 391 | + | ||
| 392 | + | SpawnPtr CreatureManager::addSpawn(const Position& centerPos, int radius) | |
| 393 | + | { | |
| 394 | + | auto iter = m_spawns.find(centerPos); | |
| 395 | + | if(iter != m_spawns.end()) { | |
| 396 | + | if(iter->second->getRadius() != radius) | |
| 397 | + | iter->second->setRadius(radius); | |
| 398 | + | return iter->second; | |
| 399 | + | } | |
| 400 | + | ||
| 401 | + | SpawnPtr ret(new Spawn); | |
| 402 | + | ||
| 403 | + | ret->setRadius(radius); | |
| 404 | + | ret->setCenterPos(centerPos); | |
| 405 | + | ||
| 406 | + | m_spawns.insert(std::make_pair(centerPos, ret)); | |
| 407 | + | return ret; | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | void CreatureManager::deleteSpawn(const SpawnPtr& spawn) | |
| 411 | + | { | |
| 412 | + | const Position& centerPos = spawn->getCenterPos(); | |
| 413 | + | auto it = m_spawns.find(centerPos); | |
| 414 | + | if(it != m_spawns.end()) | |
| 415 | + | m_spawns.erase(it); | |
| 416 | + | } | |
| 417 | + | ||
| 418 | + | std::vector<SpawnPtr> CreatureManager::getSpawns() | |
| 419 | + | { | |
| 420 | + | std::vector<SpawnPtr> spawns; | |
| 421 | + | for (auto p : m_spawns) | |
| 422 | + | spawns.push_back(p.second); | |
| 423 | + | return spawns; | |
| 424 | + | } | |
| 425 | + | ||
| 426 | + | /* vim: set ts=4 sw=4 et: */ |
| @@ -0,0 +1,147 @@ | |||
| 1 | + | /* | |
| 2 | + | * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 3 | + | * | |
| 4 | + | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 5 | + | * of this software and associated documentation files (the "Software"), to deal | |
| 6 | + | * in the Software without restriction, including without limitation the rights | |
| 7 | + | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 8 | + | * copies of the Software, and to permit persons to whom the Software is | |
| 9 | + | * furnished to do so, subject to the following conditions: | |
| 10 | + | * | |
| 11 | + | * The above copyright notice and this permission notice shall be included in | |
| 12 | + | * all copies or substantial portions of the Software. | |
| 13 | + | * | |
| 14 | + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 15 | + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 16 | + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 17 | + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 18 | + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 | + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 20 | + | * THE SOFTWARE. | |
| 21 | + | */ | |
| 22 | + | ||
| 23 | + | #ifndef CREATURES_H | |
| 24 | + | #define CREATURES_H | |
| 25 | + | ||
| 26 | + | #include "declarations.h" | |
| 27 | + | #include <framework/luaengine/luaobject.h> | |
| 28 | + | #include "outfit.h" | |
| 29 | + | ||
| 30 | + | enum CreatureAttr : uint8 | |
| 31 | + | { | |
| 32 | + | CreatureAttrPosition = 0, | |
| 33 | + | CreatureAttrName = 1, | |
| 34 | + | CreatureAttrOutfit = 2, | |
| 35 | + | CreatureAttrSpawnTime = 3, | |
| 36 | + | CreatureAttrDir = 4, | |
| 37 | + | CreatureAttrRace = 5 | |
| 38 | + | }; | |
| 39 | + | ||
| 40 | + | enum CreatureRace : uint8 | |
| 41 | + | { | |
| 42 | + | CreatureRaceNpc = 0, | |
| 43 | + | CreatureRaceMonster = 1 | |
| 44 | + | }; | |
| 45 | + | ||
| 46 | + | enum SpawnAttr : uint8 | |
| 47 | + | { | |
| 48 | + | SpawnAttrRadius = 0, | |
| 49 | + | SpawnAttrCenter = 1, | |
| 50 | + | }; | |
| 51 | + | ||
| 52 | + | class Spawn : public LuaObject | |
| 53 | + | { | |
| 54 | + | public: | |
| 55 | + | Spawn() = default; | |
| 56 | + | Spawn(int32 radius) { setRadius(radius); } | |
| 57 | + | ||
| 58 | + | void setRadius(int32 r) { m_attribs.set(SpawnAttrRadius, r) ;} | |
| 59 | + | int32 getRadius() { return m_attribs.get<int32>(SpawnAttrRadius); } | |
| 60 | + | ||
| 61 | + | void setCenterPos(const Position& pos) { m_attribs.set(SpawnAttrCenter, pos); } | |
| 62 | + | Position getCenterPos() { return m_attribs.get<Position>(SpawnAttrCenter); } | |
| 63 | + | ||
| 64 | + | std::vector<CreatureTypePtr> getCreatures(); | |
| 65 | + | void addCreature(const Position& placePos, const CreatureTypePtr& cType); | |
| 66 | + | void removeCreature(const Position& pos); | |
| 67 | + | void clear() { m_creatures.clear(); } | |
| 68 | + | ||
| 69 | + | protected: | |
| 70 | + | void load(TiXmlElement* node); | |
| 71 | + | void save(TiXmlElement* node); | |
| 72 | + | ||
| 73 | + | private: | |
| 74 | + | stdext::dynamic_storage<uint8> m_attribs; | |
| 75 | + | std::unordered_map<Position, CreatureTypePtr, PositionHasher> m_creatures; | |
| 76 | + | friend class CreatureManager; | |
| 77 | + | }; | |
| 78 | + | ||
| 79 | + | class CreatureType : public LuaObject | |
| 80 | + | { | |
| 81 | + | public: | |
| 82 | + | CreatureType() = default; | |
| 83 | + | CreatureType(const std::string& name) { setName(name); } | |
| 84 | + | ||
| 85 | + | void setSpawnTime(int32 spawnTime) { m_attribs.set(CreatureAttrSpawnTime, spawnTime); } | |
| 86 | + | int32 getSpawnTime() { return m_attribs.get<int32>(CreatureAttrSpawnTime); } | |
| 87 | + | ||
| 88 | + | void setName(const std::string& name) { m_attribs.set(CreatureAttrName, name); } | |
| 89 | + | std::string getName() { return m_attribs.get<std::string>(CreatureAttrName); } | |
| 90 | + | ||
| 91 | + | void setOutfit(const Outfit& o) { m_attribs.set(CreatureAttrOutfit, o); } | |
| 92 | + | Outfit getOutfit() { return m_attribs.get<Outfit>(CreatureAttrOutfit); } | |
| 93 | + | ||
| 94 | + | void setDirection(Otc::Direction dir) { m_attribs.set(CreatureAttrDir, dir); } | |
| 95 | + | Otc::Direction getDirection() { return m_attribs.get<Otc::Direction>(CreatureAttrDir); } | |
| 96 | + | ||
| 97 | + | void setRace(CreatureRace race) { m_attribs.set(CreatureAttrRace, race); } | |
| 98 | + | CreatureRace getRace() { return m_attribs.get<CreatureRace>(CreatureAttrRace); } | |
| 99 | + | ||
| 100 | + | CreaturePtr cast(); | |
| 101 | + | ||
| 102 | + | private: | |
| 103 | + | stdext::dynamic_storage<uint8> m_attribs; | |
| 104 | + | }; | |
| 105 | + | ||
| 106 | + | class CreatureManager | |
| 107 | + | { | |
| 108 | + | public: | |
| 109 | + | CreatureManager(); | |
| 110 | + | void clear() { m_creatures.clear(); } | |
| 111 | + | void clearSpawns(); | |
| 112 | + | void terminate(); | |
| 113 | + | ||
| 114 | + | void loadMonsters(const std::string& file); | |
| 115 | + | void loadSingleCreature(const std::string& file); | |
| 116 | + | void loadNpcs(const std::string& folder); | |
| 117 | + | void loadCreatureBuffer(const std::string& buffer); | |
| 118 | + | void loadSpawns(const std::string& fileName); | |
| 119 | + | void saveSpawns(const std::string& fileName); | |
| 120 | + | ||
| 121 | + | const CreatureTypePtr& getCreatureByName(std::string name); | |
| 122 | + | const CreatureTypePtr& getCreatureByLook(int look); | |
| 123 | + | ||
| 124 | + | std::vector<SpawnPtr> getSpawns(); | |
| 125 | + | SpawnPtr getSpawn(const Position& centerPos); | |
| 126 | + | SpawnPtr getSpawnForPlacePos(const Position& pos); | |
| 127 | + | SpawnPtr addSpawn(const Position& centerPos, int radius); | |
| 128 | + | void deleteSpawn(const SpawnPtr& spawn); | |
| 129 | + | ||
| 130 | + | bool isLoaded() { return m_loaded; } | |
| 131 | + | bool isSpawnLoaded() { return m_spawnLoaded; } | |
| 132 | + | ||
| 133 | + | const std::vector<CreatureTypePtr>& getCreatures() { return m_creatures; } | |
| 134 | + | ||
| 135 | + | protected: | |
| 136 | + | void internalLoadCreatureBuffer(TiXmlElement* elem, const CreatureTypePtr& m); | |
| 137 | + | ||
| 138 | + | private: | |
| 139 | + | std::vector<CreatureTypePtr> m_creatures; | |
| 140 | + | std::unordered_map<Position, SpawnPtr, PositionHasher> m_spawns; | |
| 141 | + | stdext::boolean<false> m_loaded, m_spawnLoaded; | |
| 142 | + | CreatureTypePtr m_nullCreature; | |
| 143 | + | }; | |
| 144 | + | ||
| 145 | + | extern CreatureManager g_creatures; | |
| 146 | + | ||
| 147 | + | #endif |