| @@ -0,0 +1,113 @@ | |||
| 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 HOUSES_H | |
| 24 | + | #define HOUSES_H | |
| 25 | + | ||
| 26 | + | #include "declarations.h" | |
| 27 | + | #include "tile.h" | |
| 28 | + | ||
| 29 | + | #include <framework/luaengine/luaobject.h> | |
| 30 | + | ||
| 31 | + | enum HouseAttr : uint8 | |
| 32 | + | { | |
| 33 | + | HouseAttrId, | |
| 34 | + | HouseAttrName, | |
| 35 | + | HouseAttrTown, | |
| 36 | + | HouseAttrEntry, | |
| 37 | + | HouseAttrSize, | |
| 38 | + | HouseAttrRent | |
| 39 | + | }; | |
| 40 | + | ||
| 41 | + | class House : public LuaObject | |
| 42 | + | { | |
| 43 | + | public: | |
| 44 | + | House(); | |
| 45 | + | House(uint32 hId, const std::string& name = "", const Position& pos=Position()); | |
| 46 | + | ~House() { m_tiles.clear(); } | |
| 47 | + | ||
| 48 | + | void setTile(const TilePtr& tile); | |
| 49 | + | TilePtr getTile(const Position& pos); | |
| 50 | + | ||
| 51 | + | void setName(const std::string& name) { m_attribs.set(HouseAttrName, name); } | |
| 52 | + | std::string getName() { return m_attribs.get<std::string>(HouseAttrName); } | |
| 53 | + | ||
| 54 | + | void setId(uint32 hId) { m_attribs.set(HouseAttrId, hId); } | |
| 55 | + | uint32 getId() { return m_attribs.get<uint32>(HouseAttrId); } | |
| 56 | + | ||
| 57 | + | void setTownId(uint32 tid) { m_attribs.set(HouseAttrTown, tid); } | |
| 58 | + | uint32 getTownId() { return m_attribs.get<uint32>(HouseAttrTown); } | |
| 59 | + | ||
| 60 | + | void setSize(uint32 s) { m_attribs.set(HouseAttrSize, s); } | |
| 61 | + | uint32 getSize() { return m_attribs.get<uint32>(HouseAttrSize); } | |
| 62 | + | ||
| 63 | + | void setRent(uint32 r) { m_attribs.set(HouseAttrRent, r); } | |
| 64 | + | uint32 getRent() { return m_attribs.get<uint32>(HouseAttrRent); } | |
| 65 | + | ||
| 66 | + | void setEntry(const Position& p) { m_attribs.set(HouseAttrEntry, p); } | |
| 67 | + | Position getEntry() { return m_attribs.get<Position>(HouseAttrEntry); } | |
| 68 | + | ||
| 69 | + | void addDoor(const ItemPtr& door); | |
| 70 | + | void removeDoor(const ItemPtr& door) { removeDoorById(door->getDoorId()); } | |
| 71 | + | void removeDoorById(uint32 doorId); | |
| 72 | + | ||
| 73 | + | protected: | |
| 74 | + | void load(const TiXmlElement* elem); | |
| 75 | + | void save(TiXmlElement* elem); | |
| 76 | + | ||
| 77 | + | private: | |
| 78 | + | stdext::packed_storage<uint8> m_attribs; | |
| 79 | + | TileMap m_tiles; | |
| 80 | + | ItemVector m_doors; | |
| 81 | + | uint32 m_lastDoorId; | |
| 82 | + | stdext::boolean<false> m_isGuildHall; | |
| 83 | + | ||
| 84 | + | friend class HouseManager; | |
| 85 | + | }; | |
| 86 | + | ||
| 87 | + | class HouseManager { | |
| 88 | + | public: | |
| 89 | + | HouseManager(); | |
| 90 | + | ||
| 91 | + | void addHouse(const HousePtr& house); | |
| 92 | + | void removeHouse(uint32 houseId); | |
| 93 | + | HousePtr getHouse(uint32 houseId); | |
| 94 | + | HousePtr getHouseByName(std::string name); | |
| 95 | + | ||
| 96 | + | void load(const std::string& fileName); | |
| 97 | + | void save(const std::string& fileName); | |
| 98 | + | ||
| 99 | + | void sort(); | |
| 100 | + | void clear() { m_houses.clear(); } | |
| 101 | + | HouseList getHouseList() { return m_houses; } | |
| 102 | + | HouseList filterHouses(uint32 townId); | |
| 103 | + | ||
| 104 | + | private: | |
| 105 | + | HouseList m_houses; | |
| 106 | + | ||
| 107 | + | protected: | |
| 108 | + | HouseList::iterator findHouse(uint32 houseId); | |
| 109 | + | }; | |
| 110 | + | ||
| 111 | + | extern HouseManager g_houses; | |
| 112 | + | ||
| 113 | + | #endif |
| @@ -0,0 +1,450 @@ | |||
| 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 "item.h" | |
| 24 | + | #include "thingtypemanager.h" | |
| 25 | + | #include "spritemanager.h" | |
| 26 | + | #include "thing.h" | |
| 27 | + | #include "tile.h" | |
| 28 | + | #include "container.h" | |
| 29 | + | #include "map.h" | |
| 30 | + | #include "houses.h" | |
| 31 | + | #include "game.h" | |
| 32 | + | ||
| 33 | + | #include <framework/core/clock.h> | |
| 34 | + | #include <framework/core/eventdispatcher.h> | |
| 35 | + | #include <framework/graphics/graphics.h> | |
| 36 | + | #include <framework/core/filestream.h> | |
| 37 | + | #include <framework/core/binarytree.h> | |
| 38 | + | #include <framework/graphics/shadermanager.h> | |
| 39 | + | ||
| 40 | + | #include <framework/util/stats.h> | |
| 41 | + | ||
| 42 | + | Item::Item() : | |
| 43 | + | m_clientId(0), | |
| 44 | + | m_serverId(0), | |
| 45 | + | m_countOrSubType(1), | |
| 46 | + | m_color(Color::alpha), | |
| 47 | + | m_async(true), | |
| 48 | + | m_quickLootFlags(0), | |
| 49 | + | m_phase(0), | |
| 50 | + | m_lastPhase(0) | |
| 51 | + | { | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | ItemPtr Item::create(int id, int countOrSubtype) | |
| 55 | + | { | |
| 56 | + | ItemPtr item(new Item); | |
| 57 | + | item->setId(id); | |
| 58 | + | item->setCountOrSubType(countOrSubtype); | |
| 59 | + | return item; | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | ItemPtr Item::createFromOtb(int id) | |
| 63 | + | { | |
| 64 | + | ItemPtr item(new Item); | |
| 65 | + | item->setOtbId(id); | |
| 66 | + | return item; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | std::string Item::getName() | |
| 70 | + | { | |
| 71 | + | return g_things.findItemTypeByClientId(m_clientId)->getName(); | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | void Item::draw(const Point& dest, bool animate, LightView* lightView) | |
| 75 | + | { | |
| 76 | + | if (m_clientId == 0) | |
| 77 | + | return; | |
| 78 | + | ||
| 79 | + | // determine animation phase | |
| 80 | + | int animationPhase = calculateAnimationPhase(animate); | |
| 81 | + | ||
| 82 | + | // determine x,y,z patterns | |
| 83 | + | int xPattern = 0, yPattern = 0, zPattern = 0; | |
| 84 | + | calculatePatterns(xPattern, yPattern, zPattern); | |
| 85 | + | ||
| 86 | + | Color color(Color::white); | |
| 87 | + | if (m_color != Color::alpha) | |
| 88 | + | color = m_color; | |
| 89 | + | size_t drawQueueSize = g_drawQueue->size(); | |
| 90 | + | if (!m_shader.empty()) { | |
| 91 | + | rawGetThingType()->drawWithShader(dest, 0, xPattern, yPattern, zPattern, animationPhase, m_shader, color, lightView); | |
| 92 | + | } | |
| 93 | + | else { | |
| 94 | + | rawGetThingType()->draw(dest, 0, xPattern, yPattern, zPattern, animationPhase, color, lightView); | |
| 95 | + | } | |
| 96 | + | if (m_marked) { | |
| 97 | + | g_drawQueue->setMark(drawQueueSize, updatedMarkedColor()); | |
| 98 | + | } | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | void Item::draw(const Rect& dest, bool animate) | |
| 102 | + | { | |
| 103 | + | if (m_clientId == 0) | |
| 104 | + | return; | |
| 105 | + | ||
| 106 | + | // determine animation phase | |
| 107 | + | int animationPhase = calculateAnimationPhase(animate); | |
| 108 | + | ||
| 109 | + | // determine x,y,z patterns | |
| 110 | + | int xPattern = 0, yPattern = 0, zPattern = 0; | |
| 111 | + | calculatePatterns(xPattern, yPattern, zPattern); | |
| 112 | + | ||
| 113 | + | Color color(Color::white); | |
| 114 | + | if (m_color != Color::alpha) | |
| 115 | + | color = m_color; | |
| 116 | + | ||
| 117 | + | if (!m_shader.empty()) { | |
| 118 | + | rawGetThingType()->drawWithShader(dest, 0, xPattern, yPattern, zPattern, animationPhase, m_shader, color); | |
| 119 | + | } | |
| 120 | + | else { | |
| 121 | + | rawGetThingType()->draw(dest, 0, xPattern, yPattern, zPattern, animationPhase, color); | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | void Item::setId(uint32 id) | |
| 126 | + | { | |
| 127 | + | if(!g_things.isValidDatId(id, ThingCategoryItem)) | |
| 128 | + | id = 0; | |
| 129 | + | m_serverId = g_things.findItemTypeByClientId(id)->getServerId(); | |
| 130 | + | m_clientId = id; | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | void Item::setOtbId(uint16 id) | |
| 134 | + | { | |
| 135 | + | if(!g_things.isValidOtbId(id)) | |
| 136 | + | id = 0; | |
| 137 | + | auto itemType = g_things.getItemType(id); | |
| 138 | + | m_serverId = id; | |
| 139 | + | ||
| 140 | + | id = itemType->getClientId(); | |
| 141 | + | if(!g_things.isValidDatId(id, ThingCategoryItem)) | |
| 142 | + | id = 0; | |
| 143 | + | m_clientId = id; | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | bool Item::isValid() | |
| 147 | + | { | |
| 148 | + | return g_things.isValidDatId(m_clientId, ThingCategoryItem); | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | void Item::unserializeItem(const BinaryTreePtr &in) | |
| 152 | + | { | |
| 153 | + | try { | |
| 154 | + | while(in->canRead()) { | |
| 155 | + | int attrib = in->getU8(); | |
| 156 | + | if(attrib == 0) | |
| 157 | + | break; | |
| 158 | + | ||
| 159 | + | switch(attrib) { | |
| 160 | + | case ATTR_COUNT: | |
| 161 | + | case ATTR_RUNE_CHARGES: | |
| 162 | + | setCount(in->getU8()); | |
| 163 | + | break; | |
| 164 | + | case ATTR_CHARGES: | |
| 165 | + | setCount(in->getU16()); | |
| 166 | + | break; | |
| 167 | + | case ATTR_HOUSEDOORID: | |
| 168 | + | case ATTR_SCRIPTPROTECTED: | |
| 169 | + | case ATTR_DUALWIELD: | |
| 170 | + | case ATTR_DECAYING_STATE: | |
| 171 | + | m_attribs.set(attrib, in->getU8()); | |
| 172 | + | break; | |
| 173 | + | case ATTR_ACTION_ID: | |
| 174 | + | case ATTR_UNIQUE_ID: | |
| 175 | + | case ATTR_DEPOT_ID: | |
| 176 | + | m_attribs.set(attrib, in->getU16()); | |
| 177 | + | break; | |
| 178 | + | case ATTR_CONTAINER_ITEMS: | |
| 179 | + | case ATTR_ATTACK: | |
| 180 | + | case ATTR_EXTRAATTACK: | |
| 181 | + | case ATTR_DEFENSE: | |
| 182 | + | case ATTR_EXTRADEFENSE: | |
| 183 | + | case ATTR_ARMOR: | |
| 184 | + | case ATTR_ATTACKSPEED: | |
| 185 | + | case ATTR_HITCHANCE: | |
| 186 | + | case ATTR_DURATION: | |
| 187 | + | case ATTR_WRITTENDATE: | |
| 188 | + | case ATTR_SLEEPERGUID: | |
| 189 | + | case ATTR_SLEEPSTART: | |
| 190 | + | case ATTR_ATTRIBUTE_MAP: | |
| 191 | + | m_attribs.set(attrib, in->getU32()); | |
| 192 | + | break; | |
| 193 | + | case ATTR_TELE_DEST: { | |
| 194 | + | Position pos; | |
| 195 | + | pos.x = in->getU16(); | |
| 196 | + | pos.y = in->getU16(); | |
| 197 | + | pos.z = in->getU8(); | |
| 198 | + | m_attribs.set(attrib, pos); | |
| 199 | + | break; | |
| 200 | + | } | |
| 201 | + | case ATTR_NAME: | |
| 202 | + | case ATTR_TEXT: | |
| 203 | + | case ATTR_DESC: | |
| 204 | + | case ATTR_ARTICLE: | |
| 205 | + | case ATTR_WRITTENBY: | |
| 206 | + | m_attribs.set(attrib, in->getString()); | |
| 207 | + | break; | |
| 208 | + | default: | |
| 209 | + | stdext::throw_exception(stdext::format("invalid item attribute %d", attrib)); | |
| 210 | + | } | |
| 211 | + | } | |
| 212 | + | } catch(stdext::exception& e) { | |
| 213 | + | g_logger.error(stdext::format("Failed to unserialize OTBM item: %s", e.what())); | |
| 214 | + | } | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | void Item::serializeItem(const OutputBinaryTreePtr& out) | |
| 218 | + | { | |
| 219 | + | out->startNode(OTBM_ITEM); | |
| 220 | + | out->addU16(getServerId()); | |
| 221 | + | ||
| 222 | + | out->addU8(ATTR_COUNT); | |
| 223 | + | out->addU8(getCount()); | |
| 224 | + | ||
| 225 | + | out->addU8(ATTR_CHARGES); | |
| 226 | + | out->addU16(getCountOrSubType()); | |
| 227 | + | ||
| 228 | + | Position dest = m_attribs.get<Position>(ATTR_TELE_DEST); | |
| 229 | + | if(dest.isValid()) { | |
| 230 | + | out->addU8(ATTR_TELE_DEST); | |
| 231 | + | out->addPos(dest.x, dest.y, dest.z); | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | if(isDepot()) { | |
| 235 | + | out->addU8(ATTR_DEPOT_ID); | |
| 236 | + | out->addU16(getDepotId()); | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | if(isHouseDoor()) { | |
| 240 | + | out->addU8(ATTR_HOUSEDOORID); | |
| 241 | + | out->addU8(getDoorId()); | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | uint16 aid = m_attribs.get<uint16>(ATTR_ACTION_ID); | |
| 245 | + | uint16 uid = m_attribs.get<uint16>(ATTR_UNIQUE_ID); | |
| 246 | + | if(aid) { | |
| 247 | + | out->addU8(ATTR_ACTION_ID); | |
| 248 | + | out->addU16(aid); | |
| 249 | + | } | |
| 250 | + | ||
| 251 | + | if(uid) { | |
| 252 | + | out->addU8(ATTR_UNIQUE_ID); | |
| 253 | + | out->addU16(uid); | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | std::string text = getText(); | |
| 257 | + | if(g_things.getItemType(m_serverId)->isWritable() && !text.empty()) { | |
| 258 | + | out->addU8(ATTR_TEXT); | |
| 259 | + | out->addString(text); | |
| 260 | + | } | |
| 261 | + | std::string desc = getDescription(); | |
| 262 | + | if(!desc.empty()) { | |
| 263 | + | out->addU8(ATTR_DESC); | |
| 264 | + | out->addString(desc); | |
| 265 | + | } | |
| 266 | + | ||
| 267 | + | out->endNode(); | |
| 268 | + | for(auto i : m_containerItems) | |
| 269 | + | i->serializeItem(out); | |
| 270 | + | } | |
| 271 | + | ||
| 272 | + | int Item::getSubType() | |
| 273 | + | { | |
| 274 | + | if(isSplash() || isFluidContainer()) | |
| 275 | + | return m_countOrSubType; | |
| 276 | + | if(g_game.getClientVersion() >= 860) | |
| 277 | + | return 0; | |
| 278 | + | return 1; | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | int Item::getCount() | |
| 282 | + | { | |
| 283 | + | if(isStackable()) | |
| 284 | + | return m_countOrSubType; | |
| 285 | + | return 1; | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | bool Item::isMoveable() | |
| 289 | + | { | |
| 290 | + | return !rawGetThingType()->isNotMoveable(); | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | bool Item::isGround() | |
| 294 | + | { | |
| 295 | + | return rawGetThingType()->isGround(); | |
| 296 | + | } | |
| 297 | + | ||
| 298 | + | ItemPtr Item::clone() | |
| 299 | + | { | |
| 300 | + | ItemPtr item = ItemPtr(new Item); | |
| 301 | + | *(item.get()) = *this; | |
| 302 | + | return item; | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | void Item::calculatePatterns(int& xPattern, int& yPattern, int& zPattern) | |
| 306 | + | { | |
| 307 | + | // Avoid crashes with invalid items | |
| 308 | + | if(!isValid()) | |
| 309 | + | return; | |
| 310 | + | ||
| 311 | + | if(isStackable() && getNumPatternX() == 4 && getNumPatternY() == 2) { | |
| 312 | + | if(m_countOrSubType <= 0) { | |
| 313 | + | xPattern = 0; | |
| 314 | + | yPattern = 0; | |
| 315 | + | } else if(m_countOrSubType < 5) { | |
| 316 | + | xPattern = m_countOrSubType-1; | |
| 317 | + | yPattern = 0; | |
| 318 | + | } else if(m_countOrSubType < 10) { | |
| 319 | + | xPattern = 0; | |
| 320 | + | yPattern = 1; | |
| 321 | + | } else if(m_countOrSubType < 25) { | |
| 322 | + | xPattern = 1; | |
| 323 | + | yPattern = 1; | |
| 324 | + | } else if(m_countOrSubType < 50) { | |
| 325 | + | xPattern = 2; | |
| 326 | + | yPattern = 1; | |
| 327 | + | } else { | |
| 328 | + | xPattern = 3; | |
| 329 | + | yPattern = 1; | |
| 330 | + | } | |
| 331 | + | } else if(isHangable()) { | |
| 332 | + | const TilePtr& tile = getTile(); | |
| 333 | + | if(tile) { | |
| 334 | + | if(tile->mustHookSouth()) | |
| 335 | + | xPattern = getNumPatternX() >= 2 ? 1 : 0; | |
| 336 | + | else if(tile->mustHookEast()) | |
| 337 | + | xPattern = getNumPatternX() >= 3 ? 2 : 0; | |
| 338 | + | } | |
| 339 | + | } else if(isSplash() || isFluidContainer()) { | |
| 340 | + | int color = Otc::FluidTransparent; | |
| 341 | + | if(g_game.getFeature(Otc::GameNewFluids)) { | |
| 342 | + | switch(m_countOrSubType) { | |
| 343 | + | case Otc::FluidNone: | |
| 344 | + | color = Otc::FluidTransparent; | |
| 345 | + | break; | |
| 346 | + | case Otc::FluidWater: | |
| 347 | + | color = Otc::FluidBlue; | |
| 348 | + | break; | |
| 349 | + | case Otc::FluidMana: | |
| 350 | + | color = Otc::FluidPurple; | |
| 351 | + | break; | |
| 352 | + | case Otc::FluidBeer: | |
| 353 | + | color = Otc::FluidBrown; | |
| 354 | + | break; | |
| 355 | + | case Otc::FluidOil: | |
| 356 | + | color = Otc::FluidBrown; | |
| 357 | + | break; | |
| 358 | + | case Otc::FluidBlood: | |
| 359 | + | color = Otc::FluidRed; | |
| 360 | + | break; | |
| 361 | + | case Otc::FluidSlime: | |
| 362 | + | color = Otc::FluidGreen; | |
| 363 | + | break; | |
| 364 | + | case Otc::FluidMud: | |
| 365 | + | color = Otc::FluidBrown; | |
| 366 | + | break; | |
| 367 | + | case Otc::FluidLemonade: | |
| 368 | + | color = Otc::FluidYellow; | |
| 369 | + | break; | |
| 370 | + | case Otc::FluidMilk: | |
| 371 | + | color = Otc::FluidWhite; | |
| 372 | + | break; | |
| 373 | + | case Otc::FluidWine: | |
| 374 | + | color = Otc::FluidPurple; | |
| 375 | + | break; | |
| 376 | + | case Otc::FluidHealth: | |
| 377 | + | color = Otc::FluidRed; | |
| 378 | + | break; | |
| 379 | + | case Otc::FluidUrine: | |
| 380 | + | color = Otc::FluidYellow; | |
| 381 | + | break; | |
| 382 | + | case Otc::FluidRum: | |
| 383 | + | color = Otc::FluidBrown; | |
| 384 | + | break; | |
| 385 | + | case Otc::FluidFruidJuice: | |
| 386 | + | color = Otc::FluidYellow; | |
| 387 | + | break; | |
| 388 | + | case Otc::FluidCoconutMilk: | |
| 389 | + | color = Otc::FluidWhite; | |
| 390 | + | break; | |
| 391 | + | case Otc::FluidTea: | |
| 392 | + | color = Otc::FluidBrown; | |
| 393 | + | break; | |
| 394 | + | case Otc::FluidMead: | |
| 395 | + | color = Otc::FluidBrown; | |
| 396 | + | break; | |
| 397 | + | default: | |
| 398 | + | color = Otc::FluidTransparent; | |
| 399 | + | break; | |
| 400 | + | } | |
| 401 | + | } else | |
| 402 | + | color = m_countOrSubType; | |
| 403 | + | ||
| 404 | + | xPattern = (color % 4) % getNumPatternX(); | |
| 405 | + | yPattern = (color / 4) % getNumPatternY(); | |
| 406 | + | } else { | |
| 407 | + | xPattern = m_position.x % std::max<int>(1, getNumPatternX()); | |
| 408 | + | yPattern = m_position.y % std::max<int>(1, getNumPatternY()); | |
| 409 | + | zPattern = m_position.z % std::max<int>(1, getNumPatternZ()); | |
| 410 | + | } | |
| 411 | + | } | |
| 412 | + | ||
| 413 | + | int Item::calculateAnimationPhase(bool animate) | |
| 414 | + | { | |
| 415 | + | if(getAnimationPhases() > 1) { | |
| 416 | + | if(animate) { | |
| 417 | + | if(getAnimator() != nullptr) | |
| 418 | + | return getAnimator()->getPhase(); | |
| 419 | + | ||
| 420 | + | if(m_async) | |
| 421 | + | return (g_clock.millis() % ((g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::ITEM_TICKS_PER_FRAME_FAST : Otc::ITEM_TICKS_PER_FRAME) * getAnimationPhases())) / Otc::ITEM_TICKS_PER_FRAME; | |
| 422 | + | else { | |
| 423 | + | if(g_clock.millis() - m_lastPhase >= (g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::ITEM_TICKS_PER_FRAME_FAST : Otc::ITEM_TICKS_PER_FRAME)) { | |
| 424 | + | m_phase = (m_phase + 1) % getAnimationPhases(); | |
| 425 | + | m_lastPhase = g_clock.millis(); | |
| 426 | + | } | |
| 427 | + | return m_phase; | |
| 428 | + | } | |
| 429 | + | } else | |
| 430 | + | return getAnimationPhases()-1; | |
| 431 | + | } | |
| 432 | + | return 0; | |
| 433 | + | } | |
| 434 | + | ||
| 435 | + | int Item::getExactSize(int layer, int xPattern, int yPattern, int zPattern, int animationPhase) | |
| 436 | + | { | |
| 437 | + | calculatePatterns(xPattern, yPattern, zPattern); | |
| 438 | + | animationPhase = calculateAnimationPhase(true); | |
| 439 | + | return Thing::getExactSize(layer, xPattern, yPattern, zPattern, animationPhase); | |
| 440 | + | } | |
| 441 | + | ||
| 442 | + | const ThingTypePtr& Item::getThingType() | |
| 443 | + | { | |
| 444 | + | return g_things.getThingType(m_clientId, ThingCategoryItem); | |
| 445 | + | } | |
| 446 | + | ||
| 447 | + | ThingType* Item::rawGetThingType() | |
| 448 | + | { | |
| 449 | + | return g_things.rawGetThingType(m_clientId, ThingCategoryItem); | |
| 450 | + | } |
| @@ -0,0 +1,187 @@ | |||
| 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 ITEM_H | |
| 24 | + | #define ITEM_H | |
| 25 | + | ||
| 26 | + | #include <framework/global.h> | |
| 27 | + | ||
| 28 | + | #include "thing.h" | |
| 29 | + | #include "effect.h" | |
| 30 | + | #include "itemtype.h" | |
| 31 | + | ||
| 32 | + | enum ItemAttr : uint8 | |
| 33 | + | { | |
| 34 | + | ATTR_END = 0, | |
| 35 | + | //ATTR_DESCRIPTION = 1, | |
| 36 | + | //ATTR_EXT_FILE = 2, | |
| 37 | + | ATTR_TILE_FLAGS = 3, | |
| 38 | + | ATTR_ACTION_ID = 4, | |
| 39 | + | ATTR_UNIQUE_ID = 5, | |
| 40 | + | ATTR_TEXT = 6, | |
| 41 | + | ATTR_DESC = 7, | |
| 42 | + | ATTR_TELE_DEST = 8, | |
| 43 | + | ATTR_ITEM = 9, | |
| 44 | + | ATTR_DEPOT_ID = 10, | |
| 45 | + | //ATTR_EXT_SPAWN_FILE = 11, | |
| 46 | + | ATTR_RUNE_CHARGES = 12, | |
| 47 | + | //ATTR_EXT_HOUSE_FILE = 13, | |
| 48 | + | ATTR_HOUSEDOORID = 14, | |
| 49 | + | ATTR_COUNT = 15, | |
| 50 | + | ATTR_DURATION = 16, | |
| 51 | + | ATTR_DECAYING_STATE = 17, | |
| 52 | + | ATTR_WRITTENDATE = 18, | |
| 53 | + | ATTR_WRITTENBY = 19, | |
| 54 | + | ATTR_SLEEPERGUID = 20, | |
| 55 | + | ATTR_SLEEPSTART = 21, | |
| 56 | + | ATTR_CHARGES = 22, | |
| 57 | + | ATTR_CONTAINER_ITEMS = 23, | |
| 58 | + | ATTR_NAME = 30, | |
| 59 | + | ATTR_PLURALNAME = 31, | |
| 60 | + | ATTR_ATTACK = 33, | |
| 61 | + | ATTR_EXTRAATTACK = 34, | |
| 62 | + | ATTR_DEFENSE = 35, | |
| 63 | + | ATTR_EXTRADEFENSE = 36, | |
| 64 | + | ATTR_ARMOR = 37, | |
| 65 | + | ATTR_ATTACKSPEED = 38, | |
| 66 | + | ATTR_HITCHANCE = 39, | |
| 67 | + | ATTR_SHOOTRANGE = 40, | |
| 68 | + | ATTR_ARTICLE = 41, | |
| 69 | + | ATTR_SCRIPTPROTECTED = 42, | |
| 70 | + | ATTR_DUALWIELD = 43, | |
| 71 | + | ATTR_ATTRIBUTE_MAP = 128 | |
| 72 | + | }; | |
| 73 | + | ||
| 74 | + | // @bindclass | |
| 75 | + | #pragma pack(push,1) // disable memory alignment | |
| 76 | + | class Item : public Thing | |
| 77 | + | { | |
| 78 | + | public: | |
| 79 | + | Item(); | |
| 80 | + | virtual ~Item() { } | |
| 81 | + | ||
| 82 | + | static ItemPtr create(int id, int countOrSubtype = 1); | |
| 83 | + | static ItemPtr createFromOtb(int id); | |
| 84 | + | ||
| 85 | + | void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr); | |
| 86 | + | void draw(const Rect& dest, bool animate = true); | |
| 87 | + | ||
| 88 | + | void setId(uint32 id); | |
| 89 | + | void setOtbId(uint16 id); | |
| 90 | + | void setCountOrSubType(int value) { m_countOrSubType = value; } | |
| 91 | + | void setCount(int count) { m_countOrSubType = count; } | |
| 92 | + | void setSubType(int subType) { m_countOrSubType = subType; } | |
| 93 | + | void setColor(const Color& c) { m_color = c; } | |
| 94 | + | void setTooltip(const std::string& str) { m_tooltip = str; } | |
| 95 | + | void setQuickLootFlags(uint32 flags) { m_quickLootFlags = flags; } | |
| 96 | + | void setShader(const std::string& str) { m_shader = str; } | |
| 97 | + | ||
| 98 | + | int getCountOrSubType() { return m_countOrSubType; } | |
| 99 | + | int getSubType(); | |
| 100 | + | int getCount(); | |
| 101 | + | uint32 getId() { return m_clientId; } | |
| 102 | + | uint16 getClientId() { return m_clientId; } | |
| 103 | + | uint16 getServerId() { return m_serverId; } | |
| 104 | + | std::string getName(); | |
| 105 | + | bool isValid(); | |
| 106 | + | std::string getTooltip() { return m_tooltip; } | |
| 107 | + | uint32 getQuickLootFlags() { return m_quickLootFlags; } | |
| 108 | + | std::string getShader() { return m_shader; } | |
| 109 | + | ||
| 110 | + | void unserializeItem(const BinaryTreePtr& in); | |
| 111 | + | void serializeItem(const OutputBinaryTreePtr& out); | |
| 112 | + | ||
| 113 | + | void setDepotId(uint16 depotId) { m_attribs.set(ATTR_DEPOT_ID, depotId); } | |
| 114 | + | uint16 getDepotId() { return m_attribs.get<uint16>(ATTR_DEPOT_ID); } | |
| 115 | + | ||
| 116 | + | void setDoorId(uint8 doorId) { m_attribs.set(ATTR_HOUSEDOORID, doorId); } | |
| 117 | + | uint8 getDoorId() { return m_attribs.get<uint8>(ATTR_HOUSEDOORID); } | |
| 118 | + | ||
| 119 | + | uint16 getUniqueId() { return m_attribs.get<uint16>(ATTR_UNIQUE_ID); } | |
| 120 | + | uint16 getActionId() { return m_attribs.get<uint16>(ATTR_ACTION_ID); } | |
| 121 | + | void setActionId(uint16 actionId) { m_attribs.set(ATTR_ACTION_ID, actionId); } | |
| 122 | + | void setUniqueId(uint16 uniqueId) { m_attribs.set(ATTR_UNIQUE_ID, uniqueId); } | |
| 123 | + | ||
| 124 | + | std::string getText() { return m_attribs.get<std::string>(ATTR_TEXT); } | |
| 125 | + | std::string getDescription() { return m_attribs.get<std::string>(ATTR_DESC); } | |
| 126 | + | void setDescription(std::string desc) { m_attribs.set(ATTR_DESC, desc); } | |
| 127 | + | void setText(std::string txt) { m_attribs.set(ATTR_TEXT, txt); } | |
| 128 | + | ||
| 129 | + | Position getTeleportDestination() { return m_attribs.get<Position>(ATTR_TELE_DEST); } | |
| 130 | + | void setTeleportDestination(const Position& pos) { m_attribs.set(ATTR_TELE_DEST, pos); } | |
| 131 | + | ||
| 132 | + | void setAsync(bool enable) { m_async = enable; } | |
| 133 | + | ||
| 134 | + | bool isHouseDoor() { return m_attribs.has(ATTR_HOUSEDOORID); } | |
| 135 | + | bool isDepot() { return m_attribs.has(ATTR_DEPOT_ID); } | |
| 136 | + | bool isContainer() { return m_attribs.has(ATTR_CONTAINER_ITEMS); } | |
| 137 | + | bool isDoor() { return m_attribs.has(ATTR_HOUSEDOORID); } | |
| 138 | + | bool isTeleport() { return m_attribs.has(ATTR_TELE_DEST); } | |
| 139 | + | bool isMoveable(); | |
| 140 | + | bool isGround(); | |
| 141 | + | ||
| 142 | + | ItemPtr clone(); | |
| 143 | + | ItemPtr asItem() { return static_self_cast<Item>(); } | |
| 144 | + | bool isItem() { return true; } | |
| 145 | + | ||
| 146 | + | ItemVector getContainerItems() { return m_containerItems; } | |
| 147 | + | ItemPtr getContainerItem(int slot) { return m_containerItems[slot]; } | |
| 148 | + | void addContainerItemIndexed(const ItemPtr& i, int slot) { m_containerItems[slot] = i; } | |
| 149 | + | void addContainerItem(const ItemPtr& i) { m_containerItems.push_back(i); } | |
| 150 | + | void removeContainerItem(int slot) { m_containerItems[slot] = nullptr; } | |
| 151 | + | void clearContainerItems() { m_containerItems.clear(); } | |
| 152 | + | ||
| 153 | + | void calculatePatterns(int& xPattern, int& yPattern, int& zPattern); | |
| 154 | + | int calculateAnimationPhase(bool animate); | |
| 155 | + | int getExactSize(int layer = 0, int xPattern = 0, int yPattern = 0, int zPattern = 0, int animationPhase = 0); | |
| 156 | + | ||
| 157 | + | const ThingTypePtr& getThingType(); | |
| 158 | + | ThingType *rawGetThingType(); | |
| 159 | + | ||
| 160 | + | void setCustomAttribute(uint16 key, uint64 value) { | |
| 161 | + | m_customAttribs.set(key, value); | |
| 162 | + | } | |
| 163 | + | uint64 getCustomAttribute(uint16 key) { | |
| 164 | + | return m_customAttribs.get<uint64>(key); | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | private: | |
| 168 | + | uint16 m_clientId; | |
| 169 | + | uint16 m_serverId; | |
| 170 | + | uint16 m_countOrSubType; | |
| 171 | + | stdext::packed_storage<uint8> m_attribs; | |
| 172 | + | ItemVector m_containerItems; | |
| 173 | + | Color m_color; | |
| 174 | + | bool m_async; | |
| 175 | + | std::string m_tooltip; | |
| 176 | + | std::string m_shader; | |
| 177 | + | ||
| 178 | + | uint32 m_quickLootFlags; | |
| 179 | + | uint8 m_phase; | |
| 180 | + | ticks_t m_lastPhase; | |
| 181 | + | ||
| 182 | + | stdext::packed_storage<uint16> m_customAttribs; | |
| 183 | + | }; | |
| 184 | + | ||
| 185 | + | #pragma pack(pop) | |
| 186 | + | ||
| 187 | + | #endif |
| @@ -0,0 +1,93 @@ | |||
| 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 | + | ||
| 24 | + | #include "thingtypemanager.h" | |
| 25 | + | #include "thingtype.h" | |
| 26 | + | #include "game.h" | |
| 27 | + | ||
| 28 | + | #include <framework/core/filestream.h> | |
| 29 | + | #include <framework/core/binarytree.h> | |
| 30 | + | ||
| 31 | + | ItemType::ItemType() | |
| 32 | + | { | |
| 33 | + | m_category = ItemCategoryInvalid; | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | void ItemType::unserialize(const BinaryTreePtr& node) | |
| 37 | + | { | |
| 38 | + | m_null = false; | |
| 39 | + | ||
| 40 | + | m_category = (ItemCategory)node->getU8(); | |
| 41 | + | ||
| 42 | + | node->getU32(); // flags | |
| 43 | + | ||
| 44 | + | static uint16 lastId = 99; | |
| 45 | + | while(node->canRead()) { | |
| 46 | + | uint8 attr = node->getU8(); | |
| 47 | + | if(attr == 0 || attr == 0xFF) | |
| 48 | + | break; | |
| 49 | + | ||
| 50 | + | uint16 len = node->getU16(); | |
| 51 | + | switch(attr) { | |
| 52 | + | case ItemTypeAttrServerId: { | |
| 53 | + | uint16 serverId = node->getU16(); | |
| 54 | + | if(g_game.getClientVersion() < 960) { | |
| 55 | + | if(serverId > 20000 && serverId < 20100) { | |
| 56 | + | serverId -= 20000; | |
| 57 | + | } else if(lastId > 99 && lastId != serverId - 1) { | |
| 58 | + | while(lastId != serverId - 1) { | |
| 59 | + | ItemTypePtr tmp(new ItemType); | |
| 60 | + | tmp->setServerId(lastId++); | |
| 61 | + | g_things.addItemType(tmp); | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | } else { | |
| 65 | + | if(serverId > 30000 && serverId < 30100) { | |
| 66 | + | serverId -= 30000; | |
| 67 | + | } else if(lastId > 99 && lastId != serverId - 1) { | |
| 68 | + | while(lastId != serverId - 1) { | |
| 69 | + | ItemTypePtr tmp(new ItemType); | |
| 70 | + | tmp->setServerId(lastId++); | |
| 71 | + | g_things.addItemType(tmp); | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | } | |
| 75 | + | setServerId(serverId); | |
| 76 | + | lastId = serverId; | |
| 77 | + | break; | |
| 78 | + | } | |
| 79 | + | case ItemTypeAttrClientId: | |
| 80 | + | setClientId(node->getU16()); | |
| 81 | + | break; | |
| 82 | + | case ItemTypeAttrName: | |
| 83 | + | setName(node->getString(len)); | |
| 84 | + | break; | |
| 85 | + | case ItemTypeAttrWritable: | |
| 86 | + | m_attribs.set(ItemTypeAttrWritable, true); | |
| 87 | + | break; | |
| 88 | + | default: | |
| 89 | + | node->skip(len); // skip attribute | |
| 90 | + | break; | |
| 91 | + | } | |
| 92 | + | } | |
| 93 | + | } |
| @@ -0,0 +1,162 @@ | |||
| 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 | + | ||
| 24 | + | #ifndef ITEMTYPE_H | |
| 25 | + | #define ITEMTYPE_H | |
| 26 | + | ||
| 27 | + | #include <framework/core/declarations.h> | |
| 28 | + | #include <framework/luaengine/luaobject.h> | |
| 29 | + | #include <framework/xml/tinyxml.h> | |
| 30 | + | ||
| 31 | + | enum ItemCategory : uint8 { | |
| 32 | + | ItemCategoryInvalid = 0, | |
| 33 | + | ItemCategoryGround = 1, | |
| 34 | + | ItemCategoryContainer = 2, | |
| 35 | + | ItemCategoryWeapon = 3, | |
| 36 | + | ItemCategoryAmmunition = 4, | |
| 37 | + | ItemCategoryArmor = 5, | |
| 38 | + | ItemCategoryCharges = 6, | |
| 39 | + | ItemCategoryTeleport = 7, | |
| 40 | + | ItemCategoryMagicField = 8, | |
| 41 | + | ItemCategoryWritable = 9, | |
| 42 | + | ItemCategoryKey = 10, | |
| 43 | + | ItemCategorySplash = 11, | |
| 44 | + | ItemCategoryFluid = 12, | |
| 45 | + | ItemCategoryDoor = 13, | |
| 46 | + | ItemCategoryDeprecated = 14, | |
| 47 | + | ItemCategoryLast = 15 | |
| 48 | + | }; | |
| 49 | + | ||
| 50 | + | enum ItemTypeAttr : uint8 { | |
| 51 | + | ItemTypeAttrServerId = 16, | |
| 52 | + | ItemTypeAttrClientId = 17, | |
| 53 | + | ItemTypeAttrName = 18, // deprecated | |
| 54 | + | ItemTypeAttrDesc = 19, // deprecated | |
| 55 | + | ItemTypeAttrSpeed = 20, | |
| 56 | + | ItemTypeAttrSlot = 21, // deprecated | |
| 57 | + | ItemTypeAttrMaxItems = 22, // deprecated | |
| 58 | + | ItemTypeAttrWeight = 23, // deprecated | |
| 59 | + | ItemTypeAttrWeapon = 24, // deprecated | |
| 60 | + | ItemTypeAttrAmmunition = 25, // deprecated | |
| 61 | + | ItemTypeAttrArmor = 26, // deprecated | |
| 62 | + | ItemTypeAttrMagicLevel = 27, // deprecated | |
| 63 | + | ItemTypeAttrMagicField = 28, // deprecated | |
| 64 | + | ItemTypeAttrWritable = 29, // deprecated | |
| 65 | + | ItemTypeAttrRotateTo = 30, // deprecated | |
| 66 | + | ItemTypeAttrDecay = 31, // deprecated | |
| 67 | + | ItemTypeAttrSpriteHash = 32, | |
| 68 | + | ItemTypeAttrMinimapColor = 33, | |
| 69 | + | ItemTypeAttr07 = 34, | |
| 70 | + | ItemTypeAttr08 = 35, | |
| 71 | + | ItemTypeAttrLight = 36, | |
| 72 | + | ItemTypeAttrDecay2 = 37, // deprecated | |
| 73 | + | ItemTypeAttrWeapon2 = 38, // deprecated | |
| 74 | + | ItemTypeAttrAmmunition2 = 39, // deprecated | |
| 75 | + | ItemTypeAttrArmor2 = 40, // deprecated | |
| 76 | + | ItemTypeAttrWritable2 = 41, // deprecated | |
| 77 | + | ItemTypeAttrLight2 = 42, | |
| 78 | + | ItemTypeAttrTopOrder = 43, | |
| 79 | + | ItemTypeAttrWrtiable3 = 44, // deprecated | |
| 80 | + | ItemTypeAttrWareId = 45, | |
| 81 | + | ItemTypeAttrLast = 46 | |
| 82 | + | }; | |
| 83 | + | ||
| 84 | + | enum ClientVersion | |
| 85 | + | { | |
| 86 | + | ClientVersion750 = 1, | |
| 87 | + | ClientVersion755 = 2, | |
| 88 | + | ClientVersion760 = 3, | |
| 89 | + | ClientVersion770 = 3, | |
| 90 | + | ClientVersion780 = 4, | |
| 91 | + | ClientVersion790 = 5, | |
| 92 | + | ClientVersion792 = 6, | |
| 93 | + | ClientVersion800 = 7, | |
| 94 | + | ClientVersion810 = 8, | |
| 95 | + | ClientVersion811 = 9, | |
| 96 | + | ClientVersion820 = 10, | |
| 97 | + | ClientVersion830 = 11, | |
| 98 | + | ClientVersion840 = 12, | |
| 99 | + | ClientVersion841 = 13, | |
| 100 | + | ClientVersion842 = 14, | |
| 101 | + | ClientVersion850 = 15, | |
| 102 | + | ClientVersion854_OLD = 16, | |
| 103 | + | ClientVersion854 = 17, | |
| 104 | + | ClientVersion855 = 18, | |
| 105 | + | ClientVersion860_OLD = 19, | |
| 106 | + | ClientVersion860 = 20, | |
| 107 | + | ClientVersion861 = 21, | |
| 108 | + | ClientVersion862 = 22, | |
| 109 | + | ClientVersion870 = 23, | |
| 110 | + | ClientVersion871 = 24, | |
| 111 | + | ClientVersion872 = 25, | |
| 112 | + | ClientVersion873 = 26, | |
| 113 | + | ClientVersion900 = 27, | |
| 114 | + | ClientVersion910 = 28, | |
| 115 | + | ClientVersion920 = 29, | |
| 116 | + | ClientVersion940 = 30, | |
| 117 | + | ClientVersion944_V1 = 31, | |
| 118 | + | ClientVersion944_V2 = 32, | |
| 119 | + | ClientVersion944_V3 = 33, | |
| 120 | + | ClientVersion944_V4 = 34, | |
| 121 | + | ClientVersion946 = 35, | |
| 122 | + | ClientVersion950 = 36, | |
| 123 | + | ClientVersion952 = 37, | |
| 124 | + | ClientVersion953 = 38, | |
| 125 | + | ClientVersion954 = 39, | |
| 126 | + | ClientVersion960 = 40, | |
| 127 | + | ClientVersion961 = 41 | |
| 128 | + | }; | |
| 129 | + | ||
| 130 | + | class ItemType : public LuaObject | |
| 131 | + | { | |
| 132 | + | public: | |
| 133 | + | ItemType(); | |
| 134 | + | ||
| 135 | + | void unserialize(const BinaryTreePtr& node); | |
| 136 | + | ||
| 137 | + | void setServerId(uint16 serverId) { m_attribs.set(ItemTypeAttrServerId, serverId); } | |
| 138 | + | uint16 getServerId() { return m_attribs.get<uint16>(ItemTypeAttrServerId); } | |
| 139 | + | ||
| 140 | + | void setClientId(uint16 clientId) { m_attribs.set(ItemTypeAttrClientId, clientId); } | |
| 141 | + | uint16 getClientId() { return m_attribs.get<uint16>(ItemTypeAttrClientId); } | |
| 142 | + | ||
| 143 | + | void setCategory(ItemCategory category) { m_category = category; } | |
| 144 | + | ItemCategory getCategory() { return m_category; } | |
| 145 | + | ||
| 146 | + | void setName(const std::string& name) { m_attribs.set(ItemTypeAttrName, name); } | |
| 147 | + | std::string getName() { return m_attribs.get<std::string>(ItemTypeAttrName); } | |
| 148 | + | ||
| 149 | + | void setDesc(const std::string& desc) { m_attribs.set(ItemTypeAttrDesc, desc); } | |
| 150 | + | std::string getDesc() { return m_attribs.get<std::string>(ItemTypeAttrDesc); } | |
| 151 | + | ||
| 152 | + | bool isNull() { return m_null; } | |
| 153 | + | bool isWritable() { return m_attribs.get<bool>(ItemTypeAttrWritable); } | |
| 154 | + | ||
| 155 | + | private: | |
| 156 | + | ItemCategory m_category; | |
| 157 | + | stdext::boolean<true> m_null; | |
| 158 | + | ||
| 159 | + | stdext::dynamic_storage<uint8> m_attribs; | |
| 160 | + | }; | |
| 161 | + | ||
| 162 | + | #endif |
| @@ -0,0 +1,200 @@ | |||
| 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 "lightview.h" | |
| 24 | + | #include "spritemanager.h" | |
| 25 | + | #include <cmath> | |
| 26 | + | #include <framework/graphics/painter.h> | |
| 27 | + | ||
| 28 | + | // ───────────────────────────────────────────────────────────────────────────── | |
| 29 | + | // Miembros estáticos | |
| 30 | + | // ───────────────────────────────────────────────────────────────────────────── | |
| 31 | + | uint32_t LightView::s_frame = 0; | |
| 32 | + | ||
| 33 | + | // PRNG rápido basado en posición de tile | |
| 34 | + | static inline uint32_t hashPos(int tx, int ty) { | |
| 35 | + | uint32_t h = (uint32_t)(tx * 2654435761u ^ ty * 2246822519u); | |
| 36 | + | h ^= h >> 15; | |
| 37 | + | h *= 2246822519u; | |
| 38 | + | h ^= h >> 13; | |
| 39 | + | return h; | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | // ───────────────────────────────────────────────────────────────────────────── | |
| 43 | + | void LightView::addLight(const Point &pos, uint8_t color, uint8_t intensity) { | |
| 44 | + | if (!m_lights.empty()) { | |
| 45 | + | Light &prev = m_lights.back(); | |
| 46 | + | if (prev.pos == pos && prev.color == color) { | |
| 47 | + | prev.intensity = std::max(prev.intensity, intensity); | |
| 48 | + | return; | |
| 49 | + | } | |
| 50 | + | } | |
| 51 | + | m_lights.push_back(Light{pos, color, intensity}); | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | void LightView::setFieldBrightness(const Point &pos, size_t start, | |
| 55 | + | uint8_t color) { | |
| 56 | + | const int spriteSize = g_sprites.spriteSize(); | |
| 57 | + | size_t index = | |
| 58 | + | (pos.y / spriteSize) * m_mapSize.width() + (pos.x / spriteSize); | |
| 59 | + | if (index >= m_tiles.size()) | |
| 60 | + | return; | |
| 61 | + | m_tiles[index].start = start; | |
| 62 | + | m_tiles[index].color = color; | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // ───────────────────────────────────────────────────────────────────────────── | |
| 66 | + | // DRAW | |
| 67 | + | // | |
| 68 | + | // El flicker usa tiempo global (s_frame) + offset de fase por posición de tile. | |
| 69 | + | // Esto no requiere ningún estado persistente por luz, por lo que funciona | |
| 70 | + | // correctamente sin importar cuánto se mueva la cámara. | |
| 71 | + | // | |
| 72 | + | // Cada antorcha tiene su propia frecuencia y fase derivadas de hashPos. | |
| 73 | + | // Al caminar, la misma antorcha mundial cambia de posición de pantalla, | |
| 74 | + | // pero como el hash varía SUAVEMENTE entre tiles vecinos, el flicker | |
| 75 | + | // simplemente tiene una fase ligeramente diferente (no "reset" visible). | |
| 76 | + | // ───────────────────────────────────────────────────────────────────────────── | |
| 77 | + | void LightView::draw() { | |
| 78 | + | static std::vector<uint8_t> buffer; | |
| 79 | + | ||
| 80 | + | const int mapW = m_mapSize.width(); | |
| 81 | + | const int mapH = m_mapSize.height(); | |
| 82 | + | const size_t needed = 4u * (size_t)mapW * (size_t)mapH; | |
| 83 | + | ||
| 84 | + | if (buffer.size() != needed) | |
| 85 | + | buffer.resize(needed, 0); | |
| 86 | + | ||
| 87 | + | const int spriteSize = g_sprites.spriteSize(); | |
| 88 | + | s_frame++; | |
| 89 | + | ||
| 90 | + | // Tiempo global en segundos (fluye continuamente, sin reset) | |
| 91 | + | const float t = (float)s_frame / 60.f; | |
| 92 | + | ||
| 93 | + | // Límite de luces activas | |
| 94 | + | const size_t MAX_LIGHTS = 512; | |
| 95 | + | size_t lightStart = | |
| 96 | + | m_lights.size() > MAX_LIGHTS ? m_lights.size() - MAX_LIGHTS : 0; | |
| 97 | + | ||
| 98 | + | // Pre-calcular flicker de cada luz (una sola vez por frame por luz) | |
| 99 | + | // NO se almacena en caché — se calcula directo del tiempo + hash. | |
| 100 | + | struct LightCalc { | |
| 101 | + | float flicker; | |
| 102 | + | }; | |
| 103 | + | std::vector<LightCalc> calcs(m_lights.size()); | |
| 104 | + | ||
| 105 | + | for (size_t i = lightStart; i < m_lights.size(); ++i) { | |
| 106 | + | // Convertir posición de PANTALLA a posición MUNDIAL (tile coords) | |
| 107 | + | // m_worldOffsetX = cameraX - virtualCenterOffset.x | |
| 108 | + | // screenTileX = light.pos.x / spriteSize (0..mapW) | |
| 109 | + | // worldTileX = cameraX + (screenTileX - virtualCenterOffset.x) = | |
| 110 | + | // m_worldOffsetX + screenTileX | |
| 111 | + | int worldTX = m_worldOffsetX + (m_lights[i].pos.x / spriteSize); | |
| 112 | + | int worldTY = m_worldOffsetY + (m_lights[i].pos.y / spriteSize); | |
| 113 | + | uint32_t h = hashPos(worldTX, worldTY); | |
| 114 | + | ||
| 115 | + | float phOffset = (float)(h & 0xFFFF) / 65535.f * 6.2832f; | |
| 116 | + | float speed = 1.5f + (float)((h >> 16) & 0xFF) / 255.f * 2.5f; | |
| 117 | + | float amp = 0.06f + (float)((h >> 8) & 0xFF) / 255.f * 0.12f; | |
| 118 | + | ||
| 119 | + | float phase = t * speed + phOffset; | |
| 120 | + | float wave = std::sin(phase) + 0.35f * std::sin(phase * 2.3f + 0.8f); | |
| 121 | + | float fk = 1.0f + amp * wave; | |
| 122 | + | if (fk < 0.75f) | |
| 123 | + | fk = 0.75f; | |
| 124 | + | if (fk > 1.25f) | |
| 125 | + | fk = 1.25f; | |
| 126 | + | ||
| 127 | + | calcs[i].flicker = fk; | |
| 128 | + | } | |
| 129 | + | ||
| 130 | + | // Loop principal: calcular contribución de luz por pixel | |
| 131 | + | for (int y = 0; y < mapH; ++y) { | |
| 132 | + | for (int x = 0; x < mapW; ++x) { | |
| 133 | + | const int px = x * spriteSize + spriteSize / 2; | |
| 134 | + | const int py = y * spriteSize + spriteSize / 2; | |
| 135 | + | ||
| 136 | + | const int tileIndex = y * mapW + x; | |
| 137 | + | const int colorIndex = tileIndex * 4; | |
| 138 | + | ||
| 139 | + | float r = (float)m_globalLight.r(); | |
| 140 | + | float g = (float)m_globalLight.g(); | |
| 141 | + | float b = (float)m_globalLight.b(); | |
| 142 | + | ||
| 143 | + | size_t loopStart = std::max(m_tiles[tileIndex].start, lightStart); | |
| 144 | + | for (size_t i = loopStart; i < m_lights.size(); ++i) { | |
| 145 | + | Light &light = m_lights[i]; | |
| 146 | + | ||
| 147 | + | const float maxR = (float)light.intensity * spriteSize; | |
| 148 | + | const float dx = (float)(px - light.pos.x); | |
| 149 | + | const float dy = (float)(py - light.pos.y); | |
| 150 | + | const float dist2 = dx * dx + dy * dy; | |
| 151 | + | if (dist2 > maxR * maxR) | |
| 152 | + | continue; | |
| 153 | + | ||
| 154 | + | const float dist = std::sqrt(dist2) / (float)spriteSize; | |
| 155 | + | float intensity = (-dist + (float)light.intensity) * 0.2f; | |
| 156 | + | if (intensity < 0.01f) | |
| 157 | + | continue; | |
| 158 | + | if (intensity > 1.0f) | |
| 159 | + | intensity = 1.0f; | |
| 160 | + | ||
| 161 | + | // FIX: clamp intensity*flicker a ≤1.0 para evitar overflow uint8 | |
| 162 | + | // (importante con admins/GM que tienen luz máxima) | |
| 163 | + | float finalI = intensity * calcs[i].flicker; | |
| 164 | + | if (finalI > 1.0f) | |
| 165 | + | finalI = 1.0f; | |
| 166 | + | ||
| 167 | + | Color lc = Color::from8bit(light.color) * finalI; | |
| 168 | + | r = std::max(r, (float)lc.r()); | |
| 169 | + | g = std::max(g, (float)lc.g()); | |
| 170 | + | b = std::max(b, (float)lc.b()); | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | buffer[colorIndex] = (uint8_t)std::min(r, 255.f); | |
| 174 | + | buffer[colorIndex + 1] = (uint8_t)std::min(g, 255.f); | |
| 175 | + | buffer[colorIndex + 2] = (uint8_t)std::min(b, 255.f); | |
| 176 | + | buffer[colorIndex + 3] = 255; | |
| 177 | + | } | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | // Upload a GPU | |
| 181 | + | m_lightTexture->update(); | |
| 182 | + | glBindTexture(GL_TEXTURE_2D, m_lightTexture->getId()); | |
| 183 | + | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mapW, mapH, 0, GL_RGBA, | |
| 184 | + | GL_UNSIGNED_BYTE, buffer.data()); | |
| 185 | + | ||
| 186 | + | // Render | |
| 187 | + | Point offset = m_src.topLeft(); | |
| 188 | + | Size size = m_src.size(); | |
| 189 | + | CoordsBuffer coords; | |
| 190 | + | coords.addRect( | |
| 191 | + | RectF(m_dest.left(), m_dest.top(), m_dest.width(), m_dest.height()), | |
| 192 | + | RectF((float)offset.x / spriteSize, (float)offset.y / spriteSize, | |
| 193 | + | (float)size.width() / spriteSize, | |
| 194 | + | (float)size.height() / spriteSize)); | |
| 195 | + | ||
| 196 | + | g_painter->resetColor(); | |
| 197 | + | g_painter->setCompositionMode(Painter::CompositionMode_Multiply); | |
| 198 | + | g_painter->drawTextureCoords(coords, m_lightTexture); | |
| 199 | + | g_painter->resetCompositionMode(); | |
| 200 | + | } |
| @@ -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 LIGHTVIEW_H | |
| 24 | + | #define LIGHTVIEW_H | |
| 25 | + | ||
| 26 | + | #include "declarations.h" | |
| 27 | + | #include "thingtype.h" | |
| 28 | + | #include <cmath> | |
| 29 | + | #include <framework/graphics/declarations.h> | |
| 30 | + | #include <framework/graphics/drawqueue.h> | |
| 31 | + | #include <set> | |
| 32 | + | #include <unordered_map> | |
| 33 | + | ||
| 34 | + | struct TileLight { | |
| 35 | + | size_t start; | |
| 36 | + | uint8_t color; | |
| 37 | + | }; | |
| 38 | + | ||
| 39 | + | // Flicker state por cada fuente de luz (persistente entre frames) | |
| 40 | + | struct LightFlicker { | |
| 41 | + | float phase; // fase actual de la onda seno (0..2π) | |
| 42 | + | float speed; // velocidad de oscilación (varía por antorcha) | |
| 43 | + | float amplitude; // qué tanto varía la intensidad | |
| 44 | + | }; | |
| 45 | + | ||
| 46 | + | class LightView : public DrawQueueItem { | |
| 47 | + | public: | |
| 48 | + | LightView(TexturePtr &lightTexture, const Size &mapSize, const Rect &dest, | |
| 49 | + | const Rect &src, uint8_t color, uint8_t intensity, | |
| 50 | + | int worldOffsetX = 0, int worldOffsetY = 0) | |
| 51 | + | : DrawQueueItem(nullptr), m_lightTexture(lightTexture), | |
| 52 | + | m_mapSize(mapSize), m_dest(dest), m_src(src), | |
| 53 | + | m_worldOffsetX(worldOffsetX), m_worldOffsetY(worldOffsetY) { | |
| 54 | + | m_globalLight = Color::from8bit(color) * ((float)intensity / 255.f); | |
| 55 | + | m_tiles.resize(m_mapSize.area(), TileLight{0, 0}); | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | inline void addLight(const Point &pos, const Light &light) { | |
| 59 | + | return addLight(pos, light.color, light.intensity); | |
| 60 | + | } | |
| 61 | + | void addLight(const Point &pos, uint8_t color, uint8_t intensity); | |
| 62 | + | void setFieldBrightness(const Point &pos, size_t start, uint8_t color); | |
| 63 | + | size_t size() { return m_lights.size(); } | |
| 64 | + | ||
| 65 | + | void draw() override; | |
| 66 | + | ||
| 67 | + | private: | |
| 68 | + | TexturePtr m_lightTexture; | |
| 69 | + | Size m_mapSize; | |
| 70 | + | Rect m_dest, m_src; | |
| 71 | + | Color m_globalLight; | |
| 72 | + | std::vector<Light> m_lights; | |
| 73 | + | std::vector<TileLight> m_tiles; | |
| 74 | + | int m_worldOffsetX = 0; // cameraPos.x - virtualCenterOffset.x (tile coords) | |
| 75 | + | int m_worldOffsetY = 0; // cameraPos.y - virtualCenterOffset.y (tile coords) | |
| 76 | + | ||
| 77 | + | static uint32_t s_frame; | |
| 78 | + | }; | |
| 79 | + | ||
| 80 | + | #endif |
| @@ -0,0 +1,669 @@ | |||
| 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 "localplayer.h" | |
| 24 | + | #include "map.h" | |
| 25 | + | #include "game.h" | |
| 26 | + | #include "tile.h" | |
| 27 | + | #include <framework/core/eventdispatcher.h> | |
| 28 | + | #include <framework/graphics/graphics.h> | |
| 29 | + | #include <framework/util/extras.h> | |
| 30 | + | ||
| 31 | + | LocalPlayer::LocalPlayer() | |
| 32 | + | { | |
| 33 | + | m_states = 0; | |
| 34 | + | m_vocation = 0; | |
| 35 | + | m_blessings = Otc::BlessingNone; | |
| 36 | + | m_walkLockExpiration = 0; | |
| 37 | + | ||
| 38 | + | m_skillsLevel.resize(Otc::LastSkill + 1, 0); | |
| 39 | + | m_skillsBaseLevel.resize(Otc::LastSkill + 1, 0); | |
| 40 | + | m_skillsLevelPercent.resize(Otc::LastSkill + 1, 0); | |
| 41 | + | ||
| 42 | + | m_health = -1; | |
| 43 | + | m_maxHealth = -1; | |
| 44 | + | m_freeCapacity = -1; | |
| 45 | + | m_experience = -1; | |
| 46 | + | m_level = -1; | |
| 47 | + | m_levelPercent = -1; | |
| 48 | + | m_mana = -1; | |
| 49 | + | m_maxMana = -1; | |
| 50 | + | m_magicLevel = -1; | |
| 51 | + | m_magicLevelPercent = -1; | |
| 52 | + | m_baseMagicLevel = -1; | |
| 53 | + | m_soul = -1; | |
| 54 | + | m_stamina = -1; | |
| 55 | + | m_baseSpeed = -1; | |
| 56 | + | m_regenerationTime = -1; | |
| 57 | + | m_offlineTrainingTime = -1; | |
| 58 | + | m_totalCapacity = -1; | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | void LocalPlayer::draw(const Point& dest, bool animate, LightView* lightView) | |
| 62 | + | { | |
| 63 | + | Creature::draw(dest, animate, lightView); | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | ||
| 67 | + | void LocalPlayer::lockWalk(int millis) | |
| 68 | + | { | |
| 69 | + | m_walkLockExpiration = std::max<int>(m_walkLockExpiration, (ticks_t) g_clock.millis() + millis); | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | bool LocalPlayer::canWalk(Otc::Direction direction, bool ignoreLock) | |
| 73 | + | { | |
| 74 | + | // cannot walk while locked | |
| 75 | + | if ((m_walkLockExpiration != 0 && g_clock.millis() < m_walkLockExpiration) && !ignoreLock) | |
| 76 | + | return false; | |
| 77 | + | ||
| 78 | + | // paralyzed | |
| 79 | + | if (m_speed == 0) | |
| 80 | + | return false; | |
| 81 | + | ||
| 82 | + | // last walk is not done yet | |
| 83 | + | if (m_walking && (m_walkTimer.ticksElapsed() < getStepDuration()) && !isAutoWalking() && !isServerWalking()) | |
| 84 | + | return false; | |
| 85 | + | ||
| 86 | + | auto tile = g_map.getTile(getPrewalkingPosition(true)); | |
| 87 | + | if (isPreWalking() && (!m_lastPrewalkDone || (tile && tile->isBlocking()))) | |
| 88 | + | return false; | |
| 89 | + | ||
| 90 | + | // cannot walk while already walking | |
| 91 | + | if ((m_walking && !isAutoWalking() && !isServerWalking()) && (!isPreWalking() || !m_lastPrewalkDone)) | |
| 92 | + | return false; | |
| 93 | + | ||
| 94 | + | // Without new walking limit only to 1 prewalk | |
| 95 | + | if (!m_preWalking.empty() && !g_game.getFeature(Otc::GameNewWalking)) | |
| 96 | + | return false; | |
| 97 | + | ||
| 98 | + | // Limit pre walking steps | |
| 99 | + | if (m_preWalking.size() >= g_game.getMaxPreWalkingSteps()) { // max 3 extra steps | |
| 100 | + | if (m_walkTimer.ticksElapsed() >= getStepDuration() + 300) | |
| 101 | + | return true; | |
| 102 | + | return false; | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | if (!m_preWalking.empty()) { // disallow diagonal extented prewalking walking | |
| 106 | + | auto dir = m_position.getDirectionFromPosition(m_preWalking.back()); | |
| 107 | + | if ((dir == Otc::NorthWest || dir == Otc::NorthEast || dir == Otc::SouthWest || dir == Otc::SouthEast)) { | |
| 108 | + | return false; | |
| 109 | + | } | |
| 110 | + | if (!g_map.getTile(getPrewalkingPosition())->isWalkable()) | |
| 111 | + | return false; | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | return true; | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | void LocalPlayer::walk(const Position& oldPos, const Position& newPos) | |
| 118 | + | { | |
| 119 | + | if (g_extras.debugWalking) { | |
| 120 | + | g_logger.info(stdext::format("[%i] LocalPlayer::walk", (int)g_clock.millis())); | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | m_lastAutoWalkRetries = 0; | |
| 124 | + | // a prewalk was going on | |
| 125 | + | if (isPreWalking()) { | |
| 126 | + | for (auto it = m_preWalking.begin(); it != m_preWalking.end(); ++it) { | |
| 127 | + | if (*it == newPos) { | |
| 128 | + | m_preWalking.erase(m_preWalking.begin(), ++it); | |
| 129 | + | if (!isPreWalking()) // reset pre walking | |
| 130 | + | updateWalk(); | |
| 131 | + | return; | |
| 132 | + | } | |
| 133 | + | } | |
| 134 | + | if (g_extras.debugWalking) { | |
| 135 | + | g_logger.info(stdext::format("[%i] LocalPlayer::walk invalid prewalk", (int)g_clock.millis())); | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | // invalid pre walk | |
| 139 | + | m_preWalking.clear(); | |
| 140 | + | m_serverWalking = true; | |
| 141 | + | if (m_serverWalkEndEvent) | |
| 142 | + | m_serverWalkEndEvent->cancel(); | |
| 143 | + | ||
| 144 | + | Creature::walk(oldPos, newPos); | |
| 145 | + | } else { // no prewalk was going on, this must be an server side automated walk | |
| 146 | + | if (g_extras.debugWalking) { | |
| 147 | + | g_logger.info(stdext::format("[%i] LocalPlayer::walk server walk", (int)g_clock.millis())); | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | m_serverWalking = true; | |
| 151 | + | if (m_serverWalkEndEvent) | |
| 152 | + | m_serverWalkEndEvent->cancel(); | |
| 153 | + | m_lastAutoWalkRetries = 0; | |
| 154 | + | ||
| 155 | + | Creature::walk(oldPos, newPos); | |
| 156 | + | } | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | void LocalPlayer::preWalk(Otc::Direction direction) | |
| 160 | + | { | |
| 161 | + | Position startPos = m_position; | |
| 162 | + | if (!m_preWalking.empty()) | |
| 163 | + | startPos = m_preWalking.back(); | |
| 164 | + | Position newPos = startPos.translatedToDirection(direction); | |
| 165 | + | ||
| 166 | + | if (m_serverWalkEndEvent) | |
| 167 | + | m_serverWalkEndEvent->cancel(); | |
| 168 | + | ||
| 169 | + | m_lastPrewalkDone = false; | |
| 170 | + | m_preWalking.push_back(newPos); | |
| 171 | + | if (m_preWalking.size() > 1) | |
| 172 | + | g_map.requestVisibleTilesCacheUpdate(); | |
| 173 | + | ||
| 174 | + | Creature::walk(startPos, newPos); | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | void LocalPlayer::cancelNewWalk(Otc::Direction dir) | |
| 178 | + | { | |
| 179 | + | if (g_extras.debugWalking) { | |
| 180 | + | g_logger.info(stdext::format("[%i] cancelWalk", (int)g_clock.millis())); | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | bool clearedPrewalk = !m_preWalking.empty(); | |
| 184 | + | ||
| 185 | + | m_preWalking.clear(); | |
| 186 | + | g_map.requestVisibleTilesCacheUpdate(); | |
| 187 | + | ||
| 188 | + | if (clearedPrewalk) { | |
| 189 | + | stopWalk(); | |
| 190 | + | } | |
| 191 | + | ||
| 192 | + | m_idleTimer.restart(); | |
| 193 | + | ||
| 194 | + | if (retryAutoWalk()) return; | |
| 195 | + | ||
| 196 | + | if (!g_game.isIgnoringServerDirection() || !g_game.getFeature(Otc::GameNewWalking)) { | |
| 197 | + | setDirection(dir); | |
| 198 | + | } | |
| 199 | + | callLuaField("onCancelWalk", dir); | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | bool LocalPlayer::predictiveCancelWalk(const Position& pos, uint32_t predictionId, Otc::Direction dir) | |
| 203 | + | { | |
| 204 | + | if (g_extras.debugPredictiveWalking) { | |
| 205 | + | g_logger.info(stdext::format("[%i] predictiveCancelWalk: %i - %i", (int)g_clock.millis(), predictionId, (int)m_preWalking.size())); | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | m_walkMatrix.update(pos, predictionId); // for debugging, not used | |
| 209 | + | ||
| 210 | + | for (auto it = m_preWalking.begin(); it != m_preWalking.end(); ++it) { | |
| 211 | + | if (*it != pos) { | |
| 212 | + | continue; | |
| 213 | + | } | |
| 214 | + | if (g_extras.debugPredictiveWalking) { | |
| 215 | + | g_logger.info(stdext::format("[%i] predictiveCancelWalk: canceling walk", (int)g_clock.millis())); | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | if (it == m_preWalking.begin()) { | |
| 219 | + | cancelNewWalk(dir); | |
| 220 | + | return true; | |
| 221 | + | } | |
| 222 | + | while (it != m_preWalking.end()) { | |
| 223 | + | it = m_preWalking.erase(it); | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | m_walkTimer.restart(); | |
| 227 | + | m_walkTimer.adjust(-(getStepDuration(true) + 50)); | |
| 228 | + | updateWalk(); | |
| 229 | + | g_map.requestVisibleTilesCacheUpdate(); | |
| 230 | + | ||
| 231 | + | return true; | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | return false; | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | bool LocalPlayer::retryAutoWalk() | |
| 238 | + | { | |
| 239 | + | if (m_autoWalkDestination.isValid()) { | |
| 240 | + | g_game.stop(); | |
| 241 | + | auto self = asLocalPlayer(); | |
| 242 | + | ||
| 243 | + | if (m_lastAutoWalkRetries <= 3) { | |
| 244 | + | if (m_autoWalkContinueEvent) | |
| 245 | + | m_autoWalkContinueEvent->cancel(); | |
| 246 | + | m_autoWalkContinueEvent = g_dispatcher.scheduleEvent(std::bind(&LocalPlayer::autoWalk, asLocalPlayer(), m_autoWalkDestination, true), 200); | |
| 247 | + | self->m_lastAutoWalkRetries += 1; | |
| 248 | + | return true; | |
| 249 | + | } else { | |
| 250 | + | self->m_autoWalkDestination = Position(); | |
| 251 | + | } | |
| 252 | + | } | |
| 253 | + | return false; | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | ||
| 257 | + | bool LocalPlayer::autoWalk(Position destination, bool retry) | |
| 258 | + | { | |
| 259 | + | // reset state | |
| 260 | + | m_autoWalkDestination = Position(); | |
| 261 | + | m_lastAutoWalkPosition = Position(); | |
| 262 | + | if (m_autoWalkContinueEvent) | |
| 263 | + | m_autoWalkContinueEvent->cancel(); | |
| 264 | + | m_autoWalkContinueEvent = nullptr; | |
| 265 | + | ||
| 266 | + | if (!retry) | |
| 267 | + | m_lastAutoWalkRetries = 0; | |
| 268 | + | ||
| 269 | + | if (destination == getPrewalkingPosition()) | |
| 270 | + | return true; | |
| 271 | + | ||
| 272 | + | m_autoWalkDestination = destination; | |
| 273 | + | auto self(asLocalPlayer()); | |
| 274 | + | g_map.findPathAsync(getPrewalkingPosition(), destination, [self](PathFindResult_ptr result) { | |
| 275 | + | if (self->m_autoWalkDestination != result->destination) | |
| 276 | + | return; | |
| 277 | + | if (g_extras.debugWalking) { | |
| 278 | + | g_logger.info(stdext::format("Async path search finished with complexity %i/50000", result->complexity)); | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | if (result->status != Otc::PathFindResultOk) { | |
| 282 | + | if (self->m_lastAutoWalkRetries > 0 && self->m_lastAutoWalkRetries <= 3) { // try again in 300, 700, 1200 ms if canceled by server | |
| 283 | + | self->m_autoWalkContinueEvent = g_dispatcher.scheduleEvent(std::bind(&LocalPlayer::autoWalk, self, result->destination, true), 200 + self->m_lastAutoWalkRetries * 100); | |
| 284 | + | return; | |
| 285 | + | } | |
| 286 | + | self->m_autoWalkDestination = Position(); | |
| 287 | + | self->callLuaField("onAutoWalkFail", result->status); | |
| 288 | + | return; | |
| 289 | + | } | |
| 290 | + | ||
| 291 | + | if (!g_game.getFeature(Otc::GameNewWalking) && result->path.size() > 127) | |
| 292 | + | result->path.resize(127); | |
| 293 | + | else if (result->path.size() > 4095) | |
| 294 | + | result->path.resize(4095); | |
| 295 | + | ||
| 296 | + | if (result->path.empty()) { | |
| 297 | + | self->m_autoWalkDestination = Position(); | |
| 298 | + | self->callLuaField("onAutoWalkFail", result->status); | |
| 299 | + | return; | |
| 300 | + | } | |
| 301 | + | ||
| 302 | + | auto finalAutowalkPos = self->getPrewalkingPosition().translatedToDirections(result->path).back(); | |
| 303 | + | if (self->m_autoWalkDestination != finalAutowalkPos) { | |
| 304 | + | self->m_lastAutoWalkPosition = finalAutowalkPos; | |
| 305 | + | } | |
| 306 | + | ||
| 307 | + | g_game.autoWalk(result->path, result->start); | |
| 308 | + | }); | |
| 309 | + | ||
| 310 | + | if (!retry) | |
| 311 | + | lockWalk(); | |
| 312 | + | return true; | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | void LocalPlayer::cancelWalk(Otc::Direction direction) | |
| 316 | + | { | |
| 317 | + | if (g_game.getFeature(Otc::GameNewWalking)) { | |
| 318 | + | return; | |
| 319 | + | } | |
| 320 | + | ||
| 321 | + | return cancelNewWalk(direction); | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | void LocalPlayer::stopAutoWalk() | |
| 325 | + | { | |
| 326 | + | m_autoWalkDestination = Position(); | |
| 327 | + | m_lastAutoWalkPosition = Position(); | |
| 328 | + | ||
| 329 | + | if (m_autoWalkContinueEvent) { | |
| 330 | + | m_autoWalkContinueEvent->cancel(); | |
| 331 | + | m_autoWalkContinueEvent = nullptr; | |
| 332 | + | } | |
| 333 | + | } | |
| 334 | + | ||
| 335 | + | void LocalPlayer::stopWalk() { | |
| 336 | + | if (g_extras.debugWalking) { | |
| 337 | + | g_logger.info(stdext::format("[%i] stopWalk", (int)g_clock.millis())); | |
| 338 | + | } | |
| 339 | + | ||
| 340 | + | Creature::stopWalk(); // will call terminateWalk | |
| 341 | + | ||
| 342 | + | m_preWalking.clear(); | |
| 343 | + | } | |
| 344 | + | ||
| 345 | + | void LocalPlayer::updateWalkOffset(uint8 totalPixelsWalked, bool inNextFrame) | |
| 346 | + | { | |
| 347 | + | // pre walks offsets are calculated in the oposite direction | |
| 348 | + | if(isPreWalking()) { | |
| 349 | + | Point& walkOffset = inNextFrame ? m_walkOffsetInNextFrame : m_walkOffset; | |
| 350 | + | walkOffset = Point(0,0); | |
| 351 | + | if(m_walkDirection == Otc::North || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::NorthWest) | |
| 352 | + | walkOffset.y = -totalPixelsWalked; | |
| 353 | + | else if(m_walkDirection == Otc::South || m_walkDirection == Otc::SouthEast || m_walkDirection == Otc::SouthWest) | |
| 354 | + | walkOffset.y = totalPixelsWalked; | |
| 355 | + | ||
| 356 | + | if(m_walkDirection == Otc::East || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::SouthEast) | |
| 357 | + | walkOffset.x = totalPixelsWalked; | |
| 358 | + | else if(m_walkDirection == Otc::West || m_walkDirection == Otc::NorthWest || m_walkDirection == Otc::SouthWest) | |
| 359 | + | walkOffset.x = -totalPixelsWalked; | |
| 360 | + | } else | |
| 361 | + | Creature::updateWalkOffset(totalPixelsWalked, inNextFrame); | |
| 362 | + | } | |
| 363 | + | ||
| 364 | + | void LocalPlayer::updateWalk() | |
| 365 | + | { | |
| 366 | + | if (!m_walking) | |
| 367 | + | return; | |
| 368 | + | ||
| 369 | + | Creature::updateWalk(); | |
| 370 | + | ||
| 371 | + | // terminate walk only when client and server side walk are completed | |
| 372 | + | if (m_walking && m_walkTimer.ticksElapsed() >= getStepDuration()) { | |
| 373 | + | m_lastPrewalkDone = true; | |
| 374 | + | if (!isPreWalking()) { | |
| 375 | + | terminateWalk(); | |
| 376 | + | } | |
| 377 | + | } | |
| 378 | + | } | |
| 379 | + | ||
| 380 | + | void LocalPlayer::terminateWalk() | |
| 381 | + | { | |
| 382 | + | if (g_extras.debugWalking) { | |
| 383 | + | g_logger.info(stdext::format("[%i] terminateWalk", (int)g_clock.millis())); | |
| 384 | + | } | |
| 385 | + | ||
| 386 | + | Creature::terminateWalk(); | |
| 387 | + | m_idleTimer.restart(); | |
| 388 | + | m_preWalking.clear(); | |
| 389 | + | m_walking = false; | |
| 390 | + | ||
| 391 | + | if(m_serverWalking) { | |
| 392 | + | if(m_serverWalkEndEvent) | |
| 393 | + | m_serverWalkEndEvent->cancel(); | |
| 394 | + | auto self = asLocalPlayer(); | |
| 395 | + | m_serverWalkEndEvent = g_dispatcher.scheduleEvent([self] { | |
| 396 | + | self->m_serverWalking = false; | |
| 397 | + | }, 100); | |
| 398 | + | } | |
| 399 | + | ||
| 400 | + | callLuaField("onWalkFinish"); | |
| 401 | + | } | |
| 402 | + | ||
| 403 | + | void LocalPlayer::onAppear() | |
| 404 | + | { | |
| 405 | + | Creature::onAppear(); | |
| 406 | + | ||
| 407 | + | /* Does not seem to be needed anymore | |
| 408 | + | // on teleports lock the walk | |
| 409 | + | if(!m_oldPosition.isInRange(m_position,1,1)) | |
| 410 | + | lockWalk(); | |
| 411 | + | */ | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | void LocalPlayer::onPositionChange(const Position& newPos, const Position& oldPos) | |
| 415 | + | { | |
| 416 | + | Creature::onPositionChange(newPos, oldPos); | |
| 417 | + | ||
| 418 | + | if(newPos == m_autoWalkDestination) | |
| 419 | + | stopAutoWalk(); | |
| 420 | + | else if(m_autoWalkDestination.isValid() && newPos == m_lastAutoWalkPosition) | |
| 421 | + | autoWalk(m_autoWalkDestination); | |
| 422 | + | ||
| 423 | + | m_walkMatrix.updatePosition(newPos); | |
| 424 | + | } | |
| 425 | + | ||
| 426 | + | void LocalPlayer::turn(Otc::Direction direction) | |
| 427 | + | { | |
| 428 | + | Creature::setDirection(direction); | |
| 429 | + | callLuaField("onTurn", direction); | |
| 430 | + | } | |
| 431 | + | ||
| 432 | + | void LocalPlayer::setStates(int states) | |
| 433 | + | { | |
| 434 | + | if(m_states != states) { | |
| 435 | + | int oldStates = m_states; | |
| 436 | + | m_states = states; | |
| 437 | + | ||
| 438 | + | callLuaField("onStatesChange", states, oldStates); | |
| 439 | + | } | |
| 440 | + | } | |
| 441 | + | ||
| 442 | + | void LocalPlayer::setSkill(uint8_t skill, int level, int levelPercent) | |
| 443 | + | { | |
| 444 | + | if(skill >= m_skillsLevel.size()) { | |
| 445 | + | m_skillsLevel.resize(skill + 1, 0); | |
| 446 | + | m_skillsBaseLevel.resize(skill + 1, 0); | |
| 447 | + | m_skillsLevelPercent.resize(skill + 1, 0); | |
| 448 | + | } | |
| 449 | + | ||
| 450 | + | int oldLevel = m_skillsLevel[skill]; | |
| 451 | + | int oldLevelPercent = m_skillsLevelPercent[skill]; | |
| 452 | + | ||
| 453 | + | if(level != oldLevel || levelPercent != oldLevelPercent) { | |
| 454 | + | m_skillsLevel[skill] = level; | |
| 455 | + | m_skillsLevelPercent[skill] = levelPercent; | |
| 456 | + | ||
| 457 | + | callLuaField("onSkillChange", skill, level, levelPercent, oldLevel, oldLevelPercent); | |
| 458 | + | } | |
| 459 | + | } | |
| 460 | + | ||
| 461 | + | void LocalPlayer::setBaseSkill(uint8_t skill, int baseLevel) | |
| 462 | + | { | |
| 463 | + | if (skill >= m_skillsLevel.size()) { | |
| 464 | + | m_skillsLevel.resize(skill + 1, 0); | |
| 465 | + | m_skillsBaseLevel.resize(skill + 1, 0); | |
| 466 | + | m_skillsLevelPercent.resize(skill + 1, 0); | |
| 467 | + | } | |
| 468 | + | ||
| 469 | + | int oldBaseLevel = m_skillsBaseLevel[skill]; | |
| 470 | + | if(baseLevel != oldBaseLevel) { | |
| 471 | + | m_skillsBaseLevel[skill] = baseLevel; | |
| 472 | + | ||
| 473 | + | callLuaField("onBaseSkillChange", skill, baseLevel, oldBaseLevel); | |
| 474 | + | } | |
| 475 | + | } | |
| 476 | + | ||
| 477 | + | void LocalPlayer::setHealth(double health, double maxHealth) | |
| 478 | + | { | |
| 479 | + | if(m_health != health || m_maxHealth != maxHealth) { | |
| 480 | + | double oldHealth = m_health; | |
| 481 | + | double oldMaxHealth = m_maxHealth; | |
| 482 | + | m_health = health; | |
| 483 | + | m_maxHealth = maxHealth; | |
| 484 | + | ||
| 485 | + | callLuaField("onHealthChange", health, maxHealth, oldHealth, oldMaxHealth); | |
| 486 | + | ||
| 487 | + | // cannot walk while dying | |
| 488 | + | if(health == 0) { | |
| 489 | + | if(isPreWalking()) | |
| 490 | + | stopWalk(); | |
| 491 | + | lockWalk(); | |
| 492 | + | } | |
| 493 | + | } | |
| 494 | + | } | |
| 495 | + | ||
| 496 | + | void LocalPlayer::setFreeCapacity(double freeCapacity) | |
| 497 | + | { | |
| 498 | + | if(m_freeCapacity != freeCapacity) { | |
| 499 | + | double oldFreeCapacity = m_freeCapacity; | |
| 500 | + | m_freeCapacity = freeCapacity; | |
| 501 | + | ||
| 502 | + | callLuaField("onFreeCapacityChange", freeCapacity, oldFreeCapacity); | |
| 503 | + | } | |
| 504 | + | } | |
| 505 | + | ||
| 506 | + | void LocalPlayer::setTotalCapacity(double totalCapacity) | |
| 507 | + | { | |
| 508 | + | if(m_totalCapacity != totalCapacity) { | |
| 509 | + | double oldTotalCapacity = m_totalCapacity; | |
| 510 | + | m_totalCapacity = totalCapacity; | |
| 511 | + | ||
| 512 | + | callLuaField("onTotalCapacityChange", totalCapacity, oldTotalCapacity); | |
| 513 | + | } | |
| 514 | + | } | |
| 515 | + | ||
| 516 | + | void LocalPlayer::setExperience(double experience) | |
| 517 | + | { | |
| 518 | + | if(m_experience != experience) { | |
| 519 | + | double oldExperience = m_experience; | |
| 520 | + | m_experience = experience; | |
| 521 | + | ||
| 522 | + | callLuaField("onExperienceChange", experience, oldExperience); | |
| 523 | + | } | |
| 524 | + | } | |
| 525 | + | ||
| 526 | + | void LocalPlayer::setLevel(double level, double levelPercent) | |
| 527 | + | { | |
| 528 | + | if(m_level != level || m_levelPercent != levelPercent) { | |
| 529 | + | double oldLevel = m_level; | |
| 530 | + | double oldLevelPercent = m_levelPercent; | |
| 531 | + | m_level = level; | |
| 532 | + | m_levelPercent = levelPercent; | |
| 533 | + | ||
| 534 | + | callLuaField("onLevelChange", level, levelPercent, oldLevel, oldLevelPercent); | |
| 535 | + | } | |
| 536 | + | } | |
| 537 | + | ||
| 538 | + | void LocalPlayer::setMana(double mana, double maxMana) | |
| 539 | + | { | |
| 540 | + | if(m_mana != mana || m_maxMana != maxMana) { | |
| 541 | + | double oldMana = m_mana; | |
| 542 | + | double oldMaxMana = m_maxMana; | |
| 543 | + | m_mana = mana; | |
| 544 | + | m_maxMana = maxMana; | |
| 545 | + | ||
| 546 | + | callLuaField("onManaChange", mana, maxMana, oldMana, oldMaxMana); | |
| 547 | + | } | |
| 548 | + | } | |
| 549 | + | ||
| 550 | + | void LocalPlayer::setMagicLevel(double magicLevel, double magicLevelPercent) | |
| 551 | + | { | |
| 552 | + | if(m_magicLevel != magicLevel || m_magicLevelPercent != magicLevelPercent) { | |
| 553 | + | double oldMagicLevel = m_magicLevel; | |
| 554 | + | double oldMagicLevelPercent = m_magicLevelPercent; | |
| 555 | + | m_magicLevel = magicLevel; | |
| 556 | + | m_magicLevelPercent = magicLevelPercent; | |
| 557 | + | ||
| 558 | + | callLuaField("onMagicLevelChange", magicLevel, magicLevelPercent, oldMagicLevel, oldMagicLevelPercent); | |
| 559 | + | } | |
| 560 | + | } | |
| 561 | + | ||
| 562 | + | void LocalPlayer::setBaseMagicLevel(double baseMagicLevel) | |
| 563 | + | { | |
| 564 | + | if(m_baseMagicLevel != baseMagicLevel) { | |
| 565 | + | double oldBaseMagicLevel = m_baseMagicLevel; | |
| 566 | + | m_baseMagicLevel = baseMagicLevel; | |
| 567 | + | ||
| 568 | + | callLuaField("onBaseMagicLevelChange", baseMagicLevel, oldBaseMagicLevel); | |
| 569 | + | } | |
| 570 | + | } | |
| 571 | + | ||
| 572 | + | void LocalPlayer::setSoul(double soul) | |
| 573 | + | { | |
| 574 | + | if(m_soul != soul) { | |
| 575 | + | double oldSoul = m_soul; | |
| 576 | + | m_soul = soul; | |
| 577 | + | ||
| 578 | + | callLuaField("onSoulChange", soul, oldSoul); | |
| 579 | + | } | |
| 580 | + | } | |
| 581 | + | ||
| 582 | + | void LocalPlayer::setStamina(double stamina) | |
| 583 | + | { | |
| 584 | + | if(m_stamina != stamina) { | |
| 585 | + | double oldStamina = m_stamina; | |
| 586 | + | m_stamina = stamina; | |
| 587 | + | ||
| 588 | + | callLuaField("onStaminaChange", stamina, oldStamina); | |
| 589 | + | } | |
| 590 | + | } | |
| 591 | + | ||
| 592 | + | void LocalPlayer::setInventoryItem(Otc::InventorySlot inventory, const ItemPtr& item) | |
| 593 | + | { | |
| 594 | + | if(inventory >= Otc::LastInventorySlot) { | |
| 595 | + | g_logger.traceError("invalid slot"); | |
| 596 | + | return; | |
| 597 | + | } | |
| 598 | + | ||
| 599 | + | if(m_inventoryItems[inventory] != item) { | |
| 600 | + | ItemPtr oldItem = m_inventoryItems[inventory]; | |
| 601 | + | m_inventoryItems[inventory] = item; | |
| 602 | + | ||
| 603 | + | callLuaField("onInventoryChange", inventory, item, oldItem); | |
| 604 | + | } | |
| 605 | + | } | |
| 606 | + | ||
| 607 | + | void LocalPlayer::setVocation(int vocation) | |
| 608 | + | { | |
| 609 | + | if(m_vocation != vocation) { | |
| 610 | + | int oldVocation = m_vocation; | |
| 611 | + | m_vocation = vocation; | |
| 612 | + | ||
| 613 | + | callLuaField("onVocationChange", vocation, oldVocation); | |
| 614 | + | } | |
| 615 | + | } | |
| 616 | + | ||
| 617 | + | void LocalPlayer::setPremium(bool premium) | |
| 618 | + | { | |
| 619 | + | if(m_premium != premium) { | |
| 620 | + | m_premium = premium; | |
| 621 | + | ||
| 622 | + | callLuaField("onPremiumChange", premium); | |
| 623 | + | } | |
| 624 | + | } | |
| 625 | + | ||
| 626 | + | void LocalPlayer::setRegenerationTime(double regenerationTime) | |
| 627 | + | { | |
| 628 | + | if(m_regenerationTime != regenerationTime) { | |
| 629 | + | double oldRegenerationTime = m_regenerationTime; | |
| 630 | + | m_regenerationTime = regenerationTime; | |
| 631 | + | ||
| 632 | + | callLuaField("onRegenerationChange", regenerationTime, oldRegenerationTime); | |
| 633 | + | } | |
| 634 | + | } | |
| 635 | + | ||
| 636 | + | void LocalPlayer::setOfflineTrainingTime(double offlineTrainingTime) | |
| 637 | + | { | |
| 638 | + | if(m_offlineTrainingTime != offlineTrainingTime) { | |
| 639 | + | double oldOfflineTrainingTime = m_offlineTrainingTime; | |
| 640 | + | m_offlineTrainingTime = offlineTrainingTime; | |
| 641 | + | ||
| 642 | + | callLuaField("onOfflineTrainingChange", offlineTrainingTime, oldOfflineTrainingTime); | |
| 643 | + | } | |
| 644 | + | } | |
| 645 | + | ||
| 646 | + | void LocalPlayer::setSpells(const std::vector<int>& spells) | |
| 647 | + | { | |
| 648 | + | if(m_spells != spells) { | |
| 649 | + | std::vector<int> oldSpells = m_spells; | |
| 650 | + | m_spells = spells; | |
| 651 | + | ||
| 652 | + | callLuaField("onSpellsChange", spells, oldSpells); | |
| 653 | + | } | |
| 654 | + | } | |
| 655 | + | ||
| 656 | + | void LocalPlayer::setBlessings(int blessings) | |
| 657 | + | { | |
| 658 | + | if(blessings != m_blessings) { | |
| 659 | + | int oldBlessings = m_blessings; | |
| 660 | + | m_blessings = blessings; | |
| 661 | + | ||
| 662 | + | callLuaField("onBlessingsChange", blessings, oldBlessings); | |
| 663 | + | } | |
| 664 | + | } | |
| 665 | + | ||
| 666 | + | bool LocalPlayer::hasSight(const Position& pos) | |
| 667 | + | { | |
| 668 | + | return m_position.isInRange(pos, g_map.getAwareRange().left - 1, g_map.getAwareRange().top - 1); | |
| 669 | + | } |
| @@ -0,0 +1,205 @@ | |||
| 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 LOCALPLAYER_H | |
| 24 | + | #define LOCALPLAYER_H | |
| 25 | + | ||
| 26 | + | #include "player.h" | |
| 27 | + | #include "walkmatrix.h" | |
| 28 | + | ||
| 29 | + | // @bindclass | |
| 30 | + | class LocalPlayer : public Player | |
| 31 | + | { | |
| 32 | + | enum { | |
| 33 | + | PREWALK_TIMEOUT = 1000 | |
| 34 | + | }; | |
| 35 | + | ||
| 36 | + | public: | |
| 37 | + | LocalPlayer(); | |
| 38 | + | ||
| 39 | + | void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr) override; | |
| 40 | + | ||
| 41 | + | void unlockWalk() { m_walkLockExpiration = 0; } | |
| 42 | + | void lockWalk(int millis = 200); | |
| 43 | + | void stopAutoWalk(); | |
| 44 | + | bool autoWalk(Position destination, bool retry = false); | |
| 45 | + | bool canWalk(Otc::Direction direction, bool ignoreLock = false); | |
| 46 | + | bool isWalkLocked() { | |
| 47 | + | return (m_walkLockExpiration != 0 && g_clock.millis() < m_walkLockExpiration); | |
| 48 | + | } | |
| 49 | + | void turn(Otc::Direction) override; | |
| 50 | + | ||
| 51 | + | void setStates(int states); | |
| 52 | + | void setSkill(uint8_t skill, int level, int levelPercent); | |
| 53 | + | void setBaseSkill(uint8_t skill, int baseLevel); | |
| 54 | + | void setHealth(double health, double maxHealth); | |
| 55 | + | void setFreeCapacity(double freeCapacity); | |
| 56 | + | void setTotalCapacity(double totalCapacity); | |
| 57 | + | void setExperience(double experience); | |
| 58 | + | void setLevel(double level, double levelPercent); | |
| 59 | + | void setMana(double mana, double maxMana); | |
| 60 | + | void setMagicLevel(double magicLevel, double magicLevelPercent); | |
| 61 | + | void setBaseMagicLevel(double baseMagicLevel); | |
| 62 | + | void setSoul(double soul); | |
| 63 | + | void setStamina(double stamina); | |
| 64 | + | void setKnown(bool known) { m_known = known; } | |
| 65 | + | void setPendingGame(bool pending) { m_pending = pending; } | |
| 66 | + | void setInventoryItem(Otc::InventorySlot inventory, const ItemPtr& item); | |
| 67 | + | void setVocation(int vocation); | |
| 68 | + | void setPremium(bool premium); | |
| 69 | + | void setRegenerationTime(double regenerationTime); | |
| 70 | + | void setOfflineTrainingTime(double offlineTrainingTime); | |
| 71 | + | void setSpells(const std::vector<int>& spells); | |
| 72 | + | void setBlessings(int blessings); | |
| 73 | + | ||
| 74 | + | int getStates() { return m_states; } | |
| 75 | + | int getSkillLevel(uint8_t skill) { return skill < m_skillsLevel.size() ? m_skillsLevel[skill] : 0; } | |
| 76 | + | int getSkillBaseLevel(uint8_t skill) { return skill < m_skillsBaseLevel.size() ? m_skillsBaseLevel[skill] : 0; } | |
| 77 | + | int getSkillLevelPercent(uint8_t skill) { return skill < m_skillsLevelPercent.size() ? m_skillsLevelPercent[skill] : 0; } | |
| 78 | + | int getVocation() { return m_vocation; } | |
| 79 | + | double getHealth() { return m_health; } | |
| 80 | + | double getMaxHealth() { return m_maxHealth; } | |
| 81 | + | double getFreeCapacity() { return m_freeCapacity; } | |
| 82 | + | double getTotalCapacity() { return m_totalCapacity; } | |
| 83 | + | double getExperience() { return m_experience; } | |
| 84 | + | double getLevel() { return m_level; } | |
| 85 | + | double getLevelPercent() { return m_levelPercent; } | |
| 86 | + | double getMana() { return m_mana; } | |
| 87 | + | double getMaxMana() { return std::max<double>(m_mana, m_maxMana); } | |
| 88 | + | double getMagicLevel() { return m_magicLevel; } | |
| 89 | + | double getMagicLevelPercent() { return m_magicLevelPercent; } | |
| 90 | + | double getBaseMagicLevel() { return m_baseMagicLevel; } | |
| 91 | + | double getSoul() { return m_soul; } | |
| 92 | + | double getStamina() { return m_stamina; } | |
| 93 | + | double getRegenerationTime() { return m_regenerationTime; } | |
| 94 | + | double getOfflineTrainingTime() { return m_offlineTrainingTime; } | |
| 95 | + | std::vector<int> getSpells() { return m_spells; } | |
| 96 | + | ItemPtr getInventoryItem(Otc::InventorySlot inventory) { return m_inventoryItems[inventory]; } | |
| 97 | + | int getBlessings() { return m_blessings; } | |
| 98 | + | ||
| 99 | + | bool hasSight(const Position& pos); | |
| 100 | + | bool isKnown() { return m_known; } | |
| 101 | + | bool isAutoWalking() { return m_autoWalkDestination.isValid(); } | |
| 102 | + | bool isServerWalking() override { return m_serverWalking; } | |
| 103 | + | bool isPremium() { return m_premium; } | |
| 104 | + | bool isPendingGame() { return m_pending; } | |
| 105 | + | ||
| 106 | + | LocalPlayerPtr asLocalPlayer() { return static_self_cast<LocalPlayer>(); } | |
| 107 | + | bool isLocalPlayer() override { return true; } | |
| 108 | + | ||
| 109 | + | void onAppear() override; | |
| 110 | + | void onPositionChange(const Position& newPos, const Position& oldPos) override; | |
| 111 | + | ||
| 112 | + | // pre walking | |
| 113 | + | void preWalk(Otc::Direction direction); | |
| 114 | + | bool isPreWalking() override { return !m_preWalking.empty(); } | |
| 115 | + | Position getPrewalkingPosition(bool beforePrewalk = false) override { | |
| 116 | + | if(m_preWalking.empty()) | |
| 117 | + | return m_position; | |
| 118 | + | else if (!beforePrewalk && m_preWalking.size() == 1) | |
| 119 | + | return m_position; | |
| 120 | + | auto ret = m_preWalking.rbegin(); | |
| 121 | + | if(!beforePrewalk) | |
| 122 | + | ret++; | |
| 123 | + | return *ret; | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | uint32_t getWalkPrediction(const Position& pos) | |
| 127 | + | { | |
| 128 | + | return m_walkMatrix.get(pos); | |
| 129 | + | }; | |
| 130 | + | ||
| 131 | + | std::string dumpWalkMatrix() | |
| 132 | + | { | |
| 133 | + | return m_walkMatrix.dump(); | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | void startServerWalking() { m_serverWalking = true; } | |
| 137 | + | void finishServerWalking() { m_serverWalking = false; } | |
| 138 | + | ||
| 139 | + | protected: | |
| 140 | + | void walk(const Position& oldPos, const Position& newPos) override; | |
| 141 | + | void cancelWalk(Otc::Direction direction = Otc::InvalidDirection); | |
| 142 | + | ||
| 143 | + | void cancelNewWalk(Otc::Direction dir); | |
| 144 | + | bool predictiveCancelWalk(const Position& pos, uint32_t predictionId, Otc::Direction dir); | |
| 145 | + | ||
| 146 | + | bool retryAutoWalk(); | |
| 147 | + | void stopWalk() override; | |
| 148 | + | ||
| 149 | + | friend class Game; | |
| 150 | + | ||
| 151 | + | protected: | |
| 152 | + | void updateWalkOffset(uint8 totalPixelsWalked, bool inNextFrame = false) override; | |
| 153 | + | void updateWalk() override; | |
| 154 | + | void terminateWalk() override; | |
| 155 | + | ||
| 156 | + | private: | |
| 157 | + | // walk related | |
| 158 | + | Position m_autoWalkDestination; | |
| 159 | + | Position m_lastAutoWalkPosition; | |
| 160 | + | int m_lastAutoWalkRetries = 0; | |
| 161 | + | ScheduledEventPtr m_serverWalkEndEvent; | |
| 162 | + | ScheduledEventPtr m_autoWalkContinueEvent; | |
| 163 | + | ticks_t m_walkLockExpiration; | |
| 164 | + | ||
| 165 | + | // walking and pre walking | |
| 166 | + | std::list<Position> m_preWalking; | |
| 167 | + | bool m_serverWalking = false; | |
| 168 | + | bool m_lastPrewalkDone = false; | |
| 169 | + | WalkMatrix m_walkMatrix; | |
| 170 | + | ||
| 171 | + | bool m_premium = false; | |
| 172 | + | bool m_known = false; | |
| 173 | + | bool m_pending = false; | |
| 174 | + | ||
| 175 | + | ItemPtr m_inventoryItems[Otc::LastInventorySlot]; | |
| 176 | + | Timer m_idleTimer; | |
| 177 | + | ||
| 178 | + | std::vector<int> m_skillsLevel; | |
| 179 | + | std::vector<int> m_skillsBaseLevel; | |
| 180 | + | std::vector<int> m_skillsLevelPercent; | |
| 181 | + | std::vector<int> m_spells; | |
| 182 | + | ||
| 183 | + | int m_states; | |
| 184 | + | int m_vocation; | |
| 185 | + | int m_blessings; | |
| 186 | + | ||
| 187 | + | double m_health; | |
| 188 | + | double m_maxHealth; | |
| 189 | + | double m_freeCapacity; | |
| 190 | + | double m_totalCapacity; | |
| 191 | + | double m_experience; | |
| 192 | + | double m_level; | |
| 193 | + | double m_levelPercent; | |
| 194 | + | double m_mana; | |
| 195 | + | double m_maxMana; | |
| 196 | + | double m_magicLevel; | |
| 197 | + | double m_magicLevelPercent; | |
| 198 | + | double m_baseMagicLevel; | |
| 199 | + | double m_soul; | |
| 200 | + | double m_stamina; | |
| 201 | + | double m_regenerationTime; | |
| 202 | + | double m_offlineTrainingTime; | |
| 203 | + | }; | |
| 204 | + | ||
| 205 | + | #endif |