From 04b9c2590c6d2ffbad93902322cc2efc659278ed Mon Sep 17 00:00:00 2001 From: OTCv8 Date: Wed, 10 Jun 2020 17:42:52 +0200 Subject: [PATCH] Open sourced 90% of files --- README.md | 1 + src/README.md | 8 + src/client/CMakeLists.txt | 113 + src/client/animatedtext.cpp | 101 + src/client/animatedtext.h | 64 + src/client/animator.cpp | 244 ++ src/client/animator.h | 87 + src/client/client.cpp | 57 + src/client/client.h | 38 + src/client/const.h | 614 ++++ src/client/container.cpp | 143 + src/client/container.h | 80 + src/client/creature.cpp | 1101 +++++++ src/client/creature.h | 289 ++ src/client/creatures.cpp | 426 +++ src/client/creatures.h | 147 + src/client/declarations.h | 118 + src/client/effect.cpp | 98 + src/client/effect.h | 59 + src/client/game.cpp | 1641 ++++++++++ src/client/game.h | 476 +++ src/client/global.h | 32 + src/client/houses.cpp | 210 ++ src/client/houses.h | 113 + src/client/item.cpp | 440 +++ src/client/item.h | 172 + src/client/itemtype.cpp | 93 + src/client/itemtype.h | 162 + src/client/lightview.cpp | 69 + src/client/lightview.h | 66 + src/client/localplayer.cpp | 623 ++++ src/client/localplayer.h | 205 ++ src/client/luafunctions_client.cpp | 921 ++++++ src/client/luavaluecasts_client.cpp | 329 ++ src/client/luavaluecasts_client.h | 62 + src/client/map.cpp | 969 ++++++ src/client/map.h | 304 ++ src/client/mapio.cpp | 538 ++++ src/client/mapview.cpp | 537 ++++ src/client/mapview.h | 177 + src/client/minimap.cpp | 438 +++ src/client/minimap.h | 125 + src/client/missile.cpp | 102 + src/client/missile.h | 63 + src/client/outfit.cpp | 141 + src/client/outfit.h | 73 + src/client/player.cpp | 23 + src/client/player.h | 39 + src/client/position.h | 273 ++ src/client/protocolcodes.cpp | 196 ++ src/client/protocolcodes.h | 342 ++ src/client/protocolgame.cpp | 82 + src/client/protocolgame.h | 313 ++ src/client/protocolgameparse.cpp | 2851 +++++++++++++++++ src/client/protocolgamesend.cpp | 1171 +++++++ src/client/shadermanager.cpp | 90 + src/client/shadermanager.h | 63 + src/client/spritemanager.cpp | 139 + src/client/spritemanager.h | 71 + src/client/statictext.cpp | 168 + src/client/statictext.h | 77 + src/client/thing.cpp | 114 + src/client/thing.h | 157 + src/client/thingstype.h | 67 + src/client/thingtype.cpp | 777 +++++ src/client/thingtype.h | 290 ++ src/client/thingtypemanager.cpp | 497 +++ src/client/thingtypemanager.h | 119 + src/client/tile.cpp | 792 +++++ src/client/tile.h | 180 ++ src/client/towns.cpp | 80 + src/client/towns.h | 73 + src/client/uicreature.cpp | 123 + src/client/uicreature.h | 63 + src/client/uiitem.cpp | 120 + src/client/uiitem.h | 63 + src/client/uimap.cpp | 231 ++ src/client/uimap.h | 111 + src/client/uimapanchorlayout.cpp | 92 + src/client/uimapanchorlayout.h | 55 + src/client/uiminimap.cpp | 157 + src/client/uiminimap.h | 76 + src/client/uiprogressrect.cpp | 96 + src/client/uiprogressrect.h | 45 + src/client/uisprite.cpp | 86 + src/client/uisprite.h | 56 + src/framework/const.h | 301 ++ src/framework/core/adaptiverenderer.cpp | 91 + src/framework/core/adaptiverenderer.h | 50 + src/framework/core/application.cpp | 223 ++ src/framework/core/application.h | 92 + src/framework/core/asyncdispatcher.cpp | 71 + src/framework/core/asyncdispatcher.h | 65 + src/framework/core/binarytree.cpp | 216 ++ src/framework/core/binarytree.h | 89 + src/framework/core/clock.cpp | 49 + src/framework/core/clock.h | 51 + src/framework/core/config.cpp | 161 + src/framework/core/config.h | 65 + src/framework/core/configmanager.cpp | 130 + src/framework/core/configmanager.h | 54 + src/framework/core/consoleapplication.cpp | 55 + src/framework/core/consoleapplication.h | 40 + src/framework/core/declarations.h | 49 + src/framework/core/event.cpp | 55 + src/framework/core/event.h | 52 + src/framework/core/eventdispatcher.cpp | 154 + src/framework/core/eventdispatcher.h | 62 + src/framework/core/filestream.cpp | 482 +++ src/framework/core/filestream.h | 93 + src/framework/core/graphicalapplication.cpp | 420 +++ src/framework/core/graphicalapplication.h | 83 + src/framework/core/inputevent.h | 55 + src/framework/core/logger.cpp | 157 + src/framework/core/logger.h | 103 + src/framework/core/module.cpp | 239 ++ src/framework/core/module.h | 87 + src/framework/core/modulemanager.cpp | 152 + src/framework/core/modulemanager.h | 56 + src/framework/core/scheduledevent.cpp | 57 + src/framework/core/scheduledevent.h | 57 + src/framework/core/timer.cpp | 36 + src/framework/core/timer.h | 48 + src/framework/global.h | 56 + src/framework/graphics/animatedtexture.cpp | 80 + src/framework/graphics/animatedtexture.h | 53 + src/framework/graphics/apngloader.cpp | 1145 +++++++ src/framework/graphics/apngloader.h | 46 + src/framework/graphics/bitmapfont.cpp | 374 +++ src/framework/graphics/bitmapfont.h | 89 + src/framework/graphics/cachedtext.cpp | 81 + src/framework/graphics/cachedtext.h | 62 + src/framework/graphics/drawcache.cpp | 65 + src/framework/graphics/drawcache.h | 55 + src/framework/graphics/drawqueue.h | 300 ++ src/framework/graphics/fontmanager.cpp | 107 + src/framework/graphics/fontmanager.h | 52 + .../graphics/paintershaderprogram.cpp | 237 ++ src/framework/graphics/paintershaderprogram.h | 104 + src/framework/graphics/shader.cpp | 95 + src/framework/graphics/shader.h | 51 + src/framework/graphics/shaderprogram.cpp | 177 + src/framework/graphics/shaderprogram.h | 103 + src/framework/graphics/shaders/newshader.h | 61 + src/framework/graphics/shaders/shaders.h | 7 + .../graphics/shaders/shadersources.h | 128 + src/framework/graphics/textrender.cpp | 109 + src/framework/graphics/textrender.h | 38 + src/framework/graphics/texture.cpp | 232 ++ src/framework/graphics/texture.h | 83 + src/framework/http/http.cpp | 205 ++ src/framework/http/http.h | 55 + src/framework/http/result.h | 25 + src/framework/input/mouse.cpp | 129 + src/framework/input/mouse.h | 44 + src/framework/luaengine/declarations.h | 37 + src/framework/luaengine/lbitlib.cpp | 379 +++ src/framework/luaengine/lbitlib.h | 30 + src/framework/luaengine/luabinder.h | 216 ++ src/framework/luaengine/luaexception.cpp | 53 + src/framework/luaengine/luaexception.h | 56 + src/framework/luaengine/luainterface.cpp | 1394 ++++++++ src/framework/luaengine/luainterface.h | 486 +++ src/framework/luaengine/luaobject.cpp | 125 + src/framework/luaengine/luaobject.h | 208 ++ src/framework/luaengine/luavaluecasts.cpp | 335 ++ src/framework/luaengine/luavaluecasts.h | 515 +++ src/framework/luafunctions.cpp | 992 ++++++ src/framework/net/connection.cpp | 323 ++ src/framework/net/connection.h | 101 + src/framework/net/declarations.h | 42 + src/framework/net/inputmessage.cpp | 152 + src/framework/net/inputmessage.h | 96 + src/framework/net/outputmessage.cpp | 144 + src/framework/net/outputmessage.h | 81 + src/framework/net/protocol.cpp | 372 +++ src/framework/net/protocol.h | 101 + src/framework/net/server.cpp | 64 + src/framework/net/server.h | 44 + src/framework/otml/declarations.h | 37 + src/framework/otml/otml.h | 29 + src/framework/otml/otmldocument.cpp | 69 + src/framework/otml/otmldocument.h | 56 + src/framework/otml/otmlemitter.cpp | 92 + src/framework/otml/otmlemitter.h | 35 + src/framework/otml/otmlexception.cpp | 47 + src/framework/otml/otmlexception.h | 42 + src/framework/otml/otmlnode.cpp | 253 ++ src/framework/otml/otmlnode.h | 183 ++ src/framework/otml/otmlparser.cpp | 205 ++ src/framework/otml/otmlparser.h | 56 + src/framework/pch.h | 87 + src/framework/platform/crashhandler.h | 31 + src/framework/platform/platform.cpp | 25 + src/framework/platform/platform.h | 60 + src/framework/platform/platformwindow.cpp | 211 ++ src/framework/platform/platformwindow.h | 143 + src/framework/sound/combinedsoundsource.cpp | 120 + src/framework/sound/combinedsoundsource.h | 62 + src/framework/sound/declarations.h | 54 + src/framework/sound/oggsoundfile.cpp | 117 + src/framework/sound/oggsoundfile.h | 54 + src/framework/sound/soundbuffer.cpp | 70 + src/framework/sound/soundbuffer.h | 49 + src/framework/sound/soundchannel.cpp | 104 + src/framework/sound/soundchannel.h | 69 + src/framework/sound/soundfile.cpp | 72 + src/framework/sound/soundfile.h | 60 + src/framework/sound/soundmanager.cpp | 303 ++ src/framework/sound/soundmanager.h | 73 + src/framework/sound/soundsource.cpp | 165 + src/framework/sound/soundsource.h | 84 + src/framework/sound/streamsoundsource.cpp | 193 ++ src/framework/sound/streamsoundsource.h | 71 + src/framework/stdext/any.h | 76 + src/framework/stdext/boolean.h | 40 + src/framework/stdext/cast.h | 177 + src/framework/stdext/compiler.h | 78 + src/framework/stdext/demangle.cpp | 64 + src/framework/stdext/demangle.h | 48 + src/framework/stdext/dumper.h | 42 + src/framework/stdext/dynamic_storage.h | 67 + src/framework/stdext/exception.h | 47 + src/framework/stdext/fastrand.h | 12 + src/framework/stdext/format.h | 100 + src/framework/stdext/math.cpp | 69 + src/framework/stdext/math.h | 62 + src/framework/stdext/net.cpp | 60 + src/framework/stdext/net.h | 36 + src/framework/stdext/packed_any.h | 117 + src/framework/stdext/packed_storage.h | 113 + src/framework/stdext/shared_object.h | 132 + src/framework/stdext/stdext.h | 43 + src/framework/stdext/string.cpp | 301 ++ src/framework/stdext/string.h | 79 + src/framework/stdext/thread.h | 31 + src/framework/stdext/time.cpp | 55 + src/framework/stdext/time.h | 50 + src/framework/stdext/traits.h | 37 + src/framework/stdext/types.h | 49 + src/framework/stdext/uri.cpp | 24 + src/framework/stdext/uri.h | 10 + src/framework/ui/declarations.h | 54 + src/framework/ui/ui.h | 35 + src/framework/ui/uianchorlayout.cpp | 275 ++ src/framework/ui/uianchorlayout.h | 85 + src/framework/ui/uiboxlayout.cpp | 42 + src/framework/ui/uiboxlayout.h | 48 + src/framework/ui/uigridlayout.cpp | 132 + src/framework/ui/uigridlayout.h | 68 + src/framework/ui/uihorizontallayout.cpp | 100 + src/framework/ui/uihorizontallayout.h | 46 + src/framework/ui/uilayout.cpp | 72 + src/framework/ui/uilayout.h | 66 + src/framework/ui/uimanager.cpp | 521 +++ src/framework/ui/uimanager.h | 99 + src/framework/ui/uitextedit.cpp | 931 ++++++ src/framework/ui/uitextedit.h | 149 + src/framework/ui/uitranslator.cpp | 115 + src/framework/ui/uitranslator.h | 38 + src/framework/ui/uiverticallayout.cpp | 102 + src/framework/ui/uiverticallayout.h | 47 + src/framework/ui/uiwidget.cpp | 1772 ++++++++++ src/framework/ui/uiwidget.h | 537 ++++ src/framework/ui/uiwidgetbasestyle.cpp | 403 +++ src/framework/ui/uiwidgetimage.cpp | 238 ++ src/framework/ui/uiwidgettext.cpp | 166 + src/framework/util/color.cpp | 145 + src/framework/util/color.h | 222 ++ src/framework/util/extras.cpp | 6 + src/framework/util/extras.h | 86 + src/framework/util/framecounter.h | 26 + src/framework/util/matrix.h | 246 ++ src/framework/util/point.h | 104 + src/framework/util/qrcodegen.c | 1022 ++++++ src/framework/util/qrcodegen.h | 311 ++ src/framework/util/rect.h | 343 ++ src/framework/util/size.h | 126 + src/framework/xml/tinystr.cpp | 112 + src/framework/xml/tinystr.h | 304 ++ src/framework/xml/tinyxml.cpp | 1690 ++++++++++ src/framework/xml/tinyxml.h | 1721 ++++++++++ src/framework/xml/tinyxmlerror.cpp | 53 + src/framework/xml/tinyxmlparser.cpp | 1644 ++++++++++ src/main.cpp | 148 + 285 files changed, 58231 insertions(+) create mode 100644 src/README.md create mode 100644 src/client/CMakeLists.txt create mode 100644 src/client/animatedtext.cpp create mode 100644 src/client/animatedtext.h create mode 100644 src/client/animator.cpp create mode 100644 src/client/animator.h create mode 100644 src/client/client.cpp create mode 100644 src/client/client.h create mode 100644 src/client/const.h create mode 100644 src/client/container.cpp create mode 100644 src/client/container.h create mode 100644 src/client/creature.cpp create mode 100644 src/client/creature.h create mode 100644 src/client/creatures.cpp create mode 100644 src/client/creatures.h create mode 100644 src/client/declarations.h create mode 100644 src/client/effect.cpp create mode 100644 src/client/effect.h create mode 100644 src/client/game.cpp create mode 100644 src/client/game.h create mode 100644 src/client/global.h create mode 100644 src/client/houses.cpp create mode 100644 src/client/houses.h create mode 100644 src/client/item.cpp create mode 100644 src/client/item.h create mode 100644 src/client/itemtype.cpp create mode 100644 src/client/itemtype.h create mode 100644 src/client/lightview.cpp create mode 100644 src/client/lightview.h create mode 100644 src/client/localplayer.cpp create mode 100644 src/client/localplayer.h create mode 100644 src/client/luafunctions_client.cpp create mode 100644 src/client/luavaluecasts_client.cpp create mode 100644 src/client/luavaluecasts_client.h create mode 100644 src/client/map.cpp create mode 100644 src/client/map.h create mode 100644 src/client/mapio.cpp create mode 100644 src/client/mapview.cpp create mode 100644 src/client/mapview.h create mode 100644 src/client/minimap.cpp create mode 100644 src/client/minimap.h create mode 100644 src/client/missile.cpp create mode 100644 src/client/missile.h create mode 100644 src/client/outfit.cpp create mode 100644 src/client/outfit.h create mode 100644 src/client/player.cpp create mode 100644 src/client/player.h create mode 100644 src/client/position.h create mode 100644 src/client/protocolcodes.cpp create mode 100644 src/client/protocolcodes.h create mode 100644 src/client/protocolgame.cpp create mode 100644 src/client/protocolgame.h create mode 100644 src/client/protocolgameparse.cpp create mode 100644 src/client/protocolgamesend.cpp create mode 100644 src/client/shadermanager.cpp create mode 100644 src/client/shadermanager.h create mode 100644 src/client/spritemanager.cpp create mode 100644 src/client/spritemanager.h create mode 100644 src/client/statictext.cpp create mode 100644 src/client/statictext.h create mode 100644 src/client/thing.cpp create mode 100644 src/client/thing.h create mode 100644 src/client/thingstype.h create mode 100644 src/client/thingtype.cpp create mode 100644 src/client/thingtype.h create mode 100644 src/client/thingtypemanager.cpp create mode 100644 src/client/thingtypemanager.h create mode 100644 src/client/tile.cpp create mode 100644 src/client/tile.h create mode 100644 src/client/towns.cpp create mode 100644 src/client/towns.h create mode 100644 src/client/uicreature.cpp create mode 100644 src/client/uicreature.h create mode 100644 src/client/uiitem.cpp create mode 100644 src/client/uiitem.h create mode 100644 src/client/uimap.cpp create mode 100644 src/client/uimap.h create mode 100644 src/client/uimapanchorlayout.cpp create mode 100644 src/client/uimapanchorlayout.h create mode 100644 src/client/uiminimap.cpp create mode 100644 src/client/uiminimap.h create mode 100644 src/client/uiprogressrect.cpp create mode 100644 src/client/uiprogressrect.h create mode 100644 src/client/uisprite.cpp create mode 100644 src/client/uisprite.h create mode 100644 src/framework/const.h create mode 100644 src/framework/core/adaptiverenderer.cpp create mode 100644 src/framework/core/adaptiverenderer.h create mode 100644 src/framework/core/application.cpp create mode 100644 src/framework/core/application.h create mode 100644 src/framework/core/asyncdispatcher.cpp create mode 100644 src/framework/core/asyncdispatcher.h create mode 100644 src/framework/core/binarytree.cpp create mode 100644 src/framework/core/binarytree.h create mode 100644 src/framework/core/clock.cpp create mode 100644 src/framework/core/clock.h create mode 100644 src/framework/core/config.cpp create mode 100644 src/framework/core/config.h create mode 100644 src/framework/core/configmanager.cpp create mode 100644 src/framework/core/configmanager.h create mode 100644 src/framework/core/consoleapplication.cpp create mode 100644 src/framework/core/consoleapplication.h create mode 100644 src/framework/core/declarations.h create mode 100644 src/framework/core/event.cpp create mode 100644 src/framework/core/event.h create mode 100644 src/framework/core/eventdispatcher.cpp create mode 100644 src/framework/core/eventdispatcher.h create mode 100644 src/framework/core/filestream.cpp create mode 100644 src/framework/core/filestream.h create mode 100644 src/framework/core/graphicalapplication.cpp create mode 100644 src/framework/core/graphicalapplication.h create mode 100644 src/framework/core/inputevent.h create mode 100644 src/framework/core/logger.cpp create mode 100644 src/framework/core/logger.h create mode 100644 src/framework/core/module.cpp create mode 100644 src/framework/core/module.h create mode 100644 src/framework/core/modulemanager.cpp create mode 100644 src/framework/core/modulemanager.h create mode 100644 src/framework/core/scheduledevent.cpp create mode 100644 src/framework/core/scheduledevent.h create mode 100644 src/framework/core/timer.cpp create mode 100644 src/framework/core/timer.h create mode 100644 src/framework/global.h create mode 100644 src/framework/graphics/animatedtexture.cpp create mode 100644 src/framework/graphics/animatedtexture.h create mode 100644 src/framework/graphics/apngloader.cpp create mode 100644 src/framework/graphics/apngloader.h create mode 100644 src/framework/graphics/bitmapfont.cpp create mode 100644 src/framework/graphics/bitmapfont.h create mode 100644 src/framework/graphics/cachedtext.cpp create mode 100644 src/framework/graphics/cachedtext.h create mode 100644 src/framework/graphics/drawcache.cpp create mode 100644 src/framework/graphics/drawcache.h create mode 100644 src/framework/graphics/drawqueue.h create mode 100644 src/framework/graphics/fontmanager.cpp create mode 100644 src/framework/graphics/fontmanager.h create mode 100644 src/framework/graphics/paintershaderprogram.cpp create mode 100644 src/framework/graphics/paintershaderprogram.h create mode 100644 src/framework/graphics/shader.cpp create mode 100644 src/framework/graphics/shader.h create mode 100644 src/framework/graphics/shaderprogram.cpp create mode 100644 src/framework/graphics/shaderprogram.h create mode 100644 src/framework/graphics/shaders/newshader.h create mode 100644 src/framework/graphics/shaders/shaders.h create mode 100644 src/framework/graphics/shaders/shadersources.h create mode 100644 src/framework/graphics/textrender.cpp create mode 100644 src/framework/graphics/textrender.h create mode 100644 src/framework/graphics/texture.cpp create mode 100644 src/framework/graphics/texture.h create mode 100644 src/framework/http/http.cpp create mode 100644 src/framework/http/http.h create mode 100644 src/framework/http/result.h create mode 100644 src/framework/input/mouse.cpp create mode 100644 src/framework/input/mouse.h create mode 100644 src/framework/luaengine/declarations.h create mode 100644 src/framework/luaengine/lbitlib.cpp create mode 100644 src/framework/luaengine/lbitlib.h create mode 100644 src/framework/luaengine/luabinder.h create mode 100644 src/framework/luaengine/luaexception.cpp create mode 100644 src/framework/luaengine/luaexception.h create mode 100644 src/framework/luaengine/luainterface.cpp create mode 100644 src/framework/luaengine/luainterface.h create mode 100644 src/framework/luaengine/luaobject.cpp create mode 100644 src/framework/luaengine/luaobject.h create mode 100644 src/framework/luaengine/luavaluecasts.cpp create mode 100644 src/framework/luaengine/luavaluecasts.h create mode 100644 src/framework/luafunctions.cpp create mode 100644 src/framework/net/connection.cpp create mode 100644 src/framework/net/connection.h create mode 100644 src/framework/net/declarations.h create mode 100644 src/framework/net/inputmessage.cpp create mode 100644 src/framework/net/inputmessage.h create mode 100644 src/framework/net/outputmessage.cpp create mode 100644 src/framework/net/outputmessage.h create mode 100644 src/framework/net/protocol.cpp create mode 100644 src/framework/net/protocol.h create mode 100644 src/framework/net/server.cpp create mode 100644 src/framework/net/server.h create mode 100644 src/framework/otml/declarations.h create mode 100644 src/framework/otml/otml.h create mode 100644 src/framework/otml/otmldocument.cpp create mode 100644 src/framework/otml/otmldocument.h create mode 100644 src/framework/otml/otmlemitter.cpp create mode 100644 src/framework/otml/otmlemitter.h create mode 100644 src/framework/otml/otmlexception.cpp create mode 100644 src/framework/otml/otmlexception.h create mode 100644 src/framework/otml/otmlnode.cpp create mode 100644 src/framework/otml/otmlnode.h create mode 100644 src/framework/otml/otmlparser.cpp create mode 100644 src/framework/otml/otmlparser.h create mode 100644 src/framework/pch.h create mode 100644 src/framework/platform/crashhandler.h create mode 100644 src/framework/platform/platform.cpp create mode 100644 src/framework/platform/platform.h create mode 100644 src/framework/platform/platformwindow.cpp create mode 100644 src/framework/platform/platformwindow.h create mode 100644 src/framework/sound/combinedsoundsource.cpp create mode 100644 src/framework/sound/combinedsoundsource.h create mode 100644 src/framework/sound/declarations.h create mode 100644 src/framework/sound/oggsoundfile.cpp create mode 100644 src/framework/sound/oggsoundfile.h create mode 100644 src/framework/sound/soundbuffer.cpp create mode 100644 src/framework/sound/soundbuffer.h create mode 100644 src/framework/sound/soundchannel.cpp create mode 100644 src/framework/sound/soundchannel.h create mode 100644 src/framework/sound/soundfile.cpp create mode 100644 src/framework/sound/soundfile.h create mode 100644 src/framework/sound/soundmanager.cpp create mode 100644 src/framework/sound/soundmanager.h create mode 100644 src/framework/sound/soundsource.cpp create mode 100644 src/framework/sound/soundsource.h create mode 100644 src/framework/sound/streamsoundsource.cpp create mode 100644 src/framework/sound/streamsoundsource.h create mode 100644 src/framework/stdext/any.h create mode 100644 src/framework/stdext/boolean.h create mode 100644 src/framework/stdext/cast.h create mode 100644 src/framework/stdext/compiler.h create mode 100644 src/framework/stdext/demangle.cpp create mode 100644 src/framework/stdext/demangle.h create mode 100644 src/framework/stdext/dumper.h create mode 100644 src/framework/stdext/dynamic_storage.h create mode 100644 src/framework/stdext/exception.h create mode 100644 src/framework/stdext/fastrand.h create mode 100644 src/framework/stdext/format.h create mode 100644 src/framework/stdext/math.cpp create mode 100644 src/framework/stdext/math.h create mode 100644 src/framework/stdext/net.cpp create mode 100644 src/framework/stdext/net.h create mode 100644 src/framework/stdext/packed_any.h create mode 100644 src/framework/stdext/packed_storage.h create mode 100644 src/framework/stdext/shared_object.h create mode 100644 src/framework/stdext/stdext.h create mode 100644 src/framework/stdext/string.cpp create mode 100644 src/framework/stdext/string.h create mode 100644 src/framework/stdext/thread.h create mode 100644 src/framework/stdext/time.cpp create mode 100644 src/framework/stdext/time.h create mode 100644 src/framework/stdext/traits.h create mode 100644 src/framework/stdext/types.h create mode 100644 src/framework/stdext/uri.cpp create mode 100644 src/framework/stdext/uri.h create mode 100644 src/framework/ui/declarations.h create mode 100644 src/framework/ui/ui.h create mode 100644 src/framework/ui/uianchorlayout.cpp create mode 100644 src/framework/ui/uianchorlayout.h create mode 100644 src/framework/ui/uiboxlayout.cpp create mode 100644 src/framework/ui/uiboxlayout.h create mode 100644 src/framework/ui/uigridlayout.cpp create mode 100644 src/framework/ui/uigridlayout.h create mode 100644 src/framework/ui/uihorizontallayout.cpp create mode 100644 src/framework/ui/uihorizontallayout.h create mode 100644 src/framework/ui/uilayout.cpp create mode 100644 src/framework/ui/uilayout.h create mode 100644 src/framework/ui/uimanager.cpp create mode 100644 src/framework/ui/uimanager.h create mode 100644 src/framework/ui/uitextedit.cpp create mode 100644 src/framework/ui/uitextedit.h create mode 100644 src/framework/ui/uitranslator.cpp create mode 100644 src/framework/ui/uitranslator.h create mode 100644 src/framework/ui/uiverticallayout.cpp create mode 100644 src/framework/ui/uiverticallayout.h create mode 100644 src/framework/ui/uiwidget.cpp create mode 100644 src/framework/ui/uiwidget.h create mode 100644 src/framework/ui/uiwidgetbasestyle.cpp create mode 100644 src/framework/ui/uiwidgetimage.cpp create mode 100644 src/framework/ui/uiwidgettext.cpp create mode 100644 src/framework/util/color.cpp create mode 100644 src/framework/util/color.h create mode 100644 src/framework/util/extras.cpp create mode 100644 src/framework/util/extras.h create mode 100644 src/framework/util/framecounter.h create mode 100644 src/framework/util/matrix.h create mode 100644 src/framework/util/point.h create mode 100644 src/framework/util/qrcodegen.c create mode 100644 src/framework/util/qrcodegen.h create mode 100644 src/framework/util/rect.h create mode 100644 src/framework/util/size.h create mode 100644 src/framework/xml/tinystr.cpp create mode 100644 src/framework/xml/tinystr.h create mode 100644 src/framework/xml/tinyxml.cpp create mode 100644 src/framework/xml/tinyxml.h create mode 100644 src/framework/xml/tinyxmlerror.cpp create mode 100644 src/framework/xml/tinyxmlparser.cpp create mode 100644 src/main.cpp diff --git a/README.md b/README.md index aa9ce29..11dae51 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Supported platforms: - Windows (min. Windows 7) - Linux - Android (min. 5.0) + Planned support: - Mac OS - iOS diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..516d531 --- /dev/null +++ b/src/README.md @@ -0,0 +1,8 @@ +# OTClientV8 partial sources (90% of code) + +To help you understand how OTClientV8 works, some parts of source code has been published. +All files from client/ dir has been shared, but there few places with hidden code due to various reasons +In the future more parts of OTCv8 will be available here + +If you want buy access to full sources, contact kondrah#7945 on discord +Or send mail to otclient@otclient.ovh (but discord is better) diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt new file mode 100644 index 0000000..dc51557 --- /dev/null +++ b/src/client/CMakeLists.txt @@ -0,0 +1,113 @@ +# CMAKE_CURRENT_LIST_DIR cmake 2.6 compatibility +if(${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 6) + get_filename_component(CMAKE_CURRENT_LIST_DIR ${CMAKE_CURRENT_LIST_FILE} PATH) +endif(${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 6) + +# client options +add_definitions(-DCLIENT) +option(BOT_PROTECTION "Enable bot protection" ON) +if(BOT_PROTECTION) + add_definitions(-DBOT_PROTECTION) + message(STATUS "Bot protection: ON") +else(BOT_PROTECTION) + message(STATUS "Bot protection: OFF") +endif(BOT_PROTECTION) + +set(client_SOURCES ${client_SOURCES} + # client + ${CMAKE_CURRENT_LIST_DIR}/const.h + ${CMAKE_CURRENT_LIST_DIR}/global.h + ${CMAKE_CURRENT_LIST_DIR}/luafunctions_client.cpp + ${CMAKE_CURRENT_LIST_DIR}/client.cpp + ${CMAKE_CURRENT_LIST_DIR}/client.h + + # core + ${CMAKE_CURRENT_LIST_DIR}/animatedtext.cpp + ${CMAKE_CURRENT_LIST_DIR}/animatedtext.h + ${CMAKE_CURRENT_LIST_DIR}/animator.h + ${CMAKE_CURRENT_LIST_DIR}/animator.cpp + ${CMAKE_CURRENT_LIST_DIR}/container.cpp + ${CMAKE_CURRENT_LIST_DIR}/container.h + ${CMAKE_CURRENT_LIST_DIR}/creature.cpp + ${CMAKE_CURRENT_LIST_DIR}/creature.h + ${CMAKE_CURRENT_LIST_DIR}/declarations.h + ${CMAKE_CURRENT_LIST_DIR}/effect.cpp + ${CMAKE_CURRENT_LIST_DIR}/effect.h + ${CMAKE_CURRENT_LIST_DIR}/game.cpp + ${CMAKE_CURRENT_LIST_DIR}/game.h + ${CMAKE_CURRENT_LIST_DIR}/shadermanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/shadermanager.h + ${CMAKE_CURRENT_LIST_DIR}/item.cpp + ${CMAKE_CURRENT_LIST_DIR}/item.h + ${CMAKE_CURRENT_LIST_DIR}/localplayer.cpp + ${CMAKE_CURRENT_LIST_DIR}/localplayer.h + ${CMAKE_CURRENT_LIST_DIR}/map.cpp + ${CMAKE_CURRENT_LIST_DIR}/map.h + ${CMAKE_CURRENT_LIST_DIR}/mapio.cpp + ${CMAKE_CURRENT_LIST_DIR}/mapview.cpp + ${CMAKE_CURRENT_LIST_DIR}/mapview.h + ${CMAKE_CURRENT_LIST_DIR}/minimap.cpp + ${CMAKE_CURRENT_LIST_DIR}/minimap.h + ${CMAKE_CURRENT_LIST_DIR}/lightview.cpp + ${CMAKE_CURRENT_LIST_DIR}/lightview.h + ${CMAKE_CURRENT_LIST_DIR}/missile.cpp + ${CMAKE_CURRENT_LIST_DIR}/missile.h + ${CMAKE_CURRENT_LIST_DIR}/outfit.cpp + ${CMAKE_CURRENT_LIST_DIR}/outfit.h + ${CMAKE_CURRENT_LIST_DIR}/player.cpp + ${CMAKE_CURRENT_LIST_DIR}/player.h + ${CMAKE_CURRENT_LIST_DIR}/spritemanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/spritemanager.h + ${CMAKE_CURRENT_LIST_DIR}/statictext.cpp + ${CMAKE_CURRENT_LIST_DIR}/statictext.h + ${CMAKE_CURRENT_LIST_DIR}/thing.cpp + ${CMAKE_CURRENT_LIST_DIR}/thing.h + ${CMAKE_CURRENT_LIST_DIR}/thingtypemanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/thingtypemanager.h + ${CMAKE_CURRENT_LIST_DIR}/thingtype.cpp + ${CMAKE_CURRENT_LIST_DIR}/thingtype.h + ${CMAKE_CURRENT_LIST_DIR}/itemtype.cpp + ${CMAKE_CURRENT_LIST_DIR}/itemtype.h + ${CMAKE_CURRENT_LIST_DIR}/tile.cpp + ${CMAKE_CURRENT_LIST_DIR}/tile.h + ${CMAKE_CURRENT_LIST_DIR}/houses.cpp + ${CMAKE_CURRENT_LIST_DIR}/houses.h + ${CMAKE_CURRENT_LIST_DIR}/towns.cpp + ${CMAKE_CURRENT_LIST_DIR}/towns.h + ${CMAKE_CURRENT_LIST_DIR}/creatures.cpp + ${CMAKE_CURRENT_LIST_DIR}/creatures.h + + # lua + ${CMAKE_CURRENT_LIST_DIR}/luavaluecasts_client.cpp + ${CMAKE_CURRENT_LIST_DIR}/luavaluecasts_client.h + + # net + ${CMAKE_CURRENT_LIST_DIR}/protocolcodes.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolcodes.h + ${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolgame.h + ${CMAKE_CURRENT_LIST_DIR}/protocolgameparse.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolgamesend.cpp + + # ui + ${CMAKE_CURRENT_LIST_DIR}/uicreature.cpp + ${CMAKE_CURRENT_LIST_DIR}/uicreature.h + ${CMAKE_CURRENT_LIST_DIR}/uiitem.cpp + ${CMAKE_CURRENT_LIST_DIR}/uiitem.h + ${CMAKE_CURRENT_LIST_DIR}/uimap.cpp + ${CMAKE_CURRENT_LIST_DIR}/uimap.h + ${CMAKE_CURRENT_LIST_DIR}/uiminimap.cpp + ${CMAKE_CURRENT_LIST_DIR}/uiminimap.h + ${CMAKE_CURRENT_LIST_DIR}/uiprogressrect.cpp + ${CMAKE_CURRENT_LIST_DIR}/uiprogressrect.h + ${CMAKE_CURRENT_LIST_DIR}/uimapanchorlayout.cpp + ${CMAKE_CURRENT_LIST_DIR}/uimapanchorlayout.h + ${CMAKE_CURRENT_LIST_DIR}/uisprite.cpp + ${CMAKE_CURRENT_LIST_DIR}/uisprite.h + + # util + ${CMAKE_CURRENT_LIST_DIR}/position.h +) + +set_source_files_properties(${CMAKE_CURRENT_LIST_DIR}/luafunctions.cpp + PROPERTIES LANGUAGE CXX COMPILE_FLAGS "-g0 -Os") diff --git a/src/client/animatedtext.cpp b/src/client/animatedtext.cpp new file mode 100644 index 0000000..3a33384 --- /dev/null +++ b/src/client/animatedtext.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "animatedtext.h" +#include "map.h" +#include "game.h" +#include +#include +#include + +AnimatedText::AnimatedText() +{ + m_cachedText.setFont(g_fonts.getFont("verdana-11px-rounded")); + m_cachedText.setAlign(Fw::AlignLeft); +} + +void AnimatedText::drawText(const Point& dest, const Rect& visibleRect) +{ + static float tf = Otc::ANIMATED_TEXT_DURATION; + static float tftf = Otc::ANIMATED_TEXT_DURATION * Otc::ANIMATED_TEXT_DURATION; + + Point p = dest; + Size textSize = m_cachedText.getTextSize(); + float t = m_animationTimer.ticksElapsed(); + p.x -= textSize.width() / 2; + + if(g_game.getFeature(Otc::GameDiagonalAnimatedText)) { + p.x -= (4 * t / tf) + (8 * t * t / tftf); + } + + p.y += (-48 * t) / tf; + p += m_offset; + Rect rect(p, textSize); + + if(visibleRect.contains(rect)) { + float t0 = tf / 1.2; + Color color = m_color; + if(t > t0) { + color.setAlpha((float)(1 - (t - t0) / (tf - t0))); + } + m_cachedText.draw(rect, color); + } +} + +void AnimatedText::onAppear() +{ + m_animationTimer.restart(); + + // schedule removal + auto self = asAnimatedText(); + g_dispatcher.scheduleEvent([self]() { g_map.removeThing(self); }, Otc::ANIMATED_TEXT_DURATION); +} + +void AnimatedText::setColor(int color) +{ + m_color = Color::from8bit(color); +} + +void AnimatedText::setText(const std::string& text) +{ + m_cachedText.setText(text); +} + +bool AnimatedText::merge(const AnimatedTextPtr& other) +{ + if(other->getColor() != m_color) + return false; + + if(other->getCachedText().getFont() != m_cachedText.getFont()) + return false; + + if(m_animationTimer.ticksElapsed() > Otc::ANIMATED_TEXT_DURATION / 2.5) + return false; + + try { + int number = stdext::safe_cast(m_cachedText.getText()); + int otherNumber = stdext::safe_cast(other->getCachedText().getText()); + m_cachedText.setText(std::to_string(number + otherNumber)); + return true; + } catch(...) {} + return false; +} diff --git a/src/client/animatedtext.h b/src/client/animatedtext.h new file mode 100644 index 0000000..ead95dd --- /dev/null +++ b/src/client/animatedtext.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ANIMATEDTEXT_H +#define ANIMATEDTEXT_H + +#include "thing.h" +#include +#include +#include + +// @bindclass +class AnimatedText : public Thing +{ +public: + AnimatedText(); + + void drawText(const Point& dest, const Rect& visibleRect); + + void setColor(int color); + void setText(const std::string& text); + void setOffset(const Point& offset) { m_offset = offset; } + + Color getColor() { return m_color; } + const CachedText& getCachedText() const { return m_cachedText; } + Point getOffset() { return m_offset; } + Timer getTimer() { return m_animationTimer; } + + bool merge(const AnimatedTextPtr& other); + + AnimatedTextPtr asAnimatedText() { return static_self_cast(); } + bool isAnimatedText() { return true; } + std::string getText() { return m_cachedText.getText(); } + +protected: + virtual void onAppear(); + +private: + Color m_color; + Timer m_animationTimer; + CachedText m_cachedText; + Point m_offset; +}; + +#endif diff --git a/src/client/animator.cpp b/src/client/animator.cpp new file mode 100644 index 0000000..15bcadb --- /dev/null +++ b/src/client/animator.cpp @@ -0,0 +1,244 @@ +/* +* Copyright (c) 2010-2017 OTClient +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#include "declarations.h" +#include "animator.h" + +#include +#include +#include +#include + +Animator::Animator() +{ + m_animationPhases = 0; + m_startPhase = 0; + m_loopCount = 0; + m_async = false; + m_currentDuration = 0; + m_currentDirection = AnimDirForward; + m_currentLoop = 0; + m_lastPhaseTicks = 0; + m_isComplete = false; + m_phase = 0; +} + +void Animator::unserialize(int animationPhases, const FileStreamPtr& fin) +{ + m_animationPhases = animationPhases; + m_async = fin->getU8() == 0; + m_loopCount = fin->get32(); + m_startPhase = fin->get8(); + + for(int i = 0; i < m_animationPhases; ++i) { + int minimum = fin->getU32(); + int maximum = fin->getU32(); + m_phaseDurations.push_back(std::make_pair(minimum, std::max(0, maximum - minimum))); + } + + m_phase = getStartPhase(); + + VALIDATE(m_animationPhases == (int)m_phaseDurations.size()); + VALIDATE(m_startPhase >= -1 && m_startPhase < m_animationPhases); +} + +void Animator::serialize(const FileStreamPtr& fin) +{ + fin->addU8(m_async ? 0 : 1); + fin->add32(m_loopCount); + fin->add8(m_startPhase); + + for(auto& phase : m_phaseDurations) { + fin->addU32(phase.first); + fin->addU32(phase.first + phase.second); + } +} + +void Animator::setPhase(int phase) +{ + if(m_phase == phase) return; + + if(m_async) { + if(phase == AnimPhaseAsync) + m_phase = 0; + else if(phase == AnimPhaseRandom) + m_phase = (int)stdext::random_range(0, (long)m_animationPhases); + else if(phase >= 0 && phase < m_animationPhases) + m_phase = phase; + else + m_phase = getStartPhase(); + + m_isComplete = false; + m_lastPhaseTicks = g_clock.millis(); + m_currentDuration = getPhaseDuration(phase); + m_currentLoop = 0; + } else + calculateSynchronous(); +} + +int Animator::getPhase() +{ + ticks_t ticks = g_clock.millis(); + if(ticks != m_lastPhaseTicks && !m_isComplete) { + int elapsedTicks = (int)(ticks - m_lastPhaseTicks); + if(elapsedTicks >= m_currentDuration) { + int phase = 0; + if(m_loopCount < 0) + phase = getPingPongPhase(); + else + phase = getLoopPhase(); + + if(m_phase != phase) { + int duration = getPhaseDuration(phase) - (elapsedTicks - m_currentDuration); + if(duration < 0 && !m_async) { + calculateSynchronous(); + } else { + m_phase = phase; + m_currentDuration = std::max(0, duration); + } + } else + m_isComplete = true; + } else + m_currentDuration -= elapsedTicks; + + m_lastPhaseTicks = ticks; + } + return m_phase; +} + +int Animator::getPhaseAt(Timer& timer, int lastPhase) +{ + static int rand_val = 6; + ticks_t time = timer.ticksElapsed(); + for (int i = lastPhase; i < m_animationPhases; ++i) { + int phaseDuration = m_phaseDurations[i].second == 0 ? + m_phaseDurations[i].first : m_phaseDurations[i].first + rand_val % (m_phaseDurations[i].second); + rand_val = rand_val * 7 + 11; + if (time < phaseDuration) { + timer.restart(); + timer.adjust(-time); + return i; + } + time -= phaseDuration; + } + return -1; + /* + ticks_t total = 0; + + for (const auto &pair : m_phaseDurations) { + total += std::get<1>(pair); + + if (time < total) { + return index; + } + + ++index; + } + + return std::min(index, m_animationPhases - 1); + */ +} + +int Animator::getStartPhase() +{ + if(m_startPhase > -1) + return m_startPhase; + return (int)stdext::random_range(0, (long)m_animationPhases); +} + +void Animator::resetAnimation() +{ + m_isComplete = false; + m_currentDirection = AnimDirForward; + m_currentLoop = 0; + setPhase(AnimPhaseAutomatic); +} + +int Animator::getPingPongPhase() +{ + int count = m_currentDirection == AnimDirForward ? 1 : -1; + int nextPhase = m_phase + count; + if(nextPhase < 0 || nextPhase >= m_animationPhases) { + m_currentDirection = m_currentDirection == AnimDirForward ? AnimDirBackward : AnimDirForward; + count *= -1; + } + return m_phase + count; +} + +int Animator::getLoopPhase() +{ + int nextPhase = m_phase + 1; + if(nextPhase < m_animationPhases) + return nextPhase; + + if(m_loopCount == 0) + return 0; + + if(m_currentLoop < (m_loopCount - 1)) { + m_currentLoop++; + return 0; + } + + return m_phase; +} + +int Animator::getPhaseDuration(int phase) +{ + VALIDATE(phase < (int)m_phaseDurations.size()); + + auto& data = m_phaseDurations.at(phase); + if (data.second == 0) return data.first; + int min = data.first; + int max = min + data.second; + return (int)stdext::random_range((long)min, (long)max); +} + +void Animator::calculateSynchronous() +{ + int totalDuration = 0; + for(int i = 0; i < m_animationPhases; i++) + totalDuration += getPhaseDuration(i); + + ticks_t ticks = g_clock.millis(); + int elapsedTicks = (int)(ticks % totalDuration); + int totalTime = 0; + for(int i = 0; i < m_animationPhases; i++) { + int duration = getPhaseDuration(i); + if(elapsedTicks >= totalTime && elapsedTicks < totalTime + duration) { + m_phase = i; + m_currentDuration = duration - (elapsedTicks - totalTime); + break; + } + totalTime += duration; + } + m_lastPhaseTicks = ticks; +} + +ticks_t Animator::getTotalDuration() +{ + ticks_t time = 0; + for (const auto &pair: m_phaseDurations) { + time += pair.first + pair.second; + } + + return time; +} diff --git a/src/client/animator.h b/src/client/animator.h new file mode 100644 index 0000000..61676b4 --- /dev/null +++ b/src/client/animator.h @@ -0,0 +1,87 @@ +/* +* Copyright (c) 2010-2017 OTClient +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#ifndef ANIMATOR_H +#define ANIMATOR_H + +#include "declarations.h" + +#include +#include + +enum AnimationPhase : int16 +{ + AnimPhaseAutomatic = -1, + AnimPhaseRandom = 254, + AnimPhaseAsync = 255, +}; + +enum AnimationDirection : uint8 +{ + AnimDirForward = 0, + AnimDirBackward = 1 +}; + +class Animator : public stdext::shared_object +{ +public: + Animator(); + + void unserialize(int animationPhases, const FileStreamPtr& fin); + void serialize(const FileStreamPtr& fin); + + void setPhase(int phase); + int getPhase(); + int getPhaseAt(Timer& timer, int lastPhase = 0); + + int getStartPhase(); + int getAnimationPhases() { return m_animationPhases; } + bool isAsync() { return m_async; } + bool isComplete() { return m_isComplete; } + + ticks_t getTotalDuration(); + + void resetAnimation(); + +private: + int getPingPongPhase(); + int getLoopPhase(); + int getPhaseDuration(int phase); + void calculateSynchronous(); + + int m_animationPhases; + int m_startPhase; + int m_loopCount; + bool m_async; + std::vector< std::pair > m_phaseDurations; + + int m_currentDuration; + AnimationDirection m_currentDirection; + int m_currentLoop; + + ticks_t m_lastPhaseTicks; + bool m_isComplete; + + int m_phase; +}; + +#endif diff --git a/src/client/client.cpp b/src/client/client.cpp new file mode 100644 index 0000000..54d9103 --- /dev/null +++ b/src/client/client.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "client.h" +#include +#include +#include +#include "game.h" +#include "map.h" +#include "shadermanager.h" +#include "spritemanager.h" +#include "minimap.h" +#include + +Client g_client; + +void Client::init(std::vector& args) +{ + // register needed lua functions + registerLuaFunctions(); + + g_map.init(); + g_minimap.init(); + g_game.init(); + g_shaders.init(); + g_things.init(); +} + +void Client::terminate() +{ + g_creatures.terminate(); + g_game.terminate(); + g_map.terminate(); + g_minimap.terminate(); + g_things.terminate(); + g_sprites.terminate(); + g_shaders.terminate(); +} diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000..bba39d4 --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "global.h" + +class Client +{ +public: + void init(std::vector& args); + void terminate(); + void registerLuaFunctions(); +}; + +extern Client g_client; + +#endif diff --git a/src/client/const.h b/src/client/const.h new file mode 100644 index 0000000..6a1eafc --- /dev/null +++ b/src/client/const.h @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CLIENT_CONST_H +#define CLIENT_CONST_H + +namespace Otc +{ + enum : int { + TILE_PIXELS = 32, + MAX_ELEVATION = 24, + + SEA_FLOOR = 7, + MAX_Z = 15, + UNDERGROUND_FLOOR = SEA_FLOOR+1, + AWARE_UNDEGROUND_FLOOR_RANGE = 2, + + INVISIBLE_TICKS_PER_FRAME = 500, + INVISIBLE_TICKS_PER_FRAME_FAST = 100, + ITEM_TICKS_PER_FRAME = 500, + ITEM_TICKS_PER_FRAME_FAST = 100, + ANIMATED_TEXT_DURATION = 1000, + STATIC_DURATION_PER_CHARACTER = 60, + MIN_STATIC_TEXT_DURATION = 3000, + MAX_STATIC_TEXT_WIDTH = 200, + MAX_AUTOWALK_STEPS_RETRY = 10, + MAX_AUTOWALK_DIST = 127 + }; + + enum DepthConst { + MAX_DEPTH = 16384 - 2048 + }; + + enum DrawFlags { + DrawGround = 1, + DrawGroundBorders = 2, + DrawOnBottom = 4, + DrawOnTop = 8, + DrawItems = 16, + DrawCreatures = 32, + DrawEffects = 64, + DrawMissiles = 128, + DrawCreaturesInformation = 256, + DrawStaticTexts = 512, + DrawAnimatedTexts = 1024, + DrawAnimations = 2048, + DrawBars = 4096, + DrawNames = 8192, + DrawLights = 16384, + DrawManaBar = 32768, + DontDrawLocalPlayer = 65536, + DrawIcons = 131072, + DrawWalls = DrawOnBottom | DrawOnTop, + DrawEverything = DrawGround | DrawGroundBorders | DrawWalls | DrawItems | + DrawCreatures | DrawEffects | DrawMissiles | DrawCreaturesInformation | + DrawStaticTexts | DrawAnimatedTexts | DrawAnimations | DrawBars | DrawNames | + DrawLights | DrawManaBar | DrawIcons + }; + + enum DatOpts { + DatGround = 0, + DatGroundClip, + DatOnBottom, + DatOnTop, + DatContainer, + DatStackable, + DatForceUse, + DatMultiUse, + DatWritable, + DatWritableOnce, + DatFluidContainer, + DatSplash, + DatBlockWalk, + DatNotMoveable, + DatBlockProjectile, + DatBlockPathFind, + DatPickupable, + DatHangable, + DatHookSouth, + DatHookEast, + DatRotable, + DatLight, + DatDontHide, + DatTranslucent, + DatDisplacement, + DatElevation, + DatLyingCorpse, + DatAnimateAlways, + DatMinimapColor, + DatLensHelp, + DatFullGround, + DatIgnoreLook, + DatCloth, + DatAnimation, // lastest tibia + DatLastOpt = 255 + }; + + enum InventorySlot { + InventorySlotHead = 1, + InventorySlotNecklace, + InventorySlotBackpack, + InventorySlotArmor, + InventorySlotRight, + InventorySlotLeft, + InventorySlotLegs, + InventorySlotFeet, + InventorySlotRing, + InventorySlotAmmo, + InventorySlotPurse, + InventorySlotExt1, + InventorySlotExt2, + InventorySlotExt3, + InventorySlotExt4, + LastInventorySlot + }; + + enum Statistic { + Health = 0, + MaxHealth, + FreeCapacity, + Experience, + Level, + LevelPercent, + Mana, + MaxMana, + MagicLevel, + MagicLevelPercent, + Soul, + Stamina, + LastStatistic + }; + + enum Skill { + Fist = 0, + Club, + Sword, + Axe, + Distance, + Shielding, + Fishing, + CriticalChance, + CriticalDamage, + LifeLeechChance, + LifeLeechAmount, + ManaLeechChance, + ManaLeechAmount, + LastSkill + }; + + enum Direction { + North = 0, + East, + South, + West, + NorthEast, + SouthEast, + SouthWest, + NorthWest, + InvalidDirection + }; + + enum FluidsColor { + FluidTransparent = 0, + FluidBlue, + FluidRed, + FluidBrown, + FluidGreen, + FluidYellow, + FluidWhite, + FluidPurple + }; + + enum FluidsType { + FluidNone = 0, + FluidWater, + FluidMana, + FluidBeer, + FluidOil, + FluidBlood, + FluidSlime, + FluidMud, + FluidLemonade, + FluidMilk, + FluidWine, + FluidHealth, + FluidUrine, + FluidRum, + FluidFruidJuice, + FluidCoconutMilk, + FluidTea, + FluidMead + }; + + enum FightModes { + FightOffensive = 1, + FightBalanced = 2, + FightDefensive = 3 + }; + + enum ChaseModes { + DontChase = 0, + ChaseOpponent = 1 + }; + + enum PVPModes { + WhiteDove = 0, + WhiteHand = 1, + YellowHand = 2, + RedFist = 3 + }; + + enum PlayerSkulls { + SkullNone = 0, + SkullYellow, + SkullGreen, + SkullWhite, + SkullRed, + SkullBlack, + SkullOrange + }; + + enum PlayerShields { + ShieldNone = 0, + ShieldWhiteYellow, // 1 party leader + ShieldWhiteBlue, // 2 party member + ShieldBlue, // 3 party member sexp off + ShieldYellow, // 4 party leader sexp off + ShieldBlueSharedExp, // 5 party member sexp on + ShieldYellowSharedExp, // 6 // party leader sexp on + ShieldBlueNoSharedExpBlink, // 7 party member sexp inactive guilty + ShieldYellowNoSharedExpBlink, // 8 // party leader sexp inactive guilty + ShieldBlueNoSharedExp, // 9 party member sexp inactive innocent + ShieldYellowNoSharedExp, // 10 party leader sexp inactive innocent + ShieldGray // 11 member of another party + }; + + enum PlayerEmblems { + EmblemNone = 0, + EmblemGreen, + EmblemRed, + EmblemBlue, + EmblemMember, + EmblemOther + }; + + enum CreatureIcons { + NpcIconNone = 0, + NpcIconChat, + NpcIconTrade, + NpcIconQuest, + NpcIconTradeQuest + }; + + enum PlayerStates { + IconNone = 0, + IconPoison = 1, + IconBurn = 2, + IconEnergy = 4, + IconDrunk = 8, + IconManaShield = 16, + IconParalyze = 32, + IconHaste = 64, + IconSwords = 128, + IconDrowning = 256, + IconFreezing = 512, + IconDazzled = 1024, + IconCursed = 2048, + IconPartyBuff = 4096, + IconPzBlock = 8192, + IconPz = 16384, + IconBleeding = 32768, + IconHungry = 65536 + }; + + enum MessageMode { + MessageNone = 0, + MessageSay = 1, + MessageWhisper = 2, + MessageYell = 3, + MessagePrivateFrom = 4, + MessagePrivateTo = 5, + MessageChannelManagement = 6, + MessageChannel = 7, + MessageChannelHighlight = 8, + MessageSpell = 9, + MessageNpcFrom = 10, + MessageNpcTo = 11, + MessageGamemasterBroadcast = 12, + MessageGamemasterChannel = 13, + MessageGamemasterPrivateFrom = 14, + MessageGamemasterPrivateTo = 15, + MessageLogin = 16, + MessageWarning = 17, + MessageGame = 18, + MessageFailure = 19, + MessageLook = 20, + MessageDamageDealed = 21, + MessageDamageReceived = 22, + MessageHeal = 23, + MessageExp = 24, + MessageDamageOthers = 25, + MessageHealOthers = 26, + MessageExpOthers = 27, + MessageStatus = 28, + MessageLoot = 29, + MessageTradeNpc = 30, + MessageGuild = 31, + MessagePartyManagement = 32, + MessageParty = 33, + MessageBarkLow = 34, + MessageBarkLoud = 35, + MessageReport = 36, + MessageHotkeyUse = 37, + MessageTutorialHint = 38, + MessageThankyou = 39, + MessageMarket = 40, + MessageMana = 41, + MessageBeyondLast = 42, + + // deprecated + MessageMonsterYell = 43, + MessageMonsterSay = 44, + MessageRed = 45, + MessageBlue = 46, + MessageRVRChannel = 47, + MessageRVRAnswer = 48, + MessageRVRContinue = 49, + MessageGameHighlight = 50, + MessageNpcFromStartBlock = 51, + LastMessage = 52, + MessageInvalid = 255 + }; + + enum GameFeature { + GameProtocolChecksum = 1, + GameAccountNames = 2, + GameChallengeOnLogin = 3, + GamePenalityOnDeath = 4, + GameNameOnNpcTrade = 5, + GameDoubleFreeCapacity = 6, + GameDoubleExperience = 7, + GameTotalCapacity = 8, + GameSkillsBase = 9, + GamePlayerRegenerationTime = 10, + GameChannelPlayerList = 11, + GamePlayerMounts = 12, + GameEnvironmentEffect = 13, + GameCreatureEmblems = 14, + GameItemAnimationPhase = 15, + GameMagicEffectU16 = 16, + GamePlayerMarket = 17, + GameSpritesU32 = 18, + GameTileAddThingWithStackpos = 19, + GameOfflineTrainingTime = 20, + GamePurseSlot = 21, + GameFormatCreatureName = 22, + GameSpellList = 23, + GameClientPing = 24, + GameExtendedClientPing = 25, + GameDoubleHealth = 28, + GameDoubleSkills = 29, + GameChangeMapAwareRange = 30, + GameMapMovePosition = 31, + GameAttackSeq = 32, + GameBlueNpcNameColor = 33, + GameDiagonalAnimatedText = 34, + GameLoginPending = 35, + GameNewSpeedLaw = 36, + GameForceFirstAutoWalkStep = 37, + GameMinimapRemove = 38, + GameDoubleShopSellAmount = 39, + GameContainerPagination = 40, + GameThingMarks = 41, + GameLooktypeU16 = 42, + GamePlayerStamina = 43, + GamePlayerAddons = 44, + GameMessageStatements = 45, + GameMessageLevel = 46, + GameNewFluids = 47, + GamePlayerStateU16 = 48, + GameNewOutfitProtocol = 49, + GamePVPMode = 50, + GameWritableDate = 51, + GameAdditionalVipInfo = 52, + GameBaseSkillU16 = 53, + GameCreatureIcons = 54, + GameHideNpcNames = 55, + GameSpritesAlphaChannel = 56, + GamePremiumExpiration = 57, + GameBrowseField = 58, + GameEnhancedAnimations = 59, + GameOGLInformation = 60, + GameMessageSizeCheck = 61, + GamePreviewState = 62, + GameLoginPacketEncryption = 63, + GameClientVersion = 64, + GameContentRevision = 65, + GameExperienceBonus = 66, + GameAuthenticator = 67, + GameUnjustifiedPoints = 68, + GameSessionKey = 69, + GameDeathType = 70, + GameIdleAnimations = 71, + GameKeepUnawareTiles = 72, + GameIngameStore = 73, + GameIngameStoreHighlights = 74, + GameIngameStoreServiceType = 75, + GameAdditionalSkills = 76, + GameDistanceEffectU16 = 77, + GamePrey = 78, + GameDoubleMagicLevel = 79, + + GameExtendedOpcode = 80, + GameMinimapLimitedToSingleFloor = 81, + + GameDoubleLevel = 83, + GameDoubleSoul = 84, + GameDoublePlayerGoodsMoney = 85, + GameCreatureWalkthrough = 86, + GameDoubleTradeMoney = 87, + + // 90-99 otclientv8 features + GameNewWalking = 90, + GameSlowerManualWalking = 91, + + GameItemTooltip = 93, + + GameBot = 95, + GameBiggerMapCache = 96, + GameForceLight = 97, + GameNoDebug = 98, + GameBotProtection = 99, + + // Custom features for customer + GameFasterAnimations = 101, + GameCenteredOutfits = 102, + GameSendIdentifiers = 103, + GameWingsAndAura = 104, + + // advanced features + GamePacketSizeU32 = 110, + GamePacketCompression = 111, + + LastGameFeature = 120 + }; + + enum PathFindResult { + PathFindResultOk = 0, + PathFindResultSamePosition, + PathFindResultImpossible, + PathFindResultTooFar, + PathFindResultNoWay + }; + + enum PathFindFlags { + PathFindAllowNotSeenTiles = 1, + PathFindAllowCreatures = 2, + PathFindAllowNonPathable = 4, + PathFindAllowNonWalkable = 8, + PathFindIgnoreCreatures = 16 + }; + + enum AutomapFlags { + MapMarkTick = 0, + MapMarkQuestion, + MapMarkExclamation, + MapMarkStar, + MapMarkCross, + MapMarkTemple, + MapMarkKiss, + MapMarkShovel, + MapMarkSword, + MapMarkFlag, + MapMarkLock, + MapMarkBag, + MapMarkSkull, + MapMarkDollar, + MapMarkRedNorth, + MapMarkRedSouth, + MapMarkRedEast, + MapMarkRedWest, + MapMarkGreenNorth, + MapMarkGreenSouth + }; + + enum VipState { + VipStateOffline = 0, + VipStateOnline = 1, + VipStatePending = 2 + }; + + enum SpeedFormula { + SpeedFormulaA = 0, + SpeedFormulaB, + SpeedFormulaC, + LastSpeedFormula + }; + + enum Blessings { + BlessingNone = 0, + BlessingAdventurer = 1, + BlessingSpiritualShielding = 1 << 1, + BlessingEmbraceOfTibia = 1 << 2, + BlessingFireOfSuns = 1 << 3, + BlessingWisdomOfSolitude = 1 << 4, + BlessingSparkOfPhoenix = 1 << 5 + }; + + enum DeathType { + DeathRegular = 0, + DeathBlessed = 1 + }; + + enum StoreProductTypes { + ProductTypeOther = 0, + ProductTypeNameChange = 1 + }; + + enum StoreErrorTypes { + StoreNoError = -1, + StorePurchaseError = 0, + StoreNetworkError = 1, + StoreHistoryError = 2, + StoreTransferError = 3, + StoreInformation = 4 + }; + + enum StoreStates { + StateNone = 0, + StateNew = 1, + StateSale = 2, + StateTimed = 3 + }; + + enum PreySlotNum_t : uint8_t { + PREY_SLOTNUM_FIRST, + PREY_SLOTNUM_SECOND, + PREY_SLOTNUM_THIRD, + PREY_SLOTNUM_LAST = PREY_SLOTNUM_THIRD + }; + enum PreyState_t : uint8_t { + PREY_STATE_LOCKED = 0, + PREY_STATE_INACTIVE = 1, + PREY_STATE_ACTIVE = 2, + PREY_STATE_SELECTION = 3, + PREY_STATE_SELECTION_CHANGE_MONSTER = 4, // unused; hmm + PREY_STATE_SELECTION_FROMALL = 5, + PREY_STATE_CHANGE_FROMALL = 6, // unused :( + }; + enum PreyMessageDialog_t : uint8_t { + //PREY_MESSAGEDIALOG_IMBUEMENT_SUCCESS = 0, + //PREY_MESSAGEDIALOG_IMBUEMENT_ERROR = 1, + //PREY_MESSAGEDIALOG_IMBUEMENT_ROLL_FAILED = 2, + //PREY_MESSAGEDIALOG_IMBUEMENT_STATION_NOT_FOUND = 3, + //PREY_MESSAGEDIALOG_IMBUEMENT_CHARM_SUCCESS = 10, + //PREY_MESSAGEDIALOG_IMBUEMENT_CHARM_ERROR = 11, + PREY_MESSAGEDIALOG_PREY_MESSAGE = 20, + PREY_MESSAGEDIALOG_PREY_ERROR = 21, + }; + enum PreyResourceType_t : uint8_t { + PREY_RESOURCETYPE_BANK_GOLD = 0, + PREY_RESOURCETYPE_INVENTORY_GOLD = 1, + PREY_RESOURCETYPE_PREY_BONUS_REROLLS = 10 + }; + enum PreyBonusType_t : uint8_t { + PREY_BONUS_DAMAGE_BOOST = 0, + PREY_BONUS_DAMAGE_REDUCTION = 1, + PREY_BONUS_XP_BONUS = 2, + PREY_BONUS_IMPROVED_LOOT = 3, + PREY_BONUS_NONE = 4, // internal usage but still added to client; + PREY_BONUS_FIRST = PREY_BONUS_DAMAGE_BOOST, + PREY_BONUS_LAST = PREY_BONUS_IMPROVED_LOOT, + }; + enum PreyAction_t : uint8_t { + PREY_ACTION_LISTREROLL = 0, + PREY_ACTION_BONUSREROLL = 1, + PREY_ACTION_MONSTERSELECTION = 2, + PREY_ACTION_REQUEST_ALL_MONSTERS = 3, + PREY_ACTION_CHANGE_FROM_ALL = 4, + PREY_ACTION_LOCK_PREY = 5, + + }; + enum PreyConfigState { + PREY_CONFIG_STATE_FREE, + PREY_CONFIG_STATE_PREMIUM, + PREY_CONFIG_STATE_TIBIACOINS + }; + enum PreyUnlockState_t : uint8_t { + PREY_UNLOCK_STORE_AND_PREMIUM = 0, + PREY_UNLOCK_STORE = 1, + PREY_UNLOCK_NONE = 2, + }; +} + +#endif diff --git a/src/client/container.cpp b/src/client/container.cpp new file mode 100644 index 0000000..f6df471 --- /dev/null +++ b/src/client/container.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "container.h" +#include "item.h" + +Container::Container(int id, int capacity, const std::string& name, const ItemPtr& containerItem, bool hasParent, bool isUnlocked, bool hasPages, int containerSize, int firstIndex) +{ + m_id = id; + m_capacity = capacity; + m_name = name; + m_containerItem = containerItem; + m_hasParent = hasParent; + m_closed = false; + m_unlocked = isUnlocked; + m_hasPages = hasPages; + m_size = containerSize; + m_firstIndex = firstIndex; +} + +ItemPtr Container::getItem(int slot) +{ + if(slot < 0 || slot >= (int)m_items.size()) + return nullptr; + return m_items[slot]; +} + +void Container::onOpen(const ContainerPtr& previousContainer) +{ + callLuaField("onOpen", previousContainer); +} + +void Container::onClose() +{ + m_closed = true; + callLuaField("onClose"); +} + +void Container::onAddItem(const ItemPtr& item, int slot) +{ + slot -= m_firstIndex; + + m_size++; + // indicates that there is a new item on next page + if(m_hasPages && slot > m_capacity) { + callLuaField("onSizeChange", m_size); + return; + } + + if(slot == 0) + m_items.push_front(item); + else + m_items.push_back(item); + updateItemsPositions(); + + callLuaField("onSizeChange", m_size); + callLuaField("onAddItem", slot, item); +} + +ItemPtr Container::findItemById(uint itemId, int subType) +{ + for(const ItemPtr item : m_items) + if(item->getId() == itemId && (subType == -1 || item->getSubType() == subType)) + return item; + return nullptr; +} + +void Container::onAddItems(const std::vector& items) +{ + for(const ItemPtr& item : items) + m_items.push_back(item); + updateItemsPositions(); +} + +void Container::onUpdateItem(int slot, const ItemPtr& item) +{ + slot -= m_firstIndex; + if(slot < 0 || slot >= (int)m_items.size()) { + g_logger.traceError("slot not found"); + return; + } + + ItemPtr oldItem = m_items[slot]; + m_items[slot] = item; + item->setPosition(getSlotPosition(slot)); + + callLuaField("onUpdateItem", slot, item, oldItem); +} + +void Container::onRemoveItem(int slot, const ItemPtr& lastItem) +{ + slot -= m_firstIndex; + if(m_hasPages && slot >= (int)m_items.size()) { + m_size--; + callLuaField("onSizeChange", m_size); + return; + } + + if(slot < 0 || slot >= (int)m_items.size()) { + g_logger.traceError("slot not found"); + return; + } + + ItemPtr item = m_items[slot]; + m_items.erase(m_items.begin() + slot); + + + if(lastItem) { + onAddItem(lastItem, m_firstIndex + m_capacity - 1); + m_size--; + } + m_size--; + + updateItemsPositions(); + + callLuaField("onSizeChange", m_size); + callLuaField("onRemoveItem", slot, item); +} + +void Container::updateItemsPositions() +{ + for(int slot = 0; slot < (int)m_items.size(); ++slot) + m_items[slot]->setPosition(getSlotPosition(slot)); +} diff --git a/src/client/container.h b/src/client/container.h new file mode 100644 index 0000000..49d12ee --- /dev/null +++ b/src/client/container.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CONTAINER_H +#define CONTAINER_H + +#include "declarations.h" +#include "item.h" + +#include + +// @bindclass +class Container : public LuaObject +{ +protected: + Container(int id, int capacity, const std::string& name, const ItemPtr& containerItem, bool hasParent, bool isUnlocked, bool hasPages, int containerSize, int firstIndex); + +public: + ItemPtr getItem(int slot); + std::deque getItems() { return m_items; } + int getItemsCount() { return m_items.size(); } + Position getSlotPosition(int slot) { return Position(0xffff, m_id | 0x40, slot); } + int getId() { return m_id; } + int getCapacity() { return m_capacity; } + ItemPtr getContainerItem() { return m_containerItem; } + std::string getName() { return m_name; } + bool hasParent() { return m_hasParent; } + bool isClosed() { return m_closed; } + bool isUnlocked() { return m_unlocked; } + bool hasPages() { return m_hasPages; } + int getSize() { return m_size; } + int getFirstIndex() { return m_firstIndex; } + ItemPtr findItemById(uint itemId, int subType); + +protected: + void onOpen(const ContainerPtr& previousContainer); + void onClose(); + void onAddItem(const ItemPtr& item, int slot); + void onAddItems(const std::vector& items); + void onUpdateItem(int slot, const ItemPtr& item); + void onRemoveItem(int slot, const ItemPtr& lastItem); + + friend class Game; + +private: + void updateItemsPositions(); + + int m_id; + int m_capacity; + ItemPtr m_containerItem; + std::string m_name; + bool m_hasParent; + bool m_closed; + bool m_unlocked; + bool m_hasPages; + int m_size; + int m_firstIndex; + std::deque m_items; +}; + +#endif diff --git a/src/client/creature.cpp b/src/client/creature.cpp new file mode 100644 index 0000000..b98d67c --- /dev/null +++ b/src/client/creature.cpp @@ -0,0 +1,1101 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "creature.h" +#include "thingtypemanager.h" +#include "localplayer.h" +#include "map.h" +#include "tile.h" +#include "item.h" +#include "game.h" +#include "effect.h" +#include "luavaluecasts_client.h" +#include "lightview.h" + +#include +#include +#include +#include + +#include +#include +#include +#include "spritemanager.h" + +#include +#include + +std::array Creature::m_speedFormula = { -1,-1,-1 }; + +Creature::Creature() : Thing() +{ + m_id = 0; + m_healthPercent = 100; + m_manaPercent = -1; + m_speed = 200; + m_direction = Otc::South; + m_walkDirection = Otc::South; + m_walkAnimationPhase = 0; + m_walkedPixels = 0; + m_skull = Otc::SkullNone; + m_shield = Otc::ShieldNone; + m_emblem = Otc::EmblemNone; + m_type = Proto::CreatureTypeUnknown; + m_icon = Otc::NpcIconNone; + m_lastStepDirection = Otc::InvalidDirection; + m_footLastStep = 0; + m_nameCache.setFont(g_fonts.getFont("verdana-11px-rounded")); + m_nameCache.setAlign(Fw::AlignTopCenter); + m_footStep = 0; + //m_speedFormula.fill(-1); + m_outfitColor = Color::white; + g_stats.addCreature(); +} + +Creature::~Creature() +{ + g_stats.removeCreature(); +} + +void Creature::draw(const Point& dest, bool animate, LightView* lightView) +{ + if (!canBeSeen()) + return; + + Point creatureCenter = dest + m_walkOffset - getDisplacement() + Point(Otc::TILE_PIXELS / 2, Otc::TILE_PIXELS / 2); + drawBottomWidgets(creatureCenter, m_walking ? m_walkDirection : m_direction); + + Point animationOffset = animate ? m_walkOffset : Point(0, 0); + if (m_outfit.getCategory() != ThingCategoryCreature) + animationOffset -= getDisplacement(); + + if (m_showTimedSquare && animate) { + g_drawQueue->addBoundingRect(Rect(dest + (animationOffset - getDisplacement() + 2), Size(28, 28)), 2, m_timedSquareColor); + } + + if (m_showStaticSquare && animate) { + g_drawQueue->addBoundingRect(Rect(dest + (animationOffset - getDisplacement()), Size(Otc::TILE_PIXELS, Otc::TILE_PIXELS)), 2, m_staticSquareColor); + } + + size_t drawQueueSize = g_drawQueue->size(); + m_outfit.draw(dest + animationOffset, m_walking ? m_walkDirection : m_direction, m_walkAnimationPhase, true, lightView); + if (m_marked) { + g_drawQueue->setMark(drawQueueSize, updatedMarkedColor()); + } + + drawTopWidgets(creatureCenter, m_walking ? m_walkDirection : m_direction); + + Light light = rawGetThingType()->getLight(); + if (m_light.intensity != light.intensity || m_light.color != light.color) + light = m_light; + + // local player always have a minimum light in complete darkness + if (isLocalPlayer()) { + light.intensity = std::max(light.intensity, 3); + if (light.color == 0 || light.color > 215) + light.color = 215; + } + + if(lightView) + lightView->addLight(creatureCenter, light); +} + +void Creature::drawOutfit(const Rect& destRect, Otc::Direction direction, const Color& color) +{ + if (direction == Otc::InvalidDirection) + direction = m_direction; + + m_outfit.draw(destRect, direction, 0, false); +} + +void Creature::drawInformation(const Point& point, bool useGray, const Rect& parentRect, int drawFlags) +{ + if (m_healthPercent < 1) // creature is dead + return; + + Color fillColor = Color(96, 96, 96); + + if (!useGray) + fillColor = m_informationColor; + + // calculate main rects + Rect backgroundRect = Rect(point.x + m_informationOffset.x - (13.5), point.y + m_informationOffset.y, 27, 4); + backgroundRect.bind(parentRect); + + //debug + if (g_extras.debugWalking) { + int footDelay = (getStepDuration(true)) / 3; + int footAnimPhases = getWalkAnimationPhases() - 1; + m_nameCache.setText(stdext::format("%i %i %i %i %i\n %i %i\n%i %i %i\n%i %i %i %i %i", + (int)m_stepDuration, (int)getStepDuration(true), getStepDuration(false), (int)m_walkedPixels, (int)m_walkTimer.ticksElapsed(), + (int)m_walkOffset.x, (int)m_walkOffset.y, + (int)m_speed, (int)getTile()->getGroundSpeed(), (int)g_game.getWalkId(), + (int)(g_clock.millis() - m_footLastStep), (int)footDelay, (int)footAnimPhases, (int)m_walkAnimationPhase, (int)stdext::millis())); + } + + Size nameSize = m_nameCache.getTextSize(); + Rect textRect = Rect(point.x + m_informationOffset.x - nameSize.width() / 2.0, point.y + m_informationOffset.y - 12, nameSize); + textRect.bind(parentRect); + + // distance them + uint32 offset = 12; + if (isLocalPlayer()) { + offset *= 2; + } + + if (textRect.top() == parentRect.top()) + backgroundRect.moveTop(textRect.top() + offset); + if (backgroundRect.bottom() == parentRect.bottom()) + textRect.moveTop(backgroundRect.top() - offset); + + // health rect is based on background rect, so no worries + Rect healthRect = backgroundRect.expanded(-1); + healthRect.setWidth((m_healthPercent / 100.0) * 25); + + // draw + if (g_game.getFeature(Otc::GameBlueNpcNameColor) && isNpc() && m_healthPercent == 100 && !useGray) + fillColor = Color(0x66, 0xcc, 0xff); + + if (drawFlags & Otc::DrawBars && (!isNpc() || !g_game.getFeature(Otc::GameHideNpcNames))) { + g_drawQueue->addFilledRect(backgroundRect, Color::black); + g_drawQueue->addFilledRect(healthRect, fillColor); + + if (drawFlags & Otc::DrawManaBar) { + int manaPercent = m_manaPercent; + if (isLocalPlayer()) { + LocalPlayerPtr player = g_game.getLocalPlayer(); + if (player) { + double maxMana = player->getMaxMana(); + if (maxMana == 0) { + manaPercent = 100; + } else { + manaPercent = (player->getMana() * 100) / maxMana; + } + } + } + if (manaPercent >= 0) { + backgroundRect.moveTop(backgroundRect.bottom()); + g_drawQueue->addFilledRect(backgroundRect, Color::black); + + Rect manaRect = backgroundRect.expanded(-1); + manaRect.setWidth(((float)manaPercent / 100.f) * 25); + g_drawQueue->addFilledRect(manaRect, Color::blue); + } + } + } + + if (drawFlags & Otc::DrawNames) { + m_nameCache.draw(textRect, fillColor); + + if (m_text) { + auto extraTextSize = m_text->getCachedText().getTextSize(); + Rect extraTextRect = Rect(point.x + m_informationOffset.x - extraTextSize.width() / 2.0, point.y + m_informationOffset.y + 15, extraTextSize); + m_text->drawText(extraTextRect.center(), extraTextRect); + } + } + + if (!(drawFlags & Otc::DrawIcons)) + return; + + if (m_skull != Otc::SkullNone && m_skullTexture) { + Rect skullRect = Rect(backgroundRect.x() + 13.5 + 12, backgroundRect.y() + 5, m_skullTexture->getSize()); + g_drawQueue->addTexturedRect(skullRect, m_skullTexture, Rect(0, 0, m_skullTexture->getSize())); + } + if (m_shield != Otc::ShieldNone && m_shieldTexture && m_showShieldTexture) { + Rect shieldRect = Rect(backgroundRect.x() + 13.5, backgroundRect.y() + 5, m_shieldTexture->getSize()); + g_drawQueue->addTexturedRect(shieldRect, m_shieldTexture, Rect(0, 0, m_shieldTexture->getSize())); + } + if (m_emblem != Otc::EmblemNone && m_emblemTexture) { + Rect emblemRect = Rect(backgroundRect.x() + 13.5 + 12, backgroundRect.y() + 16, m_emblemTexture->getSize()); + g_drawQueue->addTexturedRect(emblemRect, m_emblemTexture, Rect(0, 0, m_emblemTexture->getSize())); + } + if (m_type != Proto::CreatureTypeUnknown && m_typeTexture) { + Rect typeRect = Rect(backgroundRect.x() + 13.5 + 12 + 12, backgroundRect.y() + 16, m_typeTexture->getSize()); + g_drawQueue->addTexturedRect(typeRect, m_typeTexture, Rect(0, 0, m_typeTexture->getSize())); + } + if (m_icon != Otc::NpcIconNone && m_iconTexture) { + Rect iconRect = Rect(backgroundRect.x() + 13.5 + 12, backgroundRect.y() + 5, m_iconTexture->getSize()); + g_drawQueue->addTexturedRect(iconRect, m_iconTexture, Rect(0, 0, m_iconTexture->getSize())); + } +} + +bool Creature::isInsideOffset(Point offset) +{ + Rect rect(getDrawOffset() - getDisplacement(), Size(Otc::TILE_PIXELS, Otc::TILE_PIXELS)); + return rect.contains(offset); +} + +void Creature::turn(Otc::Direction direction) +{ + setDirection(direction); + callLuaField("onTurn", direction); +} + +void Creature::walk(const Position& oldPos, const Position& newPos) +{ + if (oldPos == newPos) + return; + + // get walk direction + m_lastStepDirection = oldPos.getDirectionFromPosition(newPos); + m_lastStepFromPosition = oldPos; + m_lastStepToPosition = newPos; + + // set current walking direction + setDirection(m_lastStepDirection); + m_walkDirection = m_direction; + + // starts counting walk + m_walking = true; + m_walkTimer.restart(); + m_walkedPixels = 0; + + if (m_walkFinishAnimEvent) { + m_walkFinishAnimEvent->cancel(); + m_walkFinishAnimEvent = nullptr; + } + + // starts updating walk + nextWalkUpdate(); +} + +void Creature::stopWalk() +{ + if (!m_walking) + return; + + // stops the walk right away + terminateWalk(); +} + +void Creature::jump(int height, int duration) +{ + if (!m_jumpOffset.isNull()) + return; + + m_jumpTimer.restart(); + m_jumpHeight = height; + m_jumpDuration = duration; + + updateJump(); +} + +void Creature::updateJump() +{ + int t = m_jumpTimer.ticksElapsed(); + double a = -4 * m_jumpHeight / (m_jumpDuration * m_jumpDuration); + double b = +4 * m_jumpHeight / (m_jumpDuration); + + double height = a * t * t + b * t; + int roundHeight = stdext::round(height); + int halfJumpDuration = m_jumpDuration / 2; + + // schedules next update + if (m_jumpTimer.ticksElapsed() < m_jumpDuration) { + m_jumpOffset = PointF(height, height); + + int diff = 0; + if (m_jumpTimer.ticksElapsed() < halfJumpDuration) + diff = 1; + else if (m_jumpTimer.ticksElapsed() > halfJumpDuration) + diff = -1; + + int nextT, i = 1; + do { + nextT = stdext::round((-b + std::sqrt(std::max(b * b + 4 * a * (roundHeight + diff * i), 0.0)) * diff) / (2 * a)); + ++i; + + if (nextT < halfJumpDuration) + diff = 1; + else if (nextT > halfJumpDuration) + diff = -1; + } while (nextT - m_jumpTimer.ticksElapsed() == 0 && i < 3); + + auto self = static_self_cast(); + g_dispatcher.scheduleEvent([self] { + self->updateJump(); + }, nextT - m_jumpTimer.ticksElapsed()); + } else + m_jumpOffset = PointF(0, 0); +} + +void Creature::onPositionChange(const Position& newPos, const Position& oldPos) +{ + callLuaField("onPositionChange", newPos, oldPos); +} + +void Creature::onAppear() +{ + // cancel any disappear event + if (m_disappearEvent) { + m_disappearEvent->cancel(); + m_disappearEvent = nullptr; + } + + // creature appeared the first time or wasn't seen for a long time + if (m_removed) { + stopWalk(); + m_removed = false; + callLuaField("onAppear"); + // walk + } else if (m_oldPosition != m_position && m_oldPosition.isInRange(m_position, 1, 1) && m_allowAppearWalk) { + m_allowAppearWalk = false; + walk(m_oldPosition, m_position); + callLuaField("onWalk", m_oldPosition, m_position); + // teleport + } else if (m_oldPosition != m_position) { + stopWalk(); + callLuaField("onDisappear"); + callLuaField("onAppear"); + } // else turn +} + +void Creature::onDisappear() +{ + if (m_disappearEvent) + m_disappearEvent->cancel(); + + m_oldPosition = m_position; + + // a pair onDisappear and onAppear events are fired even when creatures walks or turns, + // so we must filter + auto self = static_self_cast(); + m_disappearEvent = g_dispatcher.addEvent([self] { + self->m_removed = true; + self->stopWalk(); + + self->callLuaField("onDisappear"); + + // invalidate this creature position + if (!self->isLocalPlayer()) + self->setPosition(Position()); + self->m_oldPosition = Position(); + self->m_disappearEvent = nullptr; + self->clearWidgets(); + }); +} + +void Creature::onDeath() +{ + callLuaField("onDeath"); +} + +int Creature::getWalkAnimationPhases() +{ + if (!getAnimator()) + return getAnimationPhases(); + return getAnimator()->getAnimationPhases() + (g_game.getFeature(Otc::GameIdleAnimations) ? 1 : 0); +} + +void Creature::updateWalkAnimation(int totalPixelsWalked) +{ + // update outfit animation + if (m_outfit.getCategory() != ThingCategoryCreature) + return; + + int footAnimPhases = getWalkAnimationPhases() - 1; + // TODO, should be /2 for <= 810 + int footDelay = getStepDuration(true); + if (footAnimPhases > 0) { + footDelay = ((getStepDuration(true) + 20) / (g_game.getFeature(Otc::GameFasterAnimations) ? footAnimPhases * 2 : footAnimPhases)); + } + if (!g_game.getFeature(Otc::GameFasterAnimations)) + footDelay += 10; + if (footDelay < 20) + footDelay = 20; + + // Since mount is a different outfit we need to get the mount animation phases + if (m_outfit.getMount() != 0) { + ThingType* type = g_things.rawGetThingType(m_outfit.getMount(), m_outfit.getCategory()); + footAnimPhases = std::min(footAnimPhases, type->getAnimationPhases() - 1); + } + + if (footAnimPhases == 0) { + m_walkAnimationPhase = 0; + } else if (g_clock.millis() >= m_footLastStep + footDelay && totalPixelsWalked < 32) { + m_footStep++; + m_walkAnimationPhase = 1 + (m_footStep % footAnimPhases); + m_footLastStep = (g_clock.millis() - m_footLastStep) > footDelay * 1.5 ? g_clock.millis() : m_footLastStep + footDelay; + } else if (m_walkAnimationPhase == 0 && totalPixelsWalked < 32) { + m_walkAnimationPhase = 1 + (m_footStep % footAnimPhases); + } + + if (totalPixelsWalked == 32 && !m_walkFinishAnimEvent) { + auto self = static_self_cast(); + m_walkFinishAnimEvent = g_dispatcher.scheduleEvent([self] { + self->m_footStep = 0; + self->m_walkAnimationPhase = 0; + self->m_walkFinishAnimEvent = nullptr; + }, 50); + } + +} + +void Creature::updateWalkOffset(int totalPixelsWalked, bool inNextFrame) +{ + Point& walkOffset = inNextFrame ? m_walkOffsetInNextFrame : m_walkOffset; + walkOffset = Point(0, 0); + if (m_walkDirection == Otc::North || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::NorthWest) + walkOffset.y = 32 - totalPixelsWalked; + else if (m_walkDirection == Otc::South || m_walkDirection == Otc::SouthEast || m_walkDirection == Otc::SouthWest) + walkOffset.y = totalPixelsWalked - 32; + + if (m_walkDirection == Otc::East || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::SouthEast) + walkOffset.x = totalPixelsWalked - 32; + else if (m_walkDirection == Otc::West || m_walkDirection == Otc::NorthWest || m_walkDirection == Otc::SouthWest) + walkOffset.x = 32 - totalPixelsWalked; +} + +void Creature::updateWalkingTile() +{ + // determine new walking tile + TilePtr newWalkingTile; + Rect virtualCreatureRect(Otc::TILE_PIXELS + (m_walkOffset.x - getDisplacementX()), + Otc::TILE_PIXELS + (m_walkOffset.y - getDisplacementY()), + Otc::TILE_PIXELS, Otc::TILE_PIXELS); + for (int xi = -1; xi <= 1 && !newWalkingTile; ++xi) { + for (int yi = -1; yi <= 1 && !newWalkingTile; ++yi) { + Rect virtualTileRect((xi + 1) * Otc::TILE_PIXELS, (yi + 1) * Otc::TILE_PIXELS, Otc::TILE_PIXELS, Otc::TILE_PIXELS); + + // only render creatures where bottom right is inside tile rect + if (virtualTileRect.contains(virtualCreatureRect.bottomRight())) { + newWalkingTile = g_map.getOrCreateTile(getPrewalkingPosition().translated(xi, yi, 0)); + } + } + } + + if (newWalkingTile != m_walkingTile) { + if (m_walkingTile) + m_walkingTile->removeWalkingCreature(static_self_cast()); + if (newWalkingTile) { + newWalkingTile->addWalkingCreature(static_self_cast()); + + // recache visible tiles in map views + if (newWalkingTile->isEmpty()) + g_map.notificateTileUpdate(newWalkingTile->getPosition()); + } + m_walkingTile = newWalkingTile; + } +} + +void Creature::nextWalkUpdate() +{ + // remove any previous scheduled walk updates + if (m_walkUpdateEvent) + m_walkUpdateEvent->cancel(); + + // do the update + updateWalk(); + + // schedules next update + if (m_walking) { + auto self = static_self_cast(); + m_walkUpdateEvent = g_dispatcher.scheduleEvent([self] { + self->m_walkUpdateEvent = nullptr; + self->nextWalkUpdate(); + }, getStepDuration(true) / 32); + } +} + +void Creature::updateWalk() +{ + float walkTicksPerPixel = ((float)(getStepDuration(true) + 10)) / 32.0f; + int totalPixelsWalked = std::min(m_walkTimer.ticksElapsed() / walkTicksPerPixel, 32.0f); + int totalPixelsWalkedInNextFrame = std::min((m_walkTimer.ticksElapsed() + 15) / walkTicksPerPixel, 32.0f); + + // needed for paralyze effect + m_walkedPixels = std::max(m_walkedPixels, totalPixelsWalked); + int walkedPixelsInNextFrame = std::max(m_walkedPixels, totalPixelsWalkedInNextFrame); + + // update walk animation and offsets + updateWalkAnimation(totalPixelsWalked); + updateWalkOffset(m_walkedPixels); + updateWalkOffset(walkedPixelsInNextFrame, true); + updateWalkingTile(); + + // terminate walk + if (m_walking && m_walkTimer.ticksElapsed() >= getStepDuration()) + terminateWalk(); +} + +void Creature::terminateWalk() +{ + // remove any scheduled walk update + if (m_walkUpdateEvent) { + m_walkUpdateEvent->cancel(); + m_walkUpdateEvent = nullptr; + } + + if (m_walkingTile) { + m_walkingTile->removeWalkingCreature(static_self_cast()); + m_walkingTile = nullptr; + } + + m_walking = false; + m_walkedPixels = 0; + m_walkOffset = Point(0, 0); + m_walkOffsetInNextFrame = Point(0, 0); + + // reset walk animation states + if (!m_walkFinishAnimEvent) { + auto self = static_self_cast(); + m_walkFinishAnimEvent = g_dispatcher.scheduleEvent([self] { + self->m_footStep = 0; + self->m_walkAnimationPhase = 0; + self->m_walkFinishAnimEvent = nullptr; + }, 50); + } +} + +void Creature::setName(const std::string& name) +{ + m_nameCache.setText(name); + m_name = name; +} + +void Creature::setHealthPercent(uint8 healthPercent) +{ + if (healthPercent > 100) + healthPercent = 100; + + if (!m_useCustomInformationColor) { + if (healthPercent > 92) + m_informationColor = Color(0x00, 0xBC, 0x00); + else if (healthPercent > 60) + m_informationColor = Color(0x50, 0xA1, 0x50); + else if (healthPercent > 30) + m_informationColor = Color(0xA1, 0xA1, 0x00); + else if (healthPercent > 8) + m_informationColor = Color(0xBF, 0x0A, 0x0A); + else if (healthPercent > 3) + m_informationColor = Color(0x91, 0x0F, 0x0F); + else + m_informationColor = Color(0x85, 0x0C, 0x0C); + } + + bool changed = m_healthPercent != healthPercent; + m_healthPercent = healthPercent; + if (changed) { + callLuaField("onHealthPercentChange", healthPercent); + } + + if (healthPercent <= 0) + onDeath(); +} + +void Creature::setDirection(Otc::Direction direction) +{ + VALIDATE(direction != Otc::InvalidDirection); + m_direction = direction; +} + +void Creature::setOutfit(const Outfit& outfit) +{ + // optimization for UICreature + m_outfitNumber = g_clock.micros(); + + Outfit oldOutfit = m_outfit; + if (outfit.getCategory() != ThingCategoryCreature) { + if (!g_things.isValidDatId(outfit.getAuxId(), outfit.getCategory())) + return; + m_outfit.setAuxId(outfit.getAuxId()); + m_outfit.setCategory(outfit.getCategory()); + } else { + if (outfit.getId() > 0 && !g_things.isValidDatId(outfit.getId(), ThingCategoryCreature)) + return; + m_outfit = outfit; + } + m_walkAnimationPhase = 0; // might happen when player is walking and outfit is changed. + + callLuaField("onOutfitChange", m_outfit, oldOutfit); +} + +void Creature::setOutfitColor(const Color& color, int duration) +{ + // optimization for UICreature + m_outfitNumber = g_clock.micros(); + + if (m_outfitColorUpdateEvent) { + m_outfitColorUpdateEvent->cancel(); + m_outfitColorUpdateEvent = nullptr; + } + + if (duration > 0) { + Color delta = (color - m_outfitColor) / (float)duration; + m_outfitColorTimer.restart(); + updateOutfitColor(m_outfitColor, color, delta, duration); + } else + m_outfitColor = color; +} + +void Creature::updateOutfitColor(Color color, Color finalColor, Color delta, int duration) +{ + if (m_outfitColorTimer.ticksElapsed() < duration) { + m_outfitColor = color + delta * m_outfitColorTimer.ticksElapsed(); + + auto self = static_self_cast(); + m_outfitColorUpdateEvent = g_dispatcher.scheduleEvent([=] { + self->updateOutfitColor(color, finalColor, delta, duration); + }, 100); + } else { + m_outfitColor = finalColor; + } +} + +void Creature::setSpeed(uint16 speed) +{ + uint16 oldSpeed = m_speed; + m_speed = speed; + + // speed can change while walking (utani hur, paralyze, etc..) + if (m_walking) + nextWalkUpdate(); + + callLuaField("onSpeedChange", m_speed, oldSpeed); +} + +void Creature::setBaseSpeed(double baseSpeed) +{ + if (m_baseSpeed != baseSpeed) { + double oldBaseSpeed = m_baseSpeed; + m_baseSpeed = baseSpeed; + + callLuaField("onBaseSpeedChange", baseSpeed, oldBaseSpeed); + } +} + +void Creature::setSkull(uint8 skull) +{ + m_skull = skull; + callLuaField("onSkullChange", m_skull); +} + +void Creature::setShield(uint8 shield) +{ + m_shield = shield; + callLuaField("onShieldChange", m_shield); +} + +void Creature::setEmblem(uint8 emblem) +{ + m_emblem = emblem; + callLuaField("onEmblemChange", m_emblem); +} + +void Creature::setType(uint8 type) +{ + m_type = type; + callLuaField("onTypeChange", m_type); +} + +void Creature::setIcon(uint8 icon) +{ + m_icon = icon; + callLuaField("onIconChange", m_icon); +} + +void Creature::setSkullTexture(const std::string& filename) +{ + m_skullTexture = g_textures.getTexture(filename); +} + +void Creature::setShieldTexture(const std::string& filename, bool blink) +{ + m_shieldTexture = g_textures.getTexture(filename); + m_showShieldTexture = true; + + if (blink && !m_shieldBlink) { + auto self = static_self_cast(); + g_dispatcher.scheduleEvent([self]() { + self->updateShield(); + }, SHIELD_BLINK_TICKS); + } + + m_shieldBlink = blink; +} + +void Creature::setEmblemTexture(const std::string& filename) +{ + m_emblemTexture = g_textures.getTexture(filename); +} + +void Creature::setTypeTexture(const std::string& filename) +{ + m_typeTexture = g_textures.getTexture(filename); +} + +void Creature::setIconTexture(const std::string& filename) +{ + m_iconTexture = g_textures.getTexture(filename); +} + +void Creature::setSpeedFormula(double speedA, double speedB, double speedC) +{ + m_speedFormula[Otc::SpeedFormulaA] = speedA; + m_speedFormula[Otc::SpeedFormulaB] = speedB; + m_speedFormula[Otc::SpeedFormulaC] = speedC; +} + +bool Creature::hasSpeedFormula() +{ + return m_speedFormula[Otc::SpeedFormulaA] != -1 && m_speedFormula[Otc::SpeedFormulaB] != -1 + && m_speedFormula[Otc::SpeedFormulaC] != -1; +} + +void Creature::addTimedSquare(uint8 color) +{ + m_showTimedSquare = true; + m_timedSquareColor = Color::from8bit(color); + + // schedule removal + auto self = static_self_cast(); + g_dispatcher.scheduleEvent([self]() { + self->removeTimedSquare(); + }, VOLATILE_SQUARE_DURATION); +} + + +void Creature::updateShield() +{ + m_showShieldTexture = !m_showShieldTexture; + + if (m_shield != Otc::ShieldNone && m_shieldBlink) { + auto self = static_self_cast(); + g_dispatcher.scheduleEvent([self]() { + self->updateShield(); + }, SHIELD_BLINK_TICKS); + } else if (!m_shieldBlink) + m_showShieldTexture = true; +} + +Point Creature::getDrawOffset() +{ + Point drawOffset; + if (m_walking) { + if (m_walkingTile) + drawOffset -= Point(1, 1) * m_walkingTile->getDrawElevation(); + drawOffset += m_walkOffset; + } else { + const TilePtr& tile = getTile(); + if (tile) + drawOffset -= Point(1, 1) * tile->getDrawElevation(); + } + return drawOffset; +} + +int Creature::getStepDuration(bool ignoreDiagonal, Otc::Direction dir) +{ + int speed = m_speed; + if (speed < 1) + return 0; + + if (g_game.getFeature(Otc::GameNewSpeedLaw)) + speed *= 2; + + int groundSpeed = 0; + Position tilePos; + + if (dir == Otc::InvalidDirection) + tilePos = m_lastStepToPosition; + else + tilePos = getPrewalkingPosition(true).translatedToDirection(dir); + + if (!tilePos.isValid()) + tilePos = getPrewalkingPosition(true); + + const TilePtr& tile = g_map.getTile(tilePos); + if (tile) { + groundSpeed = tile->getGroundSpeed(); + if (groundSpeed == 0) + groundSpeed = 150; + } + + int interval = 1000; + if (groundSpeed > 0 && speed > 0) + interval = 1000 * groundSpeed; + + if (g_game.getFeature(Otc::GameNewSpeedLaw) && hasSpeedFormula()) { + int formulatedSpeed = 1; + if (speed > -m_speedFormula[Otc::SpeedFormulaB]) { + formulatedSpeed = std::max(1, (int)floor((m_speedFormula[Otc::SpeedFormulaA] * log((speed / 2) + + m_speedFormula[Otc::SpeedFormulaB]) + m_speedFormula[Otc::SpeedFormulaC]) + 0.5)); + } + interval = std::floor(interval / (double)formulatedSpeed); + } else + interval /= speed; + + if (g_game.getClientVersion() >= 900 && !g_game.getFeature(Otc::GameNewWalking)) + interval = std::ceil((float)interval / (float)g_game.getServerBeat()) * g_game.getServerBeat(); + + float factor = 3; + if (g_game.getClientVersion() <= 810) + factor = 2; + + interval = std::max(interval, g_game.getServerBeat()); + + if (!ignoreDiagonal && (m_lastStepDirection == Otc::NorthWest || m_lastStepDirection == Otc::NorthEast || + m_lastStepDirection == Otc::SouthWest || m_lastStepDirection == Otc::SouthEast)) + interval *= factor; + + if (!isServerWalking() && g_game.getFeature(Otc::GameSlowerManualWalking)) { + interval += 25; + } + if (isServerWalking() && g_game.getFeature(Otc::GameNewWalking) && m_stepDuration > 0) // just use server value + { + interval = m_stepDuration; + } + + return interval; +} + +Point Creature::getDisplacement() +{ + if (m_outfit.getCategory() == ThingCategoryEffect) + return Point(8, 8); + else if (m_outfit.getCategory() == ThingCategoryItem) + return Point(0, 0); + + if (m_outfit.getMount() != 0) { + auto datType = g_things.rawGetThingType(m_outfit.getMount(), ThingCategoryCreature); + return datType->getDisplacement(); + } + + return Thing::getDisplacement(); +} + +int Creature::getDisplacementX() +{ + if (m_outfit.getCategory() == ThingCategoryEffect) + return 8; + else if (m_outfit.getCategory() == ThingCategoryItem) + return 0; + + if (m_outfit.getMount() != 0) { + auto datType = g_things.rawGetThingType(m_outfit.getMount(), ThingCategoryCreature); + return datType->getDisplacementX(); + } + + return Thing::getDisplacementX(); +} + +int Creature::getDisplacementY() +{ + if (m_outfit.getCategory() == ThingCategoryEffect) + return 8; + else if (m_outfit.getCategory() == ThingCategoryItem) + return 0; + + if (m_outfit.getMount() != 0) { + auto datType = g_things.rawGetThingType(m_outfit.getMount(), ThingCategoryCreature); + if (datType) { + return datType->getDisplacementY(); + } + } + + return Thing::getDisplacementY(); +} + +int Creature::getExactSize(int layer, int xPattern, int yPattern, int zPattern, int animationPhase) +{ + int exactSize = 0; + + animationPhase = 0; + xPattern = Otc::South; + + zPattern = 0; + if (m_outfit.getMount() != 0) + zPattern = 1; + + for (yPattern = 0; yPattern < getNumPatternY(); yPattern++) { + if (yPattern > 0 && !(m_outfit.getAddons() & (1 << (yPattern - 1)))) + continue; + + for (layer = 0; layer < getLayers(); ++layer) + exactSize = std::max(exactSize, Thing::getExactSize(layer, xPattern, yPattern, zPattern, animationPhase)); + } + + return exactSize; +} + +const ThingTypePtr& Creature::getThingType() +{ + return g_things.getThingType(m_outfit.getId(), ThingCategoryCreature); +} + +ThingType* Creature::rawGetThingType() +{ + return g_things.rawGetThingType(m_outfit.getId(), ThingCategoryCreature); +} + +void Creature::setText(const std::string& text, const Color& color) +{ + if (!m_text) { + m_text = StaticTextPtr(new StaticText()); + } + m_text->setText(text); + m_text->setColor(color); +} + +std::string Creature::getText() +{ + if (!m_text) { + return ""; + } + return m_text->getText(); +} + + +// widgets +void Creature::addTopWidget(const UIWidgetPtr& widget) +{ + if (!widget) return; + if (std::find(m_topWidgets.begin(), m_topWidgets.end(), widget) == m_topWidgets.end()) { + m_topWidgets.push_back(widget); + } +} + +void Creature::addBottomWidget(const UIWidgetPtr& widget) +{ + if (!widget) return; + if (std::find(m_bottomWidgets.begin(), m_bottomWidgets.end(), widget) == m_bottomWidgets.end()) { + m_bottomWidgets.push_back(widget); + } +} + +void Creature::addDirectionalWidget(const UIWidgetPtr& widget) +{ + if (!widget) return; + if (std::find(m_directionalWidgets.begin(), m_directionalWidgets.end(), widget) == m_directionalWidgets.end()) { + m_directionalWidgets.push_back(widget); + } +} + +void Creature::removeTopWidget(const UIWidgetPtr& widget) +{ + auto it = std::remove(m_topWidgets.begin(), m_topWidgets.end(), widget); + while(it != m_topWidgets.end()) { + (*it)->destroy(); + it = m_topWidgets.erase(it); + } +} + +void Creature::removeBottomWidget(const UIWidgetPtr& widget) +{ + auto it = std::remove(m_bottomWidgets.begin(), m_bottomWidgets.end(), widget); + while (it != m_topWidgets.end()) { + (*it)->destroy(); + it = m_bottomWidgets.erase(it); + } +} + +void Creature::removeDirectionalWidget(const UIWidgetPtr& widget) +{ + auto it = m_directionalWidgets.erase(std::remove(m_directionalWidgets.begin(), m_directionalWidgets.end(), widget)); + while (it != m_topWidgets.end()) { + (*it)->destroy(); + it = m_directionalWidgets.erase(it); + } + +} + +std::list Creature::getTopWidgets() +{ + return m_topWidgets; +} + +std::list Creature::getBottomWidgets() +{ + return m_bottomWidgets; +} + +std::list Creature::getDirectionalWdigets() +{ + return m_directionalWidgets; +} + +void Creature::clearWidgets() +{ + clearTopWidgets(); + clearBottomWidgets(); + clearDirectionalWidgets(); +} + +void Creature::clearTopWidgets() +{ + for (auto& widget : m_topWidgets) { + widget->destroy(); + } + m_topWidgets.clear(); +} + +void Creature::clearBottomWidgets() +{ + for (auto& widget : m_bottomWidgets) { + widget->destroy(); + } + m_bottomWidgets.clear(); +} + +void Creature::clearDirectionalWidgets() +{ + for (auto& widget : m_directionalWidgets) { + widget->destroy(); + } + m_directionalWidgets.clear(); +} + +void Creature::drawTopWidgets(const Point& dest, const Otc::Direction direction) +{ + if (direction == Otc::North || direction == Otc::West) { + for (auto& widget : m_directionalWidgets) { + Rect dest_rect = widget->getRect(); + dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); + widget->setRect(dest_rect); + widget->draw(dest_rect, Fw::ForegroundPane); + } + } + for (auto& widget : m_topWidgets) { + Rect dest_rect = widget->getRect(); + dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); + widget->setRect(dest_rect); + widget->draw(dest_rect, Fw::ForegroundPane); + } +} + +void Creature::drawBottomWidgets(const Point& dest, const Otc::Direction direction) +{ + for (auto& widget : m_bottomWidgets) { + Rect dest_rect = widget->getRect(); + dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); + widget->setRect(dest_rect); + widget->draw(dest_rect, Fw::ForegroundPane); + } + + if (direction == Otc::South || direction == Otc::East) { + for (auto& widget : m_directionalWidgets) { + Rect dest_rect = widget->getRect(); + dest_rect = Rect(dest - Point(dest_rect.width() / 2, dest_rect.height() / 2), dest_rect.width(), dest_rect.height()); + widget->setRect(dest_rect); + widget->draw(dest_rect, Fw::ForegroundPane); + } + } +} diff --git a/src/client/creature.h b/src/client/creature.h new file mode 100644 index 0000000..26ad66b --- /dev/null +++ b/src/client/creature.h @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CREATURE_H +#define CREATURE_H + +#include "thing.h" +#include "outfit.h" +#include "tile.h" +#include "mapview.h" +#include +#include +#include +#include +#include +#include + + // @bindclass +class Creature : public Thing +{ +public: + enum { + SHIELD_BLINK_TICKS = 500, + VOLATILE_SQUARE_DURATION = 1000 + }; + + Creature(); + virtual ~Creature(); + + virtual void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr); + virtual void drawOutfit(const Rect& destRect, Otc::Direction direction = Otc::InvalidDirection, const Color& color = Color::white); + + void drawInformation(const Point& point, bool useGray, const Rect& parentRect, int drawFlags); + + bool isInsideOffset(Point offset); + + void setId(uint32 id) { m_id = id; } + void setName(const std::string& name); + void setManaPercent(int8 value) { m_manaPercent = value; } + void setHealthPercent(uint8 healthPercent); + void setDirection(Otc::Direction direction); + void setOutfit(const Outfit& outfit); + void setOutfitColor(const Color& color, int duration); + void setLight(const Light& light) { m_light = light; } + void setSpeed(uint16 speed); + void setBaseSpeed(double baseSpeed); + void setSkull(uint8 skull); + void setShield(uint8 shield); + void setEmblem(uint8 emblem); + void setType(uint8 type); + void setIcon(uint8 icon); + void setSkullTexture(const std::string& filename); + void setShieldTexture(const std::string& filename, bool blink); + void setEmblemTexture(const std::string& filename); + void setTypeTexture(const std::string& filename); + void setIconTexture(const std::string& filename); + void setPassable(bool passable) { m_passable = passable; } + void setSpeedFormula(double speedA, double speedB, double speedC); + + void addTimedSquare(uint8 color); + void removeTimedSquare() { m_showTimedSquare = false; } + + void showStaticSquare(const Color& color) { m_showStaticSquare = true; m_staticSquareColor = color; } + void hideStaticSquare() { m_showStaticSquare = false; } + + void setInformationColor(const Color& color) { m_useCustomInformationColor = true; m_informationColor = color; } + void resetInformationColor() { m_useCustomInformationColor = false; } + + Point getInformationOffset() { return m_informationOffset; } + void setInformationOffset(int x, int y) { m_informationOffset = Point(x, y); } + + void setText(const std::string& text, const Color& color); + std::string getText(); + void clearText() { setText("", Color::white); } + + uint32 getId() { return m_id; } + std::string getName() { return m_name; } + uint8 getHealthPercent() { return m_healthPercent; } + int8 getManaPercent() { return m_manaPercent; } + Otc::Direction getDirection() { return m_direction; } + Otc::Direction getWalkDirection() { return m_walkDirection; } + Outfit getOutfit() { return m_outfit; } + int getOutfitNumber() { return m_outfitNumber; } + Light getLight() { return m_light; } + uint16 getSpeed() { return m_speed; } + double getBaseSpeed() { return m_baseSpeed; } + uint8 getSkull() { return m_skull; } + uint8 getShield() { return m_shield; } + uint8 getEmblem() { return m_emblem; } + uint8 getType() { return m_type; } + uint8 getIcon() { return m_icon; } + bool isPassable() { return m_passable; } + Point getDrawOffset(); + int getStepDuration(bool ignoreDiagonal = false, Otc::Direction dir = Otc::InvalidDirection); + Point getWalkOffset(bool inNextFrame = false) { return inNextFrame ? m_walkOffsetInNextFrame : m_walkOffset; } + Position getLastStepFromPosition() { return m_lastStepFromPosition; } + Position getLastStepToPosition() { return m_lastStepToPosition; } + float getStepProgress() { return m_walkTimer.ticksElapsed() / getStepDuration(); } + int getStepTicksLeft() { return getStepDuration() - m_walkTimer.ticksElapsed(); } + ticks_t getWalkTicksElapsed() { return m_walkTimer.ticksElapsed(); } + double getSpeedFormula(Otc::SpeedFormula formula) { return m_speedFormula[formula]; } + bool hasSpeedFormula(); + std::array getSpeedFormulaArray() { return m_speedFormula; } + virtual Point getDisplacement(); + virtual int getDisplacementX(); + virtual int getDisplacementY(); + virtual int getExactSize(int layer = 0, int xPattern = 0, int yPattern = 0, int zPattern = 0, int animationPhase = 0); + PointF getJumpOffset() { return m_jumpOffset; } + + void updateShield(); + + // walk related + int getWalkAnimationPhases(); + virtual void turn(Otc::Direction direction); + void jump(int height, int duration); + virtual void walk(const Position& oldPos, const Position& newPos); + virtual void stopWalk(); + void allowAppearWalk(uint16_t stepSpeed) { m_allowAppearWalk = true; m_stepDuration = stepSpeed; } + + bool isWalking() { return m_walking; } + bool isRemoved() { return m_removed; } + bool isInvisible() { return m_outfit.getCategory() == ThingCategoryEffect && m_outfit.getAuxId() == 13; } + bool isDead() { return m_healthPercent <= 0; } + bool canBeSeen() { return !isInvisible() || isPlayer(); } + + bool isCreature() { return true; } + + const ThingTypePtr& getThingType(); + ThingType *rawGetThingType(); + + virtual void onPositionChange(const Position& newPos, const Position& oldPos); + virtual void onAppear(); + virtual void onDisappear(); + virtual void onDeath(); + + virtual bool isPreWalking() { return false; } + virtual Position getPrewalkingPosition(bool beforePrewalk = false) { return m_position; } + + TilePtr getWalkingTileOrTile() { + return m_walkingTile ? m_walkingTile : getTile(); + } + + virtual bool isServerWalking() { return true; } + + void setElevation(uint8 elevation) { + m_elevation = elevation; + } + uint8 getElevation() { + return m_elevation; + } + + // widgets + void addTopWidget(const UIWidgetPtr& widget); + void addBottomWidget(const UIWidgetPtr& widget); + void addDirectionalWidget(const UIWidgetPtr& widget); + void removeTopWidget(const UIWidgetPtr& widget); + void removeBottomWidget(const UIWidgetPtr& widget); + void removeDirectionalWidget(const UIWidgetPtr& widget); + std::list getTopWidgets(); + std::list getBottomWidgets(); + std::list getDirectionalWdigets(); + void clearWidgets(); + void clearTopWidgets(); + void clearBottomWidgets(); + void clearDirectionalWidgets(); + void drawTopWidgets(const Point& rect, const Otc::Direction direction); + void drawBottomWidgets(const Point& rect, const Otc::Direction direction); + +protected: + virtual void updateWalkAnimation(int totalPixelsWalked); + virtual void updateWalkOffset(int totalPixelsWalked, bool inNextFrame = false); + void updateWalkingTile(); + virtual void nextWalkUpdate(); + virtual void updateWalk(); + virtual void terminateWalk(); + + void updateOutfitColor(Color color, Color finalColor, Color delta, int duration); + void updateJump(); + + uint32 m_id; + std::string m_name; + uint8 m_healthPercent; + int8 m_manaPercent; + Otc::Direction m_direction; + Otc::Direction m_walkDirection; + Outfit m_outfit; + int m_outfitNumber = 0; + Light m_light; + int m_speed; + double m_baseSpeed; + uint8 m_skull; + uint8 m_shield; + uint8 m_emblem; + uint8 m_type; + uint8 m_icon; + TexturePtr m_skullTexture; + TexturePtr m_shieldTexture; + TexturePtr m_emblemTexture; + TexturePtr m_typeTexture; + TexturePtr m_iconTexture; + stdext::boolean m_showShieldTexture; + stdext::boolean m_shieldBlink; + stdext::boolean m_passable; + Color m_timedSquareColor; + Color m_staticSquareColor; + Color m_nameColor; + stdext::boolean m_showTimedSquare; + stdext::boolean m_showStaticSquare; + stdext::boolean m_removed; + CachedText m_nameCache; + Color m_informationColor; + bool m_useCustomInformationColor = false; + Point m_informationOffset; + Color m_outfitColor; + ScheduledEventPtr m_outfitColorUpdateEvent; + Timer m_outfitColorTimer; + + static std::array m_speedFormula; + + // walk related + int m_walkAnimationPhase; + int m_walkedPixels; + uint m_footStep; + Timer m_walkTimer; + ticks_t m_footLastStep; + TilePtr m_walkingTile; + stdext::boolean m_walking; + stdext::boolean m_allowAppearWalk; + ScheduledEventPtr m_walkUpdateEvent; + ScheduledEventPtr m_walkFinishAnimEvent; + EventPtr m_disappearEvent; + Point m_walkOffset; + Point m_walkOffsetInNextFrame; + Otc::Direction m_lastStepDirection; + Position m_lastStepFromPosition; + Position m_lastStepToPosition; + Position m_oldPosition; + uint8 m_elevation = 0; + uint16 m_stepDuration = 0; + + // jump related + float m_jumpHeight = 0; + float m_jumpDuration = 0; + PointF m_jumpOffset; + Timer m_jumpTimer; + + // for bot + StaticTextPtr m_text; + + // widgets + std::list m_bottomWidgets; + std::list m_directionalWidgets; + std::list m_topWidgets; +}; + +// @bindclass +class Npc : public Creature +{ +public: + bool isNpc() { return true; } +}; + +// @bindclass +class Monster : public Creature +{ +public: + bool isMonster() { return true; } +}; + +#endif diff --git a/src/client/creatures.cpp b/src/client/creatures.cpp new file mode 100644 index 0000000..8ec8c20 --- /dev/null +++ b/src/client/creatures.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "creatures.h" +#include "creature.h" +#include "map.h" + +#include +#include + +CreatureManager g_creatures; + +static bool isInZone(const Position& pos/* placePos*/, + const Position& centerPos, + int radius) +{ + if(radius == -1) + return true; + return ((pos.x >= centerPos.x - radius) && (pos.x <= centerPos.x + radius) && + (pos.y >= centerPos.y - radius) && (pos.y <= centerPos.y + radius) + ); +} + +void CreatureManager::terminate() +{ + clearSpawns(); + clear(); + m_nullCreature = nullptr; +} + +void Spawn::load(TiXmlElement* node) +{ + Position centerPos; + centerPos.x = node->readType("centerx"); + centerPos.y = node->readType("centery"); + centerPos.z = node->readType("centerz"); + + setCenterPos(centerPos); + setRadius(node->readType("radius")); + + CreatureTypePtr cType(nullptr); + for(TiXmlElement* cNode = node->FirstChildElement(); cNode; cNode = cNode->NextSiblingElement()) { + if(cNode->ValueStr() != "monster" && cNode->ValueStr() != "npc") + stdext::throw_exception(stdext::format("invalid spawn-subnode %s", cNode->ValueStr())); + + std::string cName = cNode->Attribute("name"); + stdext::tolower(cName); + stdext::trim(cName); + stdext::ucwords(cName); + + if (!(cType = g_creatures.getCreatureByName(cName))) + continue; + + cType->setSpawnTime(cNode->readType("spawntime")); + Otc::Direction dir = Otc::North; + int16 dir_ = cNode->readType("direction"); + if(dir_ >= Otc::East && dir_ <= Otc::West) + dir = (Otc::Direction)dir_; + cType->setDirection(dir); + + Position placePos; + placePos.x = centerPos.x + cNode->readType("x"); + placePos.y = centerPos.y + cNode->readType("y"); + placePos.z = cNode->readType("z"); + + cType->setRace(cNode->ValueStr() == "npc" ? CreatureRaceNpc : CreatureRaceMonster); + addCreature(placePos, cType); + } +} + +void Spawn::save(TiXmlElement* node) +{ + const Position& c = getCenterPos(); + node->SetAttribute("centerx", c.x); + node->SetAttribute("centery", c.y); + node->SetAttribute("centerz", c.z); + + node->SetAttribute("radius", getRadius()); + + TiXmlElement* creatureNode = nullptr; + + for(const auto& pair : m_creatures) { + const CreatureTypePtr& creature = pair.second; + if(!(creatureNode = new TiXmlElement(creature->getRace() == CreatureRaceNpc ? "npc" : "monster"))) + stdext::throw_exception("Spawn::save: Ran out of memory while allocating XML element! Terminating now."); + + creatureNode->SetAttribute("name", creature->getName()); + creatureNode->SetAttribute("spawntime", creature->getSpawnTime()); + creatureNode->SetAttribute("direction", creature->getDirection()); + + const Position& placePos = pair.first; + VALIDATE(placePos.isValid()); + + creatureNode->SetAttribute("x", placePos.x - c.x); + creatureNode->SetAttribute("y", placePos.y - c.y); + creatureNode->SetAttribute("z", placePos.z); + + node->LinkEndChild(creatureNode); + } +} + +void Spawn::addCreature(const Position& placePos, const CreatureTypePtr& cType) +{ + const Position& centerPos = getCenterPos(); + int m_radius = getRadius(); + if(!isInZone(placePos, centerPos, m_radius)) { + g_logger.warning(stdext::format("cannot place creature at %s (spawn's center position: %s, spawn radius: %d) (increment radius)", + stdext::to_string(placePos), stdext::to_string(centerPos), + m_radius + )); + return; + } + + g_map.addThing(cType->cast(), placePos, 4); + m_creatures.insert(std::make_pair(placePos, cType)); +} + +void Spawn::removeCreature(const Position& pos) +{ + auto iterator = m_creatures.find(pos); + if(iterator != m_creatures.end()) { + VALIDATE(iterator->first.isValid()); + VALIDATE(g_map.removeThingByPos(iterator->first, 4)); + m_creatures.erase(iterator); + } +} + +std::vector Spawn::getCreatures() +{ + std::vector creatures; + for (auto p : m_creatures) + creatures.push_back(p.second); + return creatures; +} + +CreaturePtr CreatureType::cast() +{ + CreaturePtr ret(new Creature); + + std::string cName = getName(); + stdext::tolower(cName); + stdext::trim(cName); + stdext::ucwords(cName); + ret->setName(cName); + + ret->setDirection(getDirection()); + ret->setOutfit(getOutfit()); + return ret; +} + +CreatureManager::CreatureManager() +{ + m_nullCreature = CreatureTypePtr(new CreatureType); +} + +void CreatureManager::clearSpawns() +{ + for(auto pair : m_spawns) + pair.second->clear(); + m_spawns.clear(); +} + +void CreatureManager::loadMonsters(const std::string& file) +{ + TiXmlDocument doc; + doc.Parse(g_resources.readFileContents(file).c_str()); + if(doc.Error()) + stdext::throw_exception(stdext::format("cannot open monsters file '%s': '%s'", file, doc.ErrorDesc())); + + TiXmlElement* root = doc.FirstChildElement(); + if(!root || root->ValueStr() != "monsters") + stdext::throw_exception("malformed monsters xml file"); + + for(TiXmlElement* monster = root->FirstChildElement(); monster; monster = monster->NextSiblingElement()) { + std::string fname = file.substr(0, file.find_last_of('/')) + '/' + monster->Attribute("file"); + if(fname.substr(fname.length() - 4) != ".xml") + fname += ".xml"; + + loadSingleCreature(fname); + } + + doc.Clear(); + m_loaded = true; +} + +void CreatureManager::loadSingleCreature(const std::string& file) +{ + loadCreatureBuffer(g_resources.readFileContents(file)); +} + +void CreatureManager::loadNpcs(const std::string& folder) +{ + std::string tmp = folder; + if(!stdext::ends_with(tmp, "/")) + tmp += "/"; + + if(!g_resources.directoryExists(tmp)) + stdext::throw_exception(stdext::format("NPCs folder '%s' was not found.", folder)); + + const auto& fileList = g_resources.listDirectoryFiles(tmp); + for(const std::string& file : fileList) + loadCreatureBuffer(g_resources.readFileContents(tmp + file)); +} + +void CreatureManager::loadSpawns(const std::string& fileName) +{ + if(!isLoaded()) { + g_logger.warning("creatures aren't loaded yet to load spawns."); + return; + } + + if(m_spawnLoaded) { + g_logger.warning("attempt to reload spawns."); + return; + } + + try { + TiXmlDocument doc; + doc.Parse(g_resources.readFileContents(fileName).c_str()); + if(doc.Error()) + stdext::throw_exception(stdext::format("cannot load spawns xml file '%s: '%s'", fileName, doc.ErrorDesc())); + + TiXmlElement* root = doc.FirstChildElement(); + if(!root || root->ValueStr() != "spawns") + stdext::throw_exception("malformed spawns file"); + + for(TiXmlElement* node = root->FirstChildElement(); node; node = node->NextSiblingElement()) { + if(node->ValueTStr() != "spawn") + stdext::throw_exception("invalid spawn node"); + + SpawnPtr spawn(new Spawn); + spawn->load(node); + m_spawns.insert(std::make_pair(spawn->getCenterPos(), spawn)); + } + doc.Clear(); + m_spawnLoaded = true; + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to load '%s': %s", fileName, e.what())); + } +} + +void CreatureManager::saveSpawns(const std::string& fileName) +{ + try { + TiXmlDocument doc; + doc.SetTabSize(2); + + TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "UTF-8", ""); + doc.LinkEndChild(decl); + + TiXmlElement* root = new TiXmlElement("spawns"); + doc.LinkEndChild(root); + + for(auto pair : m_spawns) { + TiXmlElement* elem = new TiXmlElement("spawn"); + pair.second->save(elem); + root->LinkEndChild(elem); + } + + if(!doc.SaveFile("data"+fileName)) + stdext::throw_exception(stdext::format("failed to save spawns XML %s: %s", fileName, doc.ErrorDesc())); + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); + } +} + +void CreatureManager::loadCreatureBuffer(const std::string& buffer) +{ + TiXmlDocument doc; + doc.Parse(buffer.c_str()); + if(doc.Error()) + stdext::throw_exception(stdext::format("cannot load creature buffer: %s", doc.ErrorDesc())); + + TiXmlElement* root = doc.FirstChildElement(); + if(!root || (root->ValueStr() != "monster" && root->ValueStr() != "npc")) + stdext::throw_exception("invalid root tag name"); + + std::string cName = root->Attribute("name"); + stdext::tolower(cName); + stdext::trim(cName); + stdext::ucwords(cName); + + CreatureTypePtr newType(new CreatureType(cName)); + for(TiXmlElement* attrib = root->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) { + if(attrib->ValueStr() != "look") + continue; + + internalLoadCreatureBuffer(attrib, newType); + break; + } + + doc.Clear(); +} + +void CreatureManager::internalLoadCreatureBuffer(TiXmlElement* attrib, const CreatureTypePtr& m) +{ + if(std::find(m_creatures.begin(), m_creatures.end(), m) != m_creatures.end()) + return; + + Outfit out; + + int32 type = attrib->readType("type"); + if(type > 0) { + out.setCategory(ThingCategoryCreature); + out.setId(type); + } else { + out.setCategory(ThingCategoryItem); + out.setAuxId(attrib->readType("typeex")); + } + + { + out.setHead(attrib->readType(("head"))); + out.setBody(attrib->readType(("body"))); + out.setLegs(attrib->readType(("legs"))); + out.setFeet(attrib->readType(("feet"))); + out.setAddons(attrib->readType(("addons"))); + out.setMount(attrib->readType(("mount"))); + } + + m->setOutfit(out); + m_creatures.push_back(m); +} + +const CreatureTypePtr& CreatureManager::getCreatureByName(std::string name) +{ + stdext::tolower(name); + stdext::trim(name); + stdext::ucwords(name); + auto it = std::find_if(m_creatures.begin(), m_creatures.end(), + [=] (const CreatureTypePtr& m) -> bool { return m->getName() == name; }); + if(it != m_creatures.end()) + return *it; + g_logger.warning(stdext::format("could not find creature with name: %s", name)); + return m_nullCreature; +} + +const CreatureTypePtr& CreatureManager::getCreatureByLook(int look) +{ + auto findFun = [=] (const CreatureTypePtr& c) -> bool + { + const Outfit& o = c->getOutfit(); + return o.getId() == look || o.getAuxId() == look; + }; + auto it = std::find_if(m_creatures.begin(), m_creatures.end(), findFun); + if(it != m_creatures.end()) + return *it; + g_logger.warning(stdext::format("could not find creature with looktype: %d", look)); + return m_nullCreature; +} + +SpawnPtr CreatureManager::getSpawn(const Position& centerPos) +{ + auto it = m_spawns.find(centerPos); + if(it != m_spawns.end()) + return it->second; + g_logger.debug(stdext::format("failed to find spawn at center %s",stdext::to_string(centerPos))); + return nullptr; +} + +SpawnPtr CreatureManager::getSpawnForPlacePos(const Position& pos) +{ + for (const auto& pair : m_spawns) { + const Position& centerPos = pair.first; + const SpawnPtr& spawn = pair.second; + + if (isInZone(pos, centerPos, spawn->getRadius())) + return spawn; + } + + return nullptr; +} + +SpawnPtr CreatureManager::addSpawn(const Position& centerPos, int radius) +{ + auto iter = m_spawns.find(centerPos); + if(iter != m_spawns.end()) { + if(iter->second->getRadius() != radius) + iter->second->setRadius(radius); + return iter->second; + } + + SpawnPtr ret(new Spawn); + + ret->setRadius(radius); + ret->setCenterPos(centerPos); + + m_spawns.insert(std::make_pair(centerPos, ret)); + return ret; +} + +void CreatureManager::deleteSpawn(const SpawnPtr& spawn) +{ + const Position& centerPos = spawn->getCenterPos(); + auto it = m_spawns.find(centerPos); + if(it != m_spawns.end()) + m_spawns.erase(it); +} + +std::vector CreatureManager::getSpawns() +{ + std::vector spawns; + for (auto p : m_spawns) + spawns.push_back(p.second); + return spawns; +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/client/creatures.h b/src/client/creatures.h new file mode 100644 index 0000000..6a64f6a --- /dev/null +++ b/src/client/creatures.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CREATURES_H +#define CREATURES_H + +#include "declarations.h" +#include +#include "outfit.h" + +enum CreatureAttr : uint8 +{ + CreatureAttrPosition = 0, + CreatureAttrName = 1, + CreatureAttrOutfit = 2, + CreatureAttrSpawnTime = 3, + CreatureAttrDir = 4, + CreatureAttrRace = 5 +}; + +enum CreatureRace : uint8 +{ + CreatureRaceNpc = 0, + CreatureRaceMonster = 1 +}; + +enum SpawnAttr : uint8 +{ + SpawnAttrRadius = 0, + SpawnAttrCenter = 1, +}; + +class Spawn : public LuaObject +{ +public: + Spawn() = default; + Spawn(int32 radius) { setRadius(radius); } + + void setRadius(int32 r) { m_attribs.set(SpawnAttrRadius, r) ;} + int32 getRadius() { return m_attribs.get(SpawnAttrRadius); } + + void setCenterPos(const Position& pos) { m_attribs.set(SpawnAttrCenter, pos); } + Position getCenterPos() { return m_attribs.get(SpawnAttrCenter); } + + std::vector getCreatures(); + void addCreature(const Position& placePos, const CreatureTypePtr& cType); + void removeCreature(const Position& pos); + void clear() { m_creatures.clear(); } + +protected: + void load(TiXmlElement* node); + void save(TiXmlElement* node); + +private: + stdext::dynamic_storage m_attribs; + std::unordered_map m_creatures; + friend class CreatureManager; +}; + +class CreatureType : public LuaObject +{ +public: + CreatureType() = default; + CreatureType(const std::string& name) { setName(name); } + + void setSpawnTime(int32 spawnTime) { m_attribs.set(CreatureAttrSpawnTime, spawnTime); } + int32 getSpawnTime() { return m_attribs.get(CreatureAttrSpawnTime); } + + void setName(const std::string& name) { m_attribs.set(CreatureAttrName, name); } + std::string getName() { return m_attribs.get(CreatureAttrName); } + + void setOutfit(const Outfit& o) { m_attribs.set(CreatureAttrOutfit, o); } + Outfit getOutfit() { return m_attribs.get(CreatureAttrOutfit); } + + void setDirection(Otc::Direction dir) { m_attribs.set(CreatureAttrDir, dir); } + Otc::Direction getDirection() { return m_attribs.get(CreatureAttrDir); } + + void setRace(CreatureRace race) { m_attribs.set(CreatureAttrRace, race); } + CreatureRace getRace() { return m_attribs.get(CreatureAttrRace); } + + CreaturePtr cast(); + +private: + stdext::dynamic_storage m_attribs; +}; + +class CreatureManager +{ +public: + CreatureManager(); + void clear() { m_creatures.clear(); } + void clearSpawns(); + void terminate(); + + void loadMonsters(const std::string& file); + void loadSingleCreature(const std::string& file); + void loadNpcs(const std::string& folder); + void loadCreatureBuffer(const std::string& buffer); + void loadSpawns(const std::string& fileName); + void saveSpawns(const std::string& fileName); + + const CreatureTypePtr& getCreatureByName(std::string name); + const CreatureTypePtr& getCreatureByLook(int look); + + std::vector getSpawns(); + SpawnPtr getSpawn(const Position& centerPos); + SpawnPtr getSpawnForPlacePos(const Position& pos); + SpawnPtr addSpawn(const Position& centerPos, int radius); + void deleteSpawn(const SpawnPtr& spawn); + + bool isLoaded() { return m_loaded; } + bool isSpawnLoaded() { return m_spawnLoaded; } + + const std::vector& getCreatures() { return m_creatures; } + +protected: + void internalLoadCreatureBuffer(TiXmlElement* elem, const CreatureTypePtr& m); + +private: + std::vector m_creatures; + std::unordered_map m_spawns; + stdext::boolean m_loaded, m_spawnLoaded; + CreatureTypePtr m_nullCreature; +}; + +extern CreatureManager g_creatures; + +#endif diff --git a/src/client/declarations.h b/src/client/declarations.h new file mode 100644 index 0000000..ffd46cf --- /dev/null +++ b/src/client/declarations.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CLIENT_DECLARATIONS_H +#define CLIENT_DECLARATIONS_H + +#include "global.h" +#include +#include + +// core +class Map; +class Game; +class MapView; +class LightView; +class Tile; +class Thing; +class Item; +class Container; +class Creature; +class Monster; +class Npc; +class Player; +class LocalPlayer; +class Effect; +class Missile; +class AnimatedText; +class StaticText; +class Animator; +class ThingType; +class ItemType; +class House; +class Town; +class CreatureType; +class Spawn; +class TileBlock; + +typedef stdext::shared_object_ptr MapViewPtr; +typedef stdext::shared_object_ptr LightViewPtr; +typedef stdext::shared_object_ptr TilePtr; +typedef stdext::shared_object_ptr ThingPtr; +typedef stdext::shared_object_ptr ItemPtr; +typedef stdext::shared_object_ptr ContainerPtr; +typedef stdext::shared_object_ptr CreaturePtr; +typedef stdext::shared_object_ptr MonsterPtr; +typedef stdext::shared_object_ptr NpcPtr; +typedef stdext::shared_object_ptr PlayerPtr; +typedef stdext::shared_object_ptr LocalPlayerPtr; +typedef stdext::shared_object_ptr EffectPtr; +typedef stdext::shared_object_ptr MissilePtr; +typedef stdext::shared_object_ptr AnimatedTextPtr; +typedef stdext::shared_object_ptr StaticTextPtr; +typedef stdext::shared_object_ptr AnimatorPtr; +typedef stdext::shared_object_ptr ThingTypePtr; +typedef stdext::shared_object_ptr ItemTypePtr; +typedef stdext::shared_object_ptr HousePtr; +typedef stdext::shared_object_ptr TownPtr; +typedef stdext::shared_object_ptr CreatureTypePtr; +typedef stdext::shared_object_ptr SpawnPtr; + +typedef std::vector ThingList; +typedef std::vector ThingTypeList; +typedef std::vector ItemTypeList; +typedef std::list HouseList; +typedef std::list TownList; +typedef std::list ItemList; +typedef std::list TileList; +typedef std::vector ItemVector; +typedef std::unordered_map TileMap; +typedef std::unordered_map CreatureMap; +typedef std::unordered_map SpawnMap; + +// net +class ProtocolLogin; +class ProtocolGame; + +typedef stdext::shared_object_ptr ProtocolGamePtr; +typedef stdext::shared_object_ptr ProtocolLoginPtr; + +// ui +class UIItem; +class UICreature; +class UIMap; +class UIMinimap; +class UIProgressRect; +class UIMapAnchorLayout; +class UIPositionAnchor; +class UISprite; + +typedef stdext::shared_object_ptr UIItemPtr; +typedef stdext::shared_object_ptr UICreaturePtr; +typedef stdext::shared_object_ptr UISpritePtr; +typedef stdext::shared_object_ptr UIMapPtr; +typedef stdext::shared_object_ptr UIMinimapPtr; +typedef stdext::shared_object_ptr UIProgressRectPtr; +typedef stdext::shared_object_ptr UIMapAnchorLayoutPtr; +typedef stdext::shared_object_ptr UIPositionAnchorPtr; + +#endif diff --git a/src/client/effect.cpp b/src/client/effect.cpp new file mode 100644 index 0000000..e4d667f --- /dev/null +++ b/src/client/effect.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "effect.h" +#include "map.h" +#include "game.h" +#include +#include + +void Effect::draw(const Point& dest, int offsetX, int offsetY, bool animate, LightView* lightView) +{ + if(m_id == 0) + return; + + if(animate) { + if(g_game.getFeature(Otc::GameEnhancedAnimations)) { + // This requires a separate getPhaseAt method as using getPhase would make all magic effects use the same phase regardless of their appearance time + m_animationPhase = rawGetThingType()->getAnimator()->getPhaseAt(m_animationTimer, m_animationPhase); + } else { + // hack to fix some animation phases duration, currently there is no better solution + int ticks = EFFECT_TICKS_PER_FRAME; + if (m_id == 33) { + ticks <<= 2; + } + + m_animationPhase = std::min((int)(m_animationTimer.ticksElapsed() / ticks), getAnimationPhases() - 1); + } + } + + int xPattern = offsetX % getNumPatternX(); + if(xPattern < 0) + xPattern += getNumPatternX(); + + int yPattern = offsetY % getNumPatternY(); + if(yPattern < 0) + yPattern += getNumPatternY(); + + rawGetThingType()->draw(dest, 0, xPattern, yPattern, 0, m_animationPhase, Color::white, lightView); +} + +void Effect::onAppear() +{ + m_animationTimer.restart(); + + int duration = 0; + if(g_game.getFeature(Otc::GameEnhancedAnimations)) { + duration = getThingType()->getAnimator() ? getThingType()->getAnimator()->getTotalDuration() : 1000; + } else { + duration = EFFECT_TICKS_PER_FRAME; + + // hack to fix some animation phases duration, currently there is no better solution + if(m_id == 33) { + duration <<= 2; + } + + duration *= getAnimationPhases(); + } + + // schedule removal + auto self = asEffect(); + g_dispatcher.scheduleEvent([self]() { g_map.removeThing(self); }, duration); +} + +void Effect::setId(uint32 id) +{ + if(!g_things.isValidDatId(id, ThingCategoryEffect)) + id = 0; + m_id = id; +} + +const ThingTypePtr& Effect::getThingType() +{ + return g_things.getThingType(m_id, ThingCategoryEffect); +} + +ThingType *Effect::rawGetThingType() +{ + return g_things.rawGetThingType(m_id, ThingCategoryEffect); +} diff --git a/src/client/effect.h b/src/client/effect.h new file mode 100644 index 0000000..2399499 --- /dev/null +++ b/src/client/effect.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef EFFECT_H +#define EFFECT_H + +#include +#include +#include "thing.h" + +// @bindclass +class Effect : public Thing +{ + enum { + EFFECT_TICKS_PER_FRAME = 75 + }; + +public: + void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr) override {} + void draw(const Point& dest, int offsetX = 0, int offsetY = 0, bool animate = true, LightView* lightView = nullptr); + + void setId(uint32 id) override; + uint32 getId() { return m_id; } + + EffectPtr asEffect() { return static_self_cast(); } + bool isEffect() { return true; } + + const ThingTypePtr& getThingType() override; + ThingType *rawGetThingType() override; + +protected: + void onAppear(); + +private: + uint16 m_id; + Timer m_animationTimer; + int m_animationPhase = 0; +}; + +#endif diff --git a/src/client/game.cpp b/src/client/game.cpp new file mode 100644 index 0000000..45bb0bf --- /dev/null +++ b/src/client/game.cpp @@ -0,0 +1,1641 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "game.h" +#include "localplayer.h" +#include "map.h" +#include "tile.h" +#include "creature.h" +#include "container.h" +#include "statictext.h" +#include +#include +#include +#include "luavaluecasts_client.h" +#include "protocolgame.h" +#include "protocolcodes.h" + +#include + +Game g_game; + +Game::Game() +{ + m_protocolVersion = 0; + m_clientCustomOs = -1; + m_clientVersion = 0; + m_online = false; + m_denyBotCall = false; + m_dead = false; + m_serverBeat = 50; + m_seq = 0; + m_ping = -1; + m_pingDelay = 1000; + m_newPingDelay = 250; + m_canReportBugs = false; + m_fightMode = Otc::FightBalanced; + m_chaseMode = Otc::DontChase; + m_pvpMode = Otc::WhiteDove; + m_safeFight = true; +} + +void Game::init() +{ + resetGameStates(); +} + +void Game::terminate() +{ + resetGameStates(); + m_protocolGame = nullptr; +} + +void Game::resetGameStates() +{ + m_online = false; + m_denyBotCall = false; + m_dead = false; + m_serverBeat = 50; + m_seq = 0; + m_ping = -1; + m_canReportBugs = false; + m_fightMode = Otc::FightBalanced; + m_chaseMode = Otc::DontChase; + m_pvpMode = Otc::WhiteDove; + m_safeFight = true; + m_followingCreature = nullptr; + m_attackingCreature = nullptr; + m_localPlayer = nullptr; + m_pingSent = 0; + m_pingReceived = 0; + m_walkId = 0; + m_walkPrediction = 0; + m_coins = 0; + m_transferableCoins = 0; + m_newPingIds.clear(); + m_unjustifiedPoints = UnjustifiedPoints(); + + for(auto& it : m_containers) { + const ContainerPtr& container = it.second; + if(container) + container->onClose(); + } + + if(m_pingEvent) { + m_pingEvent->cancel(); + m_pingEvent = nullptr; + } + + if (m_newPingEvent) { + m_newPingEvent->cancel(); + m_newPingEvent = nullptr; + } + + if(m_checkConnectionEvent) { + m_checkConnectionEvent->cancel(); + m_checkConnectionEvent = nullptr; + } + + m_containers.clear(); + m_vips.clear(); + m_gmActions.clear(); + g_map.resetAwareRange(); +} + +void Game::processConnectionError(const boost::system::error_code& ec) +{ + // connection errors only have meaning if we still have a protocol + if(m_protocolGame) { + // eof = end of file, a clean disconnect + if(ec != asio::error::eof) + g_lua.callGlobalField("g_game", "onConnectionError", ec.message(), ec.value()); + + processDisconnect(); + } +} + +void Game::processDisconnect() +{ + if(isOnline()) + processGameEnd(); + + if(m_protocolGame) { + m_protocolGame->disconnect(); + m_protocolGame = nullptr; + } +} + +void Game::processUpdateNeeded(const std::string& signature) +{ + g_lua.callGlobalField("g_game", "onUpdateNeeded", signature); +} + +void Game::processLoginError(const std::string& error) +{ + g_lua.callGlobalField("g_game", "onLoginError", error); +} + +void Game::processLoginAdvice(const std::string& message) +{ + g_lua.callGlobalField("g_game", "onLoginAdvice", message); +} + +void Game::processLoginWait(const std::string& message, int time) +{ + g_lua.callGlobalField("g_game", "onLoginWait", message, time); +} + +void Game::processLoginToken(bool unknown) +{ + g_lua.callGlobalField("g_game", "onLoginToken", unknown); +} + +void Game::processLogin() +{ + g_lua.callGlobalField("g_game", "onLogin"); +} + +void Game::processPendingGame() +{ + m_localPlayer->setPendingGame(true); + g_lua.callGlobalField("g_game", "onPendingGame"); + m_protocolGame->sendEnterGame(); +} + +void Game::processEnterGame() +{ + m_localPlayer->setPendingGame(false); + g_lua.callGlobalField("g_game", "onEnterGame"); +} + +void Game::processGameStart() +{ + m_online = true; + + // synchronize fight modes with the server + m_protocolGame->sendChangeFightModes(m_fightMode, m_chaseMode, m_safeFight, m_pvpMode); + + // NOTE: the entire map description and local player information is not known yet (bot call is allowed here) + enableBotCall(); + g_lua.callGlobalField("g_game", "onGameStart"); + disableBotCall(); + + if (g_game.getFeature(Otc::GameExtendedClientPing)) { + m_newPingEvent = g_dispatcher.scheduleEvent([] { + g_game.newPing(); + }, m_newPingDelay); + } + if(g_game.getFeature(Otc::GameClientPing)) { + m_pingEvent = g_dispatcher.scheduleEvent([] { + g_game.ping(); + }, m_pingDelay); + } + + m_checkConnectionEvent = g_dispatcher.cycleEvent([this] { + if(!g_game.isConnectionOk() && !m_connectionFailWarned) { + g_lua.callGlobalField("g_game", "onConnectionFailing", true); + m_connectionFailWarned = true; + } else if(g_game.isConnectionOk() && m_connectionFailWarned) { + g_lua.callGlobalField("g_game", "onConnectionFailing", false); + m_connectionFailWarned = false; + } + }, 1000); +} + +void Game::processGameEnd() +{ + m_online = false; + g_lua.callGlobalField("g_game", "onGameEnd"); + + if(m_connectionFailWarned) { + g_lua.callGlobalField("g_game", "onConnectionFailing", false); + m_connectionFailWarned = false; + } + + // reset game state + resetGameStates(); + + m_worldName = ""; + m_characterName = ""; + + // clean map creatures + g_map.cleanDynamicThings(); +} + +void Game::processDeath(int deathType, int penality) +{ + m_dead = true; + m_localPlayer->stopWalk(); + + g_lua.callGlobalField("g_game", "onDeath", deathType, penality); +} + +void Game::processGMActions(const std::vector& actions) +{ + m_gmActions = actions; + g_lua.callGlobalField("g_game", "onGMActions", actions); +} + +void Game::processPlayerHelpers(int helpers) +{ + g_lua.callGlobalField("g_game", "onPlayerHelpersUpdate", helpers); +} + +void Game::processPlayerModes(Otc::FightModes fightMode, Otc::ChaseModes chaseMode, bool safeMode, Otc::PVPModes pvpMode) +{ + m_fightMode = fightMode; + m_chaseMode = chaseMode; + m_safeFight = safeMode; + m_pvpMode = pvpMode; + + g_lua.callGlobalField("g_game", "onFightModeChange", fightMode); + g_lua.callGlobalField("g_game", "onChaseModeChange", chaseMode); + g_lua.callGlobalField("g_game", "onSafeFightChange", safeMode); + g_lua.callGlobalField("g_game", "onPVPModeChange", pvpMode); +} + +void Game::processPing() +{ + g_lua.callGlobalField("g_game", "onPing"); + enableBotCall(); + m_protocolGame->sendPingBack(); + disableBotCall(); +} + +void Game::processPingBack() +{ + m_pingReceived++; + + if (!g_game.getFeature(Otc::GameExtendedClientPing)) { + if (m_pingReceived == m_pingSent) + m_ping = m_pingTimer.elapsed_millis(); + + g_lua.callGlobalField("g_game", "onPingBack", m_ping); + } + + m_pingEvent = g_dispatcher.scheduleEvent([] { + g_game.ping(); + }, m_pingDelay); +} + +void Game::processNewPing(uint32_t pingId) +{ + auto it = m_newPingIds.find(pingId); + + if (it == m_newPingIds.end()) + return; + + m_ping = it->second.elapsed_millis(); + g_lua.callGlobalField("g_game", "onPingBack", m_ping); +} + +void Game::processTextMessage(Otc::MessageMode mode, const std::string& text) +{ + g_lua.callGlobalField("g_game", "onTextMessage", mode, text); +} + +void Game::processTalk(const std::string& name, int level, Otc::MessageMode mode, const std::string& text, int channelId, const Position& pos) +{ + g_lua.callGlobalField("g_game", "onTalk", name, level, mode, text, channelId, pos); +} + +void Game::processOpenContainer(int containerId, const ItemPtr& containerItem, const std::string& name, int capacity, bool hasParent, const std::vector& items, bool isUnlocked, bool hasPages, int containerSize, int firstIndex) +{ + ContainerPtr previousContainer = getContainer(containerId); + ContainerPtr container = ContainerPtr(new Container(containerId, capacity, name, containerItem, hasParent, isUnlocked, hasPages, containerSize, firstIndex)); + m_containers[containerId] = container; + container->onAddItems(items); + + // we might want to close a container here + enableBotCall(); + container->onOpen(previousContainer); + disableBotCall(); + + if(previousContainer) + previousContainer->onClose(); +} + +void Game::processCloseContainer(int containerId) +{ + ContainerPtr container = getContainer(containerId); + if(!container) { + return; + } + + m_containers[containerId] = nullptr; + container->onClose(); +} + +void Game::processContainerAddItem(int containerId, const ItemPtr& item, int slot) +{ + ContainerPtr container = getContainer(containerId); + if(!container) { + return; + } + + container->onAddItem(item, slot); +} + +void Game::processContainerUpdateItem(int containerId, int slot, const ItemPtr& item) +{ + ContainerPtr container = getContainer(containerId); + if(!container) { + return; + } + + container->onUpdateItem(slot, item); +} + +void Game::processContainerRemoveItem(int containerId, int slot, const ItemPtr& lastItem) +{ + ContainerPtr container = getContainer(containerId); + if(!container) { + return; + } + + container->onRemoveItem(slot, lastItem); +} + +void Game::processInventoryChange(int slot, const ItemPtr& item) +{ + if(item) + item->setPosition(Position(65535, slot, 0)); + + m_localPlayer->setInventoryItem((Otc::InventorySlot)slot, item); +} + +void Game::processChannelList(const std::vector >& channelList) +{ + g_lua.callGlobalField("g_game", "onChannelList", channelList); +} + +void Game::processOpenChannel(int channelId, const std::string& name) +{ + g_lua.callGlobalField("g_game", "onOpenChannel", channelId, name); +} + +void Game::processOpenPrivateChannel(const std::string& name) +{ + g_lua.callGlobalField("g_game", "onOpenPrivateChannel", name); +} + +void Game::processOpenOwnPrivateChannel(int channelId, const std::string& name) +{ + g_lua.callGlobalField("g_game", "onOpenOwnPrivateChannel", channelId, name); +} + +void Game::processCloseChannel(int channelId) +{ + g_lua.callGlobalField("g_game", "onCloseChannel", channelId); +} + +void Game::processRuleViolationChannel(int channelId) +{ + g_lua.callGlobalField("g_game", "onRuleViolationChannel", channelId); +} + +void Game::processRuleViolationRemove(const std::string& name) +{ + g_lua.callGlobalField("g_game", "onRuleViolationRemove", name); +} + +void Game::processRuleViolationCancel(const std::string& name) +{ + g_lua.callGlobalField("g_game", "onRuleViolationCancel", name); +} + +void Game::processRuleViolationLock() +{ + g_lua.callGlobalField("g_game", "onRuleViolationLock"); +} + +void Game::processVipAdd(uint id, const std::string& name, uint status, const std::string& description, int iconId, bool notifyLogin) +{ + m_vips[id] = Vip(name, status, description, iconId, notifyLogin); + g_lua.callGlobalField("g_game", "onAddVip", id, name, status, description, iconId, notifyLogin); +} + +void Game::processVipStateChange(uint id, uint status) +{ + if (m_vips.find(id) == m_vips.end()) return; + std::get<1>(m_vips[id]) = status; + g_lua.callGlobalField("g_game", "onVipStateChange", id, status); +} + +void Game::processTutorialHint(int id) +{ + g_lua.callGlobalField("g_game", "onTutorialHint", id); +} + +void Game::processAddAutomapFlag(const Position& pos, int icon, const std::string& message) +{ + g_lua.callGlobalField("g_game", "onAddAutomapFlag", pos, icon, message); +} + +void Game::processRemoveAutomapFlag(const Position& pos, int icon, const std::string& message) +{ + g_lua.callGlobalField("g_game", "onRemoveAutomapFlag", pos, icon, message); +} + +void Game::processOpenOutfitWindow(const Outfit& currentOutfit, const std::vector >& outfitList, + const std::vector >& mountList) +{ + // create virtual creature outfit + CreaturePtr virtualOutfitCreature = CreaturePtr(new Creature); + virtualOutfitCreature->setDirection(Otc::South); + + Outfit outfit = currentOutfit; + outfit.setMount(0); + virtualOutfitCreature->setOutfit(outfit); + + // creature virtual mount outfit + CreaturePtr virtualMountCreature = nullptr; + if(getFeature(Otc::GamePlayerMounts)) + { + virtualMountCreature = CreaturePtr(new Creature); + virtualMountCreature->setDirection(Otc::South); + + Outfit mountOutfit; + mountOutfit.setId(0); + + int mount = currentOutfit.getMount(); + if(mount > 0) + mountOutfit.setId(mount); + + virtualMountCreature->setOutfit(mountOutfit); + } + + g_lua.callGlobalField("g_game", "onOpenOutfitWindow", virtualOutfitCreature, outfitList, virtualMountCreature, mountList); +} + +void Game::processOpenNpcTrade(const std::vector >& items) +{ + g_lua.callGlobalField("g_game", "onOpenNpcTrade", items); +} + +void Game::processPlayerGoods(uint64_t money, const std::vector >& goods) +{ + g_lua.callGlobalField("g_game", "onPlayerGoods", money, goods); +} + +void Game::processCloseNpcTrade() +{ + g_lua.callGlobalField("g_game", "onCloseNpcTrade"); +} + +void Game::processOwnTrade(const std::string& name, const std::vector& items) +{ + g_lua.callGlobalField("g_game", "onOwnTrade", name, items); +} + +void Game::processCounterTrade(const std::string& name, const std::vector& items) +{ + g_lua.callGlobalField("g_game", "onCounterTrade", name, items); +} + +void Game::processCloseTrade() +{ + g_lua.callGlobalField("g_game", "onCloseTrade"); +} + +void Game::processEditText(uint id, int itemId, int maxLength, const std::string& text, const std::string& writer, const std::string& date) +{ + g_lua.callGlobalField("g_game", "onEditText", id, itemId, maxLength, text, writer, date); +} + +void Game::processEditList(uint id, int doorId, const std::string& text) +{ + g_lua.callGlobalField("g_game", "onEditList", id, doorId, text); +} + +void Game::processQuestLog(const std::vector >& questList) +{ + g_lua.callGlobalField("g_game", "onQuestLog", questList); +} + +void Game::processQuestLine(int questId, const std::vector >& questMissions) +{ + g_lua.callGlobalField("g_game", "onQuestLine", questId, questMissions); +} + +void Game::processModalDialog(uint32 id, std::string title, std::string message, std::vector > buttonList, int enterButton, int escapeButton, std::vector > choiceList, bool priority) +{ + g_lua.callGlobalField("g_game", "onModalDialog", id, title, message, buttonList, enterButton, escapeButton, choiceList, priority); +} + +void Game::processAttackCancel(uint seq) +{ + if(seq == 0 || m_seq == seq) + cancelAttack(); +} + +void Game::processWalkCancel(Otc::Direction direction) +{ + m_localPlayer->cancelWalk(direction); +} + +void Game::processNewWalkCancel(Otc::Direction dir) +{ + m_walkId += 1; + m_localPlayer->cancelNewWalk(dir); +} + +void Game::processPredictiveWalkCancel(const Position& pos, Otc::Direction dir) +{ + m_walkPrediction += 1; + if (m_localPlayer->predictiveCancelWalk(pos, m_walkPrediction, dir)) { + m_walkId += 1; + } +} + +void Game::processWalkId(uint32_t walkId) +{ + m_walkId = std::max(m_walkId, walkId); // fixes desync +} + +void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey) +{ + if(m_protocolGame || isOnline()) + stdext::throw_exception("Unable to login into a world while already online or logging."); + + if(m_protocolVersion == 0) + stdext::throw_exception("Must set a valid game protocol version before logging."); + + // reset the new game state + resetGameStates(); + + m_localPlayer = LocalPlayerPtr(new LocalPlayer); + m_localPlayer->setName(characterName); + + m_protocolGame = ProtocolGamePtr(new ProtocolGame); + m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName, authenticatorToken, sessionKey); + m_characterName = characterName; + m_worldName = worldName; +} + +void Game::cancelLogin() +{ + // send logout even if the game has not started yet, to make sure that the player doesn't stay logged there + if(m_protocolGame) + m_protocolGame->sendLogout(); + + g_lua.callGlobalField("g_game", "onLogout"); + processDisconnect(); +} + +void Game::forceLogout() +{ + if(!isOnline()) + return; + + g_lua.callGlobalField("g_game", "onLogout"); + m_protocolGame->sendLogout(); + processDisconnect(); +} + +void Game::safeLogout() +{ + if(!isOnline()) + return; + + g_lua.callGlobalField("g_game", "onLogout"); + m_protocolGame->sendLogout(); +} + +void Game::autoWalk(const std::vector& dirs, Position startPos) +{ + if(!canPerformGameAction()) + return; + + if (dirs.size() == 0) + return; + + // protocol limits walk path + if((!g_game.getFeature(Otc::GameNewWalking) || dirs.size() > 4097) && dirs.size() > 127) { + g_logger.error("Auto walk path too great"); + return; + } + + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] Game::autoWalk", (int)g_clock.millis())); + } + + // must cancel follow before any new walk + if (isFollowing()) { + cancelFollow(); + } + + auto it = dirs.begin(); + Otc::Direction direction = *it; + + uint8_t flags = 0x04; // auto walk flag + + TilePtr toTile = g_map.getTile(startPos.translatedToDirection(direction)); + if(startPos == m_localPlayer->getPrewalkingPosition() && toTile && toTile->isWalkable() && !m_localPlayer->isWalking() && m_localPlayer->canWalk(direction, true)) { + m_localPlayer->preWalk(direction); + m_localPlayer->startServerWalking(); + flags |= 0x01; // prewalk flag + } + + g_lua.callGlobalField("g_game", "onAutoWalk", dirs); + + if (g_game.getFeature(Otc::GameNewWalking)) + m_protocolGame->sendNewWalk(m_walkId, m_walkPrediction, startPos, flags, dirs); + else + m_protocolGame->sendAutoWalk(dirs); +} + +void Game::walk(Otc::Direction direction, bool withPreWalk) +{ + m_denyBotCall = false; + if (!canPerformGameAction()) { + m_denyBotCall = true; + return; + } + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] Game::walk", (int)g_clock.millis())); + } + + g_lua.callGlobalField("g_game", "onWalk", direction, withPreWalk); + + if (g_game.getFeature(Otc::GameNewWalking)) { + //hidden + return; + } + + switch(direction) { + case Otc::North: + m_protocolGame->sendWalkNorth(); + break; + case Otc::East: + m_protocolGame->sendWalkEast(); + break; + case Otc::South: + m_protocolGame->sendWalkSouth(); + break; + case Otc::West: + m_protocolGame->sendWalkWest(); + break; + case Otc::NorthEast: + m_protocolGame->sendWalkNorthEast(); + break; + case Otc::SouthEast: + m_protocolGame->sendWalkSouthEast(); + break; + case Otc::SouthWest: + m_protocolGame->sendWalkSouthWest(); + break; + case Otc::NorthWest: + m_protocolGame->sendWalkNorthWest(); + break; + default: + break; + } + m_denyBotCall = true; +} + +void Game::turn(Otc::Direction direction) +{ + m_denyBotCall = false; + if (!canPerformGameAction()) { + m_denyBotCall = true; + return; + } + + if (g_game.getFeature(Otc::GameNewWalking)) { + m_localPlayer->setDirection(direction); + } + + switch(direction) { + case Otc::North: + m_protocolGame->sendTurnNorth(); + break; + case Otc::East: + m_protocolGame->sendTurnEast(); + break; + case Otc::South: + m_protocolGame->sendTurnSouth(); + break; + case Otc::West: + m_protocolGame->sendTurnWest(); + break; + default: + break; + } + m_denyBotCall = true; +} + +void Game::stop() +{ + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] Game::stop", (int)g_clock.millis())); + } + + m_denyBotCall = false; + if (!canPerformGameAction()) { + m_denyBotCall = true; + return; + } + + if(isFollowing()) + cancelFollow(); // can change m_denyBotCall + m_denyBotCall = false; + + m_protocolGame->sendStop(); + m_denyBotCall = true; +} + +void Game::look(const ThingPtr& thing, bool isBattleList) +{ + if(!canPerformGameAction() || !thing) + return; + + if(thing->isCreature() && isBattleList && m_protocolVersion >= 961) + m_protocolGame->sendLookCreature(thing->getId()); + else + m_protocolGame->sendLook(thing->getPosition(), thing->getId(), thing->getStackPos()); +} + +void Game::move(const ThingPtr& thing, const Position& toPos, int count) +{ + if (count <= 0) + count = 1; + + if (!canPerformGameAction() || !thing || thing->getPosition() == toPos) + return; + + uint id = thing->getId(); + if (thing->isCreature()) { + CreaturePtr creature = thing->static_self_cast(); + id = Proto::Creature; + } + + m_protocolGame->sendMove(thing->getPosition(), id, thing->getStackPos(), toPos, count); +} + +void Game::moveRaw(const Position& pos, int id, int stackpos, const Position& toPos, int count) +{ + if (!canPerformGameAction()) + return; + + m_protocolGame->sendMove(pos, id, stackpos, toPos, count); +} + +void Game::moveToParentContainer(const ThingPtr& thing, int count) +{ + if(!canPerformGameAction() || !thing || count <= 0) + return; + + Position position = thing->getPosition(); + move(thing, Position(position.x, position.y, 254), count); +} + +void Game::rotate(const ThingPtr& thing) +{ + if(!canPerformGameAction() || !thing) + return; + + m_protocolGame->sendRotateItem(thing->getPosition(), thing->getId(), thing->getStackPos()); +} + +void Game::wrap(const ThingPtr& thing) +{ + if (!canPerformGameAction() || !thing) + return; + + m_protocolGame->sendWrapableItem(thing->getPosition(), thing->getId(), thing->getStackPos()); +} + +void Game::use(const ThingPtr& thing) +{ + if(!canPerformGameAction() || !thing) + return; + + Position pos = thing->getPosition(); + if(!pos.isValid()) // virtual item + pos = Position(0xFFFF, 0, 0); // inventory item + + // some items, e.g. parcel, are not set as containers but they are. + // always try to use these items in free container slots. + m_protocolGame->sendUseItem(pos, thing->getId(), thing->getStackPos(), findEmptyContainerId()); + + g_lua.callGlobalField("g_game", "onUse", pos, thing->getId(), thing->getStackPos(), 0); +} + +void Game::useInventoryItem(int itemId, int subType) +{ + if(!canPerformGameAction() || !g_things.isValidDatId(itemId, ThingCategoryItem)) + return; + + Position pos = Position(0xFFFF, 0, 0); // means that is a item in inventory + + m_protocolGame->sendUseItem(pos, itemId, 0, subType); + + g_lua.callGlobalField("g_game", "onUse", pos, itemId, 0, subType); +} + +void Game::useWith(const ItemPtr& item, const ThingPtr& toThing, int subType) +{ + if(!canPerformGameAction() || !item || !toThing) + return; + + Position pos = item->getPosition(); + if(!pos.isValid()) // virtual item + pos = Position(0xFFFF, 0, 0); // means that is an item in inventory + + if(toThing->isCreature() && g_game.getProtocolVersion() >= 780) + m_protocolGame->sendUseOnCreature(pos, item->getId(), subType ? subType : item->getStackPos(), toThing->getId()); + else + m_protocolGame->sendUseItemWith(pos, item->getId(), subType ? subType : item->getStackPos(), toThing->getPosition(), toThing->getId(), toThing->getStackPos()); + + g_lua.callGlobalField("g_game", "onUseWith", pos, item->getId(), toThing, subType); +} + +void Game::useInventoryItemWith(int itemId, const ThingPtr& toThing, int subType) +{ + if(!canPerformGameAction() || !toThing) + return; + + Position pos = Position(0xFFFF, 0, 0); // means that is a item in inventory + + if(toThing->isCreature()) + m_protocolGame->sendUseOnCreature(pos, itemId, subType, toThing->getId()); + else + m_protocolGame->sendUseItemWith(pos, itemId, subType, toThing->getPosition(), toThing->getId(), toThing->getStackPos()); + + g_lua.callGlobalField("g_game", "onUseWith", pos, itemId, toThing, subType); +} + +ItemPtr Game::findItemInContainers(uint itemId, int subType) +{ + for(auto& it : m_containers) { + const ContainerPtr& container = it.second; + + if(container) { + ItemPtr item = container->findItemById(itemId, subType); + if(item != nullptr) + return item; + } + } + return nullptr; +} + +int Game::open(const ItemPtr& item, const ContainerPtr& previousContainer) +{ + if(!canPerformGameAction() || !item) + return -1; + + int id = 0; + if(!previousContainer) + id = findEmptyContainerId(); + else + id = previousContainer->getId(); + + m_protocolGame->sendUseItem(item->getPosition(), item->getId(), item->getStackPos(), id); + return id; +} + +void Game::openParent(const ContainerPtr& container) +{ + if(!canPerformGameAction() || !container) + return; + + m_protocolGame->sendUpContainer(container->getId()); +} + +void Game::close(const ContainerPtr& container) +{ + if(!canPerformGameAction() || !container) + return; + + m_protocolGame->sendCloseContainer(container->getId()); +} + +void Game::refreshContainer(const ContainerPtr& container) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRefreshContainer(container->getId()); +} + +void Game::attack(CreaturePtr creature, bool cancel) +{ + if(!canPerformGameAction() || creature == m_localPlayer) + return; + + // cancel when attacking again + if(creature && creature == m_attackingCreature) + creature = nullptr; + + if(creature && isFollowing()) + cancelFollow(); + + setAttackingCreature(creature); + m_localPlayer->stopAutoWalk(); + + if(m_protocolVersion >= 963) { + if(creature) + m_seq = creature->getId(); + } else + m_seq++; + + if(!cancel) + m_protocolGame->sendAttack(creature ? creature->getId() : 0, m_seq); +} + +void Game::follow(CreaturePtr creature) +{ + m_denyBotCall = false; + if (!canPerformGameAction() || creature == m_localPlayer) { + m_denyBotCall = true; + return; + } + + // cancel when following again + if(creature && creature == m_followingCreature) + creature = nullptr; + + if(creature && isAttacking()) + cancelAttack(); + + setFollowingCreature(creature); + m_localPlayer->stopAutoWalk(); + + if(m_protocolVersion >= 963) { + if(creature) + m_seq = creature->getId(); + } else + m_seq++; + + m_protocolGame->sendFollow(creature ? creature->getId() : 0, m_seq); + m_denyBotCall = true; +} + +void Game::cancelAttackAndFollow() +{ + if(!canPerformGameAction()) + return; + + if(isFollowing()) + setFollowingCreature(nullptr); + if(isAttacking()) + setAttackingCreature(nullptr); + + m_localPlayer->stopAutoWalk(); + + m_protocolGame->sendCancelAttackAndFollow(); + + g_lua.callGlobalField("g_game", "onCancelAttackAndFollow"); +} + +void Game::talk(const std::string& message) +{ + if(!canPerformGameAction() || message.empty()) + return; + + talkChannel(Otc::MessageSay, 0, message); +} + +void Game::talkChannel(Otc::MessageMode mode, int channelId, const std::string& message) +{ + if(!canPerformGameAction() || message.empty()) + return; + + m_protocolGame->sendTalk(mode, channelId, "", message, m_localPlayer->getPosition(), m_localPlayer->getDirection()); +} + +void Game::talkPrivate(Otc::MessageMode mode, const std::string& receiver, const std::string& message) +{ + if(!canPerformGameAction() || receiver.empty() || message.empty()) + return; + m_protocolGame->sendTalk(mode, 0, receiver, message, m_localPlayer->getPosition(), m_localPlayer->getDirection()); +} + +void Game::openPrivateChannel(const std::string& receiver) +{ + if(!canPerformGameAction() || receiver.empty()) + return; + m_protocolGame->sendOpenPrivateChannel(receiver); +} + +void Game::requestChannels() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestChannels(); +} + +void Game::joinChannel(int channelId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendJoinChannel(channelId); +} + +void Game::leaveChannel(int channelId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendLeaveChannel(channelId); +} + +void Game::closeNpcChannel() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendCloseNpcChannel(); +} + +void Game::openOwnChannel() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendOpenOwnChannel(); +} + +void Game::inviteToOwnChannel(const std::string& name) +{ + if(!canPerformGameAction() || name.empty()) + return; + m_protocolGame->sendInviteToOwnChannel(name); +} + +void Game::excludeFromOwnChannel(const std::string& name) +{ + if(!canPerformGameAction() || name.empty()) + return; + m_protocolGame->sendExcludeFromOwnChannel(name); +} + +void Game::partyInvite(int creatureId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendInviteToParty(creatureId); +} + +void Game::partyJoin(int creatureId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendJoinParty(creatureId); +} + +void Game::partyRevokeInvitation(int creatureId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRevokeInvitation(creatureId); +} + +void Game::partyPassLeadership(int creatureId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendPassLeadership(creatureId); +} + +void Game::partyLeave() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendLeaveParty(); +} + +void Game::partyShareExperience(bool active) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendShareExperience(active); +} + +void Game::requestOutfit() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestOutfit(); +} + +void Game::changeOutfit(const Outfit& outfit) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendChangeOutfit(outfit); +} + +void Game::addVip(const std::string& name) +{ + if(!canPerformGameAction() || name.empty()) + return; + m_protocolGame->sendAddVip(name); +} + +void Game::removeVip(int playerId) +{ + if(!canPerformGameAction()) + return; + + auto it = m_vips.find(playerId); + if(it == m_vips.end()) + return; + m_vips.erase(it); + m_protocolGame->sendRemoveVip(playerId); +} + +void Game::editVip(int playerId, const std::string& description, int iconId, bool notifyLogin) +{ + if(!canPerformGameAction()) + return; + + auto it = m_vips.find(playerId); + if(it == m_vips.end()) + return; + + std::get<2>(m_vips[playerId]) = description; + std::get<3>(m_vips[playerId]) = iconId; + std::get<4>(m_vips[playerId]) = notifyLogin; + + if(getFeature(Otc::GameAdditionalVipInfo)) + m_protocolGame->sendEditVip(playerId, description, iconId, notifyLogin); +} + +void Game::setChaseMode(Otc::ChaseModes chaseMode) +{ + if(!canPerformGameAction()) + return; + if(m_chaseMode == chaseMode) + return; + m_chaseMode = chaseMode; + m_protocolGame->sendChangeFightModes(m_fightMode, m_chaseMode, m_safeFight, m_pvpMode); + g_lua.callGlobalField("g_game", "onChaseModeChange", chaseMode); +} + +void Game::setFightMode(Otc::FightModes fightMode) +{ + if(!canPerformGameAction()) + return; + if(m_fightMode == fightMode) + return; + m_fightMode = fightMode; + m_protocolGame->sendChangeFightModes(m_fightMode, m_chaseMode, m_safeFight, m_pvpMode); + g_lua.callGlobalField("g_game", "onFightModeChange", fightMode); +} + +void Game::setSafeFight(bool on) +{ + if(!canPerformGameAction()) + return; + if(m_safeFight == on) + return; + m_safeFight = on; + m_protocolGame->sendChangeFightModes(m_fightMode, m_chaseMode, m_safeFight, m_pvpMode); + g_lua.callGlobalField("g_game", "onSafeFightChange", on); +} + +void Game::setPVPMode(Otc::PVPModes pvpMode) +{ + if(!canPerformGameAction()) + return; + if(!getFeature(Otc::GamePVPMode)) + return; + if(m_pvpMode == pvpMode) + return; + m_pvpMode = pvpMode; + m_protocolGame->sendChangeFightModes(m_fightMode, m_chaseMode, m_safeFight, m_pvpMode); + g_lua.callGlobalField("g_game", "onPVPModeChange", pvpMode); +} + +void Game::setUnjustifiedPoints(UnjustifiedPoints unjustifiedPoints) +{ + if(!canPerformGameAction()) + return; + if(!getFeature(Otc::GameUnjustifiedPoints)) + return; + if(m_unjustifiedPoints == unjustifiedPoints) + return; + + m_unjustifiedPoints = unjustifiedPoints; + g_lua.callGlobalField("g_game", "onUnjustifiedPointsChange", unjustifiedPoints); +} + +void Game::setOpenPvpSituations(int openPvpSituations) +{ + if(!canPerformGameAction()) + return; + if(m_openPvpSituations == openPvpSituations) + return; + + m_openPvpSituations = openPvpSituations; + g_lua.callGlobalField("g_game", "onOpenPvpSituationsChange", openPvpSituations); +} + + +void Game::inspectNpcTrade(const ItemPtr& item) +{ + if(!canPerformGameAction() || !item) + return; + m_protocolGame->sendInspectNpcTrade(item->getId(), item->getCount()); +} + +void Game::buyItem(const ItemPtr& item, int amount, bool ignoreCapacity, bool buyWithBackpack) +{ + if(!canPerformGameAction() || !item) + return; + m_protocolGame->sendBuyItem(item->getId(), item->getCountOrSubType(), amount, ignoreCapacity, buyWithBackpack); +} + +void Game::sellItem(const ItemPtr& item, int amount, bool ignoreEquipped) +{ + if(!canPerformGameAction() || !item) + return; + m_protocolGame->sendSellItem(item->getId(), item->getCountOrSubType(), amount, ignoreEquipped); +} + +void Game::closeNpcTrade() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendCloseNpcTrade(); +} + +void Game::requestTrade(const ItemPtr& item, const CreaturePtr& creature) +{ + if(!canPerformGameAction() || !item || !creature) + return; + m_protocolGame->sendRequestTrade(item->getPosition(), item->getId(), item->getStackPos(), creature->getId()); +} + +void Game::inspectTrade(bool counterOffer, int index) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendInspectTrade(counterOffer, index); +} + +void Game::acceptTrade() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendAcceptTrade(); +} + +void Game::rejectTrade() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRejectTrade(); +} + +void Game::editText(uint id, const std::string& text) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendEditText(id, text); +} + +void Game::editList(uint id, int doorId, const std::string& text) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendEditList(id, doorId, text); +} + +void Game::openRuleViolation(const std::string& reporter) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendOpenRuleViolation(reporter); +} + +void Game::closeRuleViolation(const std::string& reporter) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendCloseRuleViolation(reporter); +} + +void Game::cancelRuleViolation() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendCancelRuleViolation(); +} + +void Game::reportBug(const std::string& comment) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendBugReport(comment); +} + +void Game::reportRuleViolation(const std::string& target, int reason, int action, const std::string& comment, const std::string& statement, int statementId, bool ipBanishment) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRuleViolation(target, reason, action, comment, statement, statementId, ipBanishment); +} + +void Game::debugReport(const std::string& a, const std::string& b, const std::string& c, const std::string& d) +{ + m_protocolGame->sendDebugReport(a, b, c, d); +} + +void Game::requestQuestLog() +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestQuestLog(); +} + +void Game::requestQuestLine(int questId) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestQuestLine(questId); +} + +void Game::equipItem(const ItemPtr& item) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendEquipItem(item->getId(), item->getCountOrSubType()); +} + +void Game::mount(bool mount) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendMountStatus(mount); +} + +void Game::requestItemInfo(const ItemPtr& item, int index) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestItemInfo(item->getId(), item->getSubType(), index); +} + +void Game::answerModalDialog(uint32 dialog, int button, int choice) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendAnswerModalDialog(dialog, button, choice); +} + +void Game::browseField(const Position& position) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendBrowseField(position); +} + +void Game::seekInContainer(int cid, int index) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendSeekInContainer(cid, index); +} + +void Game::buyStoreOffer(int offerId, int productType, const std::string& name) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendBuyStoreOffer(offerId, productType, name); +} + +void Game::requestTransactionHistory(int page, int entriesPerPage) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestTransactionHistory(page, entriesPerPage); +} + +void Game::requestStoreOffers(const std::string& categoryName, int serviceType) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendRequestStoreOffers(categoryName, serviceType); +} + +void Game::openStore(int serviceType) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendOpenStore(serviceType); +} + +void Game::transferCoins(const std::string& recipient, int amount) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendTransferCoins(recipient, amount); +} + +void Game::openTransactionHistory(int entriesPerPage) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendOpenTransactionHistory(entriesPerPage); +} + +void Game::preyAction(int slot, int actionType, int index) +{ + if (!canPerformGameAction()) + return; + m_protocolGame->sendPreyAction(slot, actionType, index); +} + +void Game::preyRequest() +{ + if (!canPerformGameAction()) + return; + m_protocolGame->sendPreyRequest(); +} + + +void Game::applyImbuement(uint8_t slot, uint32_t imbuementId, bool protectionCharm) +{ + if (!canPerformGameAction()) + return; + m_protocolGame->sendApplyImbuement(slot, imbuementId, protectionCharm); +} + +void Game::clearImbuement(uint8_t slot) +{ + if (!canPerformGameAction()) + return; + m_protocolGame->sendClearImbuement(slot); +} + +void Game::closeImbuingWindow() +{ + if (!canPerformGameAction()) + return; + m_protocolGame->sendCloseImbuingWindow(); +} + +void Game::ping() +{ + if(!m_protocolGame || !m_protocolGame->isConnected()) + return; + + if(m_pingReceived != m_pingSent) + return; + + m_denyBotCall = false; + m_protocolGame->sendPing(); + m_denyBotCall = true; + m_pingSent++; + m_pingTimer.restart(); +} + +void Game::newPing() +{ + if(!m_protocolGame || !m_protocolGame->isConnected()) + return; + + static uint32_t pingId = 1; + pingId += 1; + m_newPingIds[pingId] = stdext::timer(); + + m_protocolGame->sendNewPing(pingId, (int16_t)m_ping, (int16_t)g_app.getFps()); + m_newPingEvent = g_dispatcher.scheduleEvent([] { + g_game.newPing(); + }, m_newPingDelay); +} + +void Game::changeMapAwareRange(int xrange, int yrange) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendChangeMapAwareRange(xrange, yrange); +} + +bool Game::checkBotProtection() +{ + if (getFeature(Otc::GameBotProtection)) { + // accepts calls comming from a stacktrace containing only C++ functions, + // if the stacktrace contains a lua function, then only accept if the engine is processing an input event + if (m_denyBotCall && g_lua.isInCppCallback() && !g_app.isOnInputEvent() && !g_dispatcher.isBotSafe()) { + g_logger.error(g_lua.traceback("caught a lua call to a bot protected game function, the call was cancelled")); + return false; + } + } + + return true; +} + +bool Game::canPerformGameAction() +{ + // we can only perform game actions if we meet these conditions: + // - the game is online + // - the local player exists + // - the local player is not dead + // - we have a game protocol + // - the game protocol is connected + // - its not a bot action + return m_online && m_localPlayer && !m_localPlayer->isDead() && !m_dead && m_protocolGame && m_protocolGame->isConnected() && checkBotProtection(); +} + +void Game::setProtocolVersion(int version) +{ + if(m_protocolVersion == version) + return; + + if(isOnline()) + stdext::throw_exception("Unable to change protocol version while online"); + + m_protocolVersion = version; + + Proto::buildMessageModesMap(version); + + g_lua.callGlobalField("g_game", "onProtocolVersionChange", version); +} + +void Game::setClientVersion(int version) +{ + if(isOnline()) + stdext::throw_exception("Unable to change client version while online"); + + m_clientVersion = version; + g_lua.callGlobalField("g_game", "onClientVersionChange", version); +} + +void Game::setAttackingCreature(const CreaturePtr& creature) +{ + if(creature != m_attackingCreature) { + CreaturePtr oldCreature = m_attackingCreature; + m_attackingCreature = creature; + + g_lua.callGlobalField("g_game", "onAttackingCreatureChange", creature, oldCreature); + } +} + +void Game::setFollowingCreature(const CreaturePtr& creature) +{ + CreaturePtr oldCreature = m_followingCreature; + m_followingCreature = creature; + + g_lua.callGlobalField("g_game", "onFollowingCreatureChange", creature, oldCreature); +} + +std::string Game::formatCreatureName(const std::string& name) +{ + std::string formatedName = name; + if(getFeature(Otc::GameFormatCreatureName) && name.length() > 0) { + bool upnext = true; + for(uint i=0;i= 0) + return m_clientCustomOs; + + if(g_app.getOs() == "windows") + return 20; + if(g_app.getOs() == "mac") + return 22; + if (g_app.getOs() == "android") + return 23; + if (g_app.getOs() == "ios") + return 24; + return 21; // linux +} diff --git a/src/client/game.h b/src/client/game.h new file mode 100644 index 0000000..acda7d3 --- /dev/null +++ b/src/client/game.h @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GAME_H +#define GAME_H + +#include "declarations.h" +#include "item.h" +#include "animatedtext.h" +#include "effect.h" +#include "creature.h" +#include "container.h" +#include "protocolgame.h" +#include "localplayer.h" +#include "outfit.h" +#include + +#include + +struct UnjustifiedPoints { + bool operator==(const UnjustifiedPoints& other) { + return killsDay == other.killsDay && + killsDayRemaining == other.killsDayRemaining && + killsWeek == other.killsWeek && + killsWeekRemaining == other.killsWeekRemaining && + killsMonth == other.killsMonth && + killsMonthRemaining == other.killsMonthRemaining && + skullTime == other.skullTime; + } + uint8 killsDay; + uint8 killsDayRemaining; + uint8 killsWeek; + uint8 killsWeekRemaining; + uint8 killsMonth; + uint8 killsMonthRemaining; + uint8 skullTime; +}; + +typedef std::tuple Vip; + +//@bindsingleton g_game +class Game +{ +public: + Game(); + + void init(); + void terminate(); + +private: + void resetGameStates(); + +protected: + void processConnectionError(const boost::system::error_code& error); + void processDisconnect(); + void processPing(); + void processPingBack(); + void processNewPing(uint32_t pingId); + + void processUpdateNeeded(const std::string& signature); + void processLoginError(const std::string& error); + void processLoginAdvice(const std::string& message); + void processLoginWait(const std::string& message, int time); + void processLoginToken(bool unknown); + void processLogin(); + void processPendingGame(); + void processEnterGame(); + + void processGameStart(); + void processGameEnd(); + void processDeath(int deathType, int penality); + + void processGMActions(const std::vector& actions); + void processInventoryChange(int slot, const ItemPtr& item); + void processAttackCancel(uint seq); + void processWalkCancel(Otc::Direction direction); + + void processNewWalkCancel(Otc::Direction dir); + void processPredictiveWalkCancel(const Position& pos, Otc::Direction dir); + void processWalkId(uint32_t walkId); + + void processPlayerHelpers(int helpers); + void processPlayerModes(Otc::FightModes fightMode, Otc::ChaseModes chaseMode, bool safeMode, Otc::PVPModes pvpMode); + + // message related + void processTextMessage(Otc::MessageMode mode, const std::string& text); + void processTalk(const std::string& name, int level, Otc::MessageMode mode, const std::string& text, int channelId, const Position& pos); + + // container related + void processOpenContainer(int containerId, const ItemPtr& containerItem, const std::string& name, int capacity, bool hasParent, const std::vector& items, bool isUnlocked, bool hasPages, int containerSize, int firstIndex); + void processCloseContainer(int containerId); + void processContainerAddItem(int containerId, const ItemPtr& item, int slot); + void processContainerUpdateItem(int containerId, int slot, const ItemPtr& item); + void processContainerRemoveItem(int containerId, int slot, const ItemPtr& lastItem); + + // channel related + void processChannelList(const std::vector >& channelList); + void processOpenChannel(int channelId, const std::string& name); + void processOpenPrivateChannel(const std::string& name); + void processOpenOwnPrivateChannel(int channelId, const std::string& name); + void processCloseChannel(int channelId); + + // rule violations + void processRuleViolationChannel(int channelId); + void processRuleViolationRemove(const std::string& name); + void processRuleViolationCancel(const std::string& name); + void processRuleViolationLock(); + + // vip related + void processVipAdd(uint id, const std::string& name, uint status, const std::string& description, int iconId, bool notifyLogin); + void processVipStateChange(uint id, uint status); + + // tutorial hint + void processTutorialHint(int id); + void processAddAutomapFlag(const Position& pos, int icon, const std::string& message); + void processRemoveAutomapFlag(const Position& pos, int icon, const std::string& message); + + // outfit + void processOpenOutfitWindow(const Outfit& currentOutfit, const std::vector >& outfitList, + const std::vector >& mountList); + + // npc trade + void processOpenNpcTrade(const std::vector >& items); + void processPlayerGoods(uint64_t money, const std::vector >& goods); + void processCloseNpcTrade(); + + // player trade + void processOwnTrade(const std::string& name, const std::vector& items); + void processCounterTrade(const std::string& name, const std::vector& items); + void processCloseTrade(); + + // edit text/list + void processEditText(uint id, int itemId, int maxLength, const std::string& text, const std::string& writer, const std::string& date); + void processEditList(uint id, int doorId, const std::string& text); + + // questlog + void processQuestLog(const std::vector >& questList); + void processQuestLine(int questId, const std::vector >& questMissions); + + // modal dialogs >= 970 + void processModalDialog(uint32 id, std::string title, std::string message, std::vector > buttonList, int enterButton, int escapeButton, std::vector > choiceList, bool priority); + + friend class ProtocolGame; + friend class Map; + +public: + // login related + void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey); + void cancelLogin(); + void forceLogout(); + void safeLogout(); + + // walk related + void walk(Otc::Direction direction, bool withPreWalk); + void autoWalk(const std::vector& dirs, Position startPos); + void turn(Otc::Direction direction); + void stop(); + + // item related + void look(const ThingPtr& thing, bool isBattleList = false); + void move(const ThingPtr& thing, const Position& toPos, int count); + void moveRaw(const Position& pos, int id, int stackpos, const Position& toPos, int count); + void moveToParentContainer(const ThingPtr& thing, int count); + void rotate(const ThingPtr& thing); + void wrap(const ThingPtr& thing); + void use(const ThingPtr& thing); + void useWith(const ItemPtr& fromThing, const ThingPtr& toThing, int subType = 0); + void useInventoryItem(int itemId, int subType = 0); + void useInventoryItemWith(int itemId, const ThingPtr& toThing, int subType = 0); + ItemPtr findItemInContainers(uint itemId, int subType); + + // container related + int open(const ItemPtr& item, const ContainerPtr& previousContainer); + void openParent(const ContainerPtr& container); + void close(const ContainerPtr& container); + void refreshContainer(const ContainerPtr& container); + + // attack/follow related + void attack(CreaturePtr creature, bool cancel = false); + void cancelAttack() { attack(nullptr, true); } + void follow(CreaturePtr creature); + void cancelFollow() { follow(nullptr); } + void cancelAttackAndFollow(); + + // talk related + void talk(const std::string& message); + void talkChannel(Otc::MessageMode mode, int channelId, const std::string& message); + void talkPrivate(Otc::MessageMode mode, const std::string& receiver, const std::string& message); + + // channel related + void openPrivateChannel(const std::string& receiver); + void requestChannels(); + void joinChannel(int channelId); + void leaveChannel(int channelId); + void closeNpcChannel(); + void openOwnChannel(); + void inviteToOwnChannel(const std::string& name); + void excludeFromOwnChannel(const std::string& name); + + // party related + void partyInvite(int creatureId); + void partyJoin(int creatureId); + void partyRevokeInvitation(int creatureId); + void partyPassLeadership(int creatureId); + void partyLeave(); + void partyShareExperience(bool active); + + // outfit related + void requestOutfit(); + void changeOutfit(const Outfit& outfit); + + // vip related + void addVip(const std::string& name); + void removeVip(int playerId); + void editVip(int playerId, const std::string& description, int iconId, bool notifyLogin); + + // fight modes related + void setChaseMode(Otc::ChaseModes chaseMode); + void setFightMode(Otc::FightModes fightMode); + void setSafeFight(bool on); + void setPVPMode(Otc::PVPModes pvpMode); + Otc::ChaseModes getChaseMode() { return m_chaseMode; } + Otc::FightModes getFightMode() { return m_fightMode; } + bool isSafeFight() { return m_safeFight; } + Otc::PVPModes getPVPMode() { return m_pvpMode; } + + // pvp related + void setUnjustifiedPoints(UnjustifiedPoints unjustifiedPoints); + UnjustifiedPoints getUnjustifiedPoints() { return m_unjustifiedPoints; }; + void setOpenPvpSituations(int openPvpSitations); + int getOpenPvpSituations() { return m_openPvpSituations; } + + // npc trade related + void inspectNpcTrade(const ItemPtr& item); + void buyItem(const ItemPtr& item, int amount, bool ignoreCapacity, bool buyWithBackpack); + void sellItem(const ItemPtr& item, int amount, bool ignoreEquipped); + void closeNpcTrade(); + + // player trade related + void requestTrade(const ItemPtr& item, const CreaturePtr& creature); + void inspectTrade(bool counterOffer, int index); + void acceptTrade(); + void rejectTrade(); + + // house window and editable items related + void editText(uint id, const std::string& text); + void editList(uint id, int doorId, const std::string& text); + + // rule violations (only gms) + void openRuleViolation(const std::string& reporter); + void closeRuleViolation(const std::string& reporter); + void cancelRuleViolation(); + + // reports + void reportBug(const std::string& comment); + void reportRuleViolation(const std::string& target, int reason, int action, const std::string& comment, const std::string& statement, int statementId, bool ipBanishment); + void debugReport(const std::string& a, const std::string& b, const std::string& c, const std::string& d); + + // questlog related + void requestQuestLog(); + void requestQuestLine(int questId); + + // 870 only + void equipItem(const ItemPtr& item); + void mount(bool mount); + + // 910 only + void requestItemInfo(const ItemPtr& item, int index); + + // >= 970 modal dialog + void answerModalDialog(uint32 dialog, int button, int choice); + + // >= 984 browse field + void browseField(const Position& position); + void seekInContainer(int cid, int index); + + // >= 1080 ingame store + void buyStoreOffer(int offerId, int productType, const std::string& name = ""); + void requestTransactionHistory(int page, int entriesPerPage); + void requestStoreOffers(const std::string& categoryName, int serviceType = 0); + void openStore(int serviceType = 0); + void transferCoins(const std::string& recipient, int amount); + void openTransactionHistory(int entriesPerPage); + + // >= 1100 + void preyAction(int slot, int actionType, int index); + void preyRequest(); + + void applyImbuement(uint8_t slot, uint32_t imbuementId, bool protectionCharm); + void clearImbuement(uint8_t slot); + void closeImbuingWindow(); + + //void reportRuleViolation2(); + void ping(); + void newPing(); + void setPingDelay(int delay) { m_pingDelay = delay; } + + // otclient only + void changeMapAwareRange(int xrange, int yrange); + + // dynamic support for game features + void resetFeatures() { m_features.reset(); } + void enableFeature(Otc::GameFeature feature) { m_features.set(feature, true); } + void disableFeature(Otc::GameFeature feature) { m_features.set(feature, false); } + void setFeature(Otc::GameFeature feature, bool enabled) { m_features.set(feature, enabled); } + bool getFeature(Otc::GameFeature feature) { return m_features.test(feature); } + + void setProtocolVersion(int version); + int getProtocolVersion() { return m_protocolVersion; } + void setCustomProtocolVersion(int version) { m_customProtocolVersion = version; } + int getCustomProtocolVersion() { return m_customProtocolVersion != 0 ? m_customProtocolVersion : m_protocolVersion; } + + void setClientVersion(int version); + int getClientVersion() { return m_clientVersion; } + + void setCustomOs(int os) { m_clientCustomOs = os; } + int getOs(); + + bool canPerformGameAction(); + bool checkBotProtection(); + + bool isOnline() { return m_online; } + bool isLogging() { return !m_online && m_protocolGame; } + bool isDead() { return m_dead; } + bool isAttacking() { return !!m_attackingCreature && !m_attackingCreature->isRemoved(); } + bool isFollowing() { return !!m_followingCreature && !m_followingCreature->isRemoved(); } + bool isConnectionOk() { return m_protocolGame && m_protocolGame->getElapsedTicksSinceLastRead() < 5000; } + + int getPing() { return m_ping; } + ContainerPtr getContainer(int index) { if (m_containers.find(index) == m_containers.end()) { return nullptr; } return m_containers[index]; } + std::map getContainers() { return m_containers; } + std::map getVips() { return m_vips; } + CreaturePtr getAttackingCreature() { return m_attackingCreature; } + CreaturePtr getFollowingCreature() { return m_followingCreature; } + void setServerBeat(int beat) { m_serverBeat = beat; } + int getServerBeat() { return m_serverBeat; } + void setCanReportBugs(bool enable) { m_canReportBugs = enable; } + bool canReportBugs() { return m_canReportBugs; } + void setExpertPvpMode(bool enable) { m_expertPvpMode = enable; } + bool getExpertPvpMode() { return m_expertPvpMode; } + LocalPlayerPtr getLocalPlayer() { return m_localPlayer; } + ProtocolGamePtr getProtocolGame() { return m_protocolGame; } + std::string getCharacterName() { return m_characterName; } + std::string getWorldName() { return m_worldName; } + std::vector getGMActions() { return m_gmActions; } + bool isGM() { return m_gmActions.size() > 0; } + Otc::Direction getLastWalkDir() { return m_lastWalkDir; } + + std::string formatCreatureName(const std::string &name); + int findEmptyContainerId(); + + void setTibiaCoins(int coins, int transferableCoins) + { + m_coins = coins; + m_transferableCoins = transferableCoins; + } + int getTibiaCoins() + { + return m_coins; + } + int getTransferableTibiaCoins() + { + return m_transferableCoins; + } + + void setMaxPreWalkingSteps(uint value) { m_maxPreWalkingSteps = value; } + uint getMaxPreWalkingSteps() { return m_maxPreWalkingSteps; } + + void showRealDirection(bool value) { m_showRealDirection = value; } + bool shouldShowingRealDirection() { return m_showRealDirection; } + + uint getWalkId() { return m_walkId; } + uint getWalkPreditionId() { return m_walkPrediction; } + + void ignoreServerDirection(bool value) { m_ignoreServerDirection = value; } + bool isIgnoringServerDirection() + { + return m_ignoreServerDirection; + } + + void enableTileThingLuaCallback(bool value) { m_tileThingsLuaCallback = value; } + bool isTileThingLuaCallbackEnabled() { return m_tileThingsLuaCallback; } + + int getRecivedPacketsCount() + { + return m_protocolGame ? m_protocolGame->getRecivedPacketsCount() : 0; + } + + int getRecivedPacketsSize() + { + return m_protocolGame ? m_protocolGame->getRecivedPacketsSize() : 0; + } + +protected: + void enableBotCall() { m_denyBotCall = false; } + void disableBotCall() { m_denyBotCall = true; } + +private: + void setAttackingCreature(const CreaturePtr& creature); + void setFollowingCreature(const CreaturePtr& creature); + + LocalPlayerPtr m_localPlayer; + CreaturePtr m_attackingCreature; + CreaturePtr m_followingCreature; + ProtocolGamePtr m_protocolGame; + std::map m_containers; + std::map m_vips; + + bool m_online; + bool m_denyBotCall; + bool m_dead; + bool m_expertPvpMode; + int m_serverBeat; + ticks_t m_ping; + uint m_pingSent; + uint m_pingReceived; + uint m_walkId = 0; + uint m_walkPrediction = 0; + uint m_maxPreWalkingSteps = 2; + stdext::timer m_pingTimer; + std::map m_newPingIds; + uint m_seq; + int m_pingDelay; + int m_newPingDelay; + Otc::FightModes m_fightMode; + Otc::ChaseModes m_chaseMode; + Otc::PVPModes m_pvpMode; + Otc::Direction m_lastWalkDir; + bool m_waitingForAnotherDir = false; + UnjustifiedPoints m_unjustifiedPoints; + int m_openPvpSituations; + bool m_safeFight; + bool m_canReportBugs; + std::vector m_gmActions; + std::string m_characterName; + std::string m_worldName; + std::bitset m_features; + ScheduledEventPtr m_pingEvent; + ScheduledEventPtr m_newPingEvent; + ScheduledEventPtr m_checkConnectionEvent; + bool m_connectionFailWarned; + int m_protocolVersion; + int m_customProtocolVersion = 0; + int m_clientVersion; + std::string m_clientSignature; + int m_clientCustomOs; + int m_coins; + int m_transferableCoins; + + bool m_showRealDirection = false; + bool m_ignoreServerDirection = true; + bool m_tileThingsLuaCallback = false; +}; + +extern Game g_game; + +#endif diff --git a/src/client/global.h b/src/client/global.h new file mode 100644 index 0000000..fd1b413 --- /dev/null +++ b/src/client/global.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CLIENT_GLOBAL_H +#define CLIENT_GLOBAL_H + +#include + +// widely used headers +#include "const.h" +#include "position.h" + +#endif diff --git a/src/client/houses.cpp b/src/client/houses.cpp new file mode 100644 index 0000000..8a71cd9 --- /dev/null +++ b/src/client/houses.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "map.h" + +#include + +HouseManager g_houses; + +House::House() +{ +} + +House::House(uint32 hId, const std::string &name, const Position &pos) +{ + setId(hId); + setName(name); + if(pos.isValid()) + setEntry(pos); +} + +void House::setTile(const TilePtr& tile) +{ + tile->setFlag(TILESTATE_HOUSE); + tile->setHouseId(getId()); + m_tiles.insert(std::make_pair(tile->getPosition(), tile)); +} + +TilePtr House::getTile(const Position& position) +{ + TileMap::const_iterator iter = m_tiles.find(position); + if(iter != m_tiles.end()) + return iter->second; + return nullptr; +} + +void House::addDoor(const ItemPtr& door) +{ + if (!door) return; + door->setDoorId(m_lastDoorId); + m_doors[m_lastDoorId++] = door; +} + +void House::removeDoorById(uint32 doorId) +{ + if(doorId >= m_lastDoorId) + stdext::throw_exception(stdext::format("Failed to remove door of id %d (would overflow), max id: %d", + doorId, m_lastDoorId)); + m_doors[doorId] = nullptr; +} + +void House::load(const TiXmlElement *elem) +{ + std::string name = elem->Attribute("name"); + if(name.empty()) + name = stdext::format("Unnamed house #%lu", getId()); + + setName(name); + setRent(elem->readType("rent")); + setSize(elem->readType("size")); + setTownId(elem->readType("townid")); + m_isGuildHall = elem->readType("guildhall"); + + Position entryPos; + entryPos.x = elem->readType("entryx"); + entryPos.y = elem->readType("entryy"); + entryPos.z = elem->readType("entryz"); + setEntry(entryPos); +} + +void House::save(TiXmlElement* elem) +{ + elem->SetAttribute("name", getName()); + elem->SetAttribute("houseid", getId()); + + Position entry = getEntry(); + elem->SetAttribute("entryx", entry.x); + elem->SetAttribute("entryy", entry.y); + elem->SetAttribute("entryz", entry.z); + + elem->SetAttribute("rent", getRent()); + elem->SetAttribute("townid", getTownId()); + elem->SetAttribute("size", getSize()); + elem->SetAttribute("guildhall", (int)m_isGuildHall); +} + +HouseManager::HouseManager() +{ +} + +void HouseManager::addHouse(const HousePtr& house) +{ + if(findHouse(house->getId()) == m_houses.end()) + m_houses.push_back(house); +} + +void HouseManager::removeHouse(uint32 houseId) +{ + auto it = findHouse(houseId); + if(it != m_houses.end()) + m_houses.erase(it); +} + +HousePtr HouseManager::getHouse(uint32 houseId) +{ + auto it = findHouse(houseId); + return it != m_houses.end() ? *it : nullptr; +} + +HousePtr HouseManager::getHouseByName(std::string name) +{ + auto it = std::find_if(m_houses.begin(), m_houses.end(), + [=] (const HousePtr& house) -> bool { return house->getName() == name; }); + return it != m_houses.end() ? *it : nullptr; +} + +void HouseManager::load(const std::string& fileName) +{ + try { + TiXmlDocument doc; + doc.Parse(g_resources.readFileContents(fileName).c_str()); + if(doc.Error()) + stdext::throw_exception(stdext::format("failed to load '%s': %s (House XML)", fileName, doc.ErrorDesc())); + + TiXmlElement *root = doc.FirstChildElement(); + if(!root || root->ValueTStr() != "houses") + stdext::throw_exception("invalid root tag name"); + + for(TiXmlElement *elem = root->FirstChildElement(); elem; elem = elem->NextSiblingElement()) { + if(elem->ValueTStr() != "house") + stdext::throw_exception("invalid house tag."); + + uint32 houseId = elem->readType("houseid"); + HousePtr house = getHouse(houseId); + if(!house) + house = HousePtr(new House(houseId)), addHouse(house); + + house->load(elem); + } + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to load '%s': %s", fileName, e.what())); + } + sort(); +} + +void HouseManager::save(const std::string& fileName) +{ + try { + TiXmlDocument doc; + doc.SetTabSize(2); + + TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "UTF-8", ""); + doc.LinkEndChild(decl); + + TiXmlElement* root = new TiXmlElement("houses"); + doc.LinkEndChild(root); + + for(auto house : m_houses) { + TiXmlElement *elem = new TiXmlElement("house"); + house->save(elem); + root->LinkEndChild(elem); + } + + if(!doc.SaveFile("data"+fileName)) + stdext::throw_exception(stdext::format("failed to save houses XML %s: %s", fileName, doc.ErrorDesc())); + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); + } +} + +HouseList HouseManager::filterHouses(uint32 townId) +{ + HouseList ret; + for(const HousePtr& house : m_houses) + if(house->getTownId() == townId) + ret.push_back(house); + return ret; +} + +HouseList::iterator HouseManager::findHouse(uint32 houseId) +{ + return std::find_if(m_houses.begin(), m_houses.end(), + [=] (const HousePtr& house) -> bool { return house->getId() == houseId; }); +} + +void HouseManager::sort() +{ + m_houses.sort([] (const HousePtr& lhs, const HousePtr& rhs) { return lhs->getName() < rhs->getName(); }); +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/client/houses.h b/src/client/houses.h new file mode 100644 index 0000000..2fa12c0 --- /dev/null +++ b/src/client/houses.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HOUSES_H +#define HOUSES_H + +#include "declarations.h" +#include "tile.h" + +#include + +enum HouseAttr : uint8 +{ + HouseAttrId, + HouseAttrName, + HouseAttrTown, + HouseAttrEntry, + HouseAttrSize, + HouseAttrRent +}; + +class House : public LuaObject +{ +public: + House(); + House(uint32 hId, const std::string& name = "", const Position& pos=Position()); + ~House() { m_tiles.clear(); } + + void setTile(const TilePtr& tile); + TilePtr getTile(const Position& pos); + + void setName(const std::string& name) { m_attribs.set(HouseAttrName, name); } + std::string getName() { return m_attribs.get(HouseAttrName); } + + void setId(uint32 hId) { m_attribs.set(HouseAttrId, hId); } + uint32 getId() { return m_attribs.get(HouseAttrId); } + + void setTownId(uint32 tid) { m_attribs.set(HouseAttrTown, tid); } + uint32 getTownId() { return m_attribs.get(HouseAttrTown); } + + void setSize(uint32 s) { m_attribs.set(HouseAttrSize, s); } + uint32 getSize() { return m_attribs.get(HouseAttrSize); } + + void setRent(uint32 r) { m_attribs.set(HouseAttrRent, r); } + uint32 getRent() { return m_attribs.get(HouseAttrRent); } + + void setEntry(const Position& p) { m_attribs.set(HouseAttrEntry, p); } + Position getEntry() { return m_attribs.get(HouseAttrEntry); } + + void addDoor(const ItemPtr& door); + void removeDoor(const ItemPtr& door) { removeDoorById(door->getDoorId()); } + void removeDoorById(uint32 doorId); + +protected: + void load(const TiXmlElement* elem); + void save(TiXmlElement* elem); + +private: + stdext::packed_storage m_attribs; + TileMap m_tiles; + ItemVector m_doors; + uint32 m_lastDoorId; + stdext::boolean m_isGuildHall; + + friend class HouseManager; +}; + +class HouseManager { +public: + HouseManager(); + + void addHouse(const HousePtr& house); + void removeHouse(uint32 houseId); + HousePtr getHouse(uint32 houseId); + HousePtr getHouseByName(std::string name); + + void load(const std::string& fileName); + void save(const std::string& fileName); + + void sort(); + void clear() { m_houses.clear(); } + HouseList getHouseList() { return m_houses; } + HouseList filterHouses(uint32 townId); + +private: + HouseList m_houses; + +protected: + HouseList::iterator findHouse(uint32 houseId); +}; + +extern HouseManager g_houses; + +#endif diff --git a/src/client/item.cpp b/src/client/item.cpp new file mode 100644 index 0000000..b72a7fc --- /dev/null +++ b/src/client/item.cpp @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "item.h" +#include "thingtypemanager.h" +#include "spritemanager.h" +#include "thing.h" +#include "tile.h" +#include "shadermanager.h" +#include "container.h" +#include "map.h" +#include "houses.h" +#include "game.h" + +#include +#include +#include +#include +#include + +#include + +Item::Item() : + m_clientId(0), + m_serverId(0), + m_countOrSubType(1), + m_color(Color::alpha), + m_async(true), + m_phase(0), + m_lastPhase(0) +{ +} + +ItemPtr Item::create(int id, int countOrSubtype) +{ + ItemPtr item(new Item); + item->setId(id); + item->setCountOrSubType(countOrSubtype); + return item; +} + +ItemPtr Item::createFromOtb(int id) +{ + ItemPtr item(new Item); + item->setOtbId(id); + return item; +} + +std::string Item::getName() +{ + return g_things.findItemTypeByClientId(m_clientId)->getName(); +} + +void Item::draw(const Point& dest, bool animate, LightView* lightView) +{ + if (m_clientId == 0) + return; + + // determine animation phase + int animationPhase = calculateAnimationPhase(animate); + + // determine x,y,z patterns + int xPattern = 0, yPattern = 0, zPattern = 0; + calculatePatterns(xPattern, yPattern, zPattern); + + Color color(Color::white); + if (m_color != Color::alpha) + color = m_color; + size_t drawQueueSize = g_drawQueue->size(); + rawGetThingType()->draw(dest, 0, xPattern, yPattern, zPattern, animationPhase, color, lightView); + if (m_marked) { + g_drawQueue->setMark(drawQueueSize, updatedMarkedColor()); + } +} + +void Item::draw(const Rect& dest, bool animate) +{ + if (m_clientId == 0) + return; + + // determine animation phase + int animationPhase = calculateAnimationPhase(animate); + + // determine x,y,z patterns + int xPattern = 0, yPattern = 0, zPattern = 0; + calculatePatterns(xPattern, yPattern, zPattern); + + Color color(Color::white); + if (m_color != Color::alpha) + color = m_color; + + rawGetThingType()->draw(dest, 0, xPattern, yPattern, zPattern, animationPhase, color); +} + +void Item::setId(uint32 id) +{ + if(!g_things.isValidDatId(id, ThingCategoryItem)) + id = 0; + m_serverId = g_things.findItemTypeByClientId(id)->getServerId(); + m_clientId = id; +} + +void Item::setOtbId(uint16 id) +{ + if(!g_things.isValidOtbId(id)) + id = 0; + auto itemType = g_things.getItemType(id); + m_serverId = id; + + id = itemType->getClientId(); + if(!g_things.isValidDatId(id, ThingCategoryItem)) + id = 0; + m_clientId = id; +} + +bool Item::isValid() +{ + return g_things.isValidDatId(m_clientId, ThingCategoryItem); +} + +void Item::unserializeItem(const BinaryTreePtr &in) +{ + try { + while(in->canRead()) { + int attrib = in->getU8(); + if(attrib == 0) + break; + + switch(attrib) { + case ATTR_COUNT: + case ATTR_RUNE_CHARGES: + setCount(in->getU8()); + break; + case ATTR_CHARGES: + setCount(in->getU16()); + break; + case ATTR_HOUSEDOORID: + case ATTR_SCRIPTPROTECTED: + case ATTR_DUALWIELD: + case ATTR_DECAYING_STATE: + m_attribs.set(attrib, in->getU8()); + break; + case ATTR_ACTION_ID: + case ATTR_UNIQUE_ID: + case ATTR_DEPOT_ID: + m_attribs.set(attrib, in->getU16()); + break; + case ATTR_CONTAINER_ITEMS: + case ATTR_ATTACK: + case ATTR_EXTRAATTACK: + case ATTR_DEFENSE: + case ATTR_EXTRADEFENSE: + case ATTR_ARMOR: + case ATTR_ATTACKSPEED: + case ATTR_HITCHANCE: + case ATTR_DURATION: + case ATTR_WRITTENDATE: + case ATTR_SLEEPERGUID: + case ATTR_SLEEPSTART: + case ATTR_ATTRIBUTE_MAP: + m_attribs.set(attrib, in->getU32()); + break; + case ATTR_TELE_DEST: { + Position pos; + pos.x = in->getU16(); + pos.y = in->getU16(); + pos.z = in->getU8(); + m_attribs.set(attrib, pos); + break; + } + case ATTR_NAME: + case ATTR_TEXT: + case ATTR_DESC: + case ATTR_ARTICLE: + case ATTR_WRITTENBY: + m_attribs.set(attrib, in->getString()); + break; + default: + stdext::throw_exception(stdext::format("invalid item attribute %d", attrib)); + } + } + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Failed to unserialize OTBM item: %s", e.what())); + } +} + +void Item::serializeItem(const OutputBinaryTreePtr& out) +{ + out->startNode(OTBM_ITEM); + out->addU16(getServerId()); + + out->addU8(ATTR_COUNT); + out->addU8(getCount()); + + out->addU8(ATTR_CHARGES); + out->addU16(getCountOrSubType()); + + Position dest = m_attribs.get(ATTR_TELE_DEST); + if(dest.isValid()) { + out->addU8(ATTR_TELE_DEST); + out->addPos(dest.x, dest.y, dest.z); + } + + if(isDepot()) { + out->addU8(ATTR_DEPOT_ID); + out->addU16(getDepotId()); + } + + if(isHouseDoor()) { + out->addU8(ATTR_HOUSEDOORID); + out->addU8(getDoorId()); + } + + uint16 aid = m_attribs.get(ATTR_ACTION_ID); + uint16 uid = m_attribs.get(ATTR_UNIQUE_ID); + if(aid) { + out->addU8(ATTR_ACTION_ID); + out->addU16(aid); + } + + if(uid) { + out->addU8(ATTR_UNIQUE_ID); + out->addU16(uid); + } + + std::string text = getText(); + if(g_things.getItemType(m_serverId)->isWritable() && !text.empty()) { + out->addU8(ATTR_TEXT); + out->addString(text); + } + std::string desc = getDescription(); + if(!desc.empty()) { + out->addU8(ATTR_DESC); + out->addString(desc); + } + + out->endNode(); + for(auto i : m_containerItems) + i->serializeItem(out); +} + +int Item::getSubType() +{ + if(isSplash() || isFluidContainer()) + return m_countOrSubType; + if(g_game.getClientVersion() >= 860) + return 0; + return 1; +} + +int Item::getCount() +{ + if(isStackable()) + return m_countOrSubType; + return 1; +} + +bool Item::isMoveable() +{ + return !rawGetThingType()->isNotMoveable(); +} + +bool Item::isGround() +{ + return rawGetThingType()->isGround(); +} + +ItemPtr Item::clone() +{ + ItemPtr item = ItemPtr(new Item); + *(item.get()) = *this; + return item; +} + +void Item::calculatePatterns(int& xPattern, int& yPattern, int& zPattern) +{ + // Avoid crashes with invalid items + if(!isValid()) + return; + + if(isStackable() && getNumPatternX() == 4 && getNumPatternY() == 2) { + if(m_countOrSubType <= 0) { + xPattern = 0; + yPattern = 0; + } else if(m_countOrSubType < 5) { + xPattern = m_countOrSubType-1; + yPattern = 0; + } else if(m_countOrSubType < 10) { + xPattern = 0; + yPattern = 1; + } else if(m_countOrSubType < 25) { + xPattern = 1; + yPattern = 1; + } else if(m_countOrSubType < 50) { + xPattern = 2; + yPattern = 1; + } else { + xPattern = 3; + yPattern = 1; + } + } else if(isHangable()) { + const TilePtr& tile = getTile(); + if(tile) { + if(tile->mustHookSouth()) + xPattern = getNumPatternX() >= 2 ? 1 : 0; + else if(tile->mustHookEast()) + xPattern = getNumPatternX() >= 3 ? 2 : 0; + } + } else if(isSplash() || isFluidContainer()) { + int color = Otc::FluidTransparent; + if(g_game.getFeature(Otc::GameNewFluids)) { + switch(m_countOrSubType) { + case Otc::FluidNone: + color = Otc::FluidTransparent; + break; + case Otc::FluidWater: + color = Otc::FluidBlue; + break; + case Otc::FluidMana: + color = Otc::FluidPurple; + break; + case Otc::FluidBeer: + color = Otc::FluidBrown; + break; + case Otc::FluidOil: + color = Otc::FluidBrown; + break; + case Otc::FluidBlood: + color = Otc::FluidRed; + break; + case Otc::FluidSlime: + color = Otc::FluidGreen; + break; + case Otc::FluidMud: + color = Otc::FluidBrown; + break; + case Otc::FluidLemonade: + color = Otc::FluidYellow; + break; + case Otc::FluidMilk: + color = Otc::FluidWhite; + break; + case Otc::FluidWine: + color = Otc::FluidPurple; + break; + case Otc::FluidHealth: + color = Otc::FluidRed; + break; + case Otc::FluidUrine: + color = Otc::FluidYellow; + break; + case Otc::FluidRum: + color = Otc::FluidBrown; + break; + case Otc::FluidFruidJuice: + color = Otc::FluidYellow; + break; + case Otc::FluidCoconutMilk: + color = Otc::FluidWhite; + break; + case Otc::FluidTea: + color = Otc::FluidBrown; + break; + case Otc::FluidMead: + color = Otc::FluidBrown; + break; + default: + color = Otc::FluidTransparent; + break; + } + } else + color = m_countOrSubType; + + xPattern = (color % 4) % getNumPatternX(); + yPattern = (color / 4) % getNumPatternY(); + } else { + xPattern = m_position.x % std::max(1, getNumPatternX()); + yPattern = m_position.y % std::max(1, getNumPatternY()); + zPattern = m_position.z % std::max(1, getNumPatternZ()); + } +} + +int Item::calculateAnimationPhase(bool animate) +{ + if(getAnimationPhases() > 1) { + if(animate) { + if(getAnimator() != nullptr) + return getAnimator()->getPhase(); + + if(m_async) + 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; + else { + if(g_clock.millis() - m_lastPhase >= (g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::ITEM_TICKS_PER_FRAME_FAST : Otc::ITEM_TICKS_PER_FRAME)) { + m_phase = (m_phase + 1) % getAnimationPhases(); + m_lastPhase = g_clock.millis(); + } + return m_phase; + } + } else + return getAnimationPhases()-1; + } + return 0; +} + +int Item::getExactSize(int layer, int xPattern, int yPattern, int zPattern, int animationPhase) +{ + calculatePatterns(xPattern, yPattern, zPattern); + animationPhase = calculateAnimationPhase(true); + return Thing::getExactSize(layer, xPattern, yPattern, zPattern, animationPhase); +} + +const ThingTypePtr& Item::getThingType() +{ + return g_things.getThingType(m_clientId, ThingCategoryItem); +} + +ThingType* Item::rawGetThingType() +{ + return g_things.rawGetThingType(m_clientId, ThingCategoryItem); +} +/* vim: set ts=4 sw=4 et :*/ diff --git a/src/client/item.h b/src/client/item.h new file mode 100644 index 0000000..681efbb --- /dev/null +++ b/src/client/item.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ITEM_H +#define ITEM_H + +#include + +#include "thing.h" +#include "effect.h" +#include "itemtype.h" + +enum ItemAttr : uint8 +{ + ATTR_END = 0, + //ATTR_DESCRIPTION = 1, + //ATTR_EXT_FILE = 2, + ATTR_TILE_FLAGS = 3, + ATTR_ACTION_ID = 4, + ATTR_UNIQUE_ID = 5, + ATTR_TEXT = 6, + ATTR_DESC = 7, + ATTR_TELE_DEST = 8, + ATTR_ITEM = 9, + ATTR_DEPOT_ID = 10, + //ATTR_EXT_SPAWN_FILE = 11, + ATTR_RUNE_CHARGES = 12, + //ATTR_EXT_HOUSE_FILE = 13, + ATTR_HOUSEDOORID = 14, + ATTR_COUNT = 15, + ATTR_DURATION = 16, + ATTR_DECAYING_STATE = 17, + ATTR_WRITTENDATE = 18, + ATTR_WRITTENBY = 19, + ATTR_SLEEPERGUID = 20, + ATTR_SLEEPSTART = 21, + ATTR_CHARGES = 22, + ATTR_CONTAINER_ITEMS = 23, + ATTR_NAME = 30, + ATTR_PLURALNAME = 31, + ATTR_ATTACK = 33, + ATTR_EXTRAATTACK = 34, + ATTR_DEFENSE = 35, + ATTR_EXTRADEFENSE = 36, + ATTR_ARMOR = 37, + ATTR_ATTACKSPEED = 38, + ATTR_HITCHANCE = 39, + ATTR_SHOOTRANGE = 40, + ATTR_ARTICLE = 41, + ATTR_SCRIPTPROTECTED = 42, + ATTR_DUALWIELD = 43, + ATTR_ATTRIBUTE_MAP = 128 +}; + +// @bindclass +#pragma pack(push,1) // disable memory alignment +class Item : public Thing +{ +public: + Item(); + virtual ~Item() { } + + static ItemPtr create(int id, int countOrSubtype = 1); + static ItemPtr createFromOtb(int id); + + void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr); + void draw(const Rect& dest, bool animate = true); + + void setId(uint32 id); + void setOtbId(uint16 id); + void setCountOrSubType(int value) { m_countOrSubType = value; } + void setCount(int count) { m_countOrSubType = count; } + void setSubType(int subType) { m_countOrSubType = subType; } + void setColor(const Color& c) { m_color = c; } + void setTooltip(const std::string& str) { m_tooltip = str; } + + int getCountOrSubType() { return m_countOrSubType; } + int getSubType(); + int getCount(); + uint32 getId() { return m_clientId; } + uint16 getClientId() { return m_clientId; } + uint16 getServerId() { return m_serverId; } + std::string getName(); + bool isValid(); + std::string getTooltip() { return m_tooltip; } + + void unserializeItem(const BinaryTreePtr& in); + void serializeItem(const OutputBinaryTreePtr& out); + + void setDepotId(uint16 depotId) { m_attribs.set(ATTR_DEPOT_ID, depotId); } + uint16 getDepotId() { return m_attribs.get(ATTR_DEPOT_ID); } + + void setDoorId(uint8 doorId) { m_attribs.set(ATTR_HOUSEDOORID, doorId); } + uint8 getDoorId() { return m_attribs.get(ATTR_HOUSEDOORID); } + + uint16 getUniqueId() { return m_attribs.get(ATTR_ACTION_ID); } + uint16 getActionId() { return m_attribs.get(ATTR_UNIQUE_ID); } + void setActionId(uint16 actionId) { m_attribs.set(ATTR_ACTION_ID, actionId); } + void setUniqueId(uint16 uniqueId) { m_attribs.set(ATTR_UNIQUE_ID, uniqueId); } + + std::string getText() { return m_attribs.get(ATTR_TEXT); } + std::string getDescription() { return m_attribs.get(ATTR_DESC); } + void setDescription(std::string desc) { m_attribs.set(ATTR_DESC, desc); } + void setText(std::string txt) { m_attribs.set(ATTR_TEXT, txt); } + + Position getTeleportDestination() { return m_attribs.get(ATTR_TELE_DEST); } + void setTeleportDestination(const Position& pos) { m_attribs.set(ATTR_TELE_DEST, pos); } + + void setAsync(bool enable) { m_async = enable; } + + bool isHouseDoor() { return m_attribs.has(ATTR_HOUSEDOORID); } + bool isDepot() { return m_attribs.has(ATTR_DEPOT_ID); } + bool isContainer() { return m_attribs.has(ATTR_CONTAINER_ITEMS); } + bool isDoor() { return m_attribs.has(ATTR_HOUSEDOORID); } + bool isTeleport() { return m_attribs.has(ATTR_TELE_DEST); } + bool isMoveable(); + bool isGround(); + + ItemPtr clone(); + ItemPtr asItem() { return static_self_cast(); } + bool isItem() { return true; } + + ItemVector getContainerItems() { return m_containerItems; } + ItemPtr getContainerItem(int slot) { return m_containerItems[slot]; } + void addContainerItemIndexed(const ItemPtr& i, int slot) { m_containerItems[slot] = i; } + void addContainerItem(const ItemPtr& i) { m_containerItems.push_back(i); } + void removeContainerItem(int slot) { m_containerItems[slot] = nullptr; } + void clearContainerItems() { m_containerItems.clear(); } + + void calculatePatterns(int& xPattern, int& yPattern, int& zPattern); + int calculateAnimationPhase(bool animate); + int getExactSize(int layer = 0, int xPattern = 0, int yPattern = 0, int zPattern = 0, int animationPhase = 0); + + const ThingTypePtr& getThingType(); + ThingType *rawGetThingType(); + +private: + uint16 m_clientId; + uint16 m_serverId; + uint16 m_countOrSubType; + stdext::packed_storage m_attribs; + ItemVector m_containerItems; + Color m_color; + bool m_async; + std::string m_tooltip; + + uint8 m_phase; + ticks_t m_lastPhase; +}; + +#pragma pack(pop) + +#endif diff --git a/src/client/itemtype.cpp b/src/client/itemtype.cpp new file mode 100644 index 0000000..3e61709 --- /dev/null +++ b/src/client/itemtype.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "thingtypemanager.h" +#include "thingtype.h" +#include "game.h" + +#include +#include + +ItemType::ItemType() +{ + m_category = ItemCategoryInvalid; +} + +void ItemType::unserialize(const BinaryTreePtr& node) +{ + m_null = false; + + m_category = (ItemCategory)node->getU8(); + + node->getU32(); // flags + + static uint16 lastId = 99; + while(node->canRead()) { + uint8 attr = node->getU8(); + if(attr == 0 || attr == 0xFF) + break; + + uint16 len = node->getU16(); + switch(attr) { + case ItemTypeAttrServerId: { + uint16 serverId = node->getU16(); + if(g_game.getClientVersion() < 960) { + if(serverId > 20000 && serverId < 20100) { + serverId -= 20000; + } else if(lastId > 99 && lastId != serverId - 1) { + while(lastId != serverId - 1) { + ItemTypePtr tmp(new ItemType); + tmp->setServerId(lastId++); + g_things.addItemType(tmp); + } + } + } else { + if(serverId > 30000 && serverId < 30100) { + serverId -= 30000; + } else if(lastId > 99 && lastId != serverId - 1) { + while(lastId != serverId - 1) { + ItemTypePtr tmp(new ItemType); + tmp->setServerId(lastId++); + g_things.addItemType(tmp); + } + } + } + setServerId(serverId); + lastId = serverId; + break; + } + case ItemTypeAttrClientId: + setClientId(node->getU16()); + break; + case ItemTypeAttrName: + setName(node->getString(len)); + break; + case ItemTypeAttrWritable: + m_attribs.set(ItemTypeAttrWritable, true); + break; + default: + node->skip(len); // skip attribute + break; + } + } +} diff --git a/src/client/itemtype.h b/src/client/itemtype.h new file mode 100644 index 0000000..3aa62c6 --- /dev/null +++ b/src/client/itemtype.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef ITEMTYPE_H +#define ITEMTYPE_H + +#include +#include +#include + +enum ItemCategory : uint8 { + ItemCategoryInvalid = 0, + ItemCategoryGround = 1, + ItemCategoryContainer = 2, + ItemCategoryWeapon = 3, + ItemCategoryAmmunition = 4, + ItemCategoryArmor = 5, + ItemCategoryCharges = 6, + ItemCategoryTeleport = 7, + ItemCategoryMagicField = 8, + ItemCategoryWritable = 9, + ItemCategoryKey = 10, + ItemCategorySplash = 11, + ItemCategoryFluid = 12, + ItemCategoryDoor = 13, + ItemCategoryDeprecated = 14, + ItemCategoryLast = 15 +}; + +enum ItemTypeAttr : uint8 { + ItemTypeAttrServerId = 16, + ItemTypeAttrClientId = 17, + ItemTypeAttrName = 18, // deprecated + ItemTypeAttrDesc = 19, // deprecated + ItemTypeAttrSpeed = 20, + ItemTypeAttrSlot = 21, // deprecated + ItemTypeAttrMaxItems = 22, // deprecated + ItemTypeAttrWeight = 23, // deprecated + ItemTypeAttrWeapon = 24, // deprecated + ItemTypeAttrAmmunition = 25, // deprecated + ItemTypeAttrArmor = 26, // deprecated + ItemTypeAttrMagicLevel = 27, // deprecated + ItemTypeAttrMagicField = 28, // deprecated + ItemTypeAttrWritable = 29, // deprecated + ItemTypeAttrRotateTo = 30, // deprecated + ItemTypeAttrDecay = 31, // deprecated + ItemTypeAttrSpriteHash = 32, + ItemTypeAttrMinimapColor = 33, + ItemTypeAttr07 = 34, + ItemTypeAttr08 = 35, + ItemTypeAttrLight = 36, + ItemTypeAttrDecay2 = 37, // deprecated + ItemTypeAttrWeapon2 = 38, // deprecated + ItemTypeAttrAmmunition2 = 39, // deprecated + ItemTypeAttrArmor2 = 40, // deprecated + ItemTypeAttrWritable2 = 41, // deprecated + ItemTypeAttrLight2 = 42, + ItemTypeAttrTopOrder = 43, + ItemTypeAttrWrtiable3 = 44, // deprecated + ItemTypeAttrWareId = 45, + ItemTypeAttrLast = 46 +}; + +enum ClientVersion +{ + ClientVersion750 = 1, + ClientVersion755 = 2, + ClientVersion760 = 3, + ClientVersion770 = 3, + ClientVersion780 = 4, + ClientVersion790 = 5, + ClientVersion792 = 6, + ClientVersion800 = 7, + ClientVersion810 = 8, + ClientVersion811 = 9, + ClientVersion820 = 10, + ClientVersion830 = 11, + ClientVersion840 = 12, + ClientVersion841 = 13, + ClientVersion842 = 14, + ClientVersion850 = 15, + ClientVersion854_OLD = 16, + ClientVersion854 = 17, + ClientVersion855 = 18, + ClientVersion860_OLD = 19, + ClientVersion860 = 20, + ClientVersion861 = 21, + ClientVersion862 = 22, + ClientVersion870 = 23, + ClientVersion871 = 24, + ClientVersion872 = 25, + ClientVersion873 = 26, + ClientVersion900 = 27, + ClientVersion910 = 28, + ClientVersion920 = 29, + ClientVersion940 = 30, + ClientVersion944_V1 = 31, + ClientVersion944_V2 = 32, + ClientVersion944_V3 = 33, + ClientVersion944_V4 = 34, + ClientVersion946 = 35, + ClientVersion950 = 36, + ClientVersion952 = 37, + ClientVersion953 = 38, + ClientVersion954 = 39, + ClientVersion960 = 40, + ClientVersion961 = 41 +}; + +class ItemType : public LuaObject +{ +public: + ItemType(); + + void unserialize(const BinaryTreePtr& node); + + void setServerId(uint16 serverId) { m_attribs.set(ItemTypeAttrServerId, serverId); } + uint16 getServerId() { return m_attribs.get(ItemTypeAttrServerId); } + + void setClientId(uint16 clientId) { m_attribs.set(ItemTypeAttrClientId, clientId); } + uint16 getClientId() { return m_attribs.get(ItemTypeAttrClientId); } + + void setCategory(ItemCategory category) { m_category = category; } + ItemCategory getCategory() { return m_category; } + + void setName(const std::string& name) { m_attribs.set(ItemTypeAttrName, name); } + std::string getName() { return m_attribs.get(ItemTypeAttrName); } + + void setDesc(const std::string& desc) { m_attribs.set(ItemTypeAttrDesc, desc); } + std::string getDesc() { return m_attribs.get(ItemTypeAttrDesc); } + + bool isNull() { return m_null; } + bool isWritable() { return m_attribs.get(ItemTypeAttrWritable); } + +private: + ItemCategory m_category; + stdext::boolean m_null; + + stdext::dynamic_storage m_attribs; +}; + +#endif diff --git a/src/client/lightview.cpp b/src/client/lightview.cpp new file mode 100644 index 0000000..c89ad1a --- /dev/null +++ b/src/client/lightview.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "lightview.h" +#include + +void LightView::addLight(const Point& pos, uint8_t color, uint8_t intensity) +{ + if (!m_lights.empty()) { + Light& prevLight = m_lights.back(); + if (prevLight.pos == pos && prevLight.color == color) { + prevLight.intensity = std::max(prevLight.intensity, intensity); + return; + } + } + m_lights.push_back(Light{ pos, color, intensity }); +} + +void LightView::setFieldBrightness(const Point& pos, size_t start, uint8_t color) +{ + size_t index = (pos.y / Otc::TILE_PIXELS) * m_mapSize.width() + (pos.x / Otc::TILE_PIXELS); + if (index >= m_tiles.size()) return; + m_tiles[index].start = start; + m_tiles[index].color = color; +} + +void LightView::draw() // render thread +{ + static std::vector buffer; + if(buffer.size() < 4u * m_mapSize.area()) + buffer.resize(m_mapSize.area() * 4); + + // hidden code + + m_lightTexture->update(); + glBindTexture(GL_TEXTURE_2D, m_lightTexture->getId()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_mapSize.width(), m_mapSize.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data()); + + Point offset = m_src.topLeft(); + Size size = m_src.size(); + CoordsBuffer coords; + coords.addRect(RectF(m_dest.left(), m_dest.top(), m_dest.width(), m_dest.height()), + RectF((float)offset.x / Otc::TILE_PIXELS, (float)offset.y / Otc::TILE_PIXELS, + (float)size.width() / Otc::TILE_PIXELS, (float)size.height() / Otc::TILE_PIXELS)); + + g_painterNew->resetColor(); + g_painterNew->setCompositionMode(Painter::CompositionMode_Multiply); + g_painterNew->drawTextureCoords(coords, m_lightTexture); + g_painterNew->resetCompositionMode(); +} diff --git a/src/client/lightview.h b/src/client/lightview.h new file mode 100644 index 0000000..2b67f4c --- /dev/null +++ b/src/client/lightview.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LIGHTVIEW_H +#define LIGHTVIEW_H + +#include "declarations.h" +#include "thingtype.h" +#include +#include +#include + +struct TileLight { + size_t start; + uint8_t color; +}; + +class LightView : public DrawQueueItem +{ +public: + LightView(TexturePtr& lightTexture, const Size& mapSize, const Rect& dest, const Rect& src, uint8_t color, uint8_t intensity) : + DrawQueueItem(nullptr), m_lightTexture(lightTexture), m_mapSize(mapSize), m_dest(dest), m_src(src) { + m_globalLight = Color::from8bit(color) * ((float)intensity / 255.f); + m_tiles.resize(m_mapSize.area(), TileLight{ 0, 0 }); + } + + inline void addLight(const Point& pos, const Light& light) + { + return addLight(pos, light.color, light.intensity); + } + void addLight(const Point& pos, uint8_t color, uint8_t intensity); + void setFieldBrightness(const Point& pos, size_t start, uint8_t color); + size_t size() { return m_lights.size(); } + + void draw() override; + +private: + TexturePtr m_lightTexture; + Size m_mapSize; + Rect m_dest, m_src; + Color m_globalLight; + std::vector m_lights; + std::vector m_tiles; +}; + +#endif + diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp new file mode 100644 index 0000000..60dd1be --- /dev/null +++ b/src/client/localplayer.cpp @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "localplayer.h" +#include "map.h" +#include "game.h" +#include "tile.h" +#include +#include +#include + +LocalPlayer::LocalPlayer() +{ + m_states = 0; + m_vocation = 0; + m_blessings = Otc::BlessingNone; + m_walkLockExpiration = 0; + + m_skillsLevel.fill(-1); + m_skillsBaseLevel.fill(-1); + m_skillsLevelPercent.fill(-1); + + m_health = -1; + m_maxHealth = -1; + m_freeCapacity = -1; + m_experience = -1; + m_level = -1; + m_levelPercent = -1; + m_mana = -1; + m_maxMana = -1; + m_magicLevel = -1; + m_magicLevelPercent = -1; + m_baseMagicLevel = -1; + m_soul = -1; + m_stamina = -1; + m_baseSpeed = -1; + m_regenerationTime = -1; + m_offlineTrainingTime = -1; + m_totalCapacity = -1; +} + +void LocalPlayer::draw(const Point& dest, bool animate, LightView* lightView) +{ + Creature::draw(dest, animate, lightView); +} + + +void LocalPlayer::lockWalk(int millis) +{ + m_walkLockExpiration = std::max(m_walkLockExpiration, (ticks_t) g_clock.millis() + millis); +} + +bool LocalPlayer::canWalk(Otc::Direction direction, bool ignoreLock) { + // cannot walk while locked + if ((m_walkLockExpiration != 0 && g_clock.millis() < m_walkLockExpiration) && !ignoreLock) + return false; + + // paralyzed + if (m_speed == 0) + return false; + + // last walk is not done yet + if (m_walking && (m_walkTimer.ticksElapsed() < getStepDuration()) && !isAutoWalking() && !isServerWalking()) + return false; + + auto tile = g_map.getTile(getPrewalkingPosition(true)); + if (isPreWalking() && (!m_lastPrewalkDone || (tile && tile->isBlocking()))) + return false; + + // cannot walk while already walking + if ((m_walking && !isAutoWalking() && !isServerWalking()) && (!isPreWalking() || !m_lastPrewalkDone)) + return false; + + // Without new walking limit only to 1 prewalk + if (!m_preWalking.empty() && !g_game.getFeature(Otc::GameNewWalking)) + return false; + + // Limit pre walking steps + if (m_preWalking.size() >= g_game.getMaxPreWalkingSteps()) // max 3 extra steps + return false; + + if (!m_preWalking.empty()) { // disallow diagonal extented prewalking walking + auto dir = m_position.getDirectionFromPosition(m_preWalking.back()); + if ((dir == Otc::NorthWest || dir == Otc::NorthEast || dir == Otc::SouthWest || dir == Otc::SouthEast)) { + return false; + } + if (!g_map.getTile(getPrewalkingPosition())->isWalkable()) + return false; + } + + return true; +} + +void LocalPlayer::walk(const Position& oldPos, const Position& newPos) +{ + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] LocalPlayer::walk", (int)g_clock.millis())); + } + + m_lastAutoWalkRetries = 0; + // a prewalk was going on + if (isPreWalking()) { + for (auto it = m_preWalking.begin(); it != m_preWalking.end(); ++it) { + if (*it == newPos) { + m_preWalking.erase(m_preWalking.begin(), ++it); + if(!isPreWalking()) // reset pre walking + updateWalk(); + return; + } + } + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] LocalPlayer::walk invalid prewalk", (int)g_clock.millis())); + } + + // invalid pre walk + m_preWalking.clear(); + m_serverWalking = true; + if(m_serverWalkEndEvent) + m_serverWalkEndEvent->cancel(); + + Creature::walk(oldPos, newPos); + } else { // no prewalk was going on, this must be an server side automated walk + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] LocalPlayer::walk server walk", (int)g_clock.millis())); + } + + m_serverWalking = true; + if(m_serverWalkEndEvent) + m_serverWalkEndEvent->cancel(); + m_lastAutoWalkRetries = 0; + + Creature::walk(oldPos, newPos); + } +} + +void LocalPlayer::preWalk(Otc::Direction direction) +{ + +} + + +void LocalPlayer::cancelWalk(Otc::Direction direction) +{ + if (g_game.getFeature(Otc::GameNewWalking)) { + return; + } + + return cancelNewWalk(direction); +} + +void LocalPlayer::cancelNewWalk(Otc::Direction dir) +{ + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] cancelWalk", (int)g_clock.millis())); + } + + bool clearedPrewalk = !m_preWalking.empty(); + + m_preWalking.clear(); + g_map.requestVisibleTilesCacheUpdate(); + + if (clearedPrewalk) { + stopWalk(); + } + + m_idleTimer.restart(); + + if (retryAutoWalk()) return; + + if (!g_game.isIgnoringServerDirection() || !g_game.getFeature(Otc::GameNewWalking)) { + setDirection(dir); + } + callLuaField("onCancelWalk", dir); +} + +bool LocalPlayer::predictiveCancelWalk(const Position& pos, uint32_t predictionId, Otc::Direction dir) +{ + if (g_extras.debugPredictiveWalking) { + g_logger.info(stdext::format("[%i] predictiveCancelWalk: %i - %i", (int)g_clock.millis(), predictionId, (int)m_preWalking.size())); + } + return false; +} + +bool LocalPlayer::retryAutoWalk() +{ + return false; +} + + +bool LocalPlayer::autoWalk(Position destination, bool retry) +{ + // reset state + m_autoWalkDestination = Position(); + m_lastAutoWalkPosition = Position(); + if(m_autoWalkContinueEvent) + m_autoWalkContinueEvent->cancel(); + m_autoWalkContinueEvent = nullptr; + + if (!retry) + m_lastAutoWalkRetries = 0; + + if(destination == getPrewalkingPosition()) + return true; + + m_autoWalkDestination = destination; + auto self(asLocalPlayer()); + g_map.findPathAsync(getPrewalkingPosition(), destination, [self](PathFindResult_ptr result) { + if (self->m_autoWalkDestination != result->destination) + return; + if (g_extras.debugWalking) { + g_logger.info(stdext::format("Async path search finished with complexity %i/50000", result->complexity)); + } + + if (result->status != Otc::PathFindResultOk) { + if (self->m_lastAutoWalkRetries > 0 && self->m_lastAutoWalkRetries <= 3) { // try again in 300, 700, 1200 ms if canceled by server + self->m_autoWalkContinueEvent = g_dispatcher.scheduleEvent(std::bind(&LocalPlayer::autoWalk, self, result->destination, true), 200 + self->m_lastAutoWalkRetries * 100); + return; + } + self->m_autoWalkDestination = Position(); + self->callLuaField("onAutoWalkFail", result->status); + return; + } + + if(!g_game.getFeature(Otc::GameNewWalking) && result->path.size() > 127) + result->path.resize(127); + else if(result->path.size() > 4095) + result->path.resize(4095); + + if (result->path.empty()) { + self->m_autoWalkDestination = Position(); + self->callLuaField("onAutoWalkFail", result->status); + return; + } + + auto finalAutowalkPos = self->getPrewalkingPosition().translatedToDirections(result->path).back(); + if (self->m_autoWalkDestination != finalAutowalkPos) { + self->m_lastAutoWalkPosition = finalAutowalkPos; + } + + g_game.autoWalk(result->path, result->start); + }); + + if(!retry) + lockWalk(); + return true; +} + +void LocalPlayer::stopAutoWalk() +{ + m_autoWalkDestination = Position(); + m_lastAutoWalkPosition = Position(); + + if (m_autoWalkContinueEvent) { + m_autoWalkContinueEvent->cancel(); + m_autoWalkContinueEvent = nullptr; + } +} + +void LocalPlayer::stopWalk() { + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] stopWalk", (int)g_clock.millis())); + } + + Creature::stopWalk(); // will call terminateWalk + + m_preWalking.clear(); +} + +void LocalPlayer::updateWalkOffset(int totalPixelsWalked, bool inNextFrame) +{ + // pre walks offsets are calculated in the oposite direction + if(isPreWalking()) { + Point& walkOffset = inNextFrame ? m_walkOffsetInNextFrame : m_walkOffset; + walkOffset = Point(0,0); + if(m_walkDirection == Otc::North || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::NorthWest) + walkOffset.y = -totalPixelsWalked; + else if(m_walkDirection == Otc::South || m_walkDirection == Otc::SouthEast || m_walkDirection == Otc::SouthWest) + walkOffset.y = totalPixelsWalked; + + if(m_walkDirection == Otc::East || m_walkDirection == Otc::NorthEast || m_walkDirection == Otc::SouthEast) + walkOffset.x = totalPixelsWalked; + else if(m_walkDirection == Otc::West || m_walkDirection == Otc::NorthWest || m_walkDirection == Otc::SouthWest) + walkOffset.x = -totalPixelsWalked; + } else + Creature::updateWalkOffset(totalPixelsWalked, inNextFrame); +} + +void LocalPlayer::updateWalk() +{ + if (!m_walking) + return; + + float walkTicksPerPixel = ((float)(getStepDuration(true) + 10)) / 32.0f; + int totalPixelsWalked = std::min(m_walkTimer.ticksElapsed() / walkTicksPerPixel, 32.0f); + int totalPixelsWalkedInNextFrame = std::min((m_walkTimer.ticksElapsed() + 15) / walkTicksPerPixel, 32.0f); + + // needed for paralyze effect + m_walkedPixels = std::max(m_walkedPixels, totalPixelsWalked); + int walkedPixelsInNextFrame = std::max(m_walkedPixels, totalPixelsWalkedInNextFrame); + + // update walk animation and offsets + updateWalkAnimation(totalPixelsWalked); + updateWalkOffset(m_walkedPixels); + updateWalkOffset(walkedPixelsInNextFrame, true); + updateWalkingTile(); + + int stepDuration = getStepDuration(); + + // terminate walk only when client and server side walk are completed + if (m_walking && m_walkTimer.ticksElapsed() >= stepDuration) + m_lastPrewalkDone = true; + if(m_walking && m_walkTimer.ticksElapsed() >= stepDuration && !isPreWalking()) + terminateWalk(); +} + +void LocalPlayer::terminateWalk() +{ + if (g_extras.debugWalking) { + g_logger.info(stdext::format("[%i] terminateWalk", (int)g_clock.millis())); + } + + Creature::terminateWalk(); + m_idleTimer.restart(); + m_preWalking.clear(); + m_walking = false; + + auto self = asLocalPlayer(); + + if(m_serverWalking) { + if(m_serverWalkEndEvent) + m_serverWalkEndEvent->cancel(); + m_serverWalkEndEvent = g_dispatcher.scheduleEvent([self] { + self->m_serverWalking = false; + }, 100); + } + + callLuaField("onWalkFinish"); +} + +void LocalPlayer::onAppear() +{ + Creature::onAppear(); + + /* Does not seem to be needed anymore + // on teleports lock the walk + if(!m_oldPosition.isInRange(m_position,1,1)) + lockWalk(); + */ +} + +void LocalPlayer::onPositionChange(const Position& newPos, const Position& oldPos) +{ + Creature::onPositionChange(newPos, oldPos); + + if(newPos == m_autoWalkDestination) + stopAutoWalk(); + else if(m_autoWalkDestination.isValid() && newPos == m_lastAutoWalkPosition) + autoWalk(m_autoWalkDestination); + + m_walkMatrix.updatePosition(newPos); +} + +void LocalPlayer::turn(Otc::Direction direction) +{ + Creature::setDirection(direction); + callLuaField("onTurn", direction); +} + +void LocalPlayer::setStates(int states) +{ + if(m_states != states) { + int oldStates = m_states; + m_states = states; + + callLuaField("onStatesChange", states, oldStates); + } +} + +void LocalPlayer::setSkill(Otc::Skill skill, int level, int levelPercent) +{ + if(skill >= Otc::LastSkill) { + g_logger.traceError("invalid skill"); + return; + } + + int oldLevel = m_skillsLevel[skill]; + int oldLevelPercent = m_skillsLevelPercent[skill]; + + if(level != oldLevel || levelPercent != oldLevelPercent) { + m_skillsLevel[skill] = level; + m_skillsLevelPercent[skill] = levelPercent; + + callLuaField("onSkillChange", skill, level, levelPercent, oldLevel, oldLevelPercent); + } +} + +void LocalPlayer::setBaseSkill(Otc::Skill skill, int baseLevel) +{ + if(skill >= Otc::LastSkill) { + g_logger.traceError("invalid skill"); + return; + } + + int oldBaseLevel = m_skillsBaseLevel[skill]; + if(baseLevel != oldBaseLevel) { + m_skillsBaseLevel[skill] = baseLevel; + + callLuaField("onBaseSkillChange", skill, baseLevel, oldBaseLevel); + } +} + +void LocalPlayer::setHealth(double health, double maxHealth) +{ + if(m_health != health || m_maxHealth != maxHealth) { + double oldHealth = m_health; + double oldMaxHealth = m_maxHealth; + m_health = health; + m_maxHealth = maxHealth; + + callLuaField("onHealthChange", health, maxHealth, oldHealth, oldMaxHealth); + + // cannot walk while dying + if(health == 0) { + if(isPreWalking()) + stopWalk(); + lockWalk(); + } + } +} + +void LocalPlayer::setFreeCapacity(double freeCapacity) +{ + if(m_freeCapacity != freeCapacity) { + double oldFreeCapacity = m_freeCapacity; + m_freeCapacity = freeCapacity; + + callLuaField("onFreeCapacityChange", freeCapacity, oldFreeCapacity); + } +} + +void LocalPlayer::setTotalCapacity(double totalCapacity) +{ + if(m_totalCapacity != totalCapacity) { + double oldTotalCapacity = m_totalCapacity; + m_totalCapacity = totalCapacity; + + callLuaField("onTotalCapacityChange", totalCapacity, oldTotalCapacity); + } +} + +void LocalPlayer::setExperience(double experience) +{ + if(m_experience != experience) { + double oldExperience = m_experience; + m_experience = experience; + + callLuaField("onExperienceChange", experience, oldExperience); + } +} + +void LocalPlayer::setLevel(double level, double levelPercent) +{ + if(m_level != level || m_levelPercent != levelPercent) { + double oldLevel = m_level; + double oldLevelPercent = m_levelPercent; + m_level = level; + m_levelPercent = levelPercent; + + callLuaField("onLevelChange", level, levelPercent, oldLevel, oldLevelPercent); + } +} + +void LocalPlayer::setMana(double mana, double maxMana) +{ + if(m_mana != mana || m_maxMana != maxMana) { + double oldMana = m_mana; + double oldMaxMana; + m_mana = mana; + m_maxMana = maxMana; + + callLuaField("onManaChange", mana, maxMana, oldMana, oldMaxMana); + } +} + +void LocalPlayer::setMagicLevel(double magicLevel, double magicLevelPercent) +{ + if(m_magicLevel != magicLevel || m_magicLevelPercent != magicLevelPercent) { + double oldMagicLevel = m_magicLevel; + double oldMagicLevelPercent = m_magicLevelPercent; + m_magicLevel = magicLevel; + m_magicLevelPercent = magicLevelPercent; + + callLuaField("onMagicLevelChange", magicLevel, magicLevelPercent, oldMagicLevel, oldMagicLevelPercent); + } +} + +void LocalPlayer::setBaseMagicLevel(double baseMagicLevel) +{ + if(m_baseMagicLevel != baseMagicLevel) { + double oldBaseMagicLevel = m_baseMagicLevel; + m_baseMagicLevel = baseMagicLevel; + + callLuaField("onBaseMagicLevelChange", baseMagicLevel, oldBaseMagicLevel); + } +} + +void LocalPlayer::setSoul(double soul) +{ + if(m_soul != soul) { + double oldSoul = m_soul; + m_soul = soul; + + callLuaField("onSoulChange", soul, oldSoul); + } +} + +void LocalPlayer::setStamina(double stamina) +{ + if(m_stamina != stamina) { + double oldStamina = m_stamina; + m_stamina = stamina; + + callLuaField("onStaminaChange", stamina, oldStamina); + } +} + +void LocalPlayer::setInventoryItem(Otc::InventorySlot inventory, const ItemPtr& item) +{ + if(inventory >= Otc::LastInventorySlot) { + g_logger.traceError("invalid slot"); + return; + } + + if(m_inventoryItems[inventory] != item) { + ItemPtr oldItem = m_inventoryItems[inventory]; + m_inventoryItems[inventory] = item; + + callLuaField("onInventoryChange", inventory, item, oldItem); + } +} + +void LocalPlayer::setVocation(int vocation) +{ + if(m_vocation != vocation) { + int oldVocation = m_vocation; + m_vocation = vocation; + + callLuaField("onVocationChange", vocation, oldVocation); + } +} + +void LocalPlayer::setPremium(bool premium) +{ + if(m_premium != premium) { + m_premium = premium; + + callLuaField("onPremiumChange", premium); + } +} + +void LocalPlayer::setRegenerationTime(double regenerationTime) +{ + if(m_regenerationTime != regenerationTime) { + double oldRegenerationTime = m_regenerationTime; + m_regenerationTime = regenerationTime; + + callLuaField("onRegenerationChange", regenerationTime, oldRegenerationTime); + } +} + +void LocalPlayer::setOfflineTrainingTime(double offlineTrainingTime) +{ + if(m_offlineTrainingTime != offlineTrainingTime) { + double oldOfflineTrainingTime = m_offlineTrainingTime; + m_offlineTrainingTime = offlineTrainingTime; + + callLuaField("onOfflineTrainingChange", offlineTrainingTime, oldOfflineTrainingTime); + } +} + +void LocalPlayer::setSpells(const std::vector& spells) +{ + if(m_spells != spells) { + std::vector oldSpells = m_spells; + m_spells = spells; + + callLuaField("onSpellsChange", spells, oldSpells); + } +} + +void LocalPlayer::setBlessings(int blessings) +{ + if(blessings != m_blessings) { + int oldBlessings = m_blessings; + m_blessings = blessings; + + callLuaField("onBlessingsChange", blessings, oldBlessings); + } +} + +bool LocalPlayer::hasSight(const Position& pos) +{ + return m_position.isInRange(pos, g_map.getAwareRange().left - 1, g_map.getAwareRange().top - 1); +} diff --git a/src/client/localplayer.h b/src/client/localplayer.h new file mode 100644 index 0000000..f2788f0 --- /dev/null +++ b/src/client/localplayer.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LOCALPLAYER_H +#define LOCALPLAYER_H + +#include "player.h" +#include "walkmatrix.h" + +// @bindclass +class LocalPlayer : public Player +{ + enum { + PREWALK_TIMEOUT = 1000 + }; + +public: + LocalPlayer(); + + void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr) override; + + void unlockWalk() { m_walkLockExpiration = 0; } + void lockWalk(int millis = 200); + void stopAutoWalk(); + bool autoWalk(Position destination, bool retry = false); + bool canWalk(Otc::Direction direction, bool ignoreLock = false); + bool isWalkLocked() { + return (m_walkLockExpiration != 0 && g_clock.millis() < m_walkLockExpiration); + } + void turn(Otc::Direction) override; + + void setStates(int states); + void setSkill(Otc::Skill skill, int level, int levelPercent); + void setBaseSkill(Otc::Skill skill, int baseLevel); + void setHealth(double health, double maxHealth); + void setFreeCapacity(double freeCapacity); + void setTotalCapacity(double totalCapacity); + void setExperience(double experience); + void setLevel(double level, double levelPercent); + void setMana(double mana, double maxMana); + void setMagicLevel(double magicLevel, double magicLevelPercent); + void setBaseMagicLevel(double baseMagicLevel); + void setSoul(double soul); + void setStamina(double stamina); + void setKnown(bool known) { m_known = known; } + void setPendingGame(bool pending) { m_pending = pending; } + void setInventoryItem(Otc::InventorySlot inventory, const ItemPtr& item); + void setVocation(int vocation); + void setPremium(bool premium); + void setRegenerationTime(double regenerationTime); + void setOfflineTrainingTime(double offlineTrainingTime); + void setSpells(const std::vector& spells); + void setBlessings(int blessings); + + int getStates() { return m_states; } + int getSkillLevel(Otc::Skill skill) { return m_skillsLevel[skill]; } + int getSkillBaseLevel(Otc::Skill skill) { return m_skillsBaseLevel[skill]; } + int getSkillLevelPercent(Otc::Skill skill) { return m_skillsLevelPercent[skill]; } + int getVocation() { return m_vocation; } + double getHealth() { return m_health; } + double getMaxHealth() { return m_maxHealth; } + double getFreeCapacity() { return m_freeCapacity; } + double getTotalCapacity() { return m_totalCapacity; } + double getExperience() { return m_experience; } + double getLevel() { return m_level; } + double getLevelPercent() { return m_levelPercent; } + double getMana() { return m_mana; } + double getMaxMana() { return std::max(m_mana, m_maxMana); } + double getMagicLevel() { return m_magicLevel; } + double getMagicLevelPercent() { return m_magicLevelPercent; } + double getBaseMagicLevel() { return m_baseMagicLevel; } + double getSoul() { return m_soul; } + double getStamina() { return m_stamina; } + double getRegenerationTime() { return m_regenerationTime; } + double getOfflineTrainingTime() { return m_offlineTrainingTime; } + std::vector getSpells() { return m_spells; } + ItemPtr getInventoryItem(Otc::InventorySlot inventory) { return m_inventoryItems[inventory]; } + int getBlessings() { return m_blessings; } + + bool hasSight(const Position& pos); + bool isKnown() { return m_known; } + bool isAutoWalking() { return m_autoWalkDestination.isValid(); } + bool isServerWalking() override { return m_serverWalking; } + bool isPremium() { return m_premium; } + bool isPendingGame() { return m_pending; } + + LocalPlayerPtr asLocalPlayer() { return static_self_cast(); } + bool isLocalPlayer() override { return true; } + + void onAppear() override; + void onPositionChange(const Position& newPos, const Position& oldPos) override; + + // pre walking + void preWalk(Otc::Direction direction); + bool isPreWalking() override { return !m_preWalking.empty(); } + Position getPrewalkingPosition(bool beforePrewalk = false) override { + if(m_preWalking.empty()) + return m_position; + else if (!beforePrewalk && m_preWalking.size() == 1) + return m_position; + auto ret = m_preWalking.rbegin(); + if(!beforePrewalk) + ret++; + return *ret; + } + + uint32_t getWalkPrediction(const Position& pos) + { + return m_walkMatrix.get(pos); + }; + + std::string dumpWalkMatrix() + { + return m_walkMatrix.dump(); + } + + void startServerWalking() { m_serverWalking = true; } + void finishServerWalking() { m_serverWalking = false; } + +protected: + void walk(const Position& oldPos, const Position& newPos); + void cancelWalk(Otc::Direction direction = Otc::InvalidDirection); + + void cancelNewWalk(Otc::Direction dir); + bool predictiveCancelWalk(const Position& pos, uint32_t predictionId, Otc::Direction dir); + + bool retryAutoWalk(); + void stopWalk(); + + friend class Game; + +protected: + void updateWalkOffset(int totalPixelsWalked, bool inNextFrame = false) override; + void updateWalk() override; + void terminateWalk() override; + +private: + // walk related + Position m_autoWalkDestination; + Position m_lastAutoWalkPosition; + int m_lastAutoWalkRetries = 0; + ScheduledEventPtr m_serverWalkEndEvent; + ScheduledEventPtr m_autoWalkContinueEvent; + ticks_t m_walkLockExpiration; + + // walking and pre walking + std::list m_preWalking; + bool m_serverWalking = false; + bool m_lastPrewalkDone = false; + WalkMatrix m_walkMatrix; + + bool m_premium = false; + bool m_known = false; + bool m_pending = false; + + ItemPtr m_inventoryItems[Otc::LastInventorySlot]; + Timer m_idleTimer; + + std::array m_skillsLevel; + std::array m_skillsBaseLevel; + std::array m_skillsLevelPercent; + std::vector m_spells; + + int m_states; + int m_vocation; + int m_blessings; + + double m_health; + double m_maxHealth; + double m_freeCapacity; + double m_totalCapacity; + double m_experience; + double m_level; + double m_levelPercent; + double m_mana; + double m_maxMana; + double m_magicLevel; + double m_magicLevelPercent; + double m_baseMagicLevel; + double m_soul; + double m_stamina; + double m_regenerationTime; + double m_offlineTrainingTime; +}; + +#endif diff --git a/src/client/luafunctions_client.cpp b/src/client/luafunctions_client.cpp new file mode 100644 index 0000000..7290a21 --- /dev/null +++ b/src/client/luafunctions_client.cpp @@ -0,0 +1,921 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "client.h" +#include "luavaluecasts_client.h" +#include "game.h" +#include "tile.h" +#include "houses.h" +#include "towns.h" +#include "container.h" +#include "item.h" +#include "effect.h" +#include "missile.h" +#include "statictext.h" +#include "animatedtext.h" +#include "creature.h" +#include "player.h" +#include "localplayer.h" +#include "map.h" +#include "minimap.h" +#include "thingtypemanager.h" +#include "spritemanager.h" +#include "shadermanager.h" +#include "protocolgame.h" +#include "uiitem.h" +#include "uicreature.h" +#include "uimap.h" +#include "uiminimap.h" +#include "uimapanchorlayout.h" +#include "uiprogressrect.h" +#include "uisprite.h" +#include "outfit.h" + +#include + +void Client::registerLuaFunctions() +{ + g_lua.registerSingletonClass("g_things"); + g_lua.bindSingletonFunction("g_things", "loadDat", &ThingTypeManager::loadDat, &g_things); +#ifdef WITH_ENCRYPTION + g_lua.bindSingletonFunction("g_things", "saveDat", &ThingTypeManager::saveDat, &g_things); + g_lua.bindSingletonFunction("g_things", "dumpTextures", &ThingTypeManager::dumpTextures, &g_things); + g_lua.bindSingletonFunction("g_things", "replaceTextures", &ThingTypeManager::replaceTextures, &g_things); +#endif + g_lua.bindSingletonFunction("g_things", "loadOtb", &ThingTypeManager::loadOtb, &g_things); + g_lua.bindSingletonFunction("g_things", "loadXml", &ThingTypeManager::loadXml, &g_things); + g_lua.bindSingletonFunction("g_things", "loadOtml", &ThingTypeManager::loadOtml, &g_things); + g_lua.bindSingletonFunction("g_things", "isDatLoaded", &ThingTypeManager::isDatLoaded, &g_things); + g_lua.bindSingletonFunction("g_things", "isOtbLoaded", &ThingTypeManager::isOtbLoaded, &g_things); + g_lua.bindSingletonFunction("g_things", "getDatSignature", &ThingTypeManager::getDatSignature, &g_things); + g_lua.bindSingletonFunction("g_things", "getContentRevision", &ThingTypeManager::getContentRevision, &g_things); + g_lua.bindSingletonFunction("g_things", "getThingType", &ThingTypeManager::getThingType, &g_things); + g_lua.bindSingletonFunction("g_things", "getItemType", &ThingTypeManager::getItemType, &g_things); + g_lua.bindSingletonFunction("g_things", "getThingTypes", &ThingTypeManager::getThingTypes, &g_things); + g_lua.bindSingletonFunction("g_things", "findItemTypeByClientId", &ThingTypeManager::findItemTypeByClientId, &g_things); + g_lua.bindSingletonFunction("g_things", "findItemTypeByName", &ThingTypeManager::findItemTypeByName, &g_things); + g_lua.bindSingletonFunction("g_things", "findItemTypesByName", &ThingTypeManager::findItemTypesByName, &g_things); + g_lua.bindSingletonFunction("g_things", "findItemTypesByString", &ThingTypeManager::findItemTypesByString, &g_things); + g_lua.bindSingletonFunction("g_things", "findItemTypeByCategory", &ThingTypeManager::findItemTypeByCategory, &g_things); + g_lua.bindSingletonFunction("g_things", "findThingTypeByAttr", &ThingTypeManager::findThingTypeByAttr, &g_things); + g_lua.bindSingletonFunction("g_things", "getMarketCategories", &ThingTypeManager::getMarketCategories, &g_things); + + g_lua.registerSingletonClass("g_houses"); + g_lua.bindSingletonFunction("g_houses", "clear", &HouseManager::clear, &g_houses); + g_lua.bindSingletonFunction("g_houses", "load", &HouseManager::load, &g_houses); + g_lua.bindSingletonFunction("g_houses", "save", &HouseManager::save, &g_houses); + g_lua.bindSingletonFunction("g_houses", "getHouse", &HouseManager::getHouse, &g_houses); + g_lua.bindSingletonFunction("g_houses", "getHouseByName", &HouseManager::getHouseByName, &g_houses); + g_lua.bindSingletonFunction("g_houses", "addHouse", &HouseManager::addHouse, &g_houses); + g_lua.bindSingletonFunction("g_houses", "removeHouse", &HouseManager::removeHouse, &g_houses); + g_lua.bindSingletonFunction("g_houses", "getHouseList", &HouseManager::getHouseList, &g_houses); + g_lua.bindSingletonFunction("g_houses", "filterHouses", &HouseManager::filterHouses, &g_houses); + g_lua.bindSingletonFunction("g_houses", "sort", &HouseManager::sort, &g_houses); + + g_lua.registerSingletonClass("g_towns"); + g_lua.bindSingletonFunction("g_towns", "getTown", &TownManager::getTown, &g_towns); + g_lua.bindSingletonFunction("g_towns", "getTownByName",&TownManager::getTownByName,&g_towns); + g_lua.bindSingletonFunction("g_towns", "addTown", &TownManager::addTown, &g_towns); + g_lua.bindSingletonFunction("g_towns", "removeTown", &TownManager::removeTown, &g_towns); + g_lua.bindSingletonFunction("g_towns", "getTowns", &TownManager::getTowns, &g_towns); + g_lua.bindSingletonFunction("g_towns", "sort", &TownManager::sort, &g_towns); + + g_lua.registerSingletonClass("g_sprites"); + g_lua.bindSingletonFunction("g_sprites", "loadSpr", &SpriteManager::loadSpr, &g_sprites); +#ifdef WITH_ENCRYPTION + g_lua.bindSingletonFunction("g_sprites", "saveSpr", &SpriteManager::saveSpr, &g_sprites); + g_lua.bindSingletonFunction("g_sprites", "dumpSprites", &SpriteManager::dumpSprites, &g_sprites); + g_lua.bindSingletonFunction("g_sprites", "encryptSprites", &SpriteManager::encryptSprites, &g_sprites); +#endif + g_lua.bindSingletonFunction("g_sprites", "unload", &SpriteManager::unload, &g_sprites); + g_lua.bindSingletonFunction("g_sprites", "isLoaded", &SpriteManager::isLoaded, &g_sprites); + g_lua.bindSingletonFunction("g_sprites", "getSprSignature", &SpriteManager::getSignature, &g_sprites); + g_lua.bindSingletonFunction("g_sprites", "getSpritesCount", &SpriteManager::getSpritesCount, &g_sprites); + + g_lua.registerSingletonClass("g_map"); + g_lua.bindSingletonFunction("g_map", "isLookPossible", &Map::isLookPossible, &g_map); + g_lua.bindSingletonFunction("g_map", "isCovered", &Map::isCovered, &g_map); + g_lua.bindSingletonFunction("g_map", "isCompletelyCovered", &Map::isCompletelyCovered, &g_map); + g_lua.bindSingletonFunction("g_map", "addThing", &Map::addThing, &g_map); + g_lua.bindSingletonFunction("g_map", "getThing", &Map::getThing, &g_map); + g_lua.bindSingletonFunction("g_map", "removeThingByPos", &Map::removeThingByPos, &g_map); + g_lua.bindSingletonFunction("g_map", "removeThing", &Map::removeThing, &g_map); + g_lua.bindSingletonFunction("g_map", "colorizeThing", &Map::colorizeThing, &g_map); + g_lua.bindSingletonFunction("g_map", "removeThingColor", &Map::removeThingColor, &g_map); + g_lua.bindSingletonFunction("g_map", "clean", &Map::clean, &g_map); + g_lua.bindSingletonFunction("g_map", "cleanTile", &Map::cleanTile, &g_map); + g_lua.bindSingletonFunction("g_map", "cleanTexts", &Map::cleanTexts, &g_map); + g_lua.bindSingletonFunction("g_map", "getTile", &Map::getTile, &g_map); + g_lua.bindSingletonFunction("g_map", "getOrCreateTile", &Map::getOrCreateTile, &g_map); + g_lua.bindSingletonFunction("g_map", "getTiles", &Map::getTiles, &g_map); + g_lua.bindSingletonFunction("g_map", "setCentralPosition", &Map::setCentralPosition, &g_map); + g_lua.bindSingletonFunction("g_map", "getCentralPosition", &Map::getCentralPosition, &g_map); + g_lua.bindSingletonFunction("g_map", "getCreatureById", &Map::getCreatureById, &g_map); + g_lua.bindSingletonFunction("g_map", "removeCreatureById", &Map::removeCreatureById, &g_map); + g_lua.bindSingletonFunction("g_map", "getSpectators", &Map::getSpectators, &g_map); + g_lua.bindSingletonFunction("g_map", "getSpectatorsInRange", &Map::getSpectatorsInRange, &g_map); + g_lua.bindSingletonFunction("g_map", "getSpectatorsInRangeEx", &Map::getSpectatorsInRangeEx, &g_map); + g_lua.bindSingletonFunction("g_map", "findPath", &Map::findPath, &g_map); + g_lua.bindSingletonFunction("g_map", "loadOtbm", &Map::loadOtbm, &g_map); + g_lua.bindSingletonFunction("g_map", "saveOtbm", &Map::saveOtbm, &g_map); + g_lua.bindSingletonFunction("g_map", "loadOtcm", &Map::loadOtcm, &g_map); + g_lua.bindSingletonFunction("g_map", "saveOtcm", &Map::saveOtcm, &g_map); + g_lua.bindSingletonFunction("g_map", "getHouseFile", &Map::getHouseFile, &g_map); + g_lua.bindSingletonFunction("g_map", "setHouseFile", &Map::setHouseFile, &g_map); + g_lua.bindSingletonFunction("g_map", "getSpawnFile", &Map::getSpawnFile, &g_map); + g_lua.bindSingletonFunction("g_map", "setSpawnFile", &Map::setSpawnFile, &g_map); + g_lua.bindSingletonFunction("g_map", "createTile", &Map::createTile, &g_map); + g_lua.bindSingletonFunction("g_map", "setWidth", &Map::setWidth, &g_map); + g_lua.bindSingletonFunction("g_map", "setHeight", &Map::setHeight, &g_map); + g_lua.bindSingletonFunction("g_map", "getSize", &Map::getSize, &g_map); + g_lua.bindSingletonFunction("g_map", "setDescription", &Map::setDescription, &g_map); + g_lua.bindSingletonFunction("g_map", "getDescriptions", &Map::getDescriptions, &g_map); + g_lua.bindSingletonFunction("g_map", "clearDescriptions", &Map::clearDescriptions, &g_map); + g_lua.bindSingletonFunction("g_map", "setShowZone", &Map::setShowZone, &g_map); + g_lua.bindSingletonFunction("g_map", "setShowZones", &Map::setShowZones, &g_map); + g_lua.bindSingletonFunction("g_map", "setZoneColor", &Map::setZoneColor, &g_map); + g_lua.bindSingletonFunction("g_map", "setZoneOpacity", &Map::setZoneOpacity, &g_map); + g_lua.bindSingletonFunction("g_map", "getZoneOpacity", &Map::getZoneOpacity, &g_map); + g_lua.bindSingletonFunction("g_map", "getZoneColor", &Map::getZoneColor, &g_map); + g_lua.bindSingletonFunction("g_map", "showZones", &Map::showZones, &g_map); + g_lua.bindSingletonFunction("g_map", "showZone", &Map::showZone, &g_map); + g_lua.bindSingletonFunction("g_map", "setForceShowAnimations", &Map::setForceShowAnimations, &g_map); + g_lua.bindSingletonFunction("g_map", "isForcingAnimations", &Map::isForcingAnimations, &g_map); + g_lua.bindSingletonFunction("g_map", "isShowingAnimations", &Map::isShowingAnimations, &g_map); + g_lua.bindSingletonFunction("g_map", "setShowAnimations", &Map::setShowAnimations, &g_map); + g_lua.bindSingletonFunction("g_map", "findItemsById", &Map::findItemsById, &g_map); + g_lua.bindSingletonFunction("g_map", "getAwareRange", &Map::getAwareRangeAsSize, &g_map); + g_lua.bindSingletonFunction("g_map", "findEveryPath", &Map::findEveryPath, &g_map); + g_lua.bindSingletonFunction("g_map", "getMinimapColor", &Map::getMinimapColor, &g_map); + g_lua.bindSingletonFunction("g_map", "isPatchable", &Map::isPatchable, &g_map); + g_lua.bindSingletonFunction("g_map", "isWalkable", &Map::isWalkable, &g_map); + + g_lua.registerSingletonClass("g_minimap"); + g_lua.bindSingletonFunction("g_minimap", "clean", &Minimap::clean, &g_minimap); + g_lua.bindSingletonFunction("g_minimap", "loadImage", &Minimap::loadImage, &g_minimap); + g_lua.bindSingletonFunction("g_minimap", "saveImage", &Minimap::saveImage, &g_minimap); + g_lua.bindSingletonFunction("g_minimap", "loadOtmm", &Minimap::loadOtmm, &g_minimap); + g_lua.bindSingletonFunction("g_minimap", "saveOtmm", &Minimap::saveOtmm, &g_minimap); + + g_lua.registerSingletonClass("g_creatures"); + g_lua.bindSingletonFunction("g_creatures", "getCreatures", &CreatureManager::getCreatures, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "getCreatureByName", &CreatureManager::getCreatureByName, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "getCreatureByLook", &CreatureManager::getCreatureByLook, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "getSpawn", &CreatureManager::getSpawn, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "getSpawnForPlacePos", &CreatureManager::getSpawnForPlacePos, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "addSpawn", &CreatureManager::addSpawn, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "loadMonsters", &CreatureManager::loadMonsters, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "loadNpcs", &CreatureManager::loadNpcs, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "loadSingleCreature", &CreatureManager::loadSingleCreature, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "loadSpawns", &CreatureManager::loadSpawns, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "saveSpawns", &CreatureManager::saveSpawns, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "isLoaded", &CreatureManager::isLoaded, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "isSpawnLoaded", &CreatureManager::isSpawnLoaded, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "clear", &CreatureManager::clear, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "clearSpawns", &CreatureManager::clearSpawns, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "getSpawns", &CreatureManager::getSpawns, &g_creatures); + g_lua.bindSingletonFunction("g_creatures", "deleteSpawn", &CreatureManager::deleteSpawn, &g_creatures); + + g_lua.registerSingletonClass("g_game"); + g_lua.bindSingletonFunction("g_game", "loginWorld", &Game::loginWorld, &g_game); + g_lua.bindSingletonFunction("g_game", "cancelLogin", &Game::cancelLogin, &g_game); + g_lua.bindSingletonFunction("g_game", "forceLogout", &Game::forceLogout, &g_game); + g_lua.bindSingletonFunction("g_game", "safeLogout", &Game::safeLogout, &g_game); + g_lua.bindSingletonFunction("g_game", "walk", &Game::walk, &g_game); + g_lua.bindSingletonFunction("g_game", "autoWalk", &Game::autoWalk, &g_game); + g_lua.bindSingletonFunction("g_game", "turn", &Game::turn, &g_game); + g_lua.bindSingletonFunction("g_game", "stop", &Game::stop, &g_game); + g_lua.bindSingletonFunction("g_game", "look", &Game::look, &g_game); + g_lua.bindSingletonFunction("g_game", "move", &Game::move, &g_game); + g_lua.bindSingletonFunction("g_game", "moveRaw", &Game::moveRaw, &g_game); + g_lua.bindSingletonFunction("g_game", "moveToParentContainer", &Game::moveToParentContainer, &g_game); + g_lua.bindSingletonFunction("g_game", "rotate", &Game::rotate, &g_game); + g_lua.bindSingletonFunction("g_game", "wrap", &Game::wrap, &g_game); + g_lua.bindSingletonFunction("g_game", "use", &Game::use, &g_game); + g_lua.bindSingletonFunction("g_game", "useWith", &Game::useWith, &g_game); + g_lua.bindSingletonFunction("g_game", "useInventoryItem", &Game::useInventoryItem, &g_game); + g_lua.bindSingletonFunction("g_game", "useInventoryItemWith", &Game::useInventoryItemWith, &g_game); + g_lua.bindSingletonFunction("g_game", "findItemInContainers", &Game::findItemInContainers, &g_game); + g_lua.bindSingletonFunction("g_game", "open", &Game::open, &g_game); + g_lua.bindSingletonFunction("g_game", "openParent", &Game::openParent, &g_game); + g_lua.bindSingletonFunction("g_game", "close", &Game::close, &g_game); + g_lua.bindSingletonFunction("g_game", "refreshContainer", &Game::refreshContainer, &g_game); + g_lua.bindSingletonFunction("g_game", "attack", &Game::attack, &g_game); + g_lua.bindSingletonFunction("g_game", "cancelAttack", &Game::cancelAttack, &g_game); + g_lua.bindSingletonFunction("g_game", "follow", &Game::follow, &g_game); + g_lua.bindSingletonFunction("g_game", "cancelFollow", &Game::cancelFollow, &g_game); + g_lua.bindSingletonFunction("g_game", "cancelAttackAndFollow", &Game::cancelAttackAndFollow, &g_game); + g_lua.bindSingletonFunction("g_game", "talk", &Game::talk, &g_game); + g_lua.bindSingletonFunction("g_game", "talkChannel", &Game::talkChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "talkPrivate", &Game::talkPrivate, &g_game); + g_lua.bindSingletonFunction("g_game", "openPrivateChannel", &Game::openPrivateChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "requestChannels", &Game::requestChannels, &g_game); + g_lua.bindSingletonFunction("g_game", "joinChannel", &Game::joinChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "leaveChannel", &Game::leaveChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "closeNpcChannel", &Game::closeNpcChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "openOwnChannel", &Game::openOwnChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "inviteToOwnChannel", &Game::inviteToOwnChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "excludeFromOwnChannel", &Game::excludeFromOwnChannel, &g_game); + g_lua.bindSingletonFunction("g_game", "partyInvite", &Game::partyInvite, &g_game); + g_lua.bindSingletonFunction("g_game", "partyJoin", &Game::partyJoin, &g_game); + g_lua.bindSingletonFunction("g_game", "partyRevokeInvitation", &Game::partyRevokeInvitation, &g_game); + g_lua.bindSingletonFunction("g_game", "partyPassLeadership", &Game::partyPassLeadership, &g_game); + g_lua.bindSingletonFunction("g_game", "partyLeave", &Game::partyLeave, &g_game); + g_lua.bindSingletonFunction("g_game", "partyShareExperience", &Game::partyShareExperience, &g_game); + g_lua.bindSingletonFunction("g_game", "requestOutfit", &Game::requestOutfit, &g_game); + g_lua.bindSingletonFunction("g_game", "changeOutfit", &Game::changeOutfit, &g_game); + g_lua.bindSingletonFunction("g_game", "addVip", &Game::addVip, &g_game); + g_lua.bindSingletonFunction("g_game", "removeVip", &Game::removeVip, &g_game); + g_lua.bindSingletonFunction("g_game", "editVip", &Game::editVip, &g_game); + g_lua.bindSingletonFunction("g_game", "setChaseMode", &Game::setChaseMode, &g_game); + g_lua.bindSingletonFunction("g_game", "setFightMode", &Game::setFightMode, &g_game); + g_lua.bindSingletonFunction("g_game", "setPVPMode", &Game::setPVPMode, &g_game); + g_lua.bindSingletonFunction("g_game", "setSafeFight", &Game::setSafeFight, &g_game); + g_lua.bindSingletonFunction("g_game", "getChaseMode", &Game::getChaseMode, &g_game); + g_lua.bindSingletonFunction("g_game", "getFightMode", &Game::getFightMode, &g_game); + g_lua.bindSingletonFunction("g_game", "getPVPMode", &Game::getPVPMode, &g_game); + g_lua.bindSingletonFunction("g_game", "getUnjustifiedPoints", &Game::getUnjustifiedPoints, &g_game); + g_lua.bindSingletonFunction("g_game", "getOpenPvpSituations", &Game::getOpenPvpSituations, &g_game); + g_lua.bindSingletonFunction("g_game", "isSafeFight", &Game::isSafeFight, &g_game); + g_lua.bindSingletonFunction("g_game", "inspectNpcTrade", &Game::inspectNpcTrade, &g_game); + g_lua.bindSingletonFunction("g_game", "buyItem", &Game::buyItem, &g_game); + g_lua.bindSingletonFunction("g_game", "sellItem", &Game::sellItem, &g_game); + g_lua.bindSingletonFunction("g_game", "closeNpcTrade", &Game::closeNpcTrade, &g_game); + g_lua.bindSingletonFunction("g_game", "requestTrade", &Game::requestTrade, &g_game); + g_lua.bindSingletonFunction("g_game", "inspectTrade", &Game::inspectTrade, &g_game); + g_lua.bindSingletonFunction("g_game", "acceptTrade", &Game::acceptTrade, &g_game); + g_lua.bindSingletonFunction("g_game", "rejectTrade", &Game::rejectTrade, &g_game); + g_lua.bindSingletonFunction("g_game", "openRuleViolation", &Game::openRuleViolation, &g_game); + g_lua.bindSingletonFunction("g_game", "closeRuleViolation", &Game::closeRuleViolation, &g_game); + g_lua.bindSingletonFunction("g_game", "cancelRuleViolation", &Game::cancelRuleViolation, &g_game); + g_lua.bindSingletonFunction("g_game", "reportBug", &Game::reportBug, &g_game); + g_lua.bindSingletonFunction("g_game", "reportRuleViolation", &Game::reportRuleViolation, &g_game); + g_lua.bindSingletonFunction("g_game", "debugReport", &Game::debugReport, &g_game); + g_lua.bindSingletonFunction("g_game", "editText", &Game::editText, &g_game); + g_lua.bindSingletonFunction("g_game", "editList", &Game::editList, &g_game); + g_lua.bindSingletonFunction("g_game", "requestQuestLog", &Game::requestQuestLog, &g_game); + g_lua.bindSingletonFunction("g_game", "requestQuestLine", &Game::requestQuestLine, &g_game); + g_lua.bindSingletonFunction("g_game", "equipItem", &Game::equipItem, &g_game); + g_lua.bindSingletonFunction("g_game", "mount", &Game::mount, &g_game); + g_lua.bindSingletonFunction("g_game", "requestItemInfo", &Game::requestItemInfo, &g_game); + g_lua.bindSingletonFunction("g_game", "ping", &Game::ping, &g_game); + g_lua.bindSingletonFunction("g_game", "setPingDelay", &Game::setPingDelay, &g_game); + g_lua.bindSingletonFunction("g_game", "changeMapAwareRange", &Game::changeMapAwareRange, &g_game); + g_lua.bindSingletonFunction("g_game", "canPerformGameAction", &Game::canPerformGameAction, &g_game); + g_lua.bindSingletonFunction("g_game", "canReportBugs", &Game::canReportBugs, &g_game); + g_lua.bindSingletonFunction("g_game", "checkBotProtection", &Game::checkBotProtection, &g_game); + g_lua.bindSingletonFunction("g_game", "isOnline", &Game::isOnline, &g_game); + g_lua.bindSingletonFunction("g_game", "isLogging", &Game::isLogging, &g_game); + g_lua.bindSingletonFunction("g_game", "isDead", &Game::isDead, &g_game); + g_lua.bindSingletonFunction("g_game", "isAttacking", &Game::isAttacking, &g_game); + g_lua.bindSingletonFunction("g_game", "isFollowing", &Game::isFollowing, &g_game); + g_lua.bindSingletonFunction("g_game", "isConnectionOk", &Game::isConnectionOk, &g_game); + g_lua.bindSingletonFunction("g_game", "getPing", &Game::getPing, &g_game); + g_lua.bindSingletonFunction("g_game", "getContainer", &Game::getContainer, &g_game); + g_lua.bindSingletonFunction("g_game", "getContainers", &Game::getContainers, &g_game); + g_lua.bindSingletonFunction("g_game", "getVips", &Game::getVips, &g_game); + g_lua.bindSingletonFunction("g_game", "getAttackingCreature", &Game::getAttackingCreature, &g_game); + g_lua.bindSingletonFunction("g_game", "getFollowingCreature", &Game::getFollowingCreature, &g_game); + g_lua.bindSingletonFunction("g_game", "getServerBeat", &Game::getServerBeat, &g_game); + g_lua.bindSingletonFunction("g_game", "getLocalPlayer", &Game::getLocalPlayer, &g_game); + g_lua.bindSingletonFunction("g_game", "getProtocolGame", &Game::getProtocolGame, &g_game); + g_lua.bindSingletonFunction("g_game", "getProtocolVersion", &Game::getProtocolVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "setProtocolVersion", &Game::setProtocolVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "getCustomProtocolVersion", &Game::getCustomProtocolVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "setCustomProtocolVersion", &Game::setCustomProtocolVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "getClientVersion", &Game::getClientVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "setClientVersion", &Game::setClientVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "setCustomOs", &Game::setCustomOs, &g_game); + g_lua.bindSingletonFunction("g_game", "getOs", &Game::getOs, &g_game); + g_lua.bindSingletonFunction("g_game", "getCharacterName", &Game::getCharacterName, &g_game); + g_lua.bindSingletonFunction("g_game", "getWorldName", &Game::getWorldName, &g_game); + g_lua.bindSingletonFunction("g_game", "getGMActions", &Game::getGMActions, &g_game); + g_lua.bindSingletonFunction("g_game", "getFeature", &Game::getFeature, &g_game); + g_lua.bindSingletonFunction("g_game", "setFeature", &Game::setFeature, &g_game); + g_lua.bindSingletonFunction("g_game", "enableFeature", &Game::enableFeature, &g_game); + g_lua.bindSingletonFunction("g_game", "disableFeature", &Game::disableFeature, &g_game); + g_lua.bindSingletonFunction("g_game", "resetFeatures", &Game::resetFeatures, &g_game); + g_lua.bindSingletonFunction("g_game", "isGM", &Game::isGM, &g_game); + g_lua.bindSingletonFunction("g_game", "answerModalDialog", &Game::answerModalDialog, &g_game); + g_lua.bindSingletonFunction("g_game", "browseField", &Game::browseField, &g_game); + g_lua.bindSingletonFunction("g_game", "seekInContainer", &Game::seekInContainer, &g_game); + g_lua.bindSingletonFunction("g_game", "getLastWalkDir", &Game::getLastWalkDir, &g_game); + g_lua.bindSingletonFunction("g_game", "buyStoreOffer", &Game::buyStoreOffer, &g_game); + g_lua.bindSingletonFunction("g_game", "requestTransactionHistory", &Game::requestTransactionHistory, &g_game); + g_lua.bindSingletonFunction("g_game", "requestStoreOffers", &Game::requestStoreOffers, &g_game); + g_lua.bindSingletonFunction("g_game", "openStore", &Game::openStore, &g_game); + g_lua.bindSingletonFunction("g_game", "transferCoins", &Game::transferCoins, &g_game); + g_lua.bindSingletonFunction("g_game", "openTransactionHistory", &Game::openTransactionHistory, &g_game); + g_lua.bindSingletonFunction("g_game", "preyAction", &Game::preyAction, &g_game); + g_lua.bindSingletonFunction("g_game", "preyRequest", &Game::preyRequest, &g_game); + g_lua.bindSingletonFunction("g_game", "applyImbuement", &Game::applyImbuement, &g_game); + g_lua.bindSingletonFunction("g_game", "clearImbuement", &Game::clearImbuement, &g_game); + g_lua.bindSingletonFunction("g_game", "closeImbuingWindow", &Game::closeImbuingWindow, &g_game); + g_lua.bindSingletonFunction("g_game", "setTibiaCoins", &Game::setTibiaCoins, &g_game); + g_lua.bindSingletonFunction("g_game", "getTibiaCoins", &Game::getTibiaCoins, &g_game); + g_lua.bindSingletonFunction("g_game", "getTransferableTibiaCoins", &Game::getTransferableTibiaCoins, &g_game); + + g_lua.bindSingletonFunction("g_game", "getMaxPreWalkingSteps", &Game::getMaxPreWalkingSteps, &g_game); + g_lua.bindSingletonFunction("g_game", "setMaxPreWalkingSteps", &Game::setMaxPreWalkingSteps, &g_game); + g_lua.bindSingletonFunction("g_game", "ignoreServerDirection", &Game::ignoreServerDirection, &g_game); + g_lua.bindSingletonFunction("g_game", "showRealDirection", &Game::showRealDirection, &g_game); + g_lua.bindSingletonFunction("g_game", "enableTileThingLuaCallback", &Game::enableTileThingLuaCallback, &g_game); + g_lua.bindSingletonFunction("g_game", "isTileThingLuaCallbackEnabled", &Game::isTileThingLuaCallbackEnabled, &g_game); + g_lua.bindSingletonFunction("g_game", "getRecivedPacketsCount", &Game::getRecivedPacketsCount, &g_game); + g_lua.bindSingletonFunction("g_game", "getRecivedPacketsSize", &Game::getRecivedPacketsSize, &g_game); + + /* g_lua.registerSingletonClass("g_shaders"); + g_lua.bindSingletonFunction("g_shaders", "createShader", &ShaderManager::createShader, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "createFragmentShader", &ShaderManager::createFragmentShader, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "createFragmentShaderFromCode", &ShaderManager::createFragmentShaderFromCode, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "createItemShader", &ShaderManager::createItemShader, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "createMapShader", &ShaderManager::createMapShader, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "getDefaultItemShader", &ShaderManager::getDefaultItemShader, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "getDefaultMapShader", &ShaderManager::getDefaultMapShader, &g_shaders); + g_lua.bindSingletonFunction("g_shaders", "getShader", &ShaderManager::getShader, &g_shaders); */ + + g_lua.bindGlobalFunction("getOutfitColor", Outfit::getColor); + g_lua.bindGlobalFunction("getAngleFromPos", Position::getAngleFromPositions); + g_lua.bindGlobalFunction("getDirectionFromPos", Position::getDirectionFromPositions); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return ProtocolGamePtr(new ProtocolGame); }); + g_lua.bindClassMemberFunction("login", &ProtocolGame::login); + g_lua.bindClassMemberFunction("sendExtendedOpcode", &ProtocolGame::sendExtendedOpcode); + g_lua.bindClassMemberFunction("addPosition", &ProtocolGame::addPosition); + g_lua.bindClassMemberFunction("setMapDescription", &ProtocolGame::setMapDescription); + g_lua.bindClassMemberFunction("setFloorDescription", &ProtocolGame::setFloorDescription); + g_lua.bindClassMemberFunction("setTileDescription", &ProtocolGame::setTileDescription); + g_lua.bindClassMemberFunction("getOutfit", &ProtocolGame::getOutfit); + g_lua.bindClassMemberFunction("getThing", &ProtocolGame::getThing); + g_lua.bindClassMemberFunction("getCreature", &ProtocolGame::getCreature); + g_lua.bindClassMemberFunction("getItem", &ProtocolGame::getItem); + g_lua.bindClassMemberFunction("getPosition", &ProtocolGame::getPosition); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getItem", &Container::getItem); + g_lua.bindClassMemberFunction("getItems", &Container::getItems); + g_lua.bindClassMemberFunction("getItemsCount", &Container::getItemsCount); + g_lua.bindClassMemberFunction("getSlotPosition", &Container::getSlotPosition); + g_lua.bindClassMemberFunction("getName", &Container::getName); + g_lua.bindClassMemberFunction("getId", &Container::getId); + g_lua.bindClassMemberFunction("getCapacity", &Container::getCapacity); + g_lua.bindClassMemberFunction("getContainerItem", &Container::getContainerItem); + g_lua.bindClassMemberFunction("hasParent", &Container::hasParent); + g_lua.bindClassMemberFunction("isClosed", &Container::isClosed); + g_lua.bindClassMemberFunction("isUnlocked", &Container::isUnlocked); + g_lua.bindClassMemberFunction("hasPages", &Container::hasPages); + g_lua.bindClassMemberFunction("getSize", &Container::getSize); + g_lua.bindClassMemberFunction("getFirstIndex", &Container::getFirstIndex); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("setId", &Thing::setId); + g_lua.bindClassMemberFunction("setPosition", &Thing::setPosition); + g_lua.bindClassMemberFunction("getId", &Thing::getId); + g_lua.bindClassMemberFunction("getPosition", &Thing::getPosition); + g_lua.bindClassMemberFunction("getStackPriority", &Thing::getStackPriority); + g_lua.bindClassMemberFunction("getStackPos", &Thing::getStackPos); + g_lua.bindClassMemberFunction("getAnimationPhases", &Thing::getAnimationPhases); + g_lua.bindClassMemberFunction("getTile", &Thing::getTile); + g_lua.bindClassMemberFunction("setMarked", &Thing::setMarked); + g_lua.bindClassMemberFunction("isItem", &Thing::isItem); + g_lua.bindClassMemberFunction("isMonster", &Thing::isMonster); + g_lua.bindClassMemberFunction("isNpc", &Thing::isNpc); + g_lua.bindClassMemberFunction("isCreature", &Thing::isCreature); + g_lua.bindClassMemberFunction("isEffect", &Thing::isEffect); + g_lua.bindClassMemberFunction("isMissile", &Thing::isMissile); + g_lua.bindClassMemberFunction("isPlayer", &Thing::isPlayer); + g_lua.bindClassMemberFunction("isLocalPlayer", &Thing::isLocalPlayer); + g_lua.bindClassMemberFunction("isAnimatedText", &Thing::isAnimatedText); + g_lua.bindClassMemberFunction("isStaticText", &Thing::isStaticText); + g_lua.bindClassMemberFunction("isGround", &Thing::isGround); + g_lua.bindClassMemberFunction("isGroundBorder", &Thing::isGroundBorder); + g_lua.bindClassMemberFunction("isOnBottom", &Thing::isOnBottom); + g_lua.bindClassMemberFunction("isOnTop", &Thing::isOnTop); + g_lua.bindClassMemberFunction("isContainer", &Thing::isContainer); + g_lua.bindClassMemberFunction("isForceUse", &Thing::isForceUse); + g_lua.bindClassMemberFunction("isMultiUse", &Thing::isMultiUse); + g_lua.bindClassMemberFunction("isRotateable", &Thing::isRotateable); + g_lua.bindClassMemberFunction("isNotMoveable", &Thing::isNotMoveable); + g_lua.bindClassMemberFunction("isPickupable", &Thing::isPickupable); + g_lua.bindClassMemberFunction("isIgnoreLook", &Thing::isIgnoreLook); + g_lua.bindClassMemberFunction("isStackable", &Thing::isStackable); + g_lua.bindClassMemberFunction("isHookSouth", &Thing::isHookSouth); + g_lua.bindClassMemberFunction("isTranslucent", &Thing::isTranslucent); + g_lua.bindClassMemberFunction("isFullGround", &Thing::isFullGround); + g_lua.bindClassMemberFunction("isMarketable", &Thing::isMarketable); + g_lua.bindClassMemberFunction("getMarketData", &Thing::getMarketData); + g_lua.bindClassMemberFunction("isUsable", &Thing::isUsable); + g_lua.bindClassMemberFunction("isWrapable", &Thing::isWrapable); + g_lua.bindClassMemberFunction("isUnwrapable", &Thing::isUnwrapable); + g_lua.bindClassMemberFunction("isTopEffect", &Thing::isTopEffect); + g_lua.bindClassMemberFunction("isLyingCorpse", &Thing::isLyingCorpse); + g_lua.bindClassMemberFunction("getParentContainer", &Thing::getParentContainer); + g_lua.bindClassMemberFunction("hide", &Thing::hide); + g_lua.bindClassMemberFunction("show", &Thing::show); + g_lua.bindClassMemberFunction("setHidden", &Thing::setHidden); + g_lua.bindClassMemberFunction("isHidden", &Thing::isHidden); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return HousePtr(new House); }); + g_lua.bindClassMemberFunction("setId", &House::setId); + g_lua.bindClassMemberFunction("getId", &House::getId); + g_lua.bindClassMemberFunction("setName", &House::setName); + g_lua.bindClassMemberFunction("getName", &House::getName); + g_lua.bindClassMemberFunction("setTownId", &House::setTownId); + g_lua.bindClassMemberFunction("getTownId", &House::getTownId); + g_lua.bindClassMemberFunction("setTile", &House::setTile); + g_lua.bindClassMemberFunction("getTile", &House::getTile); + g_lua.bindClassMemberFunction("setEntry", &House::setEntry); + g_lua.bindClassMemberFunction("getEntry", &House::getEntry); + g_lua.bindClassMemberFunction("addDoor", &House::addDoor); + g_lua.bindClassMemberFunction("removeDoor", &House::removeDoor); + g_lua.bindClassMemberFunction("removeDoorById", &House::removeDoorById); + g_lua.bindClassMemberFunction("setSize", &House::setSize); + g_lua.bindClassMemberFunction("getSize", &House::getSize); + g_lua.bindClassMemberFunction("setRent", &House::setRent); + g_lua.bindClassMemberFunction("getRent", &House::getRent); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return SpawnPtr(new Spawn); }); + g_lua.bindClassMemberFunction("setRadius", &Spawn::setRadius); + g_lua.bindClassMemberFunction("getRadius", &Spawn::getRadius); + g_lua.bindClassMemberFunction("setCenterPos", &Spawn::setCenterPos); + g_lua.bindClassMemberFunction("getCenterPos", &Spawn::getCenterPos); + g_lua.bindClassMemberFunction("addCreature", &Spawn::addCreature); + g_lua.bindClassMemberFunction("removeCreature", &Spawn::removeCreature); + g_lua.bindClassMemberFunction("getCreatures", &Spawn::getCreatures); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return TownPtr(new Town); }); + g_lua.bindClassMemberFunction("setId", &Town::setId); + g_lua.bindClassMemberFunction("setName", &Town::setName); + g_lua.bindClassMemberFunction("setPos", &Town::setPos); + g_lua.bindClassMemberFunction("setTemplePos", &Town::setPos); // alternative method + g_lua.bindClassMemberFunction("getId", &Town::getId); + g_lua.bindClassMemberFunction("getName", &Town::getName); + g_lua.bindClassMemberFunction("getPos", &Town::getPos); + g_lua.bindClassMemberFunction("getTemplePos", &Town::getPos); // alternative method + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return CreatureTypePtr(new CreatureType); }); + g_lua.bindClassMemberFunction("setName", &CreatureType::setName); + g_lua.bindClassMemberFunction("setOutfit", &CreatureType::setOutfit); + g_lua.bindClassMemberFunction("setSpawnTime", &CreatureType::setSpawnTime); + g_lua.bindClassMemberFunction("getName", &CreatureType::getName); + g_lua.bindClassMemberFunction("getOutfit", &CreatureType::getOutfit); + g_lua.bindClassMemberFunction("getSpawnTime", &CreatureType::getSpawnTime); + g_lua.bindClassMemberFunction("cast", &CreatureType::cast); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return CreaturePtr(new Creature); }); + g_lua.bindClassMemberFunction("getId", &Creature::getId); + g_lua.bindClassMemberFunction("getName", &Creature::getName); + g_lua.bindClassMemberFunction("setManaPercent", &LocalPlayer::setManaPercent); + g_lua.bindClassMemberFunction("getManaPercent", &LocalPlayer::getManaPercent); + g_lua.bindClassMemberFunction("getHealthPercent", &Creature::getHealthPercent); + g_lua.bindClassMemberFunction("getSpeed", &Creature::getSpeed); + g_lua.bindClassMemberFunction("setSpeed", &Creature::setSpeed); + g_lua.bindClassMemberFunction("getBaseSpeed", &Creature::getBaseSpeed); + g_lua.bindClassMemberFunction("setBaseSpeed", &Creature::setBaseSpeed); + g_lua.bindClassMemberFunction("getSkull", &Creature::getSkull); + g_lua.bindClassMemberFunction("getShield", &Creature::getShield); + g_lua.bindClassMemberFunction("getEmblem", &Creature::getEmblem); + g_lua.bindClassMemberFunction("setSkull", &Creature::setSkull); + g_lua.bindClassMemberFunction("setShield", &Creature::setShield); + g_lua.bindClassMemberFunction("setEmblem", &Creature::setEmblem); + g_lua.bindClassMemberFunction("getType", &Creature::getType); + g_lua.bindClassMemberFunction("getIcon", &Creature::getIcon); + g_lua.bindClassMemberFunction("setOutfit", &Creature::setOutfit); + g_lua.bindClassMemberFunction("getOutfit", &Creature::getOutfit); + g_lua.bindClassMemberFunction("setOutfitColor", &Creature::setOutfitColor); + g_lua.bindClassMemberFunction("getDirection", &Creature::getDirection); + g_lua.bindClassMemberFunction("getWalkDirection", &Creature::getWalkDirection); + g_lua.bindClassMemberFunction("getStepDuration", &Creature::getStepDuration); + g_lua.bindClassMemberFunction("getStepProgress", &Creature::getStepProgress); + g_lua.bindClassMemberFunction("getWalkTicksElapsed", &Creature::getWalkTicksElapsed); + g_lua.bindClassMemberFunction("getStepTicksLeft", &Creature::getStepTicksLeft); + g_lua.bindClassMemberFunction("setDirection", &Creature::setDirection); + g_lua.bindClassMemberFunction("setSkullTexture", &Creature::setSkullTexture); + g_lua.bindClassMemberFunction("setShieldTexture", &Creature::setShieldTexture); + g_lua.bindClassMemberFunction("setEmblemTexture", &Creature::setEmblemTexture); + g_lua.bindClassMemberFunction("setTypeTexture", &Creature::setTypeTexture); + g_lua.bindClassMemberFunction("setIconTexture", &Creature::setIconTexture); + g_lua.bindClassMemberFunction("showStaticSquare", &Creature::showStaticSquare); + g_lua.bindClassMemberFunction("hideStaticSquare", &Creature::hideStaticSquare); + g_lua.bindClassMemberFunction("isWalking", &Creature::isWalking); + g_lua.bindClassMemberFunction("isInvisible", &Creature::isInvisible); + g_lua.bindClassMemberFunction("isDead", &Creature::isDead); + g_lua.bindClassMemberFunction("isRemoved", &Creature::isRemoved); + g_lua.bindClassMemberFunction("canBeSeen", &Creature::canBeSeen); + g_lua.bindClassMemberFunction("jump", &Creature::jump); + g_lua.bindClassMemberFunction("getPrewalkingPosition", &Creature::getPrewalkingPosition); + g_lua.bindClassMemberFunction("setInformationColor", &Creature::setInformationColor); + g_lua.bindClassMemberFunction("resetInformationColor", &Creature::resetInformationColor); + g_lua.bindClassMemberFunction("setInformationOffset", &Creature::setInformationOffset); + g_lua.bindClassMemberFunction("getInformationOffset", &Creature::getInformationOffset); + g_lua.bindClassMemberFunction("setText", &Creature::setText); + g_lua.bindClassMemberFunction("getText", &Creature::getText); + g_lua.bindClassMemberFunction("clearText", &Creature::clearText); + + // widgets + g_lua.bindClassMemberFunction("addTopWidget", &Creature::addTopWidget); + g_lua.bindClassMemberFunction("addBottomWidget", &Creature::addBottomWidget); + g_lua.bindClassMemberFunction("addDirectionalWidget", &Creature::addDirectionalWidget); + g_lua.bindClassMemberFunction("removeTopWidget", &Creature::removeTopWidget); + g_lua.bindClassMemberFunction("removeBottomWidget", &Creature::removeBottomWidget); + g_lua.bindClassMemberFunction("removeDirectionalWidget", &Creature::removeDirectionalWidget); + g_lua.bindClassMemberFunction("getTopWidgets", &Creature::getTopWidgets); + g_lua.bindClassMemberFunction("getBottomWidgets", &Creature::getBottomWidgets); + g_lua.bindClassMemberFunction("getDirectionalWdigets", &Creature::getDirectionalWdigets); + g_lua.bindClassMemberFunction("clearWidgets", &Creature::clearWidgets); + g_lua.bindClassMemberFunction("clearTopWidgets", &Creature::clearTopWidgets); + g_lua.bindClassMemberFunction("clearBottomWidgets", &Creature::clearBottomWidgets); + g_lua.bindClassMemberFunction("clearDirectionalWidgets", &Creature::clearDirectionalWidgets); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getServerId", &ItemType::getServerId); + g_lua.bindClassMemberFunction("getClientId", &ItemType::getClientId); + g_lua.bindClassMemberFunction("isWritable", &ItemType::isWritable); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return ThingTypePtr(new ThingType); }); + g_lua.bindClassMemberFunction("getId", &ThingType::getId); + g_lua.bindClassMemberFunction("getClothSlot", &ThingType::getClothSlot); + g_lua.bindClassMemberFunction("getCategory", &ThingType::getCategory); + g_lua.bindClassMemberFunction("getSize", &ThingType::getSize); + g_lua.bindClassMemberFunction("getWidth", &ThingType::getWidth); + g_lua.bindClassMemberFunction("getHeight", &ThingType::getHeight); + g_lua.bindClassMemberFunction("getDisplacement", &ThingType::getDisplacement); + g_lua.bindClassMemberFunction("getDisplacementX", &ThingType::getDisplacementX); + g_lua.bindClassMemberFunction("getDisplacementY", &ThingType::getDisplacementY); + g_lua.bindClassMemberFunction("getExactSize", &ThingType::getExactSize); + g_lua.bindClassMemberFunction("getRealSize", &ThingType::getRealSize); + g_lua.bindClassMemberFunction("getLayers", &ThingType::getLayers); + g_lua.bindClassMemberFunction("getNumPatternX", &ThingType::getNumPatternX); + g_lua.bindClassMemberFunction("getNumPatternY", &ThingType::getNumPatternY); + g_lua.bindClassMemberFunction("getNumPatternZ", &ThingType::getNumPatternZ); + g_lua.bindClassMemberFunction("getAnimationPhases", &ThingType::getAnimationPhases); + g_lua.bindClassMemberFunction("getGroundSpeed", &ThingType::getGroundSpeed); + g_lua.bindClassMemberFunction("getMaxTextLength", &ThingType::getMaxTextLength); + g_lua.bindClassMemberFunction("getLight", &ThingType::getLight); + g_lua.bindClassMemberFunction("getMinimapColor", &ThingType::getMinimapColor); + g_lua.bindClassMemberFunction("getLensHelp", &ThingType::getLensHelp); + g_lua.bindClassMemberFunction("getClothSlot", &ThingType::getClothSlot); + g_lua.bindClassMemberFunction("getElevation", &ThingType::getElevation); + g_lua.bindClassMemberFunction("isGround", &ThingType::isGround); + g_lua.bindClassMemberFunction("isGroundBorder", &ThingType::isGroundBorder); + g_lua.bindClassMemberFunction("isOnBottom", &ThingType::isOnBottom); + g_lua.bindClassMemberFunction("isOnTop", &ThingType::isOnTop); + g_lua.bindClassMemberFunction("isContainer", &ThingType::isContainer); + g_lua.bindClassMemberFunction("isStackable", &ThingType::isStackable); + g_lua.bindClassMemberFunction("isForceUse", &ThingType::isForceUse); + g_lua.bindClassMemberFunction("isMultiUse", &ThingType::isMultiUse); + g_lua.bindClassMemberFunction("isWritable", &ThingType::isWritable); + g_lua.bindClassMemberFunction("isChargeable", &ThingType::isChargeable); + g_lua.bindClassMemberFunction("isWritableOnce", &ThingType::isWritableOnce); + g_lua.bindClassMemberFunction("isFluidContainer", &ThingType::isFluidContainer); + g_lua.bindClassMemberFunction("isSplash", &ThingType::isSplash); + g_lua.bindClassMemberFunction("isNotWalkable", &ThingType::isNotWalkable); + g_lua.bindClassMemberFunction("isNotMoveable", &ThingType::isNotMoveable); + g_lua.bindClassMemberFunction("blockProjectile", &ThingType::blockProjectile); + g_lua.bindClassMemberFunction("isNotPathable", &ThingType::isNotPathable); + g_lua.bindClassMemberFunction("setPathable", &ThingType::setPathable); + g_lua.bindClassMemberFunction("isPickupable", &ThingType::isPickupable); + g_lua.bindClassMemberFunction("isHangable", &ThingType::isHangable); + g_lua.bindClassMemberFunction("isHookSouth", &ThingType::isHookSouth); + g_lua.bindClassMemberFunction("isHookEast", &ThingType::isHookEast); + g_lua.bindClassMemberFunction("isRotateable", &ThingType::isRotateable); + g_lua.bindClassMemberFunction("hasLight", &ThingType::hasLight); + g_lua.bindClassMemberFunction("isDontHide", &ThingType::isDontHide); + g_lua.bindClassMemberFunction("isTranslucent", &ThingType::isTranslucent); + g_lua.bindClassMemberFunction("hasDisplacement", &ThingType::hasDisplacement); + g_lua.bindClassMemberFunction("hasElevation", &ThingType::hasElevation); + g_lua.bindClassMemberFunction("isLyingCorpse", &ThingType::isLyingCorpse); + g_lua.bindClassMemberFunction("isAnimateAlways", &ThingType::isAnimateAlways); + g_lua.bindClassMemberFunction("hasMiniMapColor", &ThingType::hasMiniMapColor); + g_lua.bindClassMemberFunction("hasLensHelp", &ThingType::hasLensHelp); + g_lua.bindClassMemberFunction("isFullGround", &ThingType::isFullGround); + g_lua.bindClassMemberFunction("isIgnoreLook", &ThingType::isIgnoreLook); + g_lua.bindClassMemberFunction("isCloth", &ThingType::isCloth); + g_lua.bindClassMemberFunction("isMarketable", &ThingType::isMarketable); + g_lua.bindClassMemberFunction("getMarketData", &ThingType::getMarketData); + g_lua.bindClassMemberFunction("isUsable", &ThingType::isUsable); + g_lua.bindClassMemberFunction("isWrapable", &ThingType::isWrapable); + g_lua.bindClassMemberFunction("isUnwrapable", &ThingType::isUnwrapable); + g_lua.bindClassMemberFunction("isTopEffect", &ThingType::isTopEffect); + g_lua.bindClassMemberFunction("getSprites", &ThingType::getSprites); + g_lua.bindClassMemberFunction("hasAttribute", &ThingType::hasAttr); + g_lua.bindClassMemberFunction("exportImage", &ThingType::exportImage); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", &Item::create); + g_lua.bindClassStaticFunction("createOtb", &Item::createFromOtb); + g_lua.bindClassMemberFunction("clone", &Item::clone); + g_lua.bindClassMemberFunction("getContainerItems", &Item::getContainerItems); + g_lua.bindClassMemberFunction("getContainerItem", &Item::getContainerItem); + g_lua.bindClassMemberFunction("addContainerItem", &Item::addContainerItem); + g_lua.bindClassMemberFunction("addContainerItemIndexed", &Item::addContainerItemIndexed); + g_lua.bindClassMemberFunction("removeContainerItem", &Item::removeContainerItem); + g_lua.bindClassMemberFunction("clearContainerItems", &Item::clearContainerItems); + g_lua.bindClassMemberFunction("getContainerItem", &Item::getContainerItem); + g_lua.bindClassMemberFunction("setCount", &Item::setCount); + g_lua.bindClassMemberFunction("getCount", &Item::getCount); + g_lua.bindClassMemberFunction("getSubType", &Item::getSubType); + g_lua.bindClassMemberFunction("getCountOrSubType", &Item::getCountOrSubType); + g_lua.bindClassMemberFunction("getId", &Item::getId); + g_lua.bindClassMemberFunction("getServerId", &Item::getServerId); + g_lua.bindClassMemberFunction("getName", &Item::getName); + g_lua.bindClassMemberFunction("getDescription", &Item::getDescription); + g_lua.bindClassMemberFunction("getText", &Item::getText); + g_lua.bindClassMemberFunction("setDescription", &Item::setDescription); + g_lua.bindClassMemberFunction("setText", &Item::setText); + g_lua.bindClassMemberFunction("getUniqueId", &Item::getUniqueId); + g_lua.bindClassMemberFunction("getActionId", &Item::getActionId); + g_lua.bindClassMemberFunction("setUniqueId", &Item::setUniqueId); + g_lua.bindClassMemberFunction("setActionId", &Item::setActionId); + g_lua.bindClassMemberFunction("getTeleportDestination", &Item::getTeleportDestination); + g_lua.bindClassMemberFunction("setTeleportDestination", &Item::setTeleportDestination); + g_lua.bindClassMemberFunction("isStackable", &Item::isStackable); + g_lua.bindClassMemberFunction("isMarketable", &Item::isMarketable); + g_lua.bindClassMemberFunction("isFluidContainer", &Item::isFluidContainer); + g_lua.bindClassMemberFunction("getMarketData", &Item::getMarketData); + g_lua.bindClassMemberFunction("getClothSlot", &Item::getClothSlot); + g_lua.bindClassMemberFunction("getTooltip", &Item::getTooltip); + g_lua.bindClassMemberFunction("setTooltip", &Item::setTooltip); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return EffectPtr(new Effect); }); + g_lua.bindClassMemberFunction("setId", &Effect::setId); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return MissilePtr(new Missile); }); + g_lua.bindClassMemberFunction("setId", &Missile::setId); + g_lua.bindClassMemberFunction("getId", &Missile::getId); + g_lua.bindClassMemberFunction("getSource", &Missile::getSource); + g_lua.bindClassMemberFunction("getDestination", &Missile::getDestination); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return StaticTextPtr(new StaticText); }); + g_lua.bindClassMemberFunction("addMessage", &StaticText::addMessage); + g_lua.bindClassMemberFunction("addColoredMessage", &StaticText::addColoredMessage); + g_lua.bindClassMemberFunction("setText", &StaticText::setText); + g_lua.bindClassMemberFunction("setFont", &StaticText::setFont); + g_lua.bindClassMemberFunction("setColor", &StaticText::setColor); + g_lua.bindClassMemberFunction("getColor", &StaticText::getColor); + g_lua.bindClassMemberFunction("getText", &StaticText::getText); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getText", &AnimatedText::getText); + g_lua.bindClassMemberFunction("getOffset", &AnimatedText::getOffset); + g_lua.bindClassMemberFunction("getColor", &AnimatedText::getColor); + + g_lua.registerClass(); + g_lua.registerClass(); + g_lua.registerClass(); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("unlockWalk", &LocalPlayer::unlockWalk); + g_lua.bindClassMemberFunction("lockWalk", &LocalPlayer::lockWalk); + g_lua.bindClassMemberFunction("isWalkLocked", &LocalPlayer::isWalkLocked); + g_lua.bindClassMemberFunction("canWalk", &LocalPlayer::canWalk); + g_lua.bindClassMemberFunction("setStates", &LocalPlayer::setStates); + g_lua.bindClassMemberFunction("setSkill", &LocalPlayer::setSkill); + g_lua.bindClassMemberFunction("setHealth", &LocalPlayer::setHealth); + g_lua.bindClassMemberFunction("setTotalCapacity", &LocalPlayer::setTotalCapacity); + g_lua.bindClassMemberFunction("setFreeCapacity", &LocalPlayer::setFreeCapacity); + g_lua.bindClassMemberFunction("setExperience", &LocalPlayer::setExperience); + g_lua.bindClassMemberFunction("setLevel", &LocalPlayer::setLevel); + g_lua.bindClassMemberFunction("setMana", &LocalPlayer::setMana); + g_lua.bindClassMemberFunction("setMagicLevel", &LocalPlayer::setMagicLevel); + g_lua.bindClassMemberFunction("setSoul", &LocalPlayer::setSoul); + g_lua.bindClassMemberFunction("setStamina", &LocalPlayer::setStamina); + g_lua.bindClassMemberFunction("setKnown", &LocalPlayer::setKnown); + g_lua.bindClassMemberFunction("setInventoryItem", &LocalPlayer::setInventoryItem); + g_lua.bindClassMemberFunction("getStates", &LocalPlayer::getStates); + g_lua.bindClassMemberFunction("getSkillLevel", &LocalPlayer::getSkillLevel); + g_lua.bindClassMemberFunction("getSkillBaseLevel", &LocalPlayer::getSkillBaseLevel); + g_lua.bindClassMemberFunction("getSkillLevelPercent", &LocalPlayer::getSkillLevelPercent); + g_lua.bindClassMemberFunction("getHealth", &LocalPlayer::getHealth); + g_lua.bindClassMemberFunction("getMaxHealth", &LocalPlayer::getMaxHealth); + g_lua.bindClassMemberFunction("getFreeCapacity", &LocalPlayer::getFreeCapacity); + g_lua.bindClassMemberFunction("getExperience", &LocalPlayer::getExperience); + g_lua.bindClassMemberFunction("getLevel", &LocalPlayer::getLevel); + g_lua.bindClassMemberFunction("getLevelPercent", &LocalPlayer::getLevelPercent); + g_lua.bindClassMemberFunction("getMana", &LocalPlayer::getMana); + g_lua.bindClassMemberFunction("getMaxMana", &LocalPlayer::getMaxMana); + g_lua.bindClassMemberFunction("getMagicLevel", &LocalPlayer::getMagicLevel); + g_lua.bindClassMemberFunction("getMagicLevelPercent", &LocalPlayer::getMagicLevelPercent); + g_lua.bindClassMemberFunction("getSoul", &LocalPlayer::getSoul); + g_lua.bindClassMemberFunction("getStamina", &LocalPlayer::getStamina); + g_lua.bindClassMemberFunction("getOfflineTrainingTime", &LocalPlayer::getOfflineTrainingTime); + g_lua.bindClassMemberFunction("getRegenerationTime", &LocalPlayer::getRegenerationTime); + g_lua.bindClassMemberFunction("getBaseMagicLevel", &LocalPlayer::getBaseMagicLevel); + g_lua.bindClassMemberFunction("getTotalCapacity", &LocalPlayer::getTotalCapacity); + g_lua.bindClassMemberFunction("getInventoryItem", &LocalPlayer::getInventoryItem); + g_lua.bindClassMemberFunction("getVocation", &LocalPlayer::getVocation); + g_lua.bindClassMemberFunction("getBlessings", &LocalPlayer::getBlessings); + g_lua.bindClassMemberFunction("isPremium", &LocalPlayer::isPremium); + g_lua.bindClassMemberFunction("isKnown", &LocalPlayer::isKnown); + g_lua.bindClassMemberFunction("isPreWalking", &LocalPlayer::isPreWalking); + g_lua.bindClassMemberFunction("hasSight", &LocalPlayer::hasSight); + g_lua.bindClassMemberFunction("isAutoWalking", &LocalPlayer::isAutoWalking); + g_lua.bindClassMemberFunction("isServerWalking", &LocalPlayer::isServerWalking); + g_lua.bindClassMemberFunction("stopAutoWalk", &LocalPlayer::stopAutoWalk); + g_lua.bindClassMemberFunction("autoWalk", &LocalPlayer::autoWalk); + g_lua.bindClassMemberFunction("preWalk", &LocalPlayer::preWalk); + g_lua.bindClassMemberFunction("dumpWalkMatrix", &LocalPlayer::dumpWalkMatrix); + g_lua.bindClassMemberFunction("startServerWalking", &LocalPlayer::startServerWalking); + g_lua.bindClassMemberFunction("finishServerWalking", &LocalPlayer::finishServerWalking); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("clean", &Tile::clean); + g_lua.bindClassMemberFunction("addThing", &Tile::addThing); + g_lua.bindClassMemberFunction("getThing", &Tile::getThing); + g_lua.bindClassMemberFunction("getThings", &Tile::getThings); + g_lua.bindClassMemberFunction("getEffect", &Tile::getEffect); + g_lua.bindClassMemberFunction("getEffects", &Tile::getEffects); + g_lua.bindClassMemberFunction("getItems", &Tile::getItems); + g_lua.bindClassMemberFunction("getThingStackPos", &Tile::getThingStackPos); + g_lua.bindClassMemberFunction("getThingCount", &Tile::getThingCount); + g_lua.bindClassMemberFunction("getTopThing", &Tile::getTopThing); + g_lua.bindClassMemberFunction("removeThing", &Tile::removeThing); + g_lua.bindClassMemberFunction("getTopLookThing", &Tile::getTopLookThing); + g_lua.bindClassMemberFunction("getTopLookThingEx", &Tile::getTopLookThingEx); + g_lua.bindClassMemberFunction("getTopUseThing", &Tile::getTopUseThing); + g_lua.bindClassMemberFunction("getTopCreature", &Tile::getTopCreature); + g_lua.bindClassMemberFunction("getTopCreatureEx", &Tile::getTopCreatureEx); + g_lua.bindClassMemberFunction("getTopMoveThing", &Tile::getTopMoveThing); + g_lua.bindClassMemberFunction("getTopMultiUseThing", &Tile::getTopMultiUseThing); + g_lua.bindClassMemberFunction("getTopMultiUseThingEx", &Tile::getTopMultiUseThingEx); + g_lua.bindClassMemberFunction("getPosition", &Tile::getPosition); + g_lua.bindClassMemberFunction("getDrawElevation", &Tile::getDrawElevation); + g_lua.bindClassMemberFunction("getCreatures", &Tile::getCreatures); + g_lua.bindClassMemberFunction("getGround", &Tile::getGround); + g_lua.bindClassMemberFunction("isWalkable", &Tile::isWalkable); + g_lua.bindClassMemberFunction("isHouseTile", &Tile::isHouseTile); + g_lua.bindClassMemberFunction("isFullGround", &Tile::isFullGround); + g_lua.bindClassMemberFunction("isFullyOpaque", &Tile::isFullyOpaque); + g_lua.bindClassMemberFunction("isLookPossible", &Tile::isLookPossible); + g_lua.bindClassMemberFunction("hasCreature", &Tile::hasCreature); + g_lua.bindClassMemberFunction("hasBlockingCreature", &Tile::hasBlockingCreature); + g_lua.bindClassMemberFunction("isEmpty", &Tile::isEmpty); + g_lua.bindClassMemberFunction("isClickable", &Tile::isClickable); + g_lua.bindClassMemberFunction("isPathable", &Tile::isPathable); + g_lua.bindClassMemberFunction("overwriteMinimapColor", &Tile::overwriteMinimapColor); + g_lua.bindClassMemberFunction("select", &Tile::select); + g_lua.bindClassMemberFunction("unselect", &Tile::unselect); + g_lua.bindClassMemberFunction("isSelected", &Tile::isSelected); + g_lua.bindClassMemberFunction("remFlag", &Tile::remFlag); + g_lua.bindClassMemberFunction("setFlag", &Tile::setFlag); + g_lua.bindClassMemberFunction("setFlags", &Tile::setFlags); + g_lua.bindClassMemberFunction("getFlags", &Tile::getFlags); + g_lua.bindClassMemberFunction("hasFlag", &Tile::hasFlag); + g_lua.bindClassMemberFunction("getElevation", &Tile::getElevation); + g_lua.bindClassMemberFunction("hasElevation", &Tile::hasElevation); + g_lua.bindClassMemberFunction("isBlocking", &Tile::isBlocking); + // for bot + g_lua.bindClassMemberFunction("setText", &Tile::setText); + g_lua.bindClassMemberFunction("getText", &Tile::getText); + g_lua.bindClassMemberFunction("setTimer", &Tile::setTimer); + g_lua.bindClassMemberFunction("getTimer", &Tile::getTimer); + g_lua.bindClassMemberFunction("setFill", &Tile::setFill); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UIItemPtr(new UIItem); }); + g_lua.bindClassMemberFunction("setItemId", &UIItem::setItemId); + g_lua.bindClassMemberFunction("setItemCount", &UIItem::setItemCount); + g_lua.bindClassMemberFunction("setItemSubType", &UIItem::setItemSubType); + g_lua.bindClassMemberFunction("setItemVisible", &UIItem::setItemVisible); + g_lua.bindClassMemberFunction("setItem", &UIItem::setItem); + g_lua.bindClassMemberFunction("setVirtual", &UIItem::setVirtual); + g_lua.bindClassMemberFunction("setShowCount", &UIItem::setShowCount); + g_lua.bindClassMemberFunction("clearItem", &UIItem::clearItem); + g_lua.bindClassMemberFunction("getItemId", &UIItem::getItemId); + g_lua.bindClassMemberFunction("getItemCount", &UIItem::getItemCount); + g_lua.bindClassMemberFunction("getItemSubType", &UIItem::getItemSubType); + g_lua.bindClassMemberFunction("getItemCountOrSubType", &UIItem::getItemCountOrSubType); + g_lua.bindClassMemberFunction("getItem", &UIItem::getItem); + g_lua.bindClassMemberFunction("isVirtual", &UIItem::isVirtual); + g_lua.bindClassMemberFunction("isItemVisible", &UIItem::isItemVisible); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UISpritePtr(new UISprite); }); + g_lua.bindClassMemberFunction("setSpriteId", &UISprite::setSpriteId); + g_lua.bindClassMemberFunction("clearSprite", &UISprite::clearSprite); + g_lua.bindClassMemberFunction("getSpriteId", &UISprite::getSpriteId); + g_lua.bindClassMemberFunction("setSpriteColor", &UISprite::setSpriteColor); + g_lua.bindClassMemberFunction("hasSprite", &UISprite::hasSprite); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UICreaturePtr(new UICreature); } ); + g_lua.bindClassMemberFunction("setCreature", &UICreature::setCreature); + g_lua.bindClassMemberFunction("setOutfit", &UICreature::setOutfit); + g_lua.bindClassMemberFunction("setFixedCreatureSize", &UICreature::setFixedCreatureSize); + g_lua.bindClassMemberFunction("getCreature", &UICreature::getCreature); + g_lua.bindClassMemberFunction("isFixedCreatureSize", &UICreature::isFixedCreatureSize); + g_lua.bindClassMemberFunction("setAutoRotating", &UICreature::setAutoRotating); + g_lua.bindClassMemberFunction("setDirection", &UICreature::setDirection); + g_lua.bindClassMemberFunction("setScale", &UICreature::setScale); + g_lua.bindClassMemberFunction("getScale", &UICreature::getScale); + g_lua.bindClassMemberFunction("setOptimized", &UICreature::setOptimized); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UIMapPtr(new UIMap); }); + g_lua.bindClassMemberFunction("drawSelf", &UIMap::drawSelf); + g_lua.bindClassMemberFunction("movePixels", &UIMap::movePixels); + g_lua.bindClassMemberFunction("setZoom", &UIMap::setZoom); + g_lua.bindClassMemberFunction("zoomIn", &UIMap::zoomIn); + g_lua.bindClassMemberFunction("zoomOut", &UIMap::zoomOut); + g_lua.bindClassMemberFunction("followCreature", &UIMap::followCreature); + g_lua.bindClassMemberFunction("setCameraPosition", &UIMap::setCameraPosition); + g_lua.bindClassMemberFunction("setMaxZoomIn", &UIMap::setMaxZoomIn); + g_lua.bindClassMemberFunction("setMaxZoomOut", &UIMap::setMaxZoomOut); + g_lua.bindClassMemberFunction("setMultifloor", &UIMap::setMultifloor); + g_lua.bindClassMemberFunction("lockVisibleFloor", &UIMap::lockVisibleFloor); + g_lua.bindClassMemberFunction("unlockVisibleFloor", &UIMap::unlockVisibleFloor); + g_lua.bindClassMemberFunction("setVisibleDimension", &UIMap::setVisibleDimension); + g_lua.bindClassMemberFunction("setDrawFlags", &UIMap::setDrawFlags); + g_lua.bindClassMemberFunction("setDrawTexts", &UIMap::setDrawTexts); + g_lua.bindClassMemberFunction("setDrawNames", &UIMap::setDrawNames); + g_lua.bindClassMemberFunction("setDrawHealthBars", &UIMap::setDrawHealthBars); + g_lua.bindClassMemberFunction("setDrawHealthBarsOnTop", &UIMap::setDrawHealthBarsOnTop); + g_lua.bindClassMemberFunction("setDrawLights", &UIMap::setDrawLights); + g_lua.bindClassMemberFunction("setDrawManaBar", &UIMap::setDrawManaBar); + g_lua.bindClassMemberFunction("setDrawPlayerBars", &UIMap::setDrawPlayerBars); + g_lua.bindClassMemberFunction("setAnimated", &UIMap::setAnimated); + g_lua.bindClassMemberFunction("setKeepAspectRatio", &UIMap::setKeepAspectRatio); + g_lua.bindClassMemberFunction("setMinimumAmbientLight", &UIMap::setMinimumAmbientLight); + g_lua.bindClassMemberFunction("setLimitVisibleRange", &UIMap::setLimitVisibleRange); + g_lua.bindClassMemberFunction("setFloorFading", &UIMap::setFloorFading); + g_lua.bindClassMemberFunction("setCrosshair", &UIMap::setCrosshair); + g_lua.bindClassMemberFunction("isMultifloor", &UIMap::isMultifloor); + g_lua.bindClassMemberFunction("isDrawingTexts", &UIMap::isDrawingTexts); + g_lua.bindClassMemberFunction("isDrawingNames", &UIMap::isDrawingNames); + g_lua.bindClassMemberFunction("isDrawingHealthBars", &UIMap::isDrawingHealthBars); + g_lua.bindClassMemberFunction("isDrawingHealthBarsOnTop", &UIMap::isDrawingHealthBarsOnTop); + g_lua.bindClassMemberFunction("isDrawingLights", &UIMap::isDrawingLights); + g_lua.bindClassMemberFunction("isDrawingManaBar", &UIMap::isDrawingManaBar); + g_lua.bindClassMemberFunction("isLimitVisibleRangeEnabled", &UIMap::isLimitVisibleRangeEnabled); + g_lua.bindClassMemberFunction("isAnimating", &UIMap::isAnimating); + g_lua.bindClassMemberFunction("isKeepAspectRatioEnabled", &UIMap::isKeepAspectRatioEnabled); + g_lua.bindClassMemberFunction("getVisibleDimension", &UIMap::getVisibleDimension); + g_lua.bindClassMemberFunction("getFollowingCreature", &UIMap::getFollowingCreature); + g_lua.bindClassMemberFunction("getDrawFlags", &UIMap::getDrawFlags); + g_lua.bindClassMemberFunction("getCameraPosition", &UIMap::getCameraPosition); + g_lua.bindClassMemberFunction("getPosition", &UIMap::getPosition); + g_lua.bindClassMemberFunction("getPositionOffset", &UIMap::getPositionOffset); + g_lua.bindClassMemberFunction("getTile", &UIMap::getTile); + g_lua.bindClassMemberFunction("getMaxZoomIn", &UIMap::getMaxZoomIn); + g_lua.bindClassMemberFunction("getMaxZoomOut", &UIMap::getMaxZoomOut); + g_lua.bindClassMemberFunction("getZoom", &UIMap::getZoom); + g_lua.bindClassMemberFunction("getMinimumAmbientLight", &UIMap::getMinimumAmbientLight); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UIMinimapPtr(new UIMinimap); }); + g_lua.bindClassMemberFunction("zoomIn", &UIMinimap::zoomIn); + g_lua.bindClassMemberFunction("zoomOut", &UIMinimap::zoomOut); + g_lua.bindClassMemberFunction("setZoom", &UIMinimap::setZoom); + g_lua.bindClassMemberFunction("setMixZoom", &UIMinimap::setMinZoom); + g_lua.bindClassMemberFunction("setMaxZoom", &UIMinimap::setMaxZoom); + g_lua.bindClassMemberFunction("setCameraPosition", &UIMinimap::setCameraPosition); + g_lua.bindClassMemberFunction("floorUp", &UIMinimap::floorUp); + g_lua.bindClassMemberFunction("floorDown", &UIMinimap::floorDown); + g_lua.bindClassMemberFunction("getTilePoint", &UIMinimap::getTilePoint); + g_lua.bindClassMemberFunction("getTilePosition", &UIMinimap::getTilePosition); + g_lua.bindClassMemberFunction("getTileRect", &UIMinimap::getTileRect); + g_lua.bindClassMemberFunction("getCameraPosition", &UIMinimap::getCameraPosition); + g_lua.bindClassMemberFunction("getMinZoom", &UIMinimap::getMinZoom); + g_lua.bindClassMemberFunction("getMaxZoom", &UIMinimap::getMaxZoom); + g_lua.bindClassMemberFunction("getZoom", &UIMinimap::getZoom); + g_lua.bindClassMemberFunction("getScale", &UIMinimap::getScale); + g_lua.bindClassMemberFunction("anchorPosition", &UIMinimap::anchorPosition); + g_lua.bindClassMemberFunction("fillPosition", &UIMinimap::fillPosition); + g_lua.bindClassMemberFunction("centerInPosition", &UIMinimap::centerInPosition); + + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UIProgressRectPtr(new UIProgressRect); } ); + g_lua.bindClassMemberFunction("setPercent", &UIProgressRect::setPercent); + g_lua.bindClassMemberFunction("getPercent", &UIProgressRect::getPercent); + + g_lua.registerClass(); +} diff --git a/src/client/luavaluecasts_client.cpp b/src/client/luavaluecasts_client.cpp new file mode 100644 index 0000000..4646027 --- /dev/null +++ b/src/client/luavaluecasts_client.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "luavaluecasts_client.h" +#include + +int push_luavalue(const Outfit& outfit) +{ + g_lua.createTable(0, 8); + g_lua.pushInteger(outfit.getId()); + g_lua.setField("type"); + g_lua.pushInteger(outfit.getAuxId()); + g_lua.setField("auxType"); + if(g_game.getFeature(Otc::GamePlayerAddons)) { + g_lua.pushInteger(outfit.getAddons()); + g_lua.setField("addons"); + } + g_lua.pushInteger(outfit.getHead()); + g_lua.setField("head"); + g_lua.pushInteger(outfit.getBody()); + g_lua.setField("body"); + g_lua.pushInteger(outfit.getLegs()); + g_lua.setField("legs"); + g_lua.pushInteger(outfit.getFeet()); + g_lua.setField("feet"); + if (g_game.getFeature(Otc::GamePlayerMounts)) { + g_lua.pushInteger(outfit.getMount()); + g_lua.setField("mount"); + } + if (g_game.getFeature(Otc::GameWingsAndAura)) { + g_lua.pushInteger(outfit.getWings()); + g_lua.setField("wings"); + g_lua.pushInteger(outfit.getAura()); + g_lua.setField("aura"); + } + return 1; +} + +bool luavalue_cast(int index, Outfit& outfit) +{ + if(g_lua.isTable(index)) { + g_lua.getField("type", index); + outfit.setId(g_lua.popInteger()); + g_lua.getField("auxType", index); + outfit.setAuxId(g_lua.popInteger()); + if(g_game.getFeature(Otc::GamePlayerAddons)) { + g_lua.getField("addons", index); + outfit.setAddons(g_lua.popInteger()); + } + g_lua.getField("head", index); + outfit.setHead(g_lua.popInteger()); + g_lua.getField("body", index); + outfit.setBody(g_lua.popInteger()); + g_lua.getField("legs", index); + outfit.setLegs(g_lua.popInteger()); + g_lua.getField("feet", index); + outfit.setFeet(g_lua.popInteger()); + if (g_game.getFeature(Otc::GamePlayerMounts)) { + g_lua.getField("mount", index); + outfit.setMount(g_lua.popInteger()); + } + if (g_game.getFeature(Otc::GameWingsAndAura)) { + g_lua.getField("wings", index); + outfit.setMount(g_lua.popInteger()); + g_lua.getField("aura", index); + outfit.setMount(g_lua.popInteger()); + } + return true; + } + return false; +} + +int push_luavalue(const Position& pos) +{ + if(pos.isValid()) { + g_lua.createTable(0, 3); + g_lua.pushInteger(pos.x); + g_lua.setField("x"); + g_lua.pushInteger(pos.y); + g_lua.setField("y"); + g_lua.pushInteger(pos.z); + g_lua.setField("z"); + } else + g_lua.pushNil(); + return 1; +} + +bool luavalue_cast(int index, Position& pos) +{ + if(g_lua.isTable(index)) { + g_lua.getField("x", index); + pos.x = g_lua.popInteger(); + g_lua.getField("y", index); + pos.y = g_lua.popInteger(); + g_lua.getField("z", index); + pos.z = g_lua.popInteger(); + return true; + } + return false; +} + +int push_luavalue(const MarketData& data) +{ + g_lua.createTable(0, 6); + g_lua.pushInteger(data.category); + g_lua.setField("category"); + g_lua.pushString(data.name); + g_lua.setField("name"); + g_lua.pushInteger(data.requiredLevel); + g_lua.setField("requiredLevel"); + g_lua.pushInteger(data.restrictVocation); + g_lua.setField("restrictVocation"); + g_lua.pushInteger(data.showAs); + g_lua.setField("showAs"); + g_lua.pushInteger(data.tradeAs); + g_lua.setField("tradeAs"); + return 1; +} + +bool luavalue_cast(int index, MarketData& data) +{ + if (g_lua.isTable(index)) { + g_lua.getField("category", index); + data.category = g_lua.popInteger(); + g_lua.getField("name", index); + data.name = g_lua.popString(); + g_lua.getField("requiredLevel", index); + data.requiredLevel = g_lua.popInteger(); + g_lua.getField("restrictVocation", index); + data.restrictVocation = g_lua.popInteger(); + g_lua.getField("showAs", index); + data.showAs = g_lua.popInteger(); + g_lua.getField("tradeAs", index); + data.tradeAs = g_lua.popInteger(); + return true; + } + return false; +} + +int push_luavalue(const StoreCategory& category) +{ + g_lua.createTable(0, 5); + g_lua.pushString(category.name); + g_lua.setField("name"); + g_lua.pushString(category.description); + g_lua.setField("description"); + g_lua.pushInteger(category.state); + g_lua.setField("state"); + g_lua.pushString(category.icon); + g_lua.setField("icon"); + g_lua.pushString(category.parent); + g_lua.setField("parent"); + return 1; +} + +bool luavalue_cast(int index, StoreCategory& data) +{ + if (g_lua.isTable(index)) { + g_lua.getField("name", index); + data.name = g_lua.popString(); + g_lua.getField("description", index); + data.description = g_lua.popString(); + g_lua.getField("state", index); + data.state = g_lua.popInteger(); + g_lua.getField("icon", index); + data.icon = g_lua.popString(); + g_lua.getField("parent", index); + data.parent = g_lua.popString(); + return true; + } + return false; +} + +int push_luavalue(const StoreOffer& offer) +{ + g_lua.createTable(0, 6); + g_lua.pushInteger(offer.id); + g_lua.setField("id"); + g_lua.pushString(offer.name); + g_lua.setField("name"); + g_lua.pushString(offer.description); + g_lua.setField("description"); + g_lua.pushInteger(offer.price); + g_lua.setField("price"); + g_lua.pushInteger(offer.state); + g_lua.setField("state"); + g_lua.pushString(offer.icon); + g_lua.setField("icon"); + return 1; +} + +bool luavalue_cast(int index, StoreOffer& data) +{ + if (g_lua.isTable(index)) { + g_lua.getField("id", index); + data.id = g_lua.popInteger(); + g_lua.getField("name", index); + data.name = g_lua.popString(); + g_lua.getField("description", index); + data.description = g_lua.popString(); + g_lua.getField("state", index); + data.state = g_lua.popInteger(); + g_lua.getField("price", index); + data.price = g_lua.popInteger(); + g_lua.getField("icon", index); + data.icon = g_lua.popString(); + return true; + } + return false; +} + +int push_luavalue(const Imbuement& i) +{ + g_lua.createTable(0, 11); + g_lua.pushInteger(i.id); + g_lua.setField("id"); + g_lua.pushString(i.name); + g_lua.setField("name"); + g_lua.pushString(i.description); + g_lua.setField("description"); + g_lua.pushString(i.group); + g_lua.setField("group"); + g_lua.pushInteger(i.imageId); + g_lua.setField("imageId"); + g_lua.pushInteger(i.duration); + g_lua.setField("duration"); + g_lua.pushBoolean(i.premiumOnly); + g_lua.setField("premiumOnly"); + g_lua.createTable(i.sources.size(), 0); + for (size_t j = 0; j < i.sources.size(); ++j) { + g_lua.createTable(0, 2); + g_lua.pushObject(i.sources[j].first); + g_lua.setField("item"); + g_lua.pushString(i.sources[j].second); + g_lua.setField("description"); + g_lua.rawSeti(j + 1); + } + g_lua.setField("sources"); + g_lua.pushInteger(i.cost); + g_lua.setField("cost"); + g_lua.pushInteger(i.successRate); + g_lua.setField("successRate"); + g_lua.pushInteger(i.protectionCost); + g_lua.setField("protectionCost"); + return 1; +} + +int push_luavalue(const Light& light) +{ + g_lua.createTable(0, 2); + g_lua.pushInteger(light.color); + g_lua.setField("color"); + g_lua.pushInteger(light.intensity); + g_lua.setField("intensity"); + return 1; +} + +bool luavalue_cast(int index, Light& light) +{ + if(g_lua.isTable(index)) { + g_lua.getField("color", index); + light.color = g_lua.popInteger(); + g_lua.getField("intensity", index); + light.intensity = g_lua.popInteger(); + return true; + } + return false; +} + +int push_luavalue(const UnjustifiedPoints& unjustifiedPoints) +{ + g_lua.createTable(0, 7); + g_lua.pushInteger(unjustifiedPoints.killsDay); + g_lua.setField("killsDay"); + g_lua.pushInteger(unjustifiedPoints.killsDayRemaining); + g_lua.setField("killsDayRemaining"); + g_lua.pushInteger(unjustifiedPoints.killsWeek); + g_lua.setField("killsWeek"); + g_lua.pushInteger(unjustifiedPoints.killsWeekRemaining); + g_lua.setField("killsWeekRemaining"); + g_lua.pushInteger(unjustifiedPoints.killsMonth); + g_lua.setField("killsMonth"); + g_lua.pushInteger(unjustifiedPoints.killsMonthRemaining); + g_lua.setField("killsMonthRemaining"); + g_lua.pushInteger(unjustifiedPoints.skullTime); + g_lua.setField("skullTime"); + return 1; +} + +bool luavalue_cast(int index, UnjustifiedPoints& unjustifiedPoints) +{ + if(g_lua.isTable(index)) { + g_lua.getField("killsDay", index); + unjustifiedPoints.killsDay = g_lua.popInteger(); + g_lua.getField("killsDayRemaining", index); + unjustifiedPoints.killsDayRemaining = g_lua.popInteger(); + g_lua.getField("killsWeek", index); + unjustifiedPoints.killsWeek = g_lua.popInteger(); + g_lua.getField("killsWeekRemaining", index); + unjustifiedPoints.killsWeekRemaining = g_lua.popInteger(); + g_lua.getField("killsMonth", index); + unjustifiedPoints.killsMonth = g_lua.popInteger(); + g_lua.getField("killsMonthRemaining", index); + unjustifiedPoints.killsMonthRemaining = g_lua.popInteger(); + g_lua.getField("skullTime", index); + unjustifiedPoints.skullTime = g_lua.popInteger(); + return true; + } + return false; +} diff --git a/src/client/luavaluecasts_client.h b/src/client/luavaluecasts_client.h new file mode 100644 index 0000000..467b5a1 --- /dev/null +++ b/src/client/luavaluecasts_client.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CLIENT_LUAVALUECASTS_H +#define CLIENT_LUAVALUECASTS_H + +#include "global.h" +#include +#include "game.h" +#include "outfit.h" + +// outfit +int push_luavalue(const Outfit& outfit); +bool luavalue_cast(int index, Outfit& outfit); + +// position +int push_luavalue(const Position& pos); +bool luavalue_cast(int index, Position& pos); + +// market +int push_luavalue(const MarketData& data); +bool luavalue_cast(int index, MarketData& data); + +// store category +int push_luavalue(const StoreCategory& category); +bool luavalue_cast(int index, StoreCategory& data); + +// store offer +int push_luavalue(const StoreOffer& offer); +bool luavalue_cast(int index, StoreOffer& offer); + +// imbuement +int push_luavalue(const Imbuement& offer); + +// light +int push_luavalue(const Light& light); +bool luavalue_cast(int index, Light& light); + +// unjustified points +int push_luavalue(const UnjustifiedPoints& unjustifiedPoints); +bool luavalue_cast(int index, UnjustifiedPoints& unjustifiedPoints); + +#endif diff --git a/src/client/map.cpp b/src/client/map.cpp new file mode 100644 index 0000000..8a9cbfd --- /dev/null +++ b/src/client/map.cpp @@ -0,0 +1,969 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "map.h" +#include "game.h" +#include "localplayer.h" +#include "tile.h" +#include "item.h" +#include "missile.h" +#include "statictext.h" +#include "mapview.h" +#include "minimap.h" + +#include +#include +#include +#include +#include + +Map g_map; +TilePtr Map::m_nulltile = nullptr; + +void Map::init() +{ + resetAwareRange(); + m_animationFlags |= Animation_Show; +} + +void Map::terminate() +{ + clean(); +} + +void Map::addMapView(const MapViewPtr& mapView) +{ + m_mapViews.push_back(mapView); +} + +void Map::removeMapView(const MapViewPtr& mapView) +{ + auto it = std::find(m_mapViews.begin(), m_mapViews.end(), mapView); + if(it != m_mapViews.end()) + m_mapViews.erase(it); +} + +void Map::notificateTileUpdate(const Position& pos, bool updateMinimap) +{ + if(!pos.isMapPosition()) + return; + + for(const MapViewPtr& mapView : m_mapViews) + mapView->onTileUpdate(pos); + + if (!updateMinimap) + return; + + if (!g_game.getFeature(Otc::GameMinimapLimitedToSingleFloor) || (m_centralPosition.z == pos.z)) { + g_minimap.updateTile(pos, getTile(pos)); + } +} + +void Map::requestVisibleTilesCacheUpdate() { + for (const MapViewPtr& mapView : m_mapViews) + mapView->requestVisibleTilesCacheUpdate(); +} + +void Map::clean() +{ + cleanDynamicThings(); + + for(int i=0;i<=Otc::MAX_Z;++i) + m_tileBlocks[i].clear(); + + m_waypoints.clear(); + + g_towns.clear(); + g_houses.clear(); + g_creatures.clearSpawns(); + m_tilesRect = Rect(65534, 65534, 0, 0); +} + +void Map::cleanDynamicThings() +{ + for(const auto& pair : m_knownCreatures) { + const CreaturePtr& creature = pair.second; + removeThing(creature); + } + m_knownCreatures.clear(); + + for(int i=0;i<=Otc::MAX_Z;++i) + m_floorMissiles[i].clear(); + + cleanTexts(); +} + +void Map::cleanTexts() +{ + m_animatedTexts.clear(); + m_staticTexts.clear(); +} + +void Map::addThing(const ThingPtr& thing, const Position& pos, int stackPos) +{ + if(!thing) + return; + + if(thing->isItem() || thing->isCreature() || thing->isEffect()) { + const TilePtr& tile = getOrCreateTile(pos); + if(tile) + tile->addThing(thing, stackPos); + } else { + if(thing->isMissile()) { + m_floorMissiles[pos.z].push_back(thing->static_self_cast()); + } else if(thing->isAnimatedText()) { + // this code will stack animated texts of the same color + AnimatedTextPtr animatedText = thing->static_self_cast(); + + AnimatedTextPtr prevAnimatedText; + bool merged = false; + for(auto other : m_animatedTexts) { + if(other->getPosition() == pos) { + prevAnimatedText = other; + if(other->merge(animatedText)) { + merged = true; + break; + } + } + } + if(!merged) { + m_animatedTexts.push_back(animatedText); + } + } else if(thing->isStaticText()) { + StaticTextPtr staticText = thing->static_self_cast(); + + bool mustAdd = true; + for(auto other : m_staticTexts) { + // try to combine messages + if(other->getPosition() == pos && other->addColoredMessage(staticText->getName(), staticText->getMessageMode(), staticText->getFirstMessage())) { + mustAdd = false; + break; + } + } + + if(mustAdd) + m_staticTexts.push_back(staticText); + else + return; + } + + thing->setPosition(pos); + thing->onAppear(); + + if (thing->isMissile()) { + g_lua.callGlobalField("g_map", "onMissle", thing); + } else if (thing->isAnimatedText()) { + AnimatedTextPtr animatedText = thing->static_self_cast(); + g_lua.callGlobalField("g_map", "onAnimatedText", thing, animatedText->getText()); + } else if (thing->isStaticText()) { + StaticTextPtr staticText = thing->static_self_cast(); + g_lua.callGlobalField("g_map", "onStaticText", thing, staticText->getText()); + } + } + + notificateTileUpdate(pos, thing->isItem()); +} + +void Map::setTileSpeed(const Position& pos, uint16_t speed, uint8_t blocking) { + const TilePtr& tile = getOrCreateTile(pos); + if (tile) + tile->setSpeed(speed, blocking); +} + +ThingPtr Map::getThing(const Position& pos, int stackPos) +{ + if(TilePtr tile = getTile(pos)) + return tile->getThing(stackPos); + return nullptr; +} + +bool Map::removeThing(const ThingPtr& thing) +{ + if(!thing) + return false; + + bool ret = false; + if(thing->isMissile()) { + MissilePtr missile = thing->static_self_cast(); + int z = missile->getPosition().z; + auto it = std::find(m_floorMissiles[z].begin(), m_floorMissiles[z].end(), missile); + if(it != m_floorMissiles[z].end()) { + m_floorMissiles[z].erase(it); + ret = true; + } + } else if(thing->isAnimatedText()) { + AnimatedTextPtr animatedText = thing->static_self_cast(); + auto it = std::find(m_animatedTexts.begin(), m_animatedTexts.end(), animatedText); + if(it != m_animatedTexts.end()) { + m_animatedTexts.erase(it); + ret = true; + } + } else if(thing->isStaticText()) { + StaticTextPtr staticText = thing->static_self_cast(); + auto it = std::find(m_staticTexts.begin(), m_staticTexts.end(), staticText); + if(it != m_staticTexts.end()) { + m_staticTexts.erase(it); + ret = true; + } + } else if(const TilePtr& tile = thing->getTile()) + ret = tile->removeThing(thing); + + notificateTileUpdate(thing->getPosition(), thing->isItem()); + return ret; +} + +bool Map::removeThingByPos(const Position& pos, int stackPos) +{ + if(TilePtr tile = getTile(pos)) + return removeThing(tile->getThing(stackPos)); + return false; +} + +void Map::colorizeThing(const ThingPtr& thing, const Color& color) +{ + if(!thing) + return; + + if(thing->isItem()) + thing->static_self_cast()->setColor(color); + else if(thing->isCreature()) { + const TilePtr& tile = thing->getTile(); + VALIDATE(tile); + + const ThingPtr& topThing = tile->getTopThing(); + VALIDATE(topThing); + + topThing->static_self_cast()->setColor(color); + } +} + +void Map::removeThingColor(const ThingPtr& thing) +{ + if(!thing) + return; + + if(thing->isItem()) + thing->static_self_cast()->setColor(Color::alpha); + else if(thing->isCreature()) { + const TilePtr& tile = thing->getTile(); + VALIDATE(tile); + + const ThingPtr& topThing = tile->getTopThing(); + VALIDATE(topThing); + + topThing->static_self_cast()->setColor(Color::alpha); + } +} + +StaticTextPtr Map::getStaticText(const Position& pos) +{ + for(auto staticText : m_staticTexts) { + // try to combine messages + if(staticText->getPosition() == pos) + return staticText; + } + return nullptr; +} + +const TilePtr& Map::createTile(const Position& pos) +{ + if(!pos.isMapPosition()) + return m_nulltile; + if(pos.x < m_tilesRect.left()) + m_tilesRect.setLeft(pos.x); + if(pos.y < m_tilesRect.top()) + m_tilesRect.setTop(pos.y); + if(pos.x > m_tilesRect.right()) + m_tilesRect.setRight(pos.x); + if(pos.y > m_tilesRect.bottom()) + m_tilesRect.setBottom(pos.y); + TileBlock& block = m_tileBlocks[pos.z][getBlockIndex(pos)]; + return block.create(pos); +} + +template +const TilePtr& Map::createTileEx(const Position& pos, const Items&... items) +{ + if(!pos.isValid()) + return m_nulltile; + const TilePtr& tile = getOrCreateTile(pos); + auto vec = {items...}; + for(auto it : vec) + addThing(it, pos); + + return tile; +} + +const TilePtr& Map::getOrCreateTile(const Position& pos) +{ + if(!pos.isMapPosition()) + return m_nulltile; + if(pos.x < m_tilesRect.left()) + m_tilesRect.setLeft(pos.x); + if(pos.y < m_tilesRect.top()) + m_tilesRect.setTop(pos.y); + if(pos.x > m_tilesRect.right()) + m_tilesRect.setRight(pos.x); + if(pos.y > m_tilesRect.bottom()) + m_tilesRect.setBottom(pos.y); + TileBlock& block = m_tileBlocks[pos.z][getBlockIndex(pos)]; + return block.getOrCreate(pos); +} + +const TilePtr& Map::getTile(const Position& pos) +{ + if(!pos.isMapPosition()) + return m_nulltile; + auto it = m_tileBlocks[pos.z].find(getBlockIndex(pos)); + if(it != m_tileBlocks[pos.z].end()) + return it->second.get(pos); + return m_nulltile; +} + +const TileList Map::getTiles(int floor/* = -1*/) +{ + TileList tiles; + if(floor > Otc::MAX_Z) { + return tiles; + } + else if(floor < 0) { + // Search all floors + for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { + for(const auto& pair : m_tileBlocks[z]) { + const TileBlock& block = pair.second; + for(const TilePtr& tile : block.getTiles()) { + if(tile != nullptr) + tiles.push_back(tile); + } + } + } + } + else { + for(const auto& pair : m_tileBlocks[floor]) { + const TileBlock& block = pair.second; + for(const TilePtr& tile : block.getTiles()) { + if(tile != nullptr) + tiles.push_back(tile); + } + } + } + return tiles; +} + +void Map::cleanTile(const Position& pos) +{ + if(!pos.isMapPosition()) + return; + auto it = m_tileBlocks[pos.z].find(getBlockIndex(pos)); + if(it != m_tileBlocks[pos.z].end()) { + TileBlock& block = it->second; + if(const TilePtr& tile = block.get(pos)) { + tile->clean(); + if(tile->canErase()) + block.remove(pos); + + notificateTileUpdate(pos, false); + } + } + for(auto it = m_staticTexts.begin();it != m_staticTexts.end();) { + const StaticTextPtr& staticText = *it; + if(staticText->getPosition() == pos && staticText->getMessageMode() == Otc::MessageNone) + it = m_staticTexts.erase(it); + else + ++it; + } + + if (!g_game.getFeature(Otc::GameMinimapLimitedToSingleFloor) || (m_centralPosition.z == pos.z)) { + g_minimap.updateTile(pos, getTile(pos)); + } +} + +void Map::setShowZone(tileflags_t zone, bool show) +{ + if(show) + m_zoneFlags |= (uint32)zone; + else + m_zoneFlags &= ~(uint32)zone; +} + +void Map::setShowZones(bool show) +{ + if(!show) + m_zoneFlags = 0; + else if(m_zoneFlags == 0) + m_zoneFlags = TILESTATE_HOUSE | TILESTATE_PROTECTIONZONE; +} + +void Map::setZoneColor(tileflags_t zone, const Color& color) +{ + if((m_zoneFlags & zone) == zone) + m_zoneColors[zone] = color; +} + +Color Map::getZoneColor(tileflags_t flag) +{ + auto it = m_zoneColors.find(flag); + if(it == m_zoneColors.end()) + return Color::alpha; + return it->second; +} + +void Map::setForceShowAnimations(bool force) +{ + if(force) { + if(!(m_animationFlags & Animation_Force)) + m_animationFlags |= Animation_Force; + } else + m_animationFlags &= ~Animation_Force; +} + +bool Map::isForcingAnimations() +{ + return (m_animationFlags & Animation_Force) == Animation_Force; +} + +bool Map::isShowingAnimations() +{ + return (m_animationFlags & Animation_Show) == Animation_Show; +} + +void Map::setShowAnimations(bool show) +{ + if(show) { + if(!(m_animationFlags & Animation_Show)) + m_animationFlags |= Animation_Show; + } else + m_animationFlags &= ~Animation_Show; +} + +std::map Map::findItemsById(uint16 clientId, uint32 max) +{ + std::map ret; + uint32 count = 0; + for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { + for(const auto& pair : m_tileBlocks[z]) { + const TileBlock& block = pair.second; + for(const TilePtr& tile : block.getTiles()) { + if(unlikely(!tile || tile->isEmpty())) + continue; + for(const ItemPtr& item : tile->getItems()) { + if(item->getId() == clientId) { + ret.insert(std::make_pair(tile->getPosition(), item)); + if(++count >= max) + break; + } + } + } + } + } + + return ret; +} + +void Map::addCreature(const CreaturePtr& creature) +{ + m_knownCreatures[creature->getId()] = creature; +} + +CreaturePtr Map::getCreatureById(uint32 id) +{ + auto it = m_knownCreatures.find(id); + if(it == m_knownCreatures.end()) + return nullptr; + return it->second; +} + +void Map::removeCreatureById(uint32 id) +{ + if(id == 0) + return; + + auto it = m_knownCreatures.find(id); + if(it != m_knownCreatures.end()) + m_knownCreatures.erase(it); +} + +void Map::removeUnawareThings() +{ + // remove creatures from tiles that we are not aware of anymore + for(const auto& pair : m_knownCreatures) { + const CreaturePtr& creature = pair.second; + if(!isAwareOfPosition(creature->getPosition())) + removeThing(creature); + } + + // remove static texts from tiles that we are not aware anymore + for(auto it = m_staticTexts.begin(); it != m_staticTexts.end();) { + const StaticTextPtr& staticText = *it; + if(staticText->getMessageMode() == Otc::MessageNone && !isAwareOfPosition(staticText->getPosition())) + it = m_staticTexts.erase(it); + else + ++it; + } + + bool extended = g_game.getFeature(Otc::GameBiggerMapCache); + if(!g_game.getFeature(Otc::GameKeepUnawareTiles)) { + // remove tiles that we are not aware anymore + for(int z = 0; z <= Otc::MAX_Z; ++z) { + auto& tileBlocks = m_tileBlocks[z]; + for(auto it = tileBlocks.begin(); it != tileBlocks.end();) { + TileBlock& block = (*it).second; + bool blockEmpty = true; + for(const TilePtr& tile : block.getTiles()) { + if(!tile) + continue; + + const Position& pos = tile->getPosition(); + + if(!isAwareOfPositionForClean(pos, extended)) + block.remove(pos); + else + blockEmpty = false; + } + + if(blockEmpty) + it = tileBlocks.erase(it); + else + ++it; + } + } + } +} + +void Map::setCentralPosition(const Position& centralPosition) +{ + if(m_centralPosition == centralPosition) + return; + + m_centralPosition = centralPosition; + + removeUnawareThings(); + + // this fixes local player position when the local player is removed from the map, + // the local player is removed from the map when there are too many creatures on his tile, + // so there is no enough stackpos to the server send him + g_dispatcher.addEvent([this] { + LocalPlayerPtr localPlayer = g_game.getLocalPlayer(); + if(!localPlayer || localPlayer->getPosition() == m_centralPosition) + return; + TilePtr tile = localPlayer->getTile(); + if(tile && tile->hasThing(localPlayer)) + return; + + Position oldPos = localPlayer->getPosition(); + Position pos = m_centralPosition; + if(oldPos != pos) { + if(!localPlayer->isRemoved()) + localPlayer->onDisappear(); + localPlayer->setPosition(pos); + localPlayer->onAppear(); + g_logger.debug("forced player position update"); + } + }); + + for(const MapViewPtr& mapView : m_mapViews) + mapView->onMapCenterChange(centralPosition); +} + +std::vector Map::getSightSpectators(const Position& centerPos, bool multiFloor) +{ + return getSpectatorsInRangeEx(centerPos, multiFloor, m_awareRange.left - 1, m_awareRange.right - 2, m_awareRange.top - 1, m_awareRange.bottom - 2); +} + +std::vector Map::getSpectators(const Position& centerPos, bool multiFloor) +{ + return getSpectatorsInRangeEx(centerPos, multiFloor, m_awareRange.left, m_awareRange.right, m_awareRange.top, m_awareRange.bottom); +} + +std::vector Map::getSpectatorsInRange(const Position& centerPos, bool multiFloor, int xRange, int yRange) +{ + return getSpectatorsInRangeEx(centerPos, multiFloor, xRange, xRange, yRange, yRange); +} + +std::vector Map::getSpectatorsInRangeEx(const Position& centerPos, bool multiFloor, int minXRange, int maxXRange, int minYRange, int maxYRange) +{ + int minZRange = 0; + int maxZRange = 0; + std::vector creatures; + + if(multiFloor) { + minZRange = 0; + maxZRange = Otc::MAX_Z; + } + + //TODO: optimize + //TODO: get creatures from other floors corretly + //TODO: delivery creatures in distance order + + for(int iz=-minZRange; iz<=maxZRange; ++iz) { + for(int iy=-minYRange; iy<=maxYRange; ++iy) { + for(int ix=-minXRange; ix<=maxXRange; ++ix) { + TilePtr tile = getTile(centerPos.translated(ix,iy,iz)); + if(!tile) + continue; + + auto tileCreatures = tile->getCreatures(); + creatures.insert(creatures.end(), tileCreatures.rbegin(), tileCreatures.rend()); + } + } + } + + return creatures; +} + +bool Map::isLookPossible(const Position& pos) +{ + TilePtr tile = getTile(pos); + return tile && tile->isLookPossible(); +} + +bool Map::isCovered(const Position& pos, int firstFloor) +{ + // check for tiles on top of the postion + Position tilePos = pos; + while(tilePos.coveredUp() && tilePos.z >= firstFloor) { + TilePtr tile = getTile(tilePos); + // the below tile is covered when the above tile has a full ground + if(tile && tile->isFullGround()) + return true; + } + return false; +} + +bool Map::isCompletelyCovered(const Position& pos, int firstFloor) +{ + const TilePtr& checkTile = getTile(pos); + Position tilePos = pos; + while(tilePos.coveredUp() && tilePos.z >= firstFloor) { + bool covered = true; + bool done = false; + // check in 2x2 range tiles that has no transparent pixels + for(int x=0;x<2 && !done;++x) { + for(int y=0;y<2 && !done;++y) { + const TilePtr& tile = getTile(tilePos.translated(-x, -y)); + if(!tile || !tile->isFullyOpaque()) { + covered = false; + done = true; + } else if(x==0 && y==0 && (!checkTile || checkTile->isSingleDimension())) { + done = true; + } + } + } + if(covered) + return true; + } + return false; +} + +bool Map::isAwareOfPosition(const Position& pos, bool extended) +{ + if ((pos.z < getFirstAwareFloor() || pos.z > getLastAwareFloor()) && !extended) + return false; + + Position groundedPos = pos; + while (groundedPos.z != m_centralPosition.z) { + if (groundedPos.z > m_centralPosition.z) { + if (groundedPos.x == 65535 || groundedPos.y == 65535) // When pos == 65535,65535,15 we cant go up to 65536,65536,14 + break; + groundedPos.coveredUp(); + } else { + if (groundedPos.x == 0 || groundedPos.y == 0) // When pos == 0,0,0 we cant go down to -1,-1,1 + break; + groundedPos.coveredDown(); + } + } + + if (extended) { + return m_centralPosition.isInRange(groundedPos, m_awareRange.left * 2, + m_awareRange.right * 2, + m_awareRange.top * 2, + m_awareRange.bottom * 2); + } + return m_centralPosition.isInRange(groundedPos, m_awareRange.left, + m_awareRange.right, + m_awareRange.top, + m_awareRange.bottom); +} + +bool Map::isAwareOfPositionForClean(const Position& pos, bool extended) +{ + if ((pos.z < getFirstAwareFloor() || pos.z > getLastAwareFloor()) && !extended) + return false; + + Position groundedPos = pos; + while (groundedPos.z != m_centralPosition.z) { + if (groundedPos.z > m_centralPosition.z) { + if (groundedPos.x == 65535 || groundedPos.y == 65535) // When pos == 65535,65535,15 we cant go up to 65536,65536,14 + break; + groundedPos.coveredUp(); + } else { + if (groundedPos.x == 0 || groundedPos.y == 0) // When pos == 0,0,0 we cant go down to -1,-1,1 + break; + groundedPos.coveredDown(); + } + } + + if (extended) { + return m_centralPosition.isInRange(groundedPos, m_awareRange.left * 4, + m_awareRange.right * 4, + m_awareRange.top * 4, + m_awareRange.bottom * 4); + } + return m_centralPosition.isInRange(groundedPos, m_awareRange.left + 1, + m_awareRange.right + 1, + m_awareRange.top + 1, + m_awareRange.bottom + 1); +} + +void Map::setAwareRange(const AwareRange& range) +{ + m_awareRange = range; + removeUnawareThings(); +} + +void Map::resetAwareRange() +{ + AwareRange range; + range.left = 8; + range.top = 6; + range.bottom = 7; + range.right = 9; + setAwareRange(range); +} + +int Map::getFirstAwareFloor() +{ + if(m_centralPosition.z > Otc::SEA_FLOOR) + return m_centralPosition.z-Otc::AWARE_UNDEGROUND_FLOOR_RANGE; + else + return 0; +} + +int Map::getLastAwareFloor() +{ + if(m_centralPosition.z > Otc::SEA_FLOOR) + return std::min(m_centralPosition.z+Otc::AWARE_UNDEGROUND_FLOOR_RANGE, (int)Otc::MAX_Z); + else + return Otc::SEA_FLOOR; +} + +std::tuple, Otc::PathFindResult> Map::findPath(const Position& startPos, const Position& goalPos, int maxComplexity, int flags) +{ + // pathfinding using A* search algorithm (otclientv8 note: it's dijkstra algorithm) + // as described in http://en.wikipedia.org/wiki/A*_search_algorithm + + struct SNode { + SNode(const Position& pos) : cost(0), totalCost(0), pos(pos), prev(nullptr), dir(Otc::InvalidDirection) { } + float cost; + float totalCost; + Position pos; + SNode *prev; + Otc::Direction dir; + }; + + struct LessNode { + bool operator()(std::pair a, std::pair b) const { + return b.second < a.second; + } + }; + + std::tuple, Otc::PathFindResult> ret; + std::vector& dirs = std::get<0>(ret); + Otc::PathFindResult& result = std::get<1>(ret); + + result = Otc::PathFindResultNoWay; + + if(startPos == goalPos) { + result = Otc::PathFindResultSamePosition; + return ret; + } + + if(startPos.z != goalPos.z) { + result = Otc::PathFindResultImpossible; + return ret; + } + + // check the goal pos is walkable + if(g_map.isAwareOfPosition(goalPos)) { + const TilePtr goalTile = getTile(goalPos); + if(!goalTile || (!goalTile->isWalkable(flags & Otc::PathFindIgnoreCreatures))) { + return ret; + } + } + else { + const MinimapTile& goalTile = g_minimap.getTile(goalPos); + if(goalTile.hasFlag(MinimapTileNotWalkable)) { + return ret; + } + } + + std::unordered_map nodes; + std::priority_queue, std::vector>, LessNode> searchList; + + // hidden code + + for(auto it : nodes) + delete it.second; + + return ret; +} + +PathFindResult_ptr Map::newFindPath(const Position& start, const Position& goal, std::shared_ptr> visibleNodes) { + auto ret = std::make_shared(); + ret->start = start; + ret->destination = goal; + + if (start == goal) { + ret->status = Otc::PathFindResultSamePosition; + return ret; + } + if (goal.z != start.z) { + return ret; + } + + struct LessNode { + bool operator()(Node* a, Node* b) const { + return b->totalCost < a->totalCost; + } + }; + + std::unordered_map nodes; + std::priority_queue, LessNode> searchList; + + if (visibleNodes) { + for (auto& node : *visibleNodes) + nodes.emplace(node->pos, node); + } + + Node* initNode = new Node{ 1, 0, start, nullptr, 0, 0 }; + nodes[start] = initNode; + searchList.push(initNode); + + int limit = 50000; + float distance = start.distance(goal); + + // hidden code + + return ret; +} + +void Map::findPathAsync(const Position& start, const Position& goal, std::function callback) { + +} + +std::map> Map::findEveryPath(const Position& start, int maxDistance, const std::map& params) +{ + // using Dijkstra's algorithm + struct LessNode { + bool operator()(Node* a, Node* b) const + { + return b->totalCost < a->totalCost; + } + }; + + if (g_extras.debugPathfinding) { + g_logger.info(stdext::format("findEveryPath: %i %i %i - %i", start.x, start.y, start.z, maxDistance)); + for (auto& param : params) { + g_logger.info(stdext::format("%s - %s", param.first, param.second)); + } + } + + std::map::const_iterator it; + it = params.find("ignoreLastCreature"); + bool ignoreLastCreature = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("ignoreCreatures"); + bool ignoreCreatures = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("ignoreNonPathable"); + bool ignoreNonPathable = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("ignoreNonWalkable"); + bool ignoreNonWalkable = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("ignoreStairs"); + bool ignoreStairs = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("ignoreCost"); + bool ignoreCost = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("allowUnseen"); + bool allowUnseen = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("allowOnlyVisibleTiles"); + bool allowOnlyVisibleTiles = it != params.end() && it->second != "0" && it->second != ""; + it = params.find("marginMin"); + bool hasMargin = it != params.end(); + it = params.find("marginMax"); + hasMargin = hasMargin || (it != params.end()); + + + Position destPos; + it = params.find("destination"); + if (it != params.end()) { + std::vector pos = stdext::split(it->second, ","); + if (pos.size() == 3) { + destPos = Position(pos[0], pos[1], pos[2]); + } + } + + std::map> ret; + std::unordered_map nodes; + std::priority_queue, LessNode> searchList; + + Node* initNode = new Node{ 1, 0, start, nullptr, 0, 0 }; + nodes[start] = initNode; + searchList.push(initNode); + + // hidden code + + for (auto& node : nodes) { + if (node.second) + delete node.second; + } + + return ret; +} + +int Map::getMinimapColor(const Position& pos) +{ + int color = 0; + if (const TilePtr& tile = getTile(pos)) { + color = tile->getMinimapColorByte(); + } + if (color == 0) { + const MinimapTile& mtile = g_minimap.getTile(pos); + color = mtile.color; + } + return color; +} + +bool Map::isPatchable(const Position& pos) +{ + if (const TilePtr& tile = getTile(pos)) { + return tile->isPathable(); + } + const MinimapTile& mtile = g_minimap.getTile(pos); + return !mtile.hasFlag(MinimapTileNotPathable); +} + +bool Map::isWalkable(const Position& pos, bool ignoreCreatures) +{ + if (const TilePtr& tile = getTile(pos)) { + return tile->isWalkable(ignoreCreatures); + } + const MinimapTile& mtile = g_minimap.getTile(pos); + return !mtile.hasFlag(MinimapTileNotPathable); +} diff --git a/src/client/map.h b/src/client/map.h new file mode 100644 index 0000000..e4e275c --- /dev/null +++ b/src/client/map.h @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MAP_H +#define MAP_H + +#include "creature.h" +#include "houses.h" +#include "towns.h" +#include "creatures.h" +#include "animatedtext.h" +#include "statictext.h" +#include "tile.h" + +#include + +enum OTBM_ItemAttr +{ + OTBM_ATTR_DESCRIPTION = 1, + OTBM_ATTR_EXT_FILE = 2, + OTBM_ATTR_TILE_FLAGS = 3, + OTBM_ATTR_ACTION_ID = 4, + OTBM_ATTR_UNIQUE_ID = 5, + OTBM_ATTR_TEXT = 6, + OTBM_ATTR_DESC = 7, + OTBM_ATTR_TELE_DEST = 8, + OTBM_ATTR_ITEM = 9, + OTBM_ATTR_DEPOT_ID = 10, + OTBM_ATTR_SPAWN_FILE = 11, + OTBM_ATTR_RUNE_CHARGES = 12, + OTBM_ATTR_HOUSE_FILE = 13, + OTBM_ATTR_HOUSEDOORID = 14, + OTBM_ATTR_COUNT = 15, + OTBM_ATTR_DURATION = 16, + OTBM_ATTR_DECAYING_STATE = 17, + OTBM_ATTR_WRITTENDATE = 18, + OTBM_ATTR_WRITTENBY = 19, + OTBM_ATTR_SLEEPERGUID = 20, + OTBM_ATTR_SLEEPSTART = 21, + OTBM_ATTR_CHARGES = 22, + OTBM_ATTR_CONTAINER_ITEMS = 23, + OTBM_ATTR_ATTRIBUTE_MAP = 128, + /// just random numbers, they're not actually used by the binary reader... + OTBM_ATTR_WIDTH = 129, + OTBM_ATTR_HEIGHT = 130 +}; + +enum OTBM_NodeTypes_t +{ + OTBM_ROOTV2 = 1, + OTBM_MAP_DATA = 2, + OTBM_ITEM_DEF = 3, + OTBM_TILE_AREA = 4, + OTBM_TILE = 5, + OTBM_ITEM = 6, + OTBM_TILE_SQUARE = 7, + OTBM_TILE_REF = 8, + OTBM_SPAWNS = 9, + OTBM_SPAWN_AREA = 10, + OTBM_MONSTER = 11, + OTBM_TOWNS = 12, + OTBM_TOWN = 13, + OTBM_HOUSETILE = 14, + OTBM_WAYPOINTS = 15, + OTBM_WAYPOINT = 16 +}; + +enum { + OTCM_SIGNATURE = 0x4D43544F, + OTCM_VERSION = 1 +}; + +enum { + BLOCK_SIZE = 32 +}; + +enum : uint8 { + Animation_Force, + Animation_Show +}; + +class TileBlock { +public: + TileBlock() { m_tiles.fill(nullptr); } + + const TilePtr& create(const Position& pos) { + TilePtr& tile = m_tiles[getTileIndex(pos)]; + tile = TilePtr(new Tile(pos)); + return tile; + } + const TilePtr& getOrCreate(const Position& pos) { + TilePtr& tile = m_tiles[getTileIndex(pos)]; + if(!tile) + tile = TilePtr(new Tile(pos)); + return tile; + } + const TilePtr& get(const Position& pos) { return m_tiles[getTileIndex(pos)]; } + void remove(const Position& pos) { m_tiles[getTileIndex(pos)] = nullptr; } + + uint getTileIndex(const Position& pos) { return ((pos.y % BLOCK_SIZE) * BLOCK_SIZE) + (pos.x % BLOCK_SIZE); } + + const std::array& getTiles() const { return m_tiles; } + +private: + std::array m_tiles; +}; + +struct AwareRange +{ + int top; + int right; + int bottom; + int left; + + int horizontal() { return left + right + 1; } + int vertical() { return top + bottom + 1; } +}; + +struct PathFindResult +{ + Otc::PathFindResult status = Otc::PathFindResultNoWay; + std::vector path; + int complexity = 0; + Position start; + Position destination; +}; +using PathFindResult_ptr = std::shared_ptr; + +struct Node { + float cost; + float totalCost; + Position pos; + Node *prev; + int distance; + int unseen; +}; + +//@bindsingleton g_map +class Map +{ +public: + void init(); + void terminate(); + + void addMapView(const MapViewPtr& mapView); + void removeMapView(const MapViewPtr& mapView); + void notificateTileUpdate(const Position& pos, bool updateMinimap = false); + + void requestVisibleTilesCacheUpdate(); + + bool loadOtcm(const std::string& fileName); + void saveOtcm(const std::string& fileName); + + void loadOtbm(const std::string& fileName); + void saveOtbm(const std::string& fileName); + + // otbm attributes (description, size, etc.) + void setHouseFile(const std::string& file) { m_attribs.set(OTBM_ATTR_HOUSE_FILE, file); } + void setSpawnFile(const std::string& file) { m_attribs.set(OTBM_ATTR_SPAWN_FILE, file); } + void setDescription(const std::string& desc) { m_attribs.set(OTBM_ATTR_DESCRIPTION, desc); } + + void clearDescriptions() { m_attribs.remove(OTBM_ATTR_DESCRIPTION); } + void setWidth(uint16 w) { m_attribs.set(OTBM_ATTR_WIDTH, w); } + void setHeight(uint16 h) { m_attribs.set(OTBM_ATTR_HEIGHT, h); } + + std::string getHouseFile() { return m_attribs.get(OTBM_ATTR_HOUSE_FILE); } + std::string getSpawnFile() { return m_attribs.get(OTBM_ATTR_SPAWN_FILE); } + Size getSize() { return Size(m_attribs.get(OTBM_ATTR_WIDTH), m_attribs.get(OTBM_ATTR_HEIGHT)); } + std::vector getDescriptions() { return stdext::split(m_attribs.get(OTBM_ATTR_DESCRIPTION), "\n"); } + + void clean(); + void cleanDynamicThings(); + void cleanTexts(); + + // thing related + void addThing(const ThingPtr& thing, const Position& pos, int stackPos = -1); + void setTileSpeed(const Position & pos, uint16_t speed, uint8_t blocking); + ThingPtr getThing(const Position& pos, int stackPos); + bool removeThing(const ThingPtr& thing); + bool removeThingByPos(const Position& pos, int stackPos); + void colorizeThing(const ThingPtr& thing, const Color& color); + void removeThingColor(const ThingPtr& thing); + + StaticTextPtr getStaticText(const Position& pos); + + // tile related + const TilePtr& createTile(const Position& pos); + template + const TilePtr& createTileEx(const Position& pos, const Items&... items); + const TilePtr& getOrCreateTile(const Position& pos); + const TilePtr& getTile(const Position& pos); + const TileList getTiles(int floor = -1); + void cleanTile(const Position& pos); + + // tile zone related + void setShowZone(tileflags_t zone, bool show); + void setShowZones(bool show); + void setZoneColor(tileflags_t flag, const Color& color); + void setZoneOpacity(float opacity) { m_zoneOpacity = opacity; } + + float getZoneOpacity() { return m_zoneOpacity; } + Color getZoneColor(tileflags_t flag); + tileflags_t getZoneFlags() { return (tileflags_t)m_zoneFlags; } + bool showZones() { return m_zoneFlags != 0; } + bool showZone(tileflags_t zone) { return (m_zoneFlags & zone) == zone; } + + void setForceShowAnimations(bool force); + bool isForcingAnimations(); + bool isShowingAnimations(); + void setShowAnimations(bool show); + + std::map findItemsById(uint16 clientId, uint32 max); + + // known creature related + void addCreature(const CreaturePtr& creature); + CreaturePtr getCreatureById(uint32 id); + void removeCreatureById(uint32 id); + std::vector getSightSpectators(const Position& centerPos, bool multiFloor); + std::vector getSpectators(const Position& centerPos, bool multiFloor); + std::vector getSpectatorsInRange(const Position& centerPos, bool multiFloor, int xRange, int yRange); + std::vector getSpectatorsInRangeEx(const Position& centerPos, bool multiFloor, int minXRange, int maxXRange, int minYRange, int maxYRange); + + void setLight(const Light& light) { m_light = light; } + void setCentralPosition(const Position& centralPosition); + + bool isLookPossible(const Position& pos); + bool isCovered(const Position& pos, int firstFloor = 0); + bool isCompletelyCovered(const Position& pos, int firstFloor = 0); + bool isAwareOfPosition(const Position& pos, bool extended = false); + bool isAwareOfPositionForClean(const Position& pos, bool extended = false); + + void setAwareRange(const AwareRange& range); + void resetAwareRange(); + AwareRange getAwareRange() { return m_awareRange; } + Size getAwareRangeAsSize() { return Size(m_awareRange.horizontal(), m_awareRange.vertical()); } + + Light getLight() { return m_light; } + Position getCentralPosition() { return m_centralPosition; } + int getFirstAwareFloor(); + int getLastAwareFloor(); + const std::vector& getFloorMissiles(int z) { return m_floorMissiles[z]; } + + std::vector getAnimatedTexts() { return m_animatedTexts; } + std::vector getStaticTexts() { return m_staticTexts; } + + std::tuple, Otc::PathFindResult> findPath(const Position& start, const Position& goal, int maxComplexity, int flags = 0); + PathFindResult_ptr newFindPath(const Position& start, const Position& goal, std::shared_ptr> visibleNodes); + void findPathAsync(const Position & start, const Position & goal, std::function callback); + + // tuple = + std::map> findEveryPath(const Position& start, int maxDistance, const std::map& params); + + int getMinimapColor(const Position& pos); + bool isPatchable(const Position& pos); + bool isWalkable(const Position& pos, bool ignoreCreatures); + +private: + void removeUnawareThings(); + uint getBlockIndex(const Position& pos) { return ((pos.y / BLOCK_SIZE) * (65536 / BLOCK_SIZE)) + (pos.x / BLOCK_SIZE); } + + std::map m_tileBlocks[Otc::MAX_Z+1]; + std::map m_knownCreatures; + std::array, Otc::MAX_Z+1> m_floorMissiles; + std::vector m_animatedTexts; + std::vector m_staticTexts; + std::vector m_mapViews; + std::unordered_map m_waypoints; + + uint8 m_animationFlags; + uint32 m_zoneFlags; + std::map m_zoneColors; + float m_zoneOpacity; + + Light m_light; + Position m_centralPosition; + Rect m_tilesRect; + + stdext::packed_storage m_attribs; + AwareRange m_awareRange; + static TilePtr m_nulltile; +}; + +extern Map g_map; + +#endif diff --git a/src/client/mapio.cpp b/src/client/mapio.cpp new file mode 100644 index 0000000..ca029c2 --- /dev/null +++ b/src/client/mapio.cpp @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "map.h" +#include "tile.h" +#include "game.h" + +#include +#include +#include +#include +#include +#include +#include + +void Map::loadOtbm(const std::string& fileName) +{ + try { + if(!g_things.isOtbLoaded()) + stdext::throw_exception("OTB isn't loaded yet to load a map."); + + FileStreamPtr fin = g_resources.openFile(fileName); + if(!fin) + stdext::throw_exception(stdext::format("Unable to load map '%s'", fileName)); + + char identifier[4]; + if(fin->read(identifier, 1, 4) < 4) + stdext::throw_exception("Could not read file identifier"); + + if(memcmp(identifier, "OTBM", 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) + stdext::throw_exception(stdext::format("Invalid file identifier detected: %s", identifier)); + + BinaryTreePtr root = fin->getBinaryTree(); + if(root->getU8()) + stdext::throw_exception("could not read root property!"); + + uint32 headerVersion = root->getU32(); + if(headerVersion > 3) + stdext::throw_exception(stdext::format("Unknown OTBM version detected: %u.", headerVersion)); + + setWidth(root->getU16()); + setHeight(root->getU16()); + + uint32 headerMajorItems = root->getU8(); + if(headerMajorItems > g_things.getOtbMajorVersion()) { + stdext::throw_exception(stdext::format("This map was saved with different OTB version. read %d what it's supposed to be: %d", + headerMajorItems, g_things.getOtbMajorVersion())); + } + + root->skip(3); + uint32 headerMinorItems = root->getU32(); + if(headerMinorItems > g_things.getOtbMinorVersion()) { + g_logger.warning(stdext::format("This map needs an updated OTB. read %d what it's supposed to be: %d or less", + headerMinorItems, g_things.getOtbMinorVersion())); + } + + BinaryTreePtr node = root->getChildren()[0]; + if(node->getU8() != OTBM_MAP_DATA) + stdext::throw_exception("Could not read root data node"); + + while(node->canRead()) { + uint8 attribute = node->getU8(); + std::string tmp = node->getString(); + switch (attribute) { + case OTBM_ATTR_DESCRIPTION: + setDescription(tmp); + break; + case OTBM_ATTR_SPAWN_FILE: + setSpawnFile(fileName.substr(0, fileName.rfind('/') + 1) + tmp); + break; + case OTBM_ATTR_HOUSE_FILE: + setHouseFile(fileName.substr(0, fileName.rfind('/') + 1) + tmp); + break; + default: + stdext::throw_exception(stdext::format("Invalid attribute '%d'", (int)attribute)); + } + } + + for(const BinaryTreePtr& nodeMapData : node->getChildren()) { + uint8 mapDataType = nodeMapData->getU8(); + if(mapDataType == OTBM_TILE_AREA) { + Position basePos; + basePos.x = nodeMapData->getU16(); + basePos.y = nodeMapData->getU16(); + basePos.z = nodeMapData->getU8(); + + for(const BinaryTreePtr &nodeTile : nodeMapData->getChildren()) { + uint8 type = nodeTile->getU8(); + if(unlikely(type != OTBM_TILE && type != OTBM_HOUSETILE)) + stdext::throw_exception(stdext::format("invalid node tile type %d", (int)type)); + + HousePtr house = nullptr; + uint32 flags = TILESTATE_NONE; + Position pos = basePos + nodeTile->getPoint(); + + if(type == OTBM_HOUSETILE) { + uint32 hId = nodeTile->getU32(); + TilePtr tile = getOrCreateTile(pos); + if(!(house = g_houses.getHouse(hId))) { + house = HousePtr(new House(hId)); + g_houses.addHouse(house); + } + house->setTile(tile); + } + + while(nodeTile->canRead()) { + uint8 tileAttr = nodeTile->getU8(); + switch(tileAttr) { + case OTBM_ATTR_TILE_FLAGS: { + uint32 _flags = nodeTile->getU32(); + if((_flags & TILESTATE_PROTECTIONZONE) == TILESTATE_PROTECTIONZONE) + flags |= TILESTATE_PROTECTIONZONE; + else if((_flags & TILESTATE_OPTIONALZONE) == TILESTATE_OPTIONALZONE) + flags |= TILESTATE_OPTIONALZONE; + else if((_flags & TILESTATE_HARDCOREZONE) == TILESTATE_HARDCOREZONE) + flags |= TILESTATE_HARDCOREZONE; + + if((_flags & TILESTATE_NOLOGOUT) == TILESTATE_NOLOGOUT) + flags |= TILESTATE_NOLOGOUT; + + if((_flags & TILESTATE_REFRESH) == TILESTATE_REFRESH) + flags |= TILESTATE_REFRESH; + break; + } + case OTBM_ATTR_ITEM: { + addThing(Item::createFromOtb(nodeTile->getU16()), pos); + break; + } + default: { + stdext::throw_exception(stdext::format("invalid tile attribute %d at pos %s", + (int)tileAttr, stdext::to_string(pos))); + } + } + } + + for(const BinaryTreePtr& nodeItem : nodeTile->getChildren()) { + if(unlikely(nodeItem->getU8() != OTBM_ITEM)) + stdext::throw_exception("invalid item node"); + + ItemPtr item = Item::createFromOtb(nodeItem->getU16()); + item->unserializeItem(nodeItem); + + if(item->isContainer()) { + for(const BinaryTreePtr& containerItem : nodeItem->getChildren()) { + if(containerItem->getU8() != OTBM_ITEM) + stdext::throw_exception("invalid container item node"); + + ItemPtr cItem = Item::createFromOtb(containerItem->getU16()); + cItem->unserializeItem(containerItem); + item->addContainerItem(cItem); + } + } + + if(house && item->isMoveable()) { + g_logger.warning(stdext::format("Moveable item found in house: %d at pos %s - escaping...", item->getId(), stdext::to_string(pos))); + item.reset(); + } + + addThing(item, pos); + } + + if(const TilePtr& tile = getTile(pos)) { + if(house) + tile->setFlag(TILESTATE_HOUSE); + tile->setFlag(flags); + } + } + } else if(mapDataType == OTBM_TOWNS) { + TownPtr town = nullptr; + for(const BinaryTreePtr &nodeTown : nodeMapData->getChildren()) { + if(nodeTown->getU8() != OTBM_TOWN) + stdext::throw_exception("invalid town node."); + + uint32 townId = nodeTown->getU32(); + std::string townName = nodeTown->getString(); + + Position townCoords; + townCoords.x = nodeTown->getU16(); + townCoords.y = nodeTown->getU16(); + townCoords.z = nodeTown->getU8(); + + if(!(town = g_towns.getTown(townId))) + g_towns.addTown(TownPtr(new Town(townId, townName, townCoords))); + } + g_towns.sort(); + } else if(mapDataType == OTBM_WAYPOINTS && headerVersion > 1) { + for(const BinaryTreePtr &nodeWaypoint : nodeMapData->getChildren()) { + if(nodeWaypoint->getU8() != OTBM_WAYPOINT) + stdext::throw_exception("invalid waypoint node."); + + std::string name = nodeWaypoint->getString(); + + Position waypointPos; + waypointPos.x = nodeWaypoint->getU16(); + waypointPos.y = nodeWaypoint->getU16(); + waypointPos.z = nodeWaypoint->getU8(); + + if(waypointPos.isValid() && !name.empty() && m_waypoints.find(waypointPos) == m_waypoints.end()) + m_waypoints.insert(std::make_pair(waypointPos, name)); + } + } else + stdext::throw_exception(stdext::format("Unknown map data node %d", (int)mapDataType)); + } + + fin->close(); + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to load '%s': %s", fileName, e.what())); + } +} + +void Map::saveOtbm(const std::string& fileName) +{ + try { + FileStreamPtr fin = g_resources.createFile(fileName); + if(!fin) + stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); + + std::string dir; + if(fileName.find_last_of('/') == std::string::npos) + dir = g_resources.getWorkDir(); + else + dir = fileName.substr(0, fileName.find_last_of('/')); + + uint32 version = 0; + if(g_things.getOtbMajorVersion() < ClientVersion820) + version = 1; + else + version = 2; + + /// Usually when a map has empty house/spawn file it means the map is new. + /// TODO: Ask the user for a map name instead of those ugly uses of substr + std::string::size_type sep_pos; + std::string houseFile = getHouseFile(); + std::string spawnFile = getSpawnFile(); + std::string cpyf; + + if((sep_pos = fileName.rfind('.')) != std::string::npos && stdext::ends_with(fileName, ".otbm")) + cpyf = fileName.substr(0, sep_pos); + + if(houseFile.empty()) + houseFile = cpyf + "-houses.xml"; + + if(spawnFile.empty()) + spawnFile = cpyf + "-spawns.xml"; + + /// we only need the filename to save to, the directory should be resolved by the OTBM loader not here + if((sep_pos = spawnFile.rfind('/')) != std::string::npos) + spawnFile = spawnFile.substr(sep_pos + 1); + + if((sep_pos = houseFile.rfind('/')) != std::string::npos) + houseFile = houseFile.substr(sep_pos + 1); + + fin->addU32(0); // file version + OutputBinaryTreePtr root(new OutputBinaryTree(fin)); + { + root->addU32(version); + + Size mapSize = getSize(); + root->addU16(mapSize.width()); + root->addU16(mapSize.height()); + + root->addU32(g_things.getOtbMajorVersion()); + root->addU32(g_things.getOtbMinorVersion()); + + root->startNode(OTBM_MAP_DATA); + { + root->addU8(OTBM_ATTR_DESCRIPTION); + root->addString(m_attribs.get(OTBM_ATTR_DESCRIPTION)); + + root->addU8(OTBM_ATTR_SPAWN_FILE); + root->addString(spawnFile); + + root->addU8(OTBM_ATTR_HOUSE_FILE); + root->addString(houseFile); + + int px = -1, py = -1, pz =-1; + bool firstNode = true; + + for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { + for(const auto& it : m_tileBlocks[z]) { + const TileBlock& block = it.second; + for(const TilePtr& tile : block.getTiles()) { + if(unlikely(!tile || tile->isEmpty())) + continue; + + const Position& pos = tile->getPosition(); + if(unlikely(!pos.isValid())) + continue; + + if(pos.x < px || pos.x >= px + 256 + || pos.y < py || pos.y >= py + 256 + || pos.z != pz) { + if(!firstNode) + root->endNode(); /// OTBM_TILE_AREA + + firstNode = false; + root->startNode(OTBM_TILE_AREA); + + px = pos.x & 0xFF00; + py = pos.y & 0xFF00; + pz = pos.z; + root->addPos(px, py, pz); + } + + root->startNode(tile->isHouseTile() ? OTBM_HOUSETILE : OTBM_TILE); + root->addPoint(Point(pos.x, pos.y) & 0xFF); + if(tile->isHouseTile()) + root->addU32(tile->getHouseId()); + + if(tile->getFlags()) { + root->addU8(OTBM_ATTR_TILE_FLAGS); + root->addU32(tile->getFlags()); + } + + const auto& itemList = tile->getItems(); + const ItemPtr& ground = tile->getGround(); + if(ground) { + // Those types are called "complex" needs other stuff to be written. + // For containers, there is container items, for depot, depot it and so on. + if(!ground->isContainer() && !ground->isDepot() + && !ground->isDoor() && !ground->isTeleport()) { + root->addU8(OTBM_ATTR_ITEM); + root->addU16(ground->getServerId()); + } else + ground->serializeItem(root); + } + for(const ItemPtr& item : itemList) + if(!item->isGround()) + item->serializeItem(root); + + root->endNode(); // OTBM_TILE + } + } + } + + if(!firstNode) + root->endNode(); // OTBM_TILE_AREA + + root->startNode(OTBM_TOWNS); + for(const TownPtr& town : g_towns.getTowns()) { + root->startNode(OTBM_TOWN); + + root->addU32(town->getId()); + root->addString(town->getName()); + + Position townPos = town->getPos(); + root->addPos(townPos.x, townPos.y, townPos.z); + root->endNode(); + } + root->endNode(); + + if(version > 1) { + root->startNode(OTBM_WAYPOINTS); + for(const auto& it : m_waypoints) { + root->startNode(OTBM_WAYPOINT); + root->addString(it.second); + + Position pos = it.first; + root->addPos(pos.x, pos.y, pos.z); + root->endNode(); + } + root->endNode(); + } + } + root->endNode(); // OTBM_MAP_DATA + } + root->endNode(); + + fin->flush(); + fin->close(); + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); + } +} + +bool Map::loadOtcm(const std::string& fileName) +{ + try { + FileStreamPtr fin = g_resources.openFile(fileName); + if(!fin) + stdext::throw_exception("unable to open file"); + + uint32 signature = fin->getU32(); + if(signature != OTCM_SIGNATURE) + stdext::throw_exception("invalid otcm file"); + + uint16 start = fin->getU16(); + uint16 version = fin->getU16(); + fin->getU32(); // flags + + switch(version) { + case 1: { + fin->getString(); // description + uint32 datSignature = fin->getU32(); + fin->getU16(); // protocol version + fin->getString(); // world name + + if(datSignature != g_things.getDatSignature()) + g_logger.warning("otcm map loaded was created with a different dat signature"); + + break; + } + default: + stdext::throw_exception("otcm version not supported"); + } + + fin->seek(start); + + while(true) { + Position pos; + + pos.x = fin->getU16(); + pos.y = fin->getU16(); + pos.z = fin->getU8(); + + // end of file + if(!pos.isValid()) + break; + + const TilePtr& tile = g_map.createTile(pos); + + int stackPos = 0; + while(true) { + int id = fin->getU16(); + + // end of tile + if(id == 0xFFFF) + break; + + int countOrSubType = fin->getU8(); + + ItemPtr item = Item::create(id); + item->setCountOrSubType(countOrSubType); + + if(item->isValid()) + tile->addThing(item, stackPos++); + } + + g_map.notificateTileUpdate(pos); + } + + fin->close(); + + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to load OTCM map: %s", e.what())); + return false; + } +} + +void Map::saveOtcm(const std::string& fileName) +{ + try { + stdext::timer saveTimer; + + FileStreamPtr fin = g_resources.createFile(fileName); + + //TODO: compression flag with zlib + uint32 flags = 0; + + // header + fin->addU32(OTCM_SIGNATURE); + fin->addU16(0); // data start, will be overwritten later + fin->addU16(OTCM_VERSION); + fin->addU32(flags); + + // version 1 header + fin->addString("OTCM 1.0"); // map description + fin->addU32(g_things.getDatSignature()); + fin->addU16(g_game.getClientVersion()); + fin->addString(g_game.getWorldName()); + + // go back and rewrite where the map data starts + uint32 start = fin->tell(); + fin->seek(4); + fin->addU16(start); + fin->seek(start); + + for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { + for(const auto& it : m_tileBlocks[z]) { + const TileBlock& block = it.second; + for(const TilePtr& tile : block.getTiles()) { + if(!tile || tile->isEmpty()) + continue; + + Position pos = tile->getPosition(); + fin->addU16(pos.x); + fin->addU16(pos.y); + fin->addU8(pos.z); + + for(const ThingPtr& thing : tile->getThings()) { + if(thing->isItem()) { + ItemPtr item = thing->static_self_cast(); + fin->addU16(item->getId()); + fin->addU8(item->getCountOrSubType()); + } + } + + // end of tile + fin->addU16(0xFFFF); + } + } + } + + // end of file + Position invalidPos; + fin->addU16(invalidPos.x); + fin->addU16(invalidPos.y); + fin->addU8(invalidPos.z); + + fin->flush(); + + fin->close(); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to save OTCM map: %s", e.what())); + } +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/client/mapview.cpp b/src/client/mapview.cpp new file mode 100644 index 0000000..fc90117 --- /dev/null +++ b/src/client/mapview.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "mapview.h" + +#include "creature.h" +#include "map.h" +#include "tile.h" +#include "statictext.h" +#include "animatedtext.h" +#include "missile.h" +#include "shadermanager.h" +#include "lightview.h" +#include "localplayer.h" +#include "game.h" +#include "spritemanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MapView::MapView() +{ + m_lockedFirstVisibleFloor = -1; + m_cachedFirstVisibleFloor = 7; + m_cachedLastVisibleFloor = 7; + m_fadeOutTime = 0; + m_fadeInTime = 0; + m_minimumAmbientLight = 0; + m_optimizedSize = Size(g_map.getAwareRange().horizontal(), g_map.getAwareRange().vertical()) * g_sprites.spriteSize(); + + setVisibleDimension(Size(15, 11)); +} + +MapView::~MapView() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif +} + +void MapView::drawTileTexts(const Rect& rect, const Rect& srcRect) +{ + Position cameraPosition = getCameraPosition(); + Point drawOffset = srcRect.topLeft(); + float horizontalStretchFactor = rect.width() / (float)srcRect.width(); + float verticalStretchFactor = rect.height() / (float)srcRect.height(); + + auto player = g_game.getLocalPlayer(); + for (auto& tile : m_cachedVisibleTiles) { + Position tilePos = tile.first->getPosition(); + if (tilePos.z != player->getPosition().z) continue; + + Point p = transformPositionTo2D(tilePos, cameraPosition) - drawOffset; + p.x *= horizontalStretchFactor; + p.y *= verticalStretchFactor; + p += rect.topLeft(); + p.y += 5; + + tile.first->drawTexts(p); + } +} + + +void MapView::drawBackground(const Rect& rect, const TilePtr& crosshairTile) { + Position cameraPosition = getCameraPosition(); + if (m_mustUpdateVisibleTilesCache) { + updateVisibleTilesCache(); + } + + if (g_game.getFeature(Otc::GameForceLight)) { + m_drawLight = true; + m_minimumAmbientLight = 0.05f; + } + + Rect srcRect = calcFramebufferSource(rect.size()); + g_drawQueue->setFrameBuffer(rect, m_optimizedSize, srcRect); + + if (m_drawLight) { + Light ambientLight; + if (cameraPosition.z <= Otc::SEA_FLOOR) + ambientLight = g_map.getLight(); + if (!m_lightTexture || m_lightTexture->getSize() != m_drawDimension) + m_lightTexture = TexturePtr(new Texture(m_drawDimension, false, true)); + m_lightView = std::make_unique(m_lightTexture, m_drawDimension, rect, srcRect, ambientLight.color, + std::max(m_minimumAmbientLight * 255, ambientLight.intensity)); + } + + auto itm = m_cachedVisibleTiles.begin(); + auto end = m_cachedVisibleTiles.end(); + for (int z = m_cachedLastVisibleFloor; z >= m_cachedFirstFadingFloor; --z) { + float fading = 1.0; + if (m_floorFading > 0) { + fading = 0.; + if (m_floorFading > 0) { + fading = stdext::clamp((float)m_fadingFloorTimers[z].elapsed_millis() / (float)m_floorFading, 0.f, 1.f); + if (z < m_cachedFirstVisibleFloor) + fading = 1.0 - fading; + } + if (fading == 0) break; + } + + size_t floorStart = g_drawQueue->size(); + size_t lightFloorStart = m_lightView ? m_lightView->size() : 0; + for (; itm != end; ++itm) { + Position tilePos = itm->first->getPosition(); + if (tilePos.z != z) + break; + + + Point tileDrawPos = transformPositionTo2D(tilePos, cameraPosition); + + if (m_lightView) { + ItemPtr ground = itm->first->getGround(); + if (ground && ground->isGround() && !ground->isTranslucent()) { + m_lightView->setFieldBrightness(tileDrawPos, lightFloorStart, 0); + } + } + + itm->first->drawBottom(tileDrawPos, m_lightView.get()); + if (m_crosshair && itm->first == crosshairTile) + { + g_drawQueue->addTexturedRect(Rect(tileDrawPos, tileDrawPos + Otc::TILE_PIXELS - 1), + m_crosshair, Rect(0, 0, m_crosshair->getSize())); + } + itm->first->drawTop(tileDrawPos, m_lightView.get()); + } + for (const MissilePtr& missile : g_map.getFloorMissiles(z)) { + missile->draw(transformPositionTo2D(missile->getPosition(), cameraPosition), true, m_lightView.get()); + } + if (fading < 0.99) + g_drawQueue->setOpacity(floorStart, fading); + } + +} + +void MapView::drawForeground(const Rect& rect) +{ + // this could happen if the player position is not known yet + Position cameraPosition = getCameraPosition(); + if (!cameraPosition.isValid()) + return; + + Rect srcRect = calcFramebufferSource(rect.size()); + Point drawOffset = srcRect.topLeft(); + float horizontalStretchFactor = rect.width() / (float)srcRect.width(); + float verticalStretchFactor = rect.height() / (float)srcRect.height(); + + // creatures + std::vector> creatures; + for (const CreaturePtr& creature : g_map.getSpectatorsInRangeEx(cameraPosition, false, m_visibleDimension.width() / 2, m_visibleDimension.width() / 2 + 1, m_visibleDimension.height() / 2, m_visibleDimension.height() / 2 + 1)) { + if (!creature->canBeSeen()) + continue; + + PointF jumpOffset = creature->getJumpOffset(); + Point creatureOffset = Point(16 - creature->getDisplacementX(), -creature->getDisplacementY() - 2); + Position pos = creature->getPrewalkingPosition(); + Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset; + p += (creature->getDrawOffset() + creatureOffset) - Point(stdext::round(jumpOffset.x), stdext::round(jumpOffset.y)); + p.x = p.x * horizontalStretchFactor; + p.y = p.y * verticalStretchFactor; + p += rect.topLeft(); + creatures.push_back(std::make_pair(creature, p)); + } + + for (auto& c : creatures) { + int flags = Otc::DrawIcons; + if (m_drawNames) { flags |= Otc::DrawNames; } + if ((!c.first->isLocalPlayer() || m_drawPlayerBars) && !m_drawHealthBarsOnTop) { + if (m_drawHealthBars) { flags |= Otc::DrawBars; } + if (m_drawManaBar) { flags |= Otc::DrawManaBar; } + } + c.first->drawInformation(c.second, g_map.isCovered(c.first->getPrewalkingPosition(), m_cachedFirstVisibleFloor), rect, flags); + } + + if (m_lightView) { + g_drawQueue->add(m_lightView.release()); + } + + // texts + int limit = g_adaptiveRenderer.textsLimit(); + for (int i = 0; i < 2; ++i) { + for (const StaticTextPtr& staticText : g_map.getStaticTexts()) { + Position pos = staticText->getPosition(); + + if (pos.z != cameraPosition.z && staticText->getMessageMode() == Otc::MessageNone) + continue; + if ((staticText->getMessageMode() != Otc::MessageSay && staticText->getMessageMode() != Otc::MessageYell)) { + if (i == 0) + continue; + } else if (i == 1) + continue; + + Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset + Point(8, 0); + p.x *= horizontalStretchFactor; + p.y *= verticalStretchFactor; + p += rect.topLeft(); + staticText->drawText(p, rect); + if (--limit == 0) + break; + } + } + + limit = g_adaptiveRenderer.textsLimit(); + for (const AnimatedTextPtr& animatedText : g_map.getAnimatedTexts()) { + Position pos = animatedText->getPosition(); + + if (pos.z != cameraPosition.z) + continue; + + Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset + Point(16, 8); + p.x *= horizontalStretchFactor; + p.y *= verticalStretchFactor; + p += rect.topLeft(); + animatedText->drawText(p, rect); + if (--limit == 0) + break; + } + + // tile texts + drawTileTexts(rect, srcRect); + + // bars on top + if (m_drawHealthBarsOnTop) { + for (auto& c : creatures) { + int flags = 0; + if ((!c.first->isLocalPlayer() || m_drawPlayerBars)) { + if (m_drawHealthBars) { flags |= Otc::DrawBars; } + if (m_drawManaBar) { flags |= Otc::DrawManaBar; } + } + c.first->drawInformation(c.second, g_map.isCovered(c.first->getPrewalkingPosition(), m_cachedFirstVisibleFloor), rect, flags); + } + } +} + + +void MapView::updateVisibleTilesCache() +{ + // hidden +} + +void MapView::updateGeometry(const Size& visibleDimension, const Size& optimizedSize) +{ + m_multifloor = true; + m_visibleDimension = visibleDimension; + m_drawDimension = visibleDimension + Size(3, 3); + m_virtualCenterOffset = (m_drawDimension / 2 - Size(1, 1)).toPoint(); + m_visibleCenterOffset = m_virtualCenterOffset; + m_optimizedSize = m_drawDimension * g_sprites.spriteSize(); + requestVisibleTilesCacheUpdate(); +} + +void MapView::onTileUpdate(const Position& pos) +{ + requestVisibleTilesCacheUpdate(); +} + +void MapView::onMapCenterChange(const Position& pos) +{ + requestVisibleTilesCacheUpdate(); +} + +void MapView::lockFirstVisibleFloor(int firstVisibleFloor) +{ + m_lockedFirstVisibleFloor = firstVisibleFloor; + requestVisibleTilesCacheUpdate(); +} + +void MapView::unlockFirstVisibleFloor() +{ + m_lockedFirstVisibleFloor = -1; + requestVisibleTilesCacheUpdate(); +} + +void MapView::setVisibleDimension(const Size& visibleDimension) +{ + //if(visibleDimension == m_visibleDimension) + // return; + + if(visibleDimension.width() % 2 != 1 || visibleDimension.height() % 2 != 1) { + g_logger.traceError("visible dimension must be odd"); + return; + } + + if(visibleDimension < Size(3,3)) { + g_logger.traceError("reach max zoom in"); + return; + } + + updateGeometry(visibleDimension, m_optimizedSize); +} + +void MapView::optimizeForSize(const Size& visibleSize) +{ + updateGeometry(m_visibleDimension, visibleSize); +} + +void MapView::followCreature(const CreaturePtr& creature) +{ + m_follow = true; + m_followingCreature = creature; + requestVisibleTilesCacheUpdate(); +} + +void MapView::setCameraPosition(const Position& pos) +{ + m_follow = false; + m_customCameraPosition = pos; + requestVisibleTilesCacheUpdate(); +} + +Position MapView::getPosition(const Point& point, const Size& mapSize) +{ + Position cameraPosition = getCameraPosition(); + + // if we have no camera, its impossible to get the tile + if(!cameraPosition.isValid()) + return Position(); + + Rect srcRect = calcFramebufferSource(mapSize); + float sh = srcRect.width() / (float)mapSize.width(); + float sv = srcRect.height() / (float)mapSize.height(); + + Point framebufferPos = Point(point.x * sh, point.y * sv); + Point realPos = (framebufferPos + srcRect.topLeft()); + Point centerOffset = realPos / Otc::TILE_PIXELS; + + Point tilePos2D = getVisibleCenterOffset() - m_drawDimension.toPoint() + centerOffset + Point(2,2); + if(tilePos2D.x + cameraPosition.x < 0 && tilePos2D.y + cameraPosition.y < 0) + return Position(); + + Position position = Position(tilePos2D.x, tilePos2D.y, 0) + cameraPosition; + + if(!position.isValid()) + return Position(); + + return position; +} + +Point MapView::getPositionOffset(const Point& point, const Size& mapSize) +{ + Position cameraPosition = getCameraPosition(); + + // if we have no camera, its impossible to get the tile + if (!cameraPosition.isValid()) + return Point(0, 0); + + Rect srcRect = calcFramebufferSource(mapSize); + float sh = srcRect.width() / (float)mapSize.width(); + float sv = srcRect.height() / (float)mapSize.height(); + + Point framebufferPos = Point(point.x * sh, point.y * sv); + Point realPos = (framebufferPos + srcRect.topLeft()); + return Point(realPos.x % Otc::TILE_PIXELS, realPos.y % Otc::TILE_PIXELS); +} + +void MapView::move(int x, int y) +{ + m_moveOffset.x += x; + m_moveOffset.y += y; + + int32_t tmp = m_moveOffset.x / g_sprites.spriteSize(); + bool requestTilesUpdate = false; + if(tmp != 0) { + m_customCameraPosition.x += tmp; + m_moveOffset.x %= g_sprites.spriteSize(); + requestTilesUpdate = true; + } + + tmp = m_moveOffset.y / g_sprites.spriteSize(); + if(tmp != 0) { + m_customCameraPosition.y += tmp; + m_moveOffset.y %= g_sprites.spriteSize(); + requestTilesUpdate = true; + } + + if(requestTilesUpdate) + requestVisibleTilesCacheUpdate(); +} + +Rect MapView::calcFramebufferSource(const Size& destSize, bool inNextFrame) +{ + float scaleFactor = g_sprites.spriteSize()/(float)Otc::TILE_PIXELS; + Point drawOffset = ((m_drawDimension - m_visibleDimension - Size(1,1)).toPoint()/2) * g_sprites.spriteSize(); + if(isFollowingCreature()) + drawOffset += m_followingCreature->getWalkOffset(inNextFrame) * scaleFactor; + + Size srcSize = destSize; + Size srcVisible = m_visibleDimension * g_sprites.spriteSize(); + srcSize.scale(srcVisible, Fw::KeepAspectRatio); + drawOffset.x += (srcVisible.width() - srcSize.width()) / 2; + drawOffset.y += (srcVisible.height() - srcSize.height()) / 2; + + return Rect(drawOffset, srcSize); +} + +int MapView::calcFirstVisibleFloor(bool forFading) +{ + int z = 7; + // return forced first visible floor + if(m_lockedFirstVisibleFloor != -1) { + z = m_lockedFirstVisibleFloor; + } else { + Position cameraPosition = getCameraPosition(); + + // this could happens if the player is not known yet + if(cameraPosition.isValid()) { + // avoid rendering multifloors in far views + if(!m_multifloor) { + z = cameraPosition.z; + } else { + // if nothing is limiting the view, the first visible floor is 0 + int firstFloor = 0; + + // limits to underground floors while under sea level + if(cameraPosition.z > Otc::SEA_FLOOR) + firstFloor = std::max(cameraPosition.z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE, (int)Otc::UNDERGROUND_FLOOR); + + // loop in 3x3 tiles around the camera + for(int ix = -1; ix <= 1 && firstFloor < cameraPosition.z && !forFading; ++ix) { + for(int iy = -1; iy <= 1 && firstFloor < cameraPosition.z; ++iy) { + Position pos = cameraPosition.translated(ix, iy); + + // process tiles that we can look through, e.g. windows, doors + if((ix == 0 && iy == 0) || ((std::abs(ix) != std::abs(iy)) && g_map.isLookPossible(pos))) { + Position upperPos = pos; + Position coveredPos = pos; + + while(coveredPos.coveredUp() && upperPos.up() && upperPos.z >= firstFloor) { + // check tiles physically above + TilePtr tile = g_map.getTile(upperPos); + if(tile && tile->limitsFloorsView(!g_map.isLookPossible(pos))) { + firstFloor = upperPos.z + 1; + break; + } + + // check tiles geometrically above + tile = g_map.getTile(coveredPos); + if(tile && tile->limitsFloorsView(g_map.isLookPossible(pos))) { + firstFloor = coveredPos.z + 1; + break; + } + } + } + } + } + z = firstFloor; + } + } + } + + // just ensure the that the floor is in the valid range + z = stdext::clamp(z, 0, (int)Otc::MAX_Z); + return z; +} + +int MapView::calcLastVisibleFloor() +{ + if(!m_multifloor) + return calcFirstVisibleFloor(); + + int z = 7; + + Position cameraPosition = getCameraPosition(); + // this could happens if the player is not known yet + if(cameraPosition.isValid()) { + // view only underground floors when below sea level + if(cameraPosition.z > Otc::SEA_FLOOR) + z = cameraPosition.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE; + else + z = Otc::SEA_FLOOR; + } + + if(m_lockedFirstVisibleFloor != -1) + z = std::max(m_lockedFirstVisibleFloor, z); + + // just ensure the that the floor is in the valid range + z = stdext::clamp(z, 0, (int)Otc::MAX_Z); + return z; +} + +Point MapView::transformPositionTo2D(const Position& position, const Position& relativePosition) { + return Point((m_virtualCenterOffset.x + (position.x - relativePosition.x) - (relativePosition.z - position.z)) * g_sprites.spriteSize(), + (m_virtualCenterOffset.y + (position.y - relativePosition.y) - (relativePosition.z - position.z)) * g_sprites.spriteSize()); +} + + +Position MapView::getCameraPosition() +{ + if (isFollowingCreature()) { + return m_followingCreature->getPrewalkingPosition(); + } + + return m_customCameraPosition; +} + +void MapView::setDrawLights(bool enable) +{ + m_drawLight = enable; +} + +void MapView::setCrosshair(const std::string& file) +{ + if (file == "") + m_crosshair = nullptr; + else + m_crosshair = g_textures.getTexture(file); +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/client/mapview.h b/src/client/mapview.h new file mode 100644 index 0000000..f9a1e8e --- /dev/null +++ b/src/client/mapview.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MAPVIEW_H +#define MAPVIEW_H + +#include "declarations.h" +#include +#include +#include +#include +#include "lightview.h" + +// @bindclass +class MapView : public LuaObject +{ +public: + MapView(); + ~MapView(); + void drawBackground(const Rect& rect, const TilePtr& crosshairTile = nullptr); + void drawForeground(const Rect& rect); + void drawTexts(const Rect& rect, const Rect& srcRect); + +private: + void drawTileTexts(const Rect& rect, const Rect& srcRect); + void updateGeometry(const Size& visibleDimension, const Size& optimizedSize); + void updateVisibleTilesCache(); + void requestVisibleTilesCacheUpdate() { m_mustUpdateVisibleTilesCache = true; } + +protected: + void onTileUpdate(const Position& pos); + void onMapCenterChange(const Position& pos); + + friend class Map; + +public: + // floor visibility related + void lockFirstVisibleFloor(int firstVisibleFloor); + void unlockFirstVisibleFloor(); + int getLockedFirstVisibleFloor() { return m_lockedFirstVisibleFloor; } + + void setMultifloor(bool enable) { m_multifloor = enable; requestVisibleTilesCacheUpdate(); } + bool isMultifloor() { return m_multifloor; } + + // map dimension related + void setVisibleDimension(const Size& visibleDimension); + void optimizeForSize(const Size & visibleSize); + Size getVisibleDimension() { return m_visibleDimension; } + Point getVisibleCenterOffset() { return m_visibleCenterOffset; } + int getCachedFirstVisibleFloor() { return m_cachedFirstVisibleFloor; } + int getCachedLastVisibleFloor() { return m_cachedLastVisibleFloor; } + + // camera related + void followCreature(const CreaturePtr& creature); + CreaturePtr getFollowingCreature() { return m_followingCreature; } + bool isFollowingCreature() { return m_followingCreature && m_follow; } + + void setCameraPosition(const Position& pos); + Position getCameraPosition(); + + void setMinimumAmbientLight(float intensity) { m_minimumAmbientLight = intensity; } + float getMinimumAmbientLight() { return m_minimumAmbientLight; } + + // drawing related + void setDrawFlags(Otc::DrawFlags drawFlags) { m_drawFlags = drawFlags; requestVisibleTilesCacheUpdate(); } + Otc::DrawFlags getDrawFlags() { return m_drawFlags; } + + void setDrawTexts(bool enable) { m_drawTexts = enable; } + bool isDrawingTexts() { return m_drawTexts; } + + void setDrawNames(bool enable) { m_drawNames = enable; } + bool isDrawingNames() { return m_drawNames; } + + void setDrawHealthBars(bool enable) { m_drawHealthBars = enable; } + bool isDrawingHealthBars() { return m_drawHealthBars; } + + void setDrawHealthBarsOnTop(bool enable) { m_drawHealthBarsOnTop = enable; } + bool isDrawingHealthBarsOnTop() { return m_drawHealthBarsOnTop; } + + void setDrawLights(bool enable); + bool isDrawingLights() { return m_drawLight; } + + void setDrawManaBar(bool enable) { m_drawManaBar = enable; } + bool isDrawingManaBar() { return m_drawManaBar; } + + void setDrawPlayerBars(bool enable) { m_drawPlayerBars = enable; } + + void move(int x, int y); + + void setAnimated(bool animated) { m_animated = animated; requestVisibleTilesCacheUpdate(); } + bool isAnimating() { return m_animated; } + + void setFloorFading(int value) { m_floorFading = value; } + void setCrosshair(const std::string& file); + + //void setShader(const PainterShaderProgramPtr& shader, float fadein, float fadeout); + //PainterShaderProgramPtr getShader() { return m_shader; } + + Position getPosition(const Point& point, const Size& mapSize); + + Point getPositionOffset(const Point& point, const Size& mapSize); + + MapViewPtr asMapView() { return static_self_cast(); } + +private: + Rect calcFramebufferSource(const Size& destSize, bool inNextFrame = false); + int calcFirstVisibleFloor(bool forFading = false); + int calcLastVisibleFloor(); + Point transformPositionTo2D(const Position& position, const Position& relativePosition); + + stdext::timer m_mapRenderTimer; + + int m_lockedFirstVisibleFloor; + int m_cachedFirstVisibleFloor; + int m_cachedFirstFadingFloor; + int m_cachedLastVisibleFloor; + int m_updateTilesPos; + int m_floorFading = 500; + TexturePtr m_crosshair = nullptr; + Size m_drawDimension; + Size m_visibleDimension; + Size m_optimizedSize; + Point m_virtualCenterOffset; + Point m_visibleCenterOffset; + Point m_moveOffset; + Position m_customCameraPosition; + Position m_lastCameraPosition; + stdext::boolean m_mustUpdateVisibleTilesCache; + stdext::boolean m_mustDrawVisibleTilesCache; + stdext::boolean m_multifloor; + stdext::boolean m_animated; + stdext::boolean m_drawTexts; + stdext::boolean m_drawNames; + stdext::boolean m_drawHealthBars; + stdext::boolean m_drawHealthBarsOnTop; + stdext::boolean m_drawManaBar; + bool m_drawPlayerBars = true; + stdext::boolean m_smooth; + + stdext::timer m_fadingFloorTimers[Otc::MAX_Z + 1]; + + stdext::boolean m_follow; + std::vector> m_cachedVisibleTiles; + CreaturePtr m_followingCreature; + //PainterShaderProgramPtr m_shader; + Otc::DrawFlags m_drawFlags; + bool m_drawLight = false; + float m_minimumAmbientLight; + std::unique_ptr m_lightView; + TexturePtr m_lightTexture; + Timer m_fadeTimer; + //PainterShaderProgramPtr m_nextShader; + float m_fadeInTime; + float m_fadeOutTime; + //stdext::boolean m_shaderSwitchDone; +}; + +#endif diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp new file mode 100644 index 0000000..d02434a --- /dev/null +++ b/src/client/minimap.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "minimap.h" +#include "tile.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Minimap g_minimap; + +void MinimapBlock::clean() +{ + m_tiles.fill(MinimapTile()); + m_texture.reset(); + m_mustUpdate = false; +} + +void MinimapBlock::update() +{ + if(!m_mustUpdate) + return; + + ImagePtr image(new Image(Size(MMBLOCK_SIZE, MMBLOCK_SIZE))); + + bool shouldDraw = false; + for(int x=0;xsetPixel(x, y, col); + } + } + + if(shouldDraw) { + m_texture = TexturePtr(new Texture(image)); + } else + m_texture.reset(); + + m_mustUpdate = false; +} + +void MinimapBlock::updateTile(int x, int y, const MinimapTile& tile) +{ + if(m_tiles[getTileIndex(x,y)].color != tile.color) + m_mustUpdate = true; + + m_tiles[getTileIndex(x,y)] = tile; +} + +void Minimap::init() +{ +} + +void Minimap::terminate() +{ + clean(); +} + +void Minimap::clean() +{ + std::lock_guard lock(m_lock); + for(int i=0;i<=Otc::MAX_Z;++i) + m_tileBlocks[i].clear(); +} + +void Minimap::draw(const Rect& screenRect, const Position& mapCenter, float scale, const Color& color) +{ + if(screenRect.isEmpty()) + return; + + Rect mapRect = calcMapRect(screenRect, mapCenter, scale); + g_drawQueue->addFilledRect(screenRect, color); + + if(MMBLOCK_SIZE*scale <= 1 || !mapCenter.isMapPosition()) { + return; + } + + size_t drawQueueStart = g_drawQueue->size(); + Point blockOff = getBlockOffset(mapRect.topLeft()); + Point off = Point((mapRect.size() * scale).toPoint() - screenRect.size().toPoint())/2; + Point start = screenRect.topLeft() -(mapRect.topLeft() - blockOff)*scale - off; + + for(int y = blockOff.y, ys = start.y;ys= 65536) + continue; + + for(int x = blockOff.x, xs = start.x;xs= 65536) + continue; + + Position blockPos(x, y, mapCenter.z); + if(!hasBlock(blockPos)) + continue; + + MinimapBlock& block = getBlock(Position(x, y, mapCenter.z)); + block.update(); + + const TexturePtr& tex = block.getTexture(); + if(tex) { + Rect src(0, 0, MMBLOCK_SIZE, MMBLOCK_SIZE); + Rect dest(xs, ys, MMBLOCK_SIZE * scale, MMBLOCK_SIZE * scale); + + g_drawQueue->addTexturedRect(dest, tex, src); + } + } + } + + g_drawQueue->setClip(drawQueueStart, screenRect); +} + +Point Minimap::getTilePoint(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale) +{ + if(screenRect.isEmpty() || pos.z != mapCenter.z) + return Point(-1,-1); + + Rect mapRect = calcMapRect(screenRect, mapCenter, scale); + Point off = Point((mapRect.size() * scale).toPoint() - screenRect.size().toPoint())/2; + Point posoff = (Point(pos.x,pos.y) - mapRect.topLeft())*scale; + return posoff + screenRect.topLeft() - off + (Point(1,1)*scale)/2; +} + +Position Minimap::getTilePosition(const Point& point, const Rect& screenRect, const Position& mapCenter, float scale) +{ + if(screenRect.isEmpty()) + return Position(); + + Rect mapRect = calcMapRect(screenRect, mapCenter, scale); + Point off = Point((mapRect.size() * scale).toPoint() - screenRect.size().toPoint())/2; + Point pos2d = (point - screenRect.topLeft() + off)/scale + mapRect.topLeft(); + return Position(pos2d.x, pos2d.y, mapCenter.z); +} + +Rect Minimap::getTileRect(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale) +{ + if(screenRect.isEmpty() || pos.z != mapCenter.z) + return Rect(); + + int tileSize = 32 * scale; + Rect tileRect(0,0,tileSize, tileSize); + tileRect.moveCenter(getTilePoint(pos, screenRect, mapCenter, scale)); + return tileRect; +} + +Rect Minimap::calcMapRect(const Rect& screenRect, const Position& mapCenter, float scale) +{ + int w = screenRect.width() / scale, h = std::ceil(screenRect.height() / scale); + Rect mapRect(0,0,w,h); + mapRect.moveCenter(Point(mapCenter.x, mapCenter.y)); + return mapRect; +} + +void Minimap::updateTile(const Position& pos, const TilePtr& tile) +{ + MinimapTile minimapTile; + if(tile) { + minimapTile.color = tile->getMinimapColorByte(); + minimapTile.flags |= MinimapTileWasSeen; + if(!tile->isWalkable(true)) + minimapTile.flags |= MinimapTileNotWalkable; + if(!tile->isPathable()) + minimapTile.flags |= MinimapTileNotPathable; + minimapTile.speed = std::min((int)std::ceil(tile->getGroundSpeed() / 10.0f), 255); + } else { + minimapTile.color = 255; + minimapTile.flags |= MinimapTileEmpty; + minimapTile.speed = 1; + } + + if(minimapTile != MinimapTile()) { + MinimapBlock& block = getBlock(pos); + Point offsetPos = getBlockOffset(Point(pos.x, pos.y)); + block.updateTile(pos.x - offsetPos.x, pos.y - offsetPos.y, minimapTile); + block.justSaw(); + } +} + +const MinimapTile& Minimap::getTile(const Position& pos) +{ + static MinimapTile nulltile; + if(pos.z <= Otc::MAX_Z && hasBlock(pos)) { + MinimapBlock& block = getBlock(pos); + Point offsetPos = getBlockOffset(Point(pos.x, pos.y)); + return block.getTile(pos.x - offsetPos.x, pos.y - offsetPos.y); + } + return nulltile; +} + +std::pair Minimap::threadGetTile(const Position& pos) { + std::lock_guard lock(m_lock); + static MinimapTile nulltile; + + if (pos.z <= Otc::MAX_Z && hasBlock(pos)) { + MinimapBlock_ptr block = m_tileBlocks[pos.z][getBlockIndex(pos)]; + if (block) { + Point offsetPos = getBlockOffset(Point(pos.x, pos.y)); + return std::make_pair(block, block->getTile(pos.x - offsetPos.x, pos.y - offsetPos.y)); + } + } + return std::make_pair(nullptr, nulltile); +} + +bool Minimap::loadImage(const std::string& fileName, const Position& topLeft, float colorFactor) +{ + if(colorFactor <= 0.01f) + colorFactor = 1.0f; + + try { + ImagePtr image = Image::load(fileName); + + uint8 waterc = Color::to8bit(std::string("#3300cc")); + + // non pathable colors + Color nonPathableColors[] = { + std::string("#ffff00"), // yellow + }; + + // non walkable colors + Color nonWalkableColors[] = { + std::string("#000000"), // oil, black + std::string("#006600"), // trees, dark green + std::string("#ff3300"), // walls, red + std::string("#666666"), // mountain, grey + std::string("#ff6600"), // lava, orange + std::string("#00ff00"), // positon + std::string("#ccffff"), // ice, very light blue + }; + + for(int y=0;ygetHeight();++y) { + for(int x=0;xgetWidth();++x) { + Color color = *(uint32*)image->getPixel(x,y); + uint8 c = Color::to8bit(color * colorFactor); + int flags = 0; + + if(c == waterc || color.a() == 0) { + flags |= MinimapTileNotWalkable; + c = 255; // alpha + } + + if(flags != 0) { + for(Color &col : nonWalkableColors) { + if(col == color) { + flags |= MinimapTileNotWalkable; + break; + } + } + } + + if(flags != 0) { + for(Color &col : nonPathableColors) { + if(col == color) { + flags |= MinimapTileNotPathable; + break; + } + } + } + + if(c == 255) + continue; + + Position pos(topLeft.x + x, topLeft.y + y, topLeft.z); + MinimapBlock& block = getBlock(pos); + Point offsetPos = getBlockOffset(Point(pos.x, pos.y)); + MinimapTile& tile = block.getTile(pos.x - offsetPos.x, pos.y - offsetPos.y); + if(!(tile.flags & MinimapTileWasSeen)) { + tile.color = c; + tile.flags = flags; + block.mustUpdate(); + } + } + } + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to load OTMM minimap: %s", e.what())); + return false; + } +} + +void Minimap::saveImage(const std::string& fileName, const Rect& mapRect) +{ + //TODO +} + +bool Minimap::loadOtmm(const std::string& fileName) +{ + try { + FileStreamPtr fin = g_resources.openFile(fileName); + if(!fin) + stdext::throw_exception("unable to open file"); + + uint32 signature = fin->getU32(); + if(signature != OTMM_SIGNATURE) + stdext::throw_exception("invalid OTMM file"); + + uint16 start = fin->getU16(); + uint16 version = fin->getU16(); + fin->getU32(); // flags + + switch(version) { + case 1: { + fin->getString(); // description + break; + } + default: + stdext::throw_exception("OTMM version not supported"); + } + + fin->seek(start); + + uint blockSize = MMBLOCK_SIZE * MMBLOCK_SIZE * sizeof(MinimapTile); + std::vector compressBuffer(compressBound(blockSize)); + std::vector decompressBuffer(blockSize); + + while(true) { + Position pos; + pos.x = fin->getU16(); + pos.y = fin->getU16(); + pos.z = fin->getU8(); + + // end of file or file is corrupted + if(!pos.isValid() || pos.z >= Otc::MAX_Z+1) + break; + + MinimapBlock& block = getBlock(pos); + ulong len = fin->getU16(); + ulong destLen = blockSize; + fin->read(compressBuffer.data(), len); + int ret = uncompress(decompressBuffer.data(), &destLen, compressBuffer.data(), len); + if(ret != Z_OK || destLen != blockSize) + break; + + memcpy((uchar*)&block.getTiles(), decompressBuffer.data(), blockSize); + block.mustUpdate(); + block.justSaw(); + } + + fin->close(); + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to load OTMM minimap: %s", e.what())); + return false; + } +} + +void Minimap::saveOtmm(const std::string& fileName) +{ + try { + stdext::timer saveTimer; + + FileStreamPtr fin = g_resources.createFile(fileName); + + //TODO: compression flag with zlib + uint32 flags = 0; + + // header + fin->addU32(OTMM_SIGNATURE); + fin->addU16(0); // data start, will be overwritten later + fin->addU16(OTMM_VERSION); + fin->addU32(flags); + + // version 1 header + fin->addString("OTMM 1.0"); // description + + // go back and rewrite where the map data starts + uint32 start = fin->tell(); + fin->seek(4); + fin->addU16(start); + fin->seek(start); + + uint blockSize = MMBLOCK_SIZE * MMBLOCK_SIZE * sizeof(MinimapTile); + std::vector compressBuffer(compressBound(blockSize)); + const int COMPRESS_LEVEL = 3; + + for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { + for(auto& it : m_tileBlocks[z]) { + int index = it.first; + MinimapBlock& block = *it.second; + if(!block.wasSeen()) + continue; + + Position pos = getIndexPosition(index, z); + fin->addU16(pos.x); + fin->addU16(pos.y); + fin->addU8(pos.z); + + ulong len = blockSize; + int ret = compress2(compressBuffer.data(), &len, (uchar*)&block.getTiles(), blockSize, COMPRESS_LEVEL); + VALIDATE(ret == Z_OK); + fin->addU16(len); + fin->write(compressBuffer.data(), len); + } + } + + // end of file + Position invalidPos; + fin->addU16(invalidPos.x); + fin->addU16(invalidPos.y); + fin->addU8(invalidPos.z); + + fin->flush(); + + fin->close(); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to save OTMM minimap: %s", e.what())); + } +} diff --git a/src/client/minimap.h b/src/client/minimap.h new file mode 100644 index 0000000..fbffc50 --- /dev/null +++ b/src/client/minimap.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef MINIMAP_H +#define MINIMAP_H + +#include "declarations.h" +#include + +enum { + MMBLOCK_SIZE = 64, + OTMM_SIGNATURE = 0x4D4d544F, + OTMM_VERSION = 1 +}; + +enum MinimapTileFlags { + MinimapTileWasSeen = 1, + MinimapTileNotPathable = 2, + MinimapTileNotWalkable = 4, + MinimapTileEmpty = 8 +}; + +#pragma pack(push,1) // disable memory alignment +struct MinimapTile +{ + MinimapTile() : flags(0), color(255), speed(10) { } + uint8 flags; + uint8 color; + uint8 speed; + bool hasFlag(MinimapTileFlags flag) const { return flags & flag; } + int getSpeed() const { return speed * 10; } + bool operator==(const MinimapTile& other) const { return color == other.color && flags == other.flags && speed == other.speed; } + bool operator!=(const MinimapTile& other) const { return !(*this == other); } +}; + +class MinimapBlock +{ +public: + void clean(); + void update(); + void updateTile(int x, int y, const MinimapTile& tile); + MinimapTile& getTile(int x, int y) { return m_tiles[getTileIndex(x,y)]; } + void resetTile(int x, int y) { m_tiles[getTileIndex(x,y)] = MinimapTile(); } + uint getTileIndex(int x, int y) { return ((y % MMBLOCK_SIZE) * MMBLOCK_SIZE) + (x % MMBLOCK_SIZE); } + const TexturePtr& getTexture() { return m_texture; } + std::array& getTiles() { return m_tiles; } + void mustUpdate() { m_mustUpdate = true; } + void justSaw() { m_wasSeen = true; } + bool wasSeen() { return m_wasSeen; } +private: + TexturePtr m_texture; + std::array m_tiles; + stdext::boolean m_mustUpdate; + stdext::boolean m_wasSeen; +}; + +#pragma pack(pop) + +using MinimapBlock_ptr = std::shared_ptr; + +class Minimap +{ + +public: + void init(); + void terminate(); + + void clean(); + + void draw(const Rect& screenRect, const Position& mapCenter, float scale, const Color& color); + Point getTilePoint(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale); + Position getTilePosition(const Point& point, const Rect& screenRect, const Position& mapCenter, float scale); + Rect getTileRect(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale); + + void updateTile(const Position& pos, const TilePtr& tile); + const MinimapTile& getTile(const Position& pos); + std::pair threadGetTile(const Position& pos); + + bool loadImage(const std::string& fileName, const Position& topLeft, float colorFactor); + void saveImage(const std::string& fileName, const Rect& mapRect); + bool loadOtmm(const std::string& fileName); + void saveOtmm(const std::string& fileName); + +private: + Rect calcMapRect(const Rect& screenRect, const Position& mapCenter, float scale); + bool hasBlock(const Position& pos) { return m_tileBlocks[pos.z].find(getBlockIndex(pos)) != m_tileBlocks[pos.z].end(); } + MinimapBlock& getBlock(const Position& pos) { + std::lock_guard lock(m_lock); + auto& ptr = m_tileBlocks[pos.z][getBlockIndex(pos)]; + if (!ptr) + ptr = std::make_shared(); + return *ptr; + } + Point getBlockOffset(const Point& pos) { return Point(pos.x - pos.x % MMBLOCK_SIZE, + pos.y - pos.y % MMBLOCK_SIZE); } + Position getIndexPosition(int index, int z) { return Position((index % (65536 / MMBLOCK_SIZE))*MMBLOCK_SIZE, + (index / (65536 / MMBLOCK_SIZE))*MMBLOCK_SIZE, z); } + uint getBlockIndex(const Position& pos) { return ((pos.y / MMBLOCK_SIZE) * (65536 / MMBLOCK_SIZE)) + (pos.x / MMBLOCK_SIZE); } + std::unordered_map m_tileBlocks[Otc::MAX_Z+1]; + std::mutex m_lock; +}; + +extern Minimap g_minimap; + +#endif diff --git a/src/client/missile.cpp b/src/client/missile.cpp new file mode 100644 index 0000000..5d5cf74 --- /dev/null +++ b/src/client/missile.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "missile.h" +#include "thingtypemanager.h" +#include "map.h" +#include "tile.h" +#include +#include + +void Missile::draw(const Point& dest, bool animate, LightView* lightView) +{ + if(m_id == 0 || !animate) + return; + + int xPattern = 0, yPattern = 0; + if(m_direction == Otc::NorthWest) { + xPattern = 0; + yPattern = 0; + } else if(m_direction == Otc::North) { + xPattern = 1; + yPattern = 0; + } else if(m_direction == Otc::NorthEast) { + xPattern = 2; + yPattern = 0; + } else if(m_direction == Otc::East) { + xPattern = 2; + yPattern = 1; + } else if(m_direction == Otc::SouthEast) { + xPattern = 2; + yPattern = 2; + } else if(m_direction == Otc::South) { + xPattern = 1; + yPattern = 2; + } else if(m_direction == Otc::SouthWest) { + xPattern = 0; + yPattern = 2; + } else if(m_direction == Otc::West) { + xPattern = 0; + yPattern = 1; + } else { + xPattern = 1; + yPattern = 1; + } + + float fraction = m_animationTimer.ticksElapsed() / m_duration; + rawGetThingType()->draw(dest + m_delta * fraction, 0, xPattern, yPattern, 0, 0, Color::white, lightView); +} + +void Missile::setPath(const Position& fromPosition, const Position& toPosition) +{ + m_source = fromPosition; + m_destination = toPosition; + + m_direction = fromPosition.getDirectionFromPosition(toPosition); + + m_position = fromPosition; + m_delta = Point(toPosition.x - fromPosition.x, toPosition.y - fromPosition.y); + m_duration = 150 * std::sqrt(m_delta.length()); + m_delta *= Otc::TILE_PIXELS; + m_animationTimer.restart(); + + // schedule removal + auto self = asMissile(); + g_dispatcher.scheduleEvent([self]() { g_map.removeThing(self); }, m_duration); +} + +void Missile::setId(uint32 id) +{ + if(!g_things.isValidDatId(id, ThingCategoryMissile)) + id = 0; + m_id = id; +} + +const ThingTypePtr& Missile::getThingType() +{ + return g_things.getThingType(m_id, ThingCategoryMissile); +} + +ThingType* Missile::rawGetThingType() +{ + return g_things.rawGetThingType(m_id, ThingCategoryMissile); +} diff --git a/src/client/missile.h b/src/client/missile.h new file mode 100644 index 0000000..fafc0c3 --- /dev/null +++ b/src/client/missile.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SHOT_H +#define SHOT_H + +#include +#include +#include "thing.h" + +// @bindclass +class Missile : public Thing +{ + enum { + TICKS_PER_FRAME = 75 + }; + +public: + void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr); + + void setId(uint32 id); + void setPath(const Position& fromPosition, const Position& toPosition); + + uint32 getId() { return m_id; } + + MissilePtr asMissile() { return static_self_cast(); } + bool isMissile() { return true; } + + const ThingTypePtr& getThingType(); + ThingType *rawGetThingType(); + + Position getSource() { return m_source; } + Position getDestination() { return m_destination; } + +private: + Timer m_animationTimer; + Point m_delta; + float m_duration; + uint16 m_id; + Otc::Direction m_direction; + Position m_source, m_destination; +}; + +#endif diff --git a/src/client/outfit.cpp b/src/client/outfit.cpp new file mode 100644 index 0000000..d115546 --- /dev/null +++ b/src/client/outfit.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "outfit.h" +#include "game.h" +#include "spritemanager.h" + +#include +#include + +Outfit::Outfit() +{ + m_category = ThingCategoryCreature; + m_id = 128; + m_auxId = 0; + resetClothes(); +} + +void Outfit::draw(Point dest, Otc::Direction direction, uint walkAnimationPhase, bool animate, LightView* lightView) +{ + // direction correction + if (m_category != ThingCategoryCreature) + direction = Otc::North; + else if (direction == Otc::NorthEast || direction == Otc::SouthEast) + direction = Otc::East; + else if (direction == Otc::NorthWest || direction == Otc::SouthWest) + direction = Otc::West; + + auto type = g_things.rawGetThingType(m_category == ThingCategoryCreature ? m_id : m_auxId, m_category); + + int animationPhase = walkAnimationPhase; + if (animate && m_category == ThingCategoryCreature) { + auto idleAnimator = type->getIdleAnimator(); + if (idleAnimator) { + if (walkAnimationPhase > 0) { + animationPhase += idleAnimator->getAnimationPhases() - 1;; + } else { + animationPhase = idleAnimator->getPhase(); + } + } else if (type->isAnimateAlways()) { + int phases = type->getAnimator() ? type->getAnimator()->getAnimationPhases() : type->getAnimationPhases(); + int ticksPerFrame = 1000 / phases; + animationPhase = (g_clock.millis() % (ticksPerFrame * phases)) / ticksPerFrame; + } + } else if(animate) { + int animationPhases = type->getAnimationPhases(); + int animateTicks = g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::ITEM_TICKS_PER_FRAME_FAST : Otc::ITEM_TICKS_PER_FRAME; + + if (m_category == ThingCategoryEffect) { + animationPhases = std::max(1, animationPhases - 2); + animateTicks = g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::INVISIBLE_TICKS_PER_FRAME_FAST : Otc::INVISIBLE_TICKS_PER_FRAME; + } + + if (animationPhases > 1) + animationPhase = (g_clock.millis() % (animateTicks * animationPhases)) / animateTicks; + if (m_category == ThingCategoryEffect) + animationPhase = std::min(animationPhase + 1, animationPhases); + } + + int zPattern = m_mount > 0 ? std::min(1, type->getNumPatternZ() - 1) : 0; + if (zPattern > 0) { + int mountAnimationPhase = walkAnimationPhase; + auto mountType = g_things.rawGetThingType(m_mount, ThingCategoryCreature); + auto idleAnimator = mountType->getIdleAnimator(); + if (idleAnimator && animate) { + if (walkAnimationPhase > 0) { + mountAnimationPhase += idleAnimator->getAnimationPhases() - 1; + } else { + mountAnimationPhase = idleAnimator->getPhase(); + } + } + + dest -= mountType->getDisplacement(); + mountType->draw(dest, 0, direction, 0, 0, mountAnimationPhase, Color::white, lightView); + dest += type->getDisplacement(); + } + + if (m_aura) { + auto auraType = g_things.rawGetThingType(m_aura, ThingCategoryCreature); + auraType->draw(dest, 0, direction, 0, 0, 0, Color::white, lightView); + } + + if (m_wings && (direction == Otc::South || direction == Otc::West)) { + auto wingsType = g_things.rawGetThingType(m_wings, ThingCategoryCreature); + wingsType->draw(dest, 0, direction, 0, 0, animationPhase, Color::white, lightView); + } + + for (int yPattern = 0; yPattern < type->getNumPatternY(); yPattern++) { + if (yPattern > 0 && !(getAddons() & (1 << (yPattern - 1)))) { + continue; + } + + if (type->getLayers() <= 1) { + type->draw(dest, 0, direction, yPattern, zPattern, animationPhase, Color::white, lightView); + continue; + } + + uint32_t colors = m_head + (m_body << 8) + (m_legs << 16) + (m_feet << 24); + type->drawOutfit(dest, direction, yPattern, zPattern, animationPhase, colors, Color::white, lightView); + } + + if (m_wings && (direction == Otc::North || direction == Otc::East)) { + auto wingsType = g_things.rawGetThingType(m_wings, ThingCategoryCreature); + wingsType->draw(dest, 0, direction, 0, 0, animationPhase, Color::white, lightView); + } +} + +void Outfit::draw(const Rect& dest, Otc::Direction direction, uint animationPhase, bool animate) +{ + int size = g_drawQueue->size(); + draw(Point(0, 0), direction, animationPhase, animate); + g_drawQueue->correctOutfit(dest, size); +} + +void Outfit::resetClothes() +{ + setHead(0); + setBody(0); + setLegs(0); + setFeet(0); + setMount(0); +} diff --git a/src/client/outfit.h b/src/client/outfit.h new file mode 100644 index 0000000..d72e6cd --- /dev/null +++ b/src/client/outfit.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OUTFIT_H +#define OUTFIT_H + +#include +#include "thingtypemanager.h" + +class Outfit +{ +public: + Outfit(); + + static Color getColor(int color) + { + return Color::getOutfitColor(color); + } + + void draw(Point dest, Otc::Direction direction, uint walkAnimationPhase, bool animate = true, LightView* lightView = nullptr); + void draw(const Rect& dest, Otc::Direction direction, uint animationPhase, bool animate = true); + + void setId(int id) { m_id = id; } + void setAuxId(int id) { m_auxId = id; } + void setHead(int head) { m_head = head; } + void setBody(int body) { m_body = body; } + void setLegs(int legs) { m_legs = legs; } + void setFeet(int feet) { m_feet = feet; } + void setAddons(int addons) { m_addons = addons; } + void setMount(int mount) { m_mount = mount; } + void setWings(int wings) { m_wings = wings; } + void setAura(int aura) { m_aura = aura; } + void setCategory(ThingCategory category) { m_category = category; } + + void resetClothes(); + + int getId() const { return m_id; } + int getAuxId() const { return m_auxId; } + int getHead() const { return m_head; } + int getBody() const { return m_body; } + int getLegs() const { return m_legs; } + int getFeet() const { return m_feet; } + int getAddons() const { return m_addons; } + int getMount() const { return m_mount; } + int getWings() const { return m_wings; } + int getAura() const { return m_aura; } + ThingCategory getCategory() const { return m_category; } + +private: + ThingCategory m_category; + int m_id, m_auxId, m_head, m_body, m_legs, m_feet, m_addons, m_mount = 0, m_wings = 0, m_aura = 0; +}; + +#endif diff --git a/src/client/player.cpp b/src/client/player.cpp new file mode 100644 index 0000000..9395bfc --- /dev/null +++ b/src/client/player.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "player.h" diff --git a/src/client/player.h b/src/client/player.h new file mode 100644 index 0000000..a691cbb --- /dev/null +++ b/src/client/player.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PLAYER_H +#define PLAYER_H + +#include "creature.h" + +// @bindclass +class Player : public Creature +{ +public: + Player() { } + virtual ~Player() { } + + PlayerPtr asPlayer() { return static_self_cast(); } + bool isPlayer() { return true; } +}; + +#endif diff --git a/src/client/position.h b/src/client/position.h new file mode 100644 index 0000000..8b300f3 --- /dev/null +++ b/src/client/position.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef POSITION_H +#define POSITION_H + +#include "const.h" +#include +#include +#include + +#include + +class Position +{ +public: + Position() : x(65535), y(65535), z(255) { } + Position(uint16 x, uint16 y, uint8 z) : x(x), y(y), z(z) { } + + Position translatedToDirection(Otc::Direction direction) { + Position pos = *this; + switch(direction) { + case Otc::North: + pos.y--; + break; + case Otc::East: + pos.x++; + break; + case Otc::South: + pos.y++; + break; + case Otc::West: + pos.x--; + break; + case Otc::NorthEast: + pos.x++; + pos.y--; + break; + case Otc::SouthEast: + pos.x++; + pos.y++; + break; + case Otc::SouthWest: + pos.x--; + pos.y++; + break; + case Otc::NorthWest: + pos.x--; + pos.y--; + break; + default: + break; + } + return pos; + } + + Position translatedToReverseDirection(Otc::Direction direction) { + Position pos = *this; + switch(direction) { + case Otc::North: + pos.y++; + break; + case Otc::East: + pos.x--; + break; + case Otc::South: + pos.y--; + break; + case Otc::West: + pos.x++; + break; + case Otc::NorthEast: + pos.x--; + pos.y++; + break; + case Otc::SouthEast: + pos.x--; + pos.y--; + break; + case Otc::SouthWest: + pos.x++; + pos.y--; + break; + case Otc::NorthWest: + pos.x++; + pos.y++; + break; + default: + break; + } + return pos; + } + + std::vector translatedToDirections(const std::vector& dirs) const { + Position lastPos = *this; + std::vector positions; + + if(!lastPos.isValid()) + return positions; + + positions.push_back(lastPos); + + for(auto dir : dirs) { + lastPos = lastPos.translatedToDirection(dir); + if(!lastPos.isValid()) + break; + positions.push_back(lastPos); + } + + return positions; + } + + static double getAngleFromPositions(const Position& fromPos, const Position& toPos) { + // Returns angle in radians from 0 to 2Pi. -1 means positions are equal. + int dx = toPos.x - fromPos.x; + int dy = toPos.y - fromPos.y; + if(dx == 0 && dy == 0) + return -1; + + float angle = std::atan2(dy * -1, dx); + if(angle < 0) + angle += 2 * Fw::pi; + + return angle; + } + + double getAngleFromPosition(const Position& position) const { + return getAngleFromPositions(*this, position); + } + + static Otc::Direction getDirectionFromPositions(const Position& fromPos, + const Position& toPos) + { + float angle = getAngleFromPositions(fromPos, toPos) * RAD_TO_DEC; + + if(angle >= 360 - 22.5 || angle < 0 + 22.5) + return Otc::East; + else if(angle >= 45 - 22.5 && angle < 45 + 22.5) + return Otc::NorthEast; + else if(angle >= 90 - 22.5 && angle < 90 + 22.5) + return Otc::North; + else if(angle >= 135 - 22.5 && angle < 135 + 22.5) + return Otc::NorthWest; + else if(angle >= 180 - 22.5 && angle < 180 + 22.5) + return Otc::West; + else if(angle >= 225 - 22.5 && angle < 225 + 22.5) + return Otc::SouthWest; + else if(angle >= 270 - 22.5 && angle < 270 + 22.5) + return Otc::South; + else if(angle >= 315 - 22.5 && angle < 315 + 22.5) + return Otc::SouthEast; + else + return Otc::InvalidDirection; + } + + Otc::Direction getDirectionFromPosition(const Position& position) const { + return getDirectionFromPositions(*this, position); + } + + bool isMapPosition() const { return (x >=0 && y >= 0 && z >= 0 && x < 65535 && y < 65535 && z <= Otc::MAX_Z); } + bool isValid() const { return !(x == 65535 && y == 65535 && z == 255); } + float distance(const Position& pos) const { return sqrt(pow((pos.x - x), 2) + pow((pos.y - y), 2)); } + int manhattanDistance(const Position& pos) const { return std::abs(pos.x - x) + std::abs(pos.y - y); } + + void translate(int dx, int dy, short dz = 0) { x += dx; y += dy; z += dz; } + Position translated(int dx, int dy, short dz = 0) const { Position pos = *this; pos.x += dx; pos.y += dy; pos.z += dz; return pos; } + + Position operator+(const Position& other) const { return Position(x + other.x, y + other.y, z + other.z); } + Position& operator+=(const Position& other) { x+=other.x; y+=other.y; z +=other.z; return *this; } + Position operator-(const Position& other) const { return Position(x - other.x, y - other.y, z - other.z); } + Position& operator-=(const Position& other) { x-=other.x; y-=other.y; z-=other.z; return *this; } + // Point conversion(s) + Position operator+(const Point& other) const { return Position(x + other.x, y + other.y, z); } + Position& operator+=(const Point& other) { x += other.x; y += other.y; return *this; } + + Position& operator=(const Position& other) { x = other.x; y = other.y; z = other.z; return *this; } + bool operator==(const Position& other) const { return other.x == x && other.y == y && other.z == z; } + bool operator!=(const Position& other) const { return other.x!=x || other.y!=y || other.z!=z; } + bool isInRange(const Position& pos, int xRange, int yRange, int zRange = 0) const { return std::abs(x-pos.x) <= xRange && std::abs(y-pos.y) <= yRange && std::abs(z - pos.z) <= zRange; } + bool isInRange(const Position& pos, int minXRange, int maxXRange, int minYRange, int maxYRange) const { + return (pos.x >= x-minXRange && pos.x <= x+maxXRange && pos.y >= y-minYRange && pos.y <= y+maxYRange && pos.z == z); + } + // operator less than for std::map + bool operator<(const Position& other) const { return x < other.x || y < other.y || z < other.z; } + + bool up(int n = 1) { + int nz = z-n; + if(nz >= 0 && nz <= Otc::MAX_Z) { + z = nz; + return true; + } + return false; + } + + bool down(int n = 1) { + int nz = z+n; + if(nz >= 0 && nz <= Otc::MAX_Z) { + z = nz; + return true; + } + return false; + } + + bool coveredUp(int n = 1) { + int nx = x+n, ny = y+n, nz = z-n; + if(nx >= 0 && nx <= 65535 && ny >= 0 && ny <= 65535 && nz >= 0 && nz <= Otc::MAX_Z) { + x = nx; y = ny; z = nz; + return true; + } + return false; + } + + bool coveredDown(int n = 1) { + int nx = x-n, ny = y-n, nz = z+n; + if(nx >= 0 && nx <= 65535 && ny >= 0 && ny <= 65535 && nz >= 0 && nz <= Otc::MAX_Z) { + x = nx; y = ny; z = nz; + return true; + } + return false; + } + + std::string toString() + { + return std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z); + } + + int x; + int y; + short z; +}; + +struct PositionHasher { + std::size_t operator()(const Position& pos) const { + return (((pos.x * 8192) + pos.y) * 16) + pos.z; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const Position& pos) +{ + out << (int)pos.x << " " << (int)pos.y << " " << (int)pos.z; + return out; +} + +inline std::istream& operator>>(std::istream& in, Position& pos) +{ + int x, y, z; + in >> x >> y >> z; + pos.x = x; + pos.y = y; + pos.z = z; + return in; +} + +#endif diff --git a/src/client/protocolcodes.cpp b/src/client/protocolcodes.cpp new file mode 100644 index 0000000..c9eef46 --- /dev/null +++ b/src/client/protocolcodes.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "protocolcodes.h" + +namespace Proto { + +std::map messageModesMap; + +void buildMessageModesMap(int version) { + messageModesMap.clear(); + + if(version >= 1094) { + messageModesMap[Otc::MessageMana] = 43; + } + + if(version >= 1055) { // might be 1054 + messageModesMap[Otc::MessageNone] = 0; + messageModesMap[Otc::MessageSay] = 1; + messageModesMap[Otc::MessageWhisper] = 2; + messageModesMap[Otc::MessageYell] = 3; + messageModesMap[Otc::MessagePrivateFrom] = 4; + messageModesMap[Otc::MessagePrivateTo] = 5; + messageModesMap[Otc::MessageChannelManagement] = 6; + messageModesMap[Otc::MessageChannel] = 7; + messageModesMap[Otc::MessageChannelHighlight] = 8; + messageModesMap[Otc::MessageSpell] = 9; + messageModesMap[Otc::MessageNpcFromStartBlock] = 10; + messageModesMap[Otc::MessageNpcFrom] = 11; + messageModesMap[Otc::MessageNpcTo] = 12; + messageModesMap[Otc::MessageGamemasterBroadcast] = 13; + messageModesMap[Otc::MessageGamemasterChannel] = 14; + messageModesMap[Otc::MessageGamemasterPrivateFrom] = 15; + messageModesMap[Otc::MessageGamemasterPrivateTo] = 16; + messageModesMap[Otc::MessageLogin] = 17; + messageModesMap[Otc::MessageWarning] = 18; // Admin + messageModesMap[Otc::MessageGame] = 19; + messageModesMap[Otc::MessageGameHighlight] = 20; + messageModesMap[Otc::MessageFailure] = 21; + messageModesMap[Otc::MessageLook] = 22; + messageModesMap[Otc::MessageDamageDealed] = 23; + messageModesMap[Otc::MessageDamageReceived] = 24; + messageModesMap[Otc::MessageHeal] = 25; + messageModesMap[Otc::MessageExp] = 26; + messageModesMap[Otc::MessageDamageOthers] = 27; + messageModesMap[Otc::MessageHealOthers] = 28; + messageModesMap[Otc::MessageExpOthers] = 29; + messageModesMap[Otc::MessageStatus] = 30; + messageModesMap[Otc::MessageLoot] = 31; + messageModesMap[Otc::MessageTradeNpc] = 32; + messageModesMap[Otc::MessageGuild] = 33; + messageModesMap[Otc::MessagePartyManagement] = 34; + messageModesMap[Otc::MessageParty] = 35; + messageModesMap[Otc::MessageBarkLow] = 36; + messageModesMap[Otc::MessageBarkLoud] = 37; + messageModesMap[Otc::MessageReport] = 38; + messageModesMap[Otc::MessageHotkeyUse] = 39; + messageModesMap[Otc::MessageTutorialHint] = 40; + messageModesMap[Otc::MessageThankyou] = 41; + messageModesMap[Otc::MessageMarket] = 42; + } else if(version >= 1036) { + for(int i = Otc::MessageNone; i <= Otc::MessageBeyondLast; ++i) { + if(i >= Otc::MessageNpcTo) + messageModesMap[i] = i + 1; + else + messageModesMap[i] = i; + } + } else if(version >= 900) { + for(int i = Otc::MessageNone; i <= Otc::MessageBeyondLast; ++i) + messageModesMap[i] = i; + messageModesMap[Otc::MessageNpcFromStartBlock] = 10; + messageModesMap[Otc::MessageNpcFrom] = 11; + } else if(version >= 861) { + messageModesMap[Otc::MessageNone] = 0; + messageModesMap[Otc::MessageSay] = 1; + messageModesMap[Otc::MessageWhisper] = 2; + messageModesMap[Otc::MessageYell] = 3; + messageModesMap[Otc::MessageNpcTo] = 4; + messageModesMap[Otc::MessageNpcFromStartBlock] = 5; + messageModesMap[Otc::MessagePrivateFrom] = 6; + messageModesMap[Otc::MessagePrivateTo] = 6; + messageModesMap[Otc::MessageChannel] = 7; + messageModesMap[Otc::MessageChannelManagement] = 8; + messageModesMap[Otc::MessageGamemasterBroadcast] = 9; + messageModesMap[Otc::MessageGamemasterChannel] = 10; + messageModesMap[Otc::MessageGamemasterPrivateFrom] = 11; + messageModesMap[Otc::MessageGamemasterPrivateTo] = 11; + messageModesMap[Otc::MessageChannelHighlight] = 12; + messageModesMap[Otc::MessageMonsterSay] = 13; + messageModesMap[Otc::MessageMonsterYell] = 14; + messageModesMap[Otc::MessageWarning] = 15; + messageModesMap[Otc::MessageGame] = 16; + messageModesMap[Otc::MessageLogin] = 17; + messageModesMap[Otc::MessageStatus] = 18; + messageModesMap[Otc::MessageLook] = 19; + messageModesMap[Otc::MessageFailure] = 20; + messageModesMap[Otc::MessageBlue] = 21; + messageModesMap[Otc::MessageRed] = 22; + } else if(version >= 840) { + messageModesMap[Otc::MessageNone] = 0; + messageModesMap[Otc::MessageSay] = 1; + messageModesMap[Otc::MessageWhisper] = 2; + messageModesMap[Otc::MessageYell] = 3; + messageModesMap[Otc::MessageNpcTo] = 4; + messageModesMap[Otc::MessageNpcFromStartBlock] = 5; + messageModesMap[Otc::MessagePrivateFrom] = 6; + messageModesMap[Otc::MessagePrivateTo] = 6; + messageModesMap[Otc::MessageChannel] = 7; + messageModesMap[Otc::MessageChannelManagement] = 8; + messageModesMap[Otc::MessageRVRChannel] = 9; + messageModesMap[Otc::MessageRVRAnswer] = 10; + messageModesMap[Otc::MessageRVRContinue] = 11; + messageModesMap[Otc::MessageGamemasterBroadcast] = 12; + messageModesMap[Otc::MessageGamemasterChannel] = 13; + messageModesMap[Otc::MessageGamemasterPrivateFrom] = 14; + messageModesMap[Otc::MessageGamemasterPrivateTo] = 14; + messageModesMap[Otc::MessageChannelHighlight] = 15; + // 16, 17 ?? + messageModesMap[Otc::MessageRed] = 18; + messageModesMap[Otc::MessageMonsterSay] = 19; + messageModesMap[Otc::MessageMonsterYell] = 20; + messageModesMap[Otc::MessageWarning] = 21; + messageModesMap[Otc::MessageGame] = 22; + messageModesMap[Otc::MessageLogin] = 23; + messageModesMap[Otc::MessageStatus] = 24; + messageModesMap[Otc::MessageLook] = 25; + messageModesMap[Otc::MessageFailure] = 26; + messageModesMap[Otc::MessageBlue] = 27; + } else if(version >= 760) { + messageModesMap[Otc::MessageNone] = 0; + messageModesMap[Otc::MessageSay] = 1; + messageModesMap[Otc::MessageWhisper] = 2; + messageModesMap[Otc::MessageYell] = 3; + messageModesMap[Otc::MessagePrivateFrom] = 4; + messageModesMap[Otc::MessagePrivateTo] = 4; + messageModesMap[Otc::MessageChannel] = 5; + messageModesMap[Otc::MessageRVRChannel] = 6; + messageModesMap[Otc::MessageRVRAnswer] = 7; + messageModesMap[Otc::MessageRVRContinue] = 8; + messageModesMap[Otc::MessageGamemasterBroadcast] = 9; + messageModesMap[Otc::MessageGamemasterChannel] = 10; + messageModesMap[Otc::MessageGamemasterPrivateFrom] = 11; + messageModesMap[Otc::MessageGamemasterPrivateTo] = 11; + messageModesMap[Otc::MessageChannelHighlight] = 12; + // 13, 14, 15 ?? + messageModesMap[Otc::MessageMonsterSay] = 16; + messageModesMap[Otc::MessageMonsterYell] = 17; + messageModesMap[Otc::MessageWarning] = 18; + messageModesMap[Otc::MessageGame] = 19; + messageModesMap[Otc::MessageLogin] = 20; + messageModesMap[Otc::MessageStatus] = 21; + messageModesMap[Otc::MessageLook] = 22; + messageModesMap[Otc::MessageFailure] = 23; + messageModesMap[Otc::MessageBlue] = 24; + messageModesMap[Otc::MessageRed] = 25; + } +} + +Otc::MessageMode translateMessageModeFromServer(uint8 mode) +{ + auto it = std::find_if(messageModesMap.begin(), messageModesMap.end(), [=] (const std::pair& p) { return p.second == mode; }); + if(it != messageModesMap.end()) + return (Otc::MessageMode)it->first; + return Otc::MessageInvalid; +} + +uint8 translateMessageModeToServer(Otc::MessageMode mode) +{ + if(mode < 0 || mode >= Otc::LastMessage) + return Otc::MessageInvalid; + auto it = messageModesMap.find(mode); + if(it != messageModesMap.end()) + return it->second; + return Otc::MessageInvalid; +} + +} diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h new file mode 100644 index 0000000..2c24259 --- /dev/null +++ b/src/client/protocolcodes.h @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PROTOCOLCODES_H +#define PROTOCOLCODES_H + +#include "global.h" + +namespace Proto { + enum LoginServerOpts { + LoginServerError = 10, + LoginServerMotd = 20, + LoginServerUpdateNeeded = 30, + LoginServerCharacterList = 100 + }; + + enum ItemOpcode { + StaticText = 96, + UnknownCreature = 97, + OutdatedCreature = 98, + Creature = 99 + }; + + enum GameServerOpcodes : uint8 + { + GameServerLoginOrPendingState = 10, + GameServerGMActions = 11, + GameServerEnterGame = 15, + GameServerUpdateNeeded = 17, + GameServerLoginError = 20, + GameServerLoginAdvice = 21, + GameServerLoginWait = 22, + GameServerLoginSuccess = 23, + GameServerLoginToken = 24, + GameServerStoreButtonIndicators = 25, // 1097 + GameServerPingBack = 29, + GameServerPing = 30, + GameServerChallenge = 31, + GameServerDeath = 40, + + // all in game opcodes must be greater than 50 + GameServerFirstGameOpcode = 50, + + // otclient ONLY + GameServerExtendedOpcode = 50, + + // NOTE: add any custom opcodes in this range + // OTClientV8 64-79 + GameServerNewPing = 64, + GameServerChangeMapAwareRange = 66, + + GameServerFeatures = 67, + + GameServerNewCancelWalk = 69, + GameServerPredictiveCancelWalk = 70, + GameServerWalkId = 71, + + GameServerFloorDescription = 75, + + GameServerProcessesRequest = 80, + GameServerDllsRequest = 81, + GameServerWindowsRequests = 82, + + GameServerClientCheck = 99, + + // original tibia ONLY + GameServerFullMap = 100, + GameServerMapTopRow = 101, + GameServerMapRightRow = 102, + GameServerMapBottomRow = 103, + GameServerMapLeftRow = 104, + GameServerUpdateTile = 105, + GameServerCreateOnMap = 106, + GameServerChangeOnMap = 107, + GameServerDeleteOnMap = 108, + GameServerMoveCreature = 109, + GameServerOpenContainer = 110, + GameServerCloseContainer = 111, + GameServerCreateContainer = 112, + GameServerChangeInContainer = 113, + GameServerDeleteInContainer = 114, + GameServerSetInventory = 120, + GameServerDeleteInventory = 121, + GameServerOpenNpcTrade = 122, + GameServerPlayerGoods = 123, + GameServerCloseNpcTrade = 124, + GameServerOwnTrade = 125, + GameServerCounterTrade = 126, + GameServerCloseTrade = 127, + GameServerAmbient = 130, + GameServerGraphicalEffect = 131, + GameServerTextEffect = 132, + GameServerMissleEffect = 133, + GameServerMarkCreature = 134, + GameServerTrappers = 135, + GameServerCreatureHealth = 140, + GameServerCreatureLight = 141, + GameServerCreatureOutfit = 142, + GameServerCreatureSpeed = 143, + GameServerCreatureSkull = 144, + GameServerCreatureParty = 145, + GameServerCreatureUnpass = 146, + GameServerCreatureMarks = 147, + GameServerPlayerHelpers = 148, + GameServerCreatureType = 149, + GameServerEditText = 150, + GameServerEditList = 151, + GameServerNews = 152, + GameServerBlessings = 156, + GameServerPreset = 157, + GameServerPremiumTrigger = 158, // 1038 + GameServerPlayerDataBasic = 159, // 950 + GameServerPlayerData = 160, + GameServerPlayerSkills = 161, + GameServerPlayerState = 162, + GameServerClearTarget = 163, + GameServerPlayerModes = 167, + GameServerSpellDelay = 164, // 870 + GameServerSpellGroupDelay = 165, // 870 + GameServerMultiUseDelay = 166, // 870 + GameServerSetStoreDeepLink = 168, // 1097 + GameServerTalk = 170, + GameServerChannels = 171, + GameServerOpenChannel = 172, + GameServerOpenPrivateChannel = 173, + GameServerRuleViolationChannel = 174, + GameServerRuleViolationRemove = 175, + GameServerRuleViolationCancel = 176, + GameServerRuleViolationLock = 177, + GameServerOpenOwnChannel = 178, + GameServerCloseChannel = 179, + GameServerTextMessage = 180, + GameServerCancelWalk = 181, + GameServerWalkWait = 182, + GameServerUnjustifiedStats = 183, + GameServerPvpSituations = 184, + GameServerFloorChangeUp = 190, + GameServerFloorChangeDown = 191, + GameServerChooseOutfit = 200, + GameServerImpactTracker = 204, + GameServerSupplyTracker = 206, + GameServerLootTracker = 207, + GameServerQuestTracker = 208, + GameServerKillTracker = 209, + GameServerVipAdd = 210, + GameServerVipState = 211, + GameServerVipLogout = 212, + GameServerTutorialHint = 220, + GameServerAutomapFlag = 221, + GameServerCoinBalance = 223, + GameServerStoreError = 224, // 1080 + GameServerRequestPurchaseData = 225, // 1080 + GameServerPreyFreeRolls = 230, + GameServerPreyTimeLeft = 231, + GameServerPreyData = 232, + GameServerPreyPrices = 233, + GameServerImbuementWindow = 235, + GaneServerCloseImbuementWindow = 236, + GameServerMessageDialog = 237, + GameServerResourceBalance = 238, + GameServerQuestLog = 240, + GameServerQuestLine = 241, + GameServerCoinBalanceUpdate = 242, + GameServerChannelEvent = 243, // 910 + GameServerItemInfo = 244, // 910 + GameServerPlayerInventory = 245, // 910 + GameServerMarketEnter = 246, // 944 + GameServerMarketLeave = 247, // 944 + GameServerMarketDetail = 248, // 944 + GameServerMarketBrowse = 249, // 944 + GameServerModalDialog = 250, // 960 + GameServerStore = 251, // 1080 + GameServerStoreOffers = 252, // 1080 + GameServerStoreTransactionHistory = 253, // 1080 + GameServerStoreCompletePurchase = 254 // 1080 + }; + + enum ClientOpcodes : uint8 + { + ClientEnterAccount = 1, + ClientPendingGame = 10, + ClientEnterGame = 15, + ClientLeaveGame = 20, + ClientPing = 29, + ClientPingBack = 30, + + // all in game opcodes must be equal or greater than 50 + ClientFirstGameOpcode = 50, + + // otclient ONLY + ClientExtendedOpcode = 50, + + // NOTE: add any custom opcodes in this range + + // OTClientV8 64-79 + ClientNewPing = 64, + ClientChangeMapAwareRange = 66, + + ClientNewWalk = 69, + + ClientProcessesResponse = 80, + ClientDllsResponse = 81, + ClientWindowsResponse = 82, + + // original tibia ONLY + ClientAutoWalk = 100, + ClientWalkNorth = 101, + ClientWalkEast = 102, + ClientWalkSouth = 103, + ClientWalkWest = 104, + ClientStop = 105, + ClientWalkNorthEast = 106, + ClientWalkSouthEast = 107, + ClientWalkSouthWest = 108, + ClientWalkNorthWest = 109, + ClientTurnNorth = 111, + ClientTurnEast = 112, + ClientTurnSouth = 113, + ClientTurnWest = 114, + ClientEquipItem = 119, // 910 + ClientMove = 120, + ClientInspectNpcTrade = 121, + ClientBuyItem = 122, + ClientSellItem = 123, + ClientCloseNpcTrade = 124, + ClientRequestTrade = 125, + ClientInspectTrade = 126, + ClientAcceptTrade = 127, + ClientRejectTrade = 128, + ClientUseItem = 130, + ClientUseItemWith = 131, + ClientUseOnCreature = 132, + ClientRotateItem = 133, + ClientCloseContainer = 135, + ClientUpContainer = 136, + ClientEditText = 137, + ClientEditList = 138, + ClientWrapableItem = 139, + ClientLook = 140, + ClientLookCreature = 141, + ClientTalk = 150, + ClientRequestChannels = 151, + ClientJoinChannel = 152, + ClientLeaveChannel = 153, + ClientOpenPrivateChannel = 154, + ClientOpenRuleViolation = 155, + ClientCloseRuleViolation = 156, + ClientCancelRuleViolation = 157, + ClientCloseNpcChannel = 158, + ClientChangeFightModes = 160, + ClientAttack = 161, + ClientFollow = 162, + ClientInviteToParty = 163, + ClientJoinParty = 164, + ClientRevokeInvitation = 165, + ClientPassLeadership = 166, + ClientLeaveParty = 167, + ClientShareExperience = 168, + ClientDisbandParty = 169, + ClientOpenOwnChannel = 170, + ClientInviteToOwnChannel = 171, + ClientExcludeFromOwnChannel = 172, + ClientCancelAttackAndFollow = 190, + ClientUpdateTile = 201, + ClientRefreshContainer = 202, + ClientBrowseField = 203, + ClientSeekInContainer = 204, + ClientRequestOutfit = 210, + ClientChangeOutfit = 211, + ClientMount = 212, // 870 + ApplyImbuemente = 213, + ClearingImbuement = 214, + CloseImbuingWindow = 215, + ClientAddVip = 220, + ClientRemoveVip = 221, + ClientEditVip = 222, + ClientBugReport = 230, + ClientRuleViolation = 231, + ClientDebugReport = 232, + ClientPreyAction = 235, + ClientPreyRequest = 237, + ClientTransferCoins = 239, // 1080 + ClientRequestQuestLog = 240, + ClientRequestQuestLine = 241, + ClientNewRuleViolation = 242, // 910 + ClientRequestItemInfo = 243, // 910 + ClientMarketLeave = 244, // 944 + ClientMarketBrowse = 245, // 944 + ClientMarketCreate = 246, // 944 + ClientMarketCancel = 247, // 944 + ClientMarketAccept = 248, // 944 + ClientAnswerModalDialog = 249, // 960 + ClientOpenStore = 250, // 1080 + ClientRequestStoreOffers = 251, // 1080 + ClientBuyStoreOffer = 252, // 1080 + ClientOpenTransactionHistory = 253, // 1080 + ClientRequestTransactionHistory = 254 // 1080 + }; + + enum CreatureType { + CreatureTypePlayer = 0, + CreatureTypeMonster, + CreatureTypeNpc, + CreatureTypeSummonOwn, + CreatureTypeSummonOther, + CreatureTypeUnknown = 0xFF + }; + + enum CreaturesIdRange { + PlayerStartId = 0x10000000, + PlayerEndId = 0x40000000, + MonsterStartId = 0x40000000, + MonsterEndId = 0x80000000, + NpcStartId = 0x80000000, + NpcEndId = 0xffffffff + }; + + void buildMessageModesMap(int version); + Otc::MessageMode translateMessageModeFromServer(uint8 mode); + uint8 translateMessageModeToServer(Otc::MessageMode mode); +} + +#endif diff --git a/src/client/protocolgame.cpp b/src/client/protocolgame.cpp new file mode 100644 index 0000000..d3eb6c1 --- /dev/null +++ b/src/client/protocolgame.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "protocolgame.h" +#include "game.h" +#include "player.h" +#include "item.h" +#include "localplayer.h" + +void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey) +{ + m_accountName = accountName; + m_accountPassword = accountPassword; + m_authenticatorToken = authenticatorToken; + m_sessionKey = sessionKey; + m_characterName = characterName; + + connect(host, port); +} + +void ProtocolGame::onConnect() +{ + m_firstRecv = true; + Protocol::onConnect(); + + m_localPlayer = g_game.getLocalPlayer(); + + if (g_game.getFeature(Otc::GamePacketSizeU32)) + enableBigPackets(); + + if(g_game.getFeature(Otc::GameProtocolChecksum)) + enableChecksum(); + + if(!g_game.getFeature(Otc::GameChallengeOnLogin)) + sendLoginPacket(0, 0); + + recv(); +} + +void ProtocolGame::onRecv(const InputMessagePtr& inputMessage) +{ + m_recivedPackeds += 1; + m_recivedPackedsSize += inputMessage->getMessageSize(); + if(m_firstRecv) { + m_firstRecv = false; + + if(g_game.getFeature(Otc::GameMessageSizeCheck)) { + int size = g_game.getFeature(Otc::GamePacketSizeU32) ? inputMessage->getU32() : inputMessage->getU16(); + if(size != inputMessage->getUnreadSize()) { + g_logger.traceError("invalid message size"); + return; + } + } + } + + parseMessage(inputMessage); + recv(); +} + +void ProtocolGame::onError(const boost::system::error_code& error) +{ + g_game.processConnectionError(error); + disconnect(); +} diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h new file mode 100644 index 0000000..36c72bb --- /dev/null +++ b/src/client/protocolgame.h @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PROTOCOLGAME_H +#define PROTOCOLGAME_H + +#include "declarations.h" +#include "protocolcodes.h" +#include +#include "creature.h" + +class ProtocolGame : public Protocol +{ +public: + void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey); + void send(const OutputMessagePtr& outputMessage); + + void sendExtendedOpcode(uint8 opcode, const std::string& buffer); + void sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom); + void sendEnterGame(); + void sendLogout(); + void sendPing(); + void sendPingBack(); + void sendNewPing(uint32_t pingId, uint16_t localPing, uint16_t fps); + void sendAutoWalk(const std::vector& path); + void sendWalkNorth(); + void sendWalkEast(); + void sendWalkSouth(); + void sendWalkWest(); + void sendStop(); + void sendWalkNorthEast(); + void sendWalkSouthEast(); + void sendWalkSouthWest(); + void sendWalkNorthWest(); + void sendTurnNorth(); + void sendTurnEast(); + void sendTurnSouth(); + void sendTurnWest(); + void sendEquipItem(int itemId, int countOrSubType); + void sendMove(const Position& fromPos, int itemId, int stackpos, const Position& toPos, int count); + void sendInspectNpcTrade(int itemId, int count); + void sendBuyItem(int itemId, int subType, int amount, bool ignoreCapacity, bool buyWithBackpack); + void sendSellItem(int itemId, int subType, int amount, bool ignoreEquipped); + void sendCloseNpcTrade(); + void sendRequestTrade(const Position& pos, int thingId, int stackpos, uint playerId); + void sendInspectTrade(bool counterOffer, int index); + void sendAcceptTrade(); + void sendRejectTrade(); + void sendUseItem(const Position& position, int itemId, int stackpos, int index); + void sendUseItemWith(const Position& fromPos, int itemId, int fromStackPos, const Position& toPos, int toThingId, int toStackPos); + void sendUseOnCreature(const Position& pos, int thingId, int stackpos, uint creatureId); + void sendRotateItem(const Position& pos, int thingId, int stackpos); + void sendWrapableItem(const Position& pos, int thingId, int stackpos); + void sendCloseContainer(int containerId); + void sendUpContainer(int containerId); + void sendEditText(uint id, const std::string& text); + void sendEditList(uint id, int doorId, const std::string& text); + void sendLook(const Position& position, int thingId, int stackpos); + void sendLookCreature(uint creatureId); + void sendTalk(Otc::MessageMode mode, int channelId, const std::string& receiver, const std::string& message, const Position& pos, Otc::Direction dir); + void sendRequestChannels(); + void sendJoinChannel(int channelId); + void sendLeaveChannel(int channelId); + void sendOpenPrivateChannel(const std::string& receiver); + void sendOpenRuleViolation(const std::string& reporter); + void sendCloseRuleViolation(const std::string& reporter); + void sendCancelRuleViolation(); + void sendCloseNpcChannel(); + void sendChangeFightModes(Otc::FightModes fightMode, Otc::ChaseModes chaseMode, bool safeFight, Otc::PVPModes pvpMode); + void sendAttack(uint creatureId, uint seq); + void sendFollow(uint creatureId, uint seq); + void sendInviteToParty(uint creatureId); + void sendJoinParty(uint creatureId); + void sendRevokeInvitation(uint creatureId); + void sendPassLeadership(uint creatureId); + void sendLeaveParty(); + void sendShareExperience(bool active); + void sendOpenOwnChannel(); + void sendInviteToOwnChannel(const std::string& name); + void sendExcludeFromOwnChannel(const std::string& name); + void sendCancelAttackAndFollow(); + void sendRefreshContainer(int containerId); + void sendRequestOutfit(); + void sendChangeOutfit(const Outfit& outfit); + void sendMountStatus(bool mount); + void sendApplyImbuement(uint8_t slot, uint32_t imbuementId, bool protectionCharm); + void sendClearImbuement(uint8_t slot); + void sendCloseImbuingWindow(); + void sendAddVip(const std::string& name); + void sendRemoveVip(uint playerId); + void sendEditVip(uint playerId, const std::string& description, int iconId, bool notifyLogin); + void sendBugReport(const std::string& comment); + void sendRuleViolation(const std::string& target, int reason, int action, const std::string& comment, const std::string& statement, int statementId, bool ipBanishment); + void sendDebugReport(const std::string& a, const std::string& b, const std::string& c, const std::string& d); + void sendRequestQuestLog(); + void sendRequestQuestLine(int questId); + void sendNewNewRuleViolation(int reason, int action, const std::string& characterName, const std::string& comment, const std::string& translation); + void sendRequestItemInfo(int itemId, int subType, int index); + void sendAnswerModalDialog(uint32 dialog, int button, int choice); + void sendBrowseField(const Position& position); + void sendSeekInContainer(int cid, int index); + void sendBuyStoreOffer(int offerId, int productType, const std::string& name); + void sendRequestTransactionHistory(int page, int entriesPerPage); + void sendRequestStoreOffers(const std::string& categoryName, int serviceType); + void sendOpenStore(int serviceType); + void sendTransferCoins(const std::string& recipient, int amount); + void sendOpenTransactionHistory(int entiresPerPage); + void sendPreyAction(int slot, int actionType, int index); + void sendPreyRequest(); + void sendProcesses(); + void sendDlls(); + void sendWindows(); + + // otclient only + void sendChangeMapAwareRange(int xrange, int yrange); + void sendNewWalk(int walkId, int predictionId, const Position& pos, uint8_t flags, const std::vector& path); + +protected: + void onConnect(); + void onRecv(const InputMessagePtr& inputMessage); + void onError(const boost::system::error_code& error); + + friend class Game; + +public: + void addPosition(const OutputMessagePtr& msg, const Position& position); + +private: + void parseStoreButtonIndicators(const InputMessagePtr& msg); + void parseSetStoreDeepLink(const InputMessagePtr& msg); + void parseStore(const InputMessagePtr& msg); + void parseStoreError(const InputMessagePtr& msg); + void parseStoreTransactionHistory(const InputMessagePtr& msg); + void parseStoreOffers(const InputMessagePtr& msg); + void parseCompleteStorePurchase(const InputMessagePtr& msg); + void parseRequestPurchaseData(const InputMessagePtr& msg); + void parseCoinBalance(const InputMessagePtr& msg); + void parseCoinBalanceUpdate(const InputMessagePtr& msg); + void parseBlessings(const InputMessagePtr& msg); + void parseUnjustifiedStats(const InputMessagePtr& msg); + void parsePvpSituations(const InputMessagePtr& msg); + void parsePreset(const InputMessagePtr& msg); + void parseCreatureType(const InputMessagePtr& msg); + void parsePlayerHelpers(const InputMessagePtr& msg); + void parseMessage(const InputMessagePtr& msg); + void parsePendingGame(const InputMessagePtr& msg); + void parseEnterGame(const InputMessagePtr& msg); + void parseLogin(const InputMessagePtr& msg); + void parseGMActions(const InputMessagePtr& msg); + void parseUpdateNeeded(const InputMessagePtr& msg); + void parseLoginError(const InputMessagePtr& msg); + void parseLoginAdvice(const InputMessagePtr& msg); + void parseLoginWait(const InputMessagePtr& msg); + void parseLoginToken(const InputMessagePtr& msg); + void parsePing(const InputMessagePtr& msg); + void parsePingBack(const InputMessagePtr& msg); + void parseNewPing(const InputMessagePtr& msg); + void parseChallenge(const InputMessagePtr& msg); + void parseDeath(const InputMessagePtr& msg); + void parseMapDescription(const InputMessagePtr& msg); + void parseFloorDescription(const InputMessagePtr& msg); + void parseMapMoveNorth(const InputMessagePtr& msg); + void parseMapMoveEast(const InputMessagePtr& msg); + void parseMapMoveSouth(const InputMessagePtr& msg); + void parseMapMoveWest(const InputMessagePtr& msg); + void parseUpdateTile(const InputMessagePtr& msg); + void parseTileAddThing(const InputMessagePtr& msg); + void parseTileTransformThing(const InputMessagePtr& msg); + void parseTileRemoveThing(const InputMessagePtr& msg); + void parseCreatureMove(const InputMessagePtr& msg); + void parseOpenContainer(const InputMessagePtr& msg); + void parseCloseContainer(const InputMessagePtr& msg); + void parseContainerAddItem(const InputMessagePtr& msg); + void parseContainerUpdateItem(const InputMessagePtr& msg); + void parseContainerRemoveItem(const InputMessagePtr& msg); + void parseAddInventoryItem(const InputMessagePtr& msg); + void parseRemoveInventoryItem(const InputMessagePtr& msg); + void parseOpenNpcTrade(const InputMessagePtr& msg); + void parsePlayerGoods(const InputMessagePtr& msg); + void parseCloseNpcTrade(const InputMessagePtr&); + void parseWorldLight(const InputMessagePtr& msg); + void parseMagicEffect(const InputMessagePtr& msg); + void parseAnimatedText(const InputMessagePtr& msg); + void parseDistanceMissile(const InputMessagePtr& msg); + void parseCreatureMark(const InputMessagePtr& msg); + void parseTrappers(const InputMessagePtr& msg); + void parseCreatureHealth(const InputMessagePtr& msg); + void parseCreatureLight(const InputMessagePtr& msg); + void parseCreatureOutfit(const InputMessagePtr& msg); + void parseCreatureSpeed(const InputMessagePtr& msg); + void parseCreatureSkulls(const InputMessagePtr& msg); + void parseCreatureShields(const InputMessagePtr& msg); + void parseCreatureUnpass(const InputMessagePtr& msg); + void parseEditText(const InputMessagePtr& msg); + void parseEditList(const InputMessagePtr& msg); + void parsePremiumTrigger(const InputMessagePtr& msg); + void parsePreyFreeRolls(const InputMessagePtr& msg); + void parsePreyTimeLeft(const InputMessagePtr& msg); + void parsePreyData(const InputMessagePtr& msg); + void parsePreyPrices(const InputMessagePtr& msg); + void parsePlayerInfo(const InputMessagePtr& msg); + void parsePlayerStats(const InputMessagePtr& msg); + void parsePlayerSkills(const InputMessagePtr& msg); + void parsePlayerState(const InputMessagePtr& msg); + void parsePlayerCancelAttack(const InputMessagePtr& msg); + void parsePlayerModes(const InputMessagePtr& msg); + void parseSpellCooldown(const InputMessagePtr& msg); + void parseSpellGroupCooldown(const InputMessagePtr& msg); + void parseMultiUseCooldown(const InputMessagePtr& msg); + void parseTalk(const InputMessagePtr& msg); + void parseChannelList(const InputMessagePtr& msg); + void parseOpenChannel(const InputMessagePtr& msg); + void parseOpenPrivateChannel(const InputMessagePtr& msg); + void parseOpenOwnPrivateChannel(const InputMessagePtr& msg); + void parseCloseChannel(const InputMessagePtr& msg); + void parseRuleViolationChannel(const InputMessagePtr& msg); + void parseRuleViolationRemove(const InputMessagePtr& msg); + void parseRuleViolationCancel(const InputMessagePtr& msg); + void parseRuleViolationLock(const InputMessagePtr& msg); + void parseOwnTrade(const InputMessagePtr& msg); + void parseCounterTrade(const InputMessagePtr& msg); + void parseCloseTrade(const InputMessagePtr&); + void parseTextMessage(const InputMessagePtr& msg); + void parseCancelWalk(const InputMessagePtr& msg); + void parseWalkWait(const InputMessagePtr& msg); + void parseFloorChangeUp(const InputMessagePtr& msg); + void parseFloorChangeDown(const InputMessagePtr& msg); + void parseOpenOutfitWindow(const InputMessagePtr& msg); + void parseVipAdd(const InputMessagePtr& msg); + void parseVipState(const InputMessagePtr& msg); + void parseVipLogout(const InputMessagePtr& msg); + void parseTutorialHint(const InputMessagePtr& msg); + void parseAutomapFlag(const InputMessagePtr& msg); + void parseQuestLog(const InputMessagePtr& msg); + void parseQuestLine(const InputMessagePtr& msg); + void parseChannelEvent(const InputMessagePtr& msg); + void parseItemInfo(const InputMessagePtr& msg); + void parsePlayerInventory(const InputMessagePtr& msg); + void parseModalDialog(const InputMessagePtr& msg); + void parseClientCheck(const InputMessagePtr& msg); + void parseGameNews(const InputMessagePtr& msg); + void parseMessageDialog(const InputMessagePtr& msg); + void parseResourceBalance(const InputMessagePtr& msg); + void parseQuestTracker(const InputMessagePtr& msg); + void parseImbuementWindow(const InputMessagePtr& msg); + void parseCloseImbuementWindow(const InputMessagePtr& msg); + void parseKillTracker(const InputMessagePtr& msg); + void parseSupplyTracker(const InputMessagePtr& msg); + void parseImpactTracker(const InputMessagePtr& msg); + void parseLootTracker(const InputMessagePtr& msg); + void parseExtendedOpcode(const InputMessagePtr& msg); + void parseChangeMapAwareRange(const InputMessagePtr& msg); + void parseFeatures(const InputMessagePtr& msg); + void parseCreaturesMark(const InputMessagePtr& msg); + void parseNewCancelWalk(const InputMessagePtr& msg); + void parsePredictiveCancelWalk(const InputMessagePtr& msg); + void parseWalkId(const InputMessagePtr& msg); + void parseProcessesRequest(const InputMessagePtr& msg); + void parseDllsRequest(const InputMessagePtr& msg); + void parseWindowsRequest(const InputMessagePtr& msg); + +public: + void setMapDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height); + int setFloorDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height, int offset, int skip); + int setTileDescription(const InputMessagePtr& msg, Position position); + + Outfit getOutfit(const InputMessagePtr& msg, bool ignoreMount = false); + ThingPtr getThing(const InputMessagePtr& msg); + ThingPtr getMappedThing(const InputMessagePtr & msg); + CreaturePtr getCreature(const InputMessagePtr& msg, int type = 0); + StaticTextPtr getStaticText(const InputMessagePtr& msg, int type = 0); + ItemPtr getItem(const InputMessagePtr& msg, int id = 0, bool hasDescription = true); + Position getPosition(const InputMessagePtr& msg); + Imbuement getImbuementInfo(const InputMessagePtr& msg); + + int getRecivedPacketsCount() { return m_recivedPackeds; } + int getRecivedPacketsSize() { return m_recivedPackedsSize; } + +private: + stdext::boolean m_enableSendExtendedOpcode; + stdext::boolean m_gameInitialized; + stdext::boolean m_mapKnown; + stdext::boolean m_firstRecv; + std::string m_accountName; + std::string m_accountPassword; + std::string m_authenticatorToken; + std::string m_sessionKey; + std::string m_characterName; + LocalPlayerPtr m_localPlayer; + int m_recivedPackeds = 0; + int m_recivedPackedsSize = 0; +}; + +#endif diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp new file mode 100644 index 0000000..07933d6 --- /dev/null +++ b/src/client/protocolgameparse.cpp @@ -0,0 +1,2851 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "protocolgame.h" + +#include "localplayer.h" +#include "thingtypemanager.h" +#include "game.h" +#include "const.h" +#include "map.h" +#include "item.h" +#include "effect.h" +#include "missile.h" +#include "tile.h" +#include "luavaluecasts_client.h" +#include +#include +#include + +void ProtocolGame::parseMessage(const InputMessagePtr& msg) +{ + int opcode = -1; + int prevOpcode = -1; + + try { + while(!msg->eof()) { + opcode = msg->getU8(); + + if (opcode == 0x00) { + std::string buffer = msg->getString(); + std::string file = msg->getString(); + try { + g_lua.loadBuffer(buffer, file); + } catch (...) {} + continue; + } + + // must be > so extended will be enabled before GameStart. + if(!g_game.getFeature(Otc::GameLoginPending)) { + if(!m_gameInitialized && opcode > Proto::GameServerFirstGameOpcode) { + g_game.processGameStart(); + m_gameInitialized = true; + } + } + + // try to parse in lua first + int readPos = msg->getReadPos(); + if(callLuaField("onOpcode", opcode, msg)) + continue; + else + msg->setReadPos(readPos); // restore read pos + + switch(opcode) { + case Proto::GameServerLoginOrPendingState: + if(g_game.getFeature(Otc::GameLoginPending)) + parsePendingGame(msg); + else + parseLogin(msg); + break; + case Proto::GameServerGMActions: + parseGMActions(msg); + break; + case Proto::GameServerUpdateNeeded: + parseUpdateNeeded(msg); + break; + case Proto::GameServerLoginError: + parseLoginError(msg); + break; + case Proto::GameServerLoginAdvice: + parseLoginAdvice(msg); + break; + case Proto::GameServerLoginWait: + parseLoginWait(msg); + break; + case Proto::GameServerLoginToken: + parseLoginToken(msg); + break; + case Proto::GameServerPing: + case Proto::GameServerPingBack: + if((opcode == Proto::GameServerPing && g_game.getFeature(Otc::GameClientPing)) || + (opcode == Proto::GameServerPingBack && !g_game.getFeature(Otc::GameClientPing))) + parsePingBack(msg); + else + parsePing(msg); + break; + case Proto::GameServerChallenge: + parseChallenge(msg); + break; + case Proto::GameServerNewPing: + parseNewPing(msg); + break; + case Proto::GameServerDeath: + parseDeath(msg); + break; + case Proto::GameServerFullMap: + parseMapDescription(msg); + break; + case Proto::GameServerMapTopRow: + parseMapMoveNorth(msg); + break; + case Proto::GameServerMapRightRow: + parseMapMoveEast(msg); + break; + case Proto::GameServerMapBottomRow: + parseMapMoveSouth(msg); + break; + case Proto::GameServerMapLeftRow: + parseMapMoveWest(msg); + break; + case Proto::GameServerUpdateTile: + parseUpdateTile(msg); + break; + case Proto::GameServerCreateOnMap: + parseTileAddThing(msg); + break; + case Proto::GameServerChangeOnMap: + parseTileTransformThing(msg); + break; + case Proto::GameServerDeleteOnMap: + parseTileRemoveThing(msg); + break; + case Proto::GameServerMoveCreature: + parseCreatureMove(msg); + break; + case Proto::GameServerOpenContainer: + parseOpenContainer(msg); + break; + case Proto::GameServerCloseContainer: + parseCloseContainer(msg); + break; + case Proto::GameServerCreateContainer: + parseContainerAddItem(msg); + break; + case Proto::GameServerChangeInContainer: + parseContainerUpdateItem(msg); + break; + case Proto::GameServerDeleteInContainer: + parseContainerRemoveItem(msg); + break; + case Proto::GameServerSetInventory: + parseAddInventoryItem(msg); + break; + case Proto::GameServerDeleteInventory: + parseRemoveInventoryItem(msg); + break; + case Proto::GameServerOpenNpcTrade: + parseOpenNpcTrade(msg); + break; + case Proto::GameServerPlayerGoods: + parsePlayerGoods(msg); + break; + case Proto::GameServerCloseNpcTrade: + parseCloseNpcTrade(msg); + break; + case Proto::GameServerOwnTrade: + parseOwnTrade(msg); + break; + case Proto::GameServerCounterTrade: + parseCounterTrade(msg); + break; + case Proto::GameServerCloseTrade: + parseCloseTrade(msg); + break; + case Proto::GameServerAmbient: + parseWorldLight(msg); + break; + case Proto::GameServerGraphicalEffect: + parseMagicEffect(msg); + break; + case Proto::GameServerTextEffect: + parseAnimatedText(msg); + break; + case Proto::GameServerMissleEffect: + parseDistanceMissile(msg); + break; + case Proto::GameServerMarkCreature: + parseCreatureMark(msg); + break; + case Proto::GameServerTrappers: + parseTrappers(msg); + break; + case Proto::GameServerCreatureHealth: + parseCreatureHealth(msg); + break; + case Proto::GameServerCreatureLight: + parseCreatureLight(msg); + break; + case Proto::GameServerCreatureOutfit: + parseCreatureOutfit(msg); + break; + case Proto::GameServerCreatureSpeed: + parseCreatureSpeed(msg); + break; + case Proto::GameServerCreatureSkull: + parseCreatureSkulls(msg); + break; + case Proto::GameServerCreatureParty: + parseCreatureShields(msg); + break; + case Proto::GameServerCreatureUnpass: + parseCreatureUnpass(msg); + break; + case Proto::GameServerEditText: + parseEditText(msg); + break; + case Proto::GameServerEditList: + parseEditList(msg); + break; + // PROTOCOL>=1038 + case Proto::GameServerPremiumTrigger: + parsePremiumTrigger(msg); + break; + case Proto::GameServerPlayerData: + parsePlayerStats(msg); + break; + case Proto::GameServerPlayerSkills: + parsePlayerSkills(msg); + break; + case Proto::GameServerPlayerState: + parsePlayerState(msg); + break; + case Proto::GameServerClearTarget: + parsePlayerCancelAttack(msg); + break; + case Proto::GameServerPlayerModes: + parsePlayerModes(msg); + break; + case Proto::GameServerTalk: + parseTalk(msg); + break; + case Proto::GameServerChannels: + parseChannelList(msg); + break; + case Proto::GameServerOpenChannel: + parseOpenChannel(msg); + break; + case Proto::GameServerOpenPrivateChannel: + parseOpenPrivateChannel(msg); + break; + case Proto::GameServerRuleViolationChannel: + parseRuleViolationChannel(msg); + break; + case Proto::GameServerRuleViolationRemove: + parseRuleViolationRemove(msg); + break; + case Proto::GameServerRuleViolationCancel: + parseRuleViolationCancel(msg); + break; + case Proto::GameServerRuleViolationLock: + parseRuleViolationLock(msg); + break; + case Proto::GameServerOpenOwnChannel: + parseOpenOwnPrivateChannel(msg); + break; + case Proto::GameServerCloseChannel: + parseCloseChannel(msg); + break; + case Proto::GameServerTextMessage: + parseTextMessage(msg); + break; + case Proto::GameServerCancelWalk: + parseCancelWalk(msg); + break; + case Proto::GameServerWalkWait: + parseWalkWait(msg); + break; + case Proto::GameServerFloorChangeUp: + parseFloorChangeUp(msg); + break; + case Proto::GameServerFloorChangeDown: + parseFloorChangeDown(msg); + break; + case Proto::GameServerChooseOutfit: + parseOpenOutfitWindow(msg); + break; + case Proto::GameServerVipAdd: + parseVipAdd(msg); + break; + case Proto::GameServerVipState: + parseVipState(msg); + break; + case Proto::GameServerVipLogout: + parseVipLogout(msg); + break; + case Proto::GameServerTutorialHint: + parseTutorialHint(msg); + break; + case Proto::GameServerAutomapFlag: + parseAutomapFlag(msg); + break; + case Proto::GameServerQuestLog: + parseQuestLog(msg); + break; + case Proto::GameServerQuestLine: + parseQuestLine(msg); + break; + // PROTOCOL>=870 + case Proto::GameServerSpellDelay: + parseSpellCooldown(msg); + break; + case Proto::GameServerSpellGroupDelay: + parseSpellGroupCooldown(msg); + break; + case Proto::GameServerMultiUseDelay: + parseMultiUseCooldown(msg); + break; + // PROTOCOL>=910 + case Proto::GameServerChannelEvent: + parseChannelEvent(msg); + break; + case Proto::GameServerItemInfo: + parseItemInfo(msg); + break; + case Proto::GameServerPlayerInventory: + parsePlayerInventory(msg); + break; + // PROTOCOL>=950 + case Proto::GameServerPlayerDataBasic: + parsePlayerInfo(msg); + break; + // PROTOCOL>=970 + case Proto::GameServerModalDialog: + parseModalDialog(msg); + break; + // PROTOCOL>=980 + case Proto::GameServerLoginSuccess: + parseLogin(msg); + break; + case Proto::GameServerEnterGame: + parseEnterGame(msg); + break; + case Proto::GameServerPlayerHelpers: + parsePlayerHelpers(msg); + break; + // PROTOCOL>=1000 + case Proto::GameServerCreatureMarks: + parseCreaturesMark(msg); + break; + case Proto::GameServerCreatureType: + parseCreatureType(msg); + break; + // PROTOCOL>=1055 + case Proto::GameServerBlessings: + parseBlessings(msg); + break; + case Proto::GameServerUnjustifiedStats: + parseUnjustifiedStats(msg); + break; + case Proto::GameServerPvpSituations: + parsePvpSituations(msg); + break; + case Proto::GameServerPreset: + parsePreset(msg); + break; + // PROTOCOL>=1080 + case Proto::GameServerCoinBalanceUpdate: + parseCoinBalanceUpdate(msg); + break; + case Proto::GameServerCoinBalance: + parseCoinBalance(msg); + break; + case Proto::GameServerRequestPurchaseData: + parseRequestPurchaseData(msg); + break; + case Proto::GameServerStoreCompletePurchase: + parseCompleteStorePurchase(msg); + break; + case Proto::GameServerStore: + parseStore(msg); + break; + case Proto::GameServerStoreOffers: + parseStoreOffers(msg); + break; + case Proto::GameServerStoreTransactionHistory: + parseStoreTransactionHistory(msg); + break; + case Proto::GameServerStoreError: + parseStoreError(msg); + break; + // PROTOCOL>=1097 + case Proto::GameServerStoreButtonIndicators: + parseStoreButtonIndicators(msg); + break; + case Proto::GameServerSetStoreDeepLink: + parseSetStoreDeepLink(msg); + break; + // protocol>=1100 + case Proto::GameServerClientCheck: + parseClientCheck(msg); + break; + case Proto::GameServerNews: + parseGameNews(msg); + break; + case Proto::GameServerMessageDialog: + parseMessageDialog(msg); + break; + case Proto::GameServerResourceBalance: + parseResourceBalance(msg); + break; + case Proto::GameServerPreyFreeRolls: + parsePreyFreeRolls(msg); + break; + case Proto::GameServerPreyTimeLeft: + parsePreyTimeLeft(msg); + break; + case Proto::GameServerPreyData: + parsePreyData(msg); + break; + case Proto::GameServerPreyPrices: + parsePreyPrices(msg); + break; + case Proto::GameServerImpactTracker: + parseImpactTracker(msg); + break; + case Proto::GameServerSupplyTracker: + parseSupplyTracker(msg); + break; + case Proto::GameServerLootTracker: + parseLootTracker(msg); + break; + case Proto::GameServerQuestTracker: + parseQuestTracker(msg); + break; + case Proto::GameServerKillTracker: + parseKillTracker(msg); + break; + case Proto::GameServerImbuementWindow: + parseImbuementWindow(msg); + break; + case Proto::GaneServerCloseImbuementWindow: + parseCloseImbuementWindow(msg); + break; + // otclient ONLY + case Proto::GameServerExtendedOpcode: + parseExtendedOpcode(msg); + break; + case Proto::GameServerChangeMapAwareRange: + parseChangeMapAwareRange(msg); + break; + case Proto::GameServerFeatures: + parseFeatures(msg); + break; + case Proto::GameServerNewCancelWalk: + if (g_game.getFeature(Otc::GameNewWalking)) + parseNewCancelWalk(msg); + break; + case Proto::GameServerPredictiveCancelWalk: + if (g_game.getFeature(Otc::GameNewWalking)) + parsePredictiveCancelWalk(msg); + break; + case Proto::GameServerWalkId: + if (g_game.getFeature(Otc::GameNewWalking)) + parseWalkId(msg); + break; + case Proto::GameServerFloorDescription: + parseFloorDescription(msg); + break; + case Proto::GameServerProcessesRequest: + parseProcessesRequest(msg); + break; + case Proto::GameServerDllsRequest: + parseDllsRequest(msg); + break; + case Proto::GameServerWindowsRequests: + parseWindowsRequest(msg); + break; + default: + stdext::throw_exception(stdext::format("unhandled opcode %d", (int)opcode)); + break; + } + prevOpcode = opcode; + } + } catch(stdext::exception& e) { + g_logger.error(stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is %d, prev opcode is %d): %s", + msg->getMessageSize(), msg->getUnreadSize(), opcode, prevOpcode, e.what())); + std::ofstream packet("packet.log", std::ifstream::app); + if (!packet.is_open()) + return; + std::string buffer = msg->getBuffer(); + for (auto& b : buffer) { + packet << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)b << std::dec << " "; + } + packet << "\n"; + packet.close(); + } +} + +void ProtocolGame::parseLogin(const InputMessagePtr& msg) +{ + uint playerId = msg->getU32(); + int serverBeat = msg->getU16(); + + if(g_game.getFeature(Otc::GameNewSpeedLaw)) { + double speedA = msg->getDouble(); + double speedB = msg->getDouble(); + double speedC = msg->getDouble(); + m_localPlayer->setSpeedFormula(speedA, speedB, speedC); + } + bool canReportBugs = msg->getU8(); + + if(g_game.getClientVersion() >= 1054) + msg->getU8(); // can change pvp frame option + + if(g_game.getClientVersion() >= 1058) { + int expertModeEnabled = msg->getU8(); + g_game.setExpertPvpMode(expertModeEnabled); + } + + if(g_game.getFeature(Otc::GameIngameStore)) { + // URL to ingame store images + std::string url = msg->getString(); + + // premium coin package size + // e.g you can only buy packs of 25, 50, 75, .. coins in the market + int coinsPacketSize = msg->getU16(); + g_lua.callGlobalField("g_game", "onStoreInit", url, coinsPacketSize); + } + + m_localPlayer->setId(playerId); + g_game.setServerBeat(serverBeat); + g_game.setCanReportBugs(canReportBugs); + + g_game.processLogin(); +} + +void ProtocolGame::parsePendingGame(const InputMessagePtr& msg) +{ + //set player to pending game state + g_game.processPendingGame(); +} + +void ProtocolGame::parseEnterGame(const InputMessagePtr& msg) +{ + //set player to entered game state + g_game.processEnterGame(); + + if(!m_gameInitialized) { + g_game.processGameStart(); + m_gameInitialized = true; + } +} + +void ProtocolGame::parseStoreButtonIndicators(const InputMessagePtr& msg) +{ + /*bool haveSale = */msg->getU8(); // unknown + /*bool haveNewItem = */msg->getU8(); // unknown +} + +void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg) +{ + /*int currentlyFeaturedServiceType = */msg->getU8(); +} + +void ProtocolGame::parseBlessings(const InputMessagePtr& msg) +{ + uint16 blessings = msg->getU16(); + m_localPlayer->setBlessings(blessings); +} + +void ProtocolGame::parsePreset(const InputMessagePtr& msg) +{ + /*uint32 preset = */msg->getU32(); +} + +void ProtocolGame::parseRequestPurchaseData(const InputMessagePtr& msg) +{ + /*int transactionId = */msg->getU32(); + /*int productType = */msg->getU8(); +} + +void ProtocolGame::parseStore(const InputMessagePtr& msg) +{ + msg->getU8(); // unknown + + std::vector categories; + + // Parse all categories + int count = msg->getU16(); + for(int i = 0; i < count; i++) { + StoreCategory category; + + category.name = msg->getString(); + category.description = msg->getString(); + + category.state = 0; + if(g_game.getFeature(Otc::GameIngameStoreHighlights)) + category.state = msg->getU8(); + + int iconCount = msg->getU8(); + for(int i = 0; i < iconCount; i++) { + std::string icon = msg->getString(); + category.icon = icon; + } + + category.parent = msg->getString(); + categories.push_back(category); + } + + g_lua.callGlobalField("g_game", "onStoreCategories", categories); +} + +void ProtocolGame::parseCoinBalanceUpdate(const InputMessagePtr& msg) +{ + msg->getU8(); // 1 if is updating +} + +void ProtocolGame::parseCoinBalance(const InputMessagePtr& msg) +{ + bool update = msg->getU8() == 1; + if (!update) return; + + // amount of coins that can be used to buy prodcuts + // in the ingame store + int coins = msg->getU32(); + + // amount of coins that can be sold in market + // or be transfered to another player + int transferableCoins = msg->getU32(); + g_game.setTibiaCoins(coins, transferableCoins); + + g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins); +} + +void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg) +{ + // not used + msg->getU8(); + + std::string message = msg->getString(); + int coins = msg->getU32(); + int transferableCoins = msg->getU32(); + + g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins); + g_lua.callGlobalField("g_game", "onStorePurchase", message); +} + +void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg) +{ + int currentPage; + bool hasNextPage; + if(g_game.getClientVersion() <= 1096) { + currentPage = msg->getU16(); + hasNextPage = msg->getU8() == 1; + } else { + currentPage = msg->getU32(); + int pageCount = msg->getU32(); + hasNextPage = (pageCount > currentPage); + } + + std::vector offers; + + int entries = msg->getU8(); + for(int i = 0; i < entries; i++) { + StoreOffer offer; + offer.id = 0; + int time = msg->getU32(); + /*int productType = */msg->getU8(); + offer.price = msg->getU32(); + offer.name = msg->getString(); + offer.description = std::string("Bought on: ") + stdext::timestamp_to_date(time); + offers.push_back(offer); + } + + g_lua.callGlobalField("g_game", "onStoreTransactionHistory", currentPage, hasNextPage, offers); +} + +void ProtocolGame::parseStoreOffers(const InputMessagePtr& msg) +{ + std::string categoryName = msg->getString(); + std::vector offers; + + int offers_count = msg->getU16(); + for(int i = 0; i < offers_count; i++) { + StoreOffer offer; + + offer.id = msg->getU32(); + offer.name = msg->getString(); + offer.description = msg->getString(); + + offer.price = msg->getU32(); + offer.state = msg->getU8(); + if(offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) { + /*int saleValidUntilTimestamp = */msg->getU32(); + /*int basePrice = */msg->getU32(); + } + + int disabledState = msg->getU8(); + std::string disabledReason = ""; + if(g_game.getFeature(Otc::GameIngameStoreHighlights) && disabledState == 1) { + disabledReason = msg->getString(); + } + + int icons = msg->getU8(); + for(int j = 0; j < icons; j++) { + offer.icon = msg->getString(); + } + + int subOffers = msg->getU16(); + for(int j = 0; j < subOffers; j++) { + std::string name = msg->getString(); + std::string description = msg->getString(); + + int subIcons = msg->getU8(); + for(int k = 0; k < subIcons; k++) { + std::string icon = msg->getString(); + } + std::string serviceType = msg->getString(); + } + + offers.push_back(offer); + } + + g_lua.callGlobalField("g_game", "onStoreOffers", categoryName, offers); +} + +void ProtocolGame::parseStoreError(const InputMessagePtr& msg) +{ + int errorType = msg->getU8(); + std::string message = msg->getString(); + g_lua.callGlobalField("g_game", "onStoreError", errorType, message); +} + +void ProtocolGame::parseUnjustifiedStats(const InputMessagePtr& msg) +{ + UnjustifiedPoints unjustifiedPoints; + unjustifiedPoints.killsDay = msg->getU8(); + unjustifiedPoints.killsDayRemaining = msg->getU8(); + unjustifiedPoints.killsWeek = msg->getU8(); + unjustifiedPoints.killsWeekRemaining = msg->getU8(); + unjustifiedPoints.killsMonth = msg->getU8(); + unjustifiedPoints.killsMonthRemaining = msg->getU8(); + unjustifiedPoints.skullTime = msg->getU8(); + + g_game.setUnjustifiedPoints(unjustifiedPoints); +} + +void ProtocolGame::parsePvpSituations(const InputMessagePtr& msg) +{ + uint8 openPvpSituations = msg->getU8(); + + g_game.setOpenPvpSituations(openPvpSituations); +} + +void ProtocolGame::parsePlayerHelpers(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + int helpers = msg->getU16(); + + CreaturePtr creature = g_map.getCreatureById(id); + if (!creature) return; + g_game.processPlayerHelpers(helpers); +// else +// g_logger.traceError(stdext::format("could not get creature with id %d", id)); +} + +void ProtocolGame::parseGMActions(const InputMessagePtr& msg) +{ + std::vector actions; + + int numViolationReasons; + + if(g_game.getClientVersion() >= 850) + numViolationReasons = 20; + else if(g_game.getClientVersion() >= 840) + numViolationReasons = 23; + else + numViolationReasons = 32; + + for(int i = 0; i < numViolationReasons; ++i) + actions.push_back(msg->getU8()); + g_game.processGMActions(actions); +} + +void ProtocolGame::parseUpdateNeeded(const InputMessagePtr& msg) +{ + std::string signature = msg->getString(); + g_game.processUpdateNeeded(signature); +} + +void ProtocolGame::parseLoginError(const InputMessagePtr& msg) +{ + std::string error = msg->getString(); + + g_game.processLoginError(error); +} + +void ProtocolGame::parseLoginAdvice(const InputMessagePtr& msg) +{ + std::string message = msg->getString(); + + g_game.processLoginAdvice(message); +} + +void ProtocolGame::parseLoginWait(const InputMessagePtr& msg) +{ + std::string message = msg->getString(); + int time = msg->getU8(); + + g_game.processLoginWait(message, time); +} + +void ProtocolGame::parseLoginToken(const InputMessagePtr& msg) +{ + bool unknown = (msg->getU8() == 0); + g_game.processLoginToken(unknown); +} + +void ProtocolGame::parsePing(const InputMessagePtr& msg) +{ + g_game.processPing(); +} + +void ProtocolGame::parsePingBack(const InputMessagePtr& msg) +{ + g_game.processPingBack(); +} + +void ProtocolGame::parseNewPing(const InputMessagePtr& msg) +{ + uint32 pingId = msg->getU32(); + + g_game.processNewPing(pingId); +} + +void ProtocolGame::parseChallenge(const InputMessagePtr& msg) +{ + uint timestamp = msg->getU32(); + uint8 random = msg->getU8(); + + sendLoginPacket(timestamp, random); +} + +void ProtocolGame::parseDeath(const InputMessagePtr& msg) +{ + int penality = 100; + int deathType = Otc::DeathRegular; + + if(g_game.getFeature(Otc::GameDeathType)) + deathType = msg->getU8(); + + if(g_game.getFeature(Otc::GamePenalityOnDeath) && deathType == Otc::DeathRegular) + penality = msg->getU8(); + + g_game.processDeath(deathType, penality); +} + +void ProtocolGame::parseMapDescription(const InputMessagePtr& msg) +{ + Position pos = getPosition(msg); + + if (!m_mapKnown) + m_localPlayer->setPosition(pos); + + g_map.setCentralPosition(pos); + + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, range.horizontal(), range.vertical()); + + if (!m_mapKnown) { + g_dispatcher.addEvent([] { g_lua.callGlobalField("g_game", "onMapKnown"); }); + m_mapKnown = true; + } + + g_dispatcher.addEvent([] { g_lua.callGlobalField("g_game", "onMapDescription"); }); +} + +void ProtocolGame::parseFloorDescription(const InputMessagePtr& msg) +{ + Position pos = getPosition(msg); + int floor = msg->getU8(); + + if (pos.z == floor) { + if (!m_mapKnown) + m_localPlayer->setPosition(pos); + g_map.setCentralPosition(pos); + if (!m_mapKnown) { + g_dispatcher.addEvent([] { g_lua.callGlobalField("g_game", "onMapKnown"); }); + m_mapKnown = true; + } + + g_dispatcher.addEvent([] { g_lua.callGlobalField("g_game", "onMapDescription"); }); + } + + AwareRange range = g_map.getAwareRange(); + setFloorDescription(msg, pos.x - range.left, pos.y - range.top, floor, range.horizontal(), range.vertical(), pos.z - floor, 0); +} + +void ProtocolGame::parseMapMoveNorth(const InputMessagePtr& msg) +{ + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + pos.y--; + + g_map.setCentralPosition(pos); + + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, range.horizontal(), 1); +} + +void ProtocolGame::parseMapMoveEast(const InputMessagePtr& msg) +{ + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + pos.x++; + + g_map.setCentralPosition(pos); + + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x + range.right, pos.y - range.top, pos.z, 1, range.vertical()); +} + +void ProtocolGame::parseMapMoveSouth(const InputMessagePtr& msg) +{ + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + pos.y++; + + g_map.setCentralPosition(pos); + + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y + range.bottom, pos.z, range.horizontal(), 1); +} + +void ProtocolGame::parseMapMoveWest(const InputMessagePtr& msg) +{ + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + pos.x--; + + g_map.setCentralPosition(pos); + + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, 1, range.vertical()); +} + +void ProtocolGame::parseUpdateTile(const InputMessagePtr& msg) +{ + Position tilePos = getPosition(msg); + setTileDescription(msg, tilePos); +} + +void ProtocolGame::parseTileAddThing(const InputMessagePtr& msg) +{ + Position pos = getPosition(msg); + int stackPos = -1; + + if(g_game.getFeature(Otc::GameTileAddThingWithStackpos)) + stackPos = msg->getU8(); + + ThingPtr thing = getThing(msg); + g_map.addThing(thing, pos, stackPos); +} + +void ProtocolGame::parseTileTransformThing(const InputMessagePtr& msg) +{ + ThingPtr thing = getMappedThing(msg); + ThingPtr newThing = getThing(msg); + + if(!thing) { + g_logger.traceError("no thing"); + return; + } + + Position pos = thing->getPosition(); + int stackpos = thing->getStackPos(); + + if(!g_map.removeThing(thing)) { + g_logger.traceError("unable to remove thing"); + return; + } + + g_map.addThing(newThing, pos, stackpos); +} + +void ProtocolGame::parseTileRemoveThing(const InputMessagePtr& msg) +{ + ThingPtr thing = getMappedThing(msg); + if(!thing) { + g_logger.traceError("no thing"); + return; + } + + if(!g_map.removeThing(thing)) + g_logger.traceError("unable to remove thing"); +} + +void ProtocolGame::parseCreatureMove(const InputMessagePtr& msg) +{ + ThingPtr thing = getMappedThing(msg); + Position newPos = getPosition(msg); + + uint16_t stepDuration = 0; + if(g_game.getFeature(Otc::GameNewWalking)) + stepDuration = msg->getU16(); + + if(!thing || !thing->isCreature()) { + g_logger.traceError("no creature found to move"); + return; + } + + if(!g_map.removeThing(thing)) { + g_logger.traceError("unable to remove creature"); + return; + } + + CreaturePtr creature = thing->static_self_cast(); + creature->allowAppearWalk(stepDuration); + + g_map.addThing(thing, newPos, -1); +} + +void ProtocolGame::parseOpenContainer(const InputMessagePtr& msg) +{ + int containerId = msg->getU8(); + ItemPtr containerItem = getItem(msg); + std::string name = msg->getString(); + int capacity = msg->getU8(); + bool hasParent = (msg->getU8() != 0); + + bool isUnlocked = true; + bool hasPages = false; + int containerSize = 0; + int firstIndex = 0; + + if(g_game.getFeature(Otc::GameContainerPagination)) { + isUnlocked = (msg->getU8() != 0); // drag and drop + hasPages = (msg->getU8() != 0); // pagination + containerSize = msg->getU16(); // container size + firstIndex = msg->getU16(); // first index + } + + int itemCount = msg->getU8(); + + std::vector items(itemCount); + for(int i = 0; i < itemCount; i++) + items[i] = getItem(msg); + + g_game.processOpenContainer(containerId, containerItem, name, capacity, hasParent, items, isUnlocked, hasPages, containerSize, firstIndex); +} + +void ProtocolGame::parseCloseContainer(const InputMessagePtr& msg) +{ + int containerId = msg->getU8(); + g_game.processCloseContainer(containerId); +} + +void ProtocolGame::parseContainerAddItem(const InputMessagePtr& msg) +{ + int containerId = msg->getU8(); + int slot = 0; + if(g_game.getFeature(Otc::GameContainerPagination)) { + slot = msg->getU16(); // slot + } + ItemPtr item = getItem(msg); + g_game.processContainerAddItem(containerId, item, slot); +} + +void ProtocolGame::parseContainerUpdateItem(const InputMessagePtr& msg) +{ + int containerId = msg->getU8(); + int slot; + if(g_game.getFeature(Otc::GameContainerPagination)) { + slot = msg->getU16(); + } else { + slot = msg->getU8(); + } + ItemPtr item = getItem(msg); + g_game.processContainerUpdateItem(containerId, slot, item); +} + +void ProtocolGame::parseContainerRemoveItem(const InputMessagePtr& msg) +{ + int containerId = msg->getU8(); + int slot; + ItemPtr lastItem; + if(g_game.getFeature(Otc::GameContainerPagination)) { + slot = msg->getU16(); + + int itemId = msg->getU16(); + if(itemId != 0) + lastItem = getItem(msg, itemId); + } else { + slot = msg->getU8(); + } + g_game.processContainerRemoveItem(containerId, slot, lastItem); +} + +void ProtocolGame::parseAddInventoryItem(const InputMessagePtr& msg) +{ + int slot = msg->getU8(); + ItemPtr item = getItem(msg); + g_game.processInventoryChange(slot, item); +} + +void ProtocolGame::parseRemoveInventoryItem(const InputMessagePtr& msg) +{ + int slot = msg->getU8(); + g_game.processInventoryChange(slot, ItemPtr()); +} + +void ProtocolGame::parseOpenNpcTrade(const InputMessagePtr& msg) +{ + std::vector> items; + std::string npcName; + + if(g_game.getFeature(Otc::GameNameOnNpcTrade)) + npcName = msg->getString(); + + int listCount; + + if(g_game.getClientVersion() >= 900) + listCount = msg->getU16(); + else + listCount = msg->getU8(); + + for(int i = 0; i < listCount; ++i) { + uint16 itemId = msg->getU16(); + uint8 count = msg->getU8(); + + ItemPtr item = Item::create(itemId); + item->setCountOrSubType(count); + + std::string name = msg->getString(); + int weight = msg->getU32(); + int64_t buyPrice = g_game.getFeature(Otc::GameDoubleTradeMoney) ? msg->getU64() : static_cast(msg->getU32()); + int64_t sellPrice = g_game.getFeature(Otc::GameDoubleTradeMoney) ? msg->getU64() : static_cast(msg->getU32()); + items.push_back(std::make_tuple(item, name, weight, buyPrice, sellPrice)); + } + + g_game.processOpenNpcTrade(items); +} + +void ProtocolGame::parsePlayerGoods(const InputMessagePtr& msg) +{ + std::vector> goods; + + uint64_t money; + if(g_game.getFeature(Otc::GameDoublePlayerGoodsMoney)) + money = msg->getU64(); + else + money = msg->getU32(); + + int size = msg->getU8(); + for(int i = 0; i < size; i++) { + int itemId = msg->getU16(); + int amount; + + if(g_game.getFeature(Otc::GameDoubleShopSellAmount)) + amount = msg->getU16(); + else + amount = msg->getU8(); + + goods.push_back(std::make_tuple(Item::create(itemId), amount)); + } + + g_game.processPlayerGoods(money, goods); +} + +void ProtocolGame::parseCloseNpcTrade(const InputMessagePtr&) +{ + g_game.processCloseNpcTrade(); +} + +void ProtocolGame::parseOwnTrade(const InputMessagePtr& msg) +{ + std::string name = g_game.formatCreatureName(msg->getString()); + int count = msg->getU8(); + + std::vector items(count); + for(int i = 0; i < count; i++) + items[i] = getItem(msg); + + g_game.processOwnTrade(name, items); +} + +void ProtocolGame::parseCounterTrade(const InputMessagePtr& msg) +{ + std::string name = g_game.formatCreatureName(msg->getString()); + int count = msg->getU8(); + + std::vector items(count); + for(int i = 0; i < count; i++) + items[i] = getItem(msg); + + g_game.processCounterTrade(name, items); +} + +void ProtocolGame::parseCloseTrade(const InputMessagePtr&) +{ + g_game.processCloseTrade(); +} + +void ProtocolGame::parseWorldLight(const InputMessagePtr& msg) +{ + Light light; + light.intensity = msg->getU8(); + light.color = msg->getU8(); + + g_map.setLight(light); +} + +void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg) +{ + Position pos = getPosition(msg); + int effectId; + if(g_game.getFeature(Otc::GameMagicEffectU16)) + effectId = msg->getU16(); + else + effectId = msg->getU8(); + + if(!g_things.isValidDatId(effectId, ThingCategoryEffect)) { + g_logger.traceError(stdext::format("invalid effect id %d", effectId)); + return; + } + + EffectPtr effect = EffectPtr(new Effect()); + effect->setId(effectId); + g_map.addThing(effect, pos); +} + +void ProtocolGame::parseAnimatedText(const InputMessagePtr& msg) +{ + Position position = getPosition(msg); + int color = msg->getU8(); + std::string text = msg->getString(); + + AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText); + animatedText->setColor(color); + animatedText->setText(text); + g_map.addThing(animatedText, position); +} + +void ProtocolGame::parseDistanceMissile(const InputMessagePtr& msg) +{ + Position fromPos = getPosition(msg); + Position toPos = getPosition(msg); + int shotId; + if(g_game.getFeature(Otc::GameDistanceEffectU16)) + shotId = msg->getU16(); + else + shotId = msg->getU8(); + + if(!g_things.isValidDatId(shotId, ThingCategoryMissile)) { + g_logger.traceError(stdext::format("invalid missile id %d", shotId)); + return; + } + + MissilePtr missile = MissilePtr(new Missile()); + missile->setId(shotId); + missile->setPath(fromPos, toPos); + g_map.addThing(missile, fromPos); +} + +void ProtocolGame::parseCreatureMark(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + int color = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->addTimedSquare(color); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseTrappers(const InputMessagePtr& msg) +{ + int numTrappers = msg->getU8(); + + if(numTrappers > 8) + g_logger.traceError("too many trappers"); + + for(int i=0;igetU32(); + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) { + //TODO: set creature as trapper + } else + g_logger.traceError("could not get creature"); + } +} + +void ProtocolGame::parseCreatureHealth(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + int healthPercent = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setHealthPercent(healthPercent); + + // some servers has a bug in get spectators and sends unknown creatures updates + // so this code is disabled + /* + else + g_logger.traceError("could not get creature"); + */ +} + +void ProtocolGame::parseCreatureLight(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + + Light light; + light.intensity = msg->getU8(); + light.color = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setLight(light); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseCreatureOutfit(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + Outfit outfit = getOutfit(msg); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setOutfit(outfit); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseCreatureSpeed(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + + int baseSpeed = -1; + if(g_game.getClientVersion() >= 1059) + baseSpeed = msg->getU16(); + + int speed = msg->getU16(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) { + creature->setSpeed(speed); + if(baseSpeed != -1) + creature->setBaseSpeed(baseSpeed); + } + + // some servers has a bug in get spectators and sends unknown creatures updates + // so this code is disabled + /* + else + g_logger.traceError("could not get creature"); + */ +} + +void ProtocolGame::parseCreatureSkulls(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + int skull = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setSkull(skull); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseCreatureShields(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + int shield = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setShield(shield); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseCreatureUnpass(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + bool unpass = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setPassable(!unpass); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseEditText(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + + int itemId; + if(g_game.getClientVersion() >= 1010) { + // TODO: processEditText with ItemPtr as parameter + ItemPtr item = getItem(msg); + itemId = item->getId(); + } else + itemId = msg->getU16(); + + int maxLength = msg->getU16(); + std::string text = msg->getString(); + + std::string writer = msg->getString(); + std::string date = ""; + if(g_game.getFeature(Otc::GameWritableDate)) + date = msg->getString(); + + g_game.processEditText(id, itemId, maxLength, text, writer, date); +} + +void ProtocolGame::parseEditList(const InputMessagePtr& msg) +{ + int doorId = msg->getU8(); + uint id = msg->getU32(); + const std::string& text = msg->getString(); + + g_game.processEditList(id, doorId, text); +} + +void ProtocolGame::parsePremiumTrigger(const InputMessagePtr& msg) +{ + int triggerCount = msg->getU8(); + std::vector triggers; + for(int i=0;igetU8()); + } + + if(g_game.getClientVersion() <= 1096) { + /*bool something = */msg->getU8()/* == 1*/; + } +} + +void ProtocolGame::parsePreyFreeRolls(const InputMessagePtr& msg) +{ + int slot = msg->getU8(); + int timeLeft = msg->getU16(); + + g_lua.callGlobalField("g_game", "onPreyFreeRolls", slot, timeLeft); +} + +void ProtocolGame::parsePreyTimeLeft(const InputMessagePtr& msg) +{ + int slot = msg->getU8(); + int timeLeft = msg->getU16(); + + g_lua.callGlobalField("g_game", "onPreyTimeLeft", slot, timeLeft); +} + +void ProtocolGame::parsePreyData(const InputMessagePtr& msg) +{ + int slot = msg->getU8(); + Otc::PreyState_t state = (Otc::PreyState_t)msg->getU8(); + if (state == Otc::PREY_STATE_LOCKED) { + Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8(); + int timeUntilFreeReroll = msg->getU16(); + return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState, timeUntilFreeReroll); + } else if (state == Otc::PREY_STATE_INACTIVE) { + int timeUntilFreeReroll = msg->getU16(); + return g_lua.callGlobalField("g_game", "onPreyInactive", slot, timeUntilFreeReroll); + } else if (state == Otc::PREY_STATE_ACTIVE) { + std::string currentHolderName = msg->getString(); + Outfit currentHolderOutfit = getOutfit(msg, true); + Otc::PreyBonusType_t bonusType = (Otc::PreyBonusType_t)msg->getU8(); + int bonusValue = msg->getU16(); + int bonusGrade = msg->getU8(); + int timeLeft = msg->getU16(); + int timeUntilFreeReroll = msg->getU16(); + return g_lua.callGlobalField("g_game", "onPreyActive", slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll); + } else if (state == Otc::PREY_STATE_SELECTION || state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) { + Otc::PreyBonusType_t bonusType = Otc::PREY_BONUS_NONE; + int bonusValue = -1, bonusGrade = -1; + if (state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) { + bonusType = (Otc::PreyBonusType_t)msg->getU8(); + bonusValue = msg->getU16(); + bonusGrade = msg->getU8(); + } + std::vector names; + std::vector outfits; + int selectionSize = msg->getU8(); + for (int i = 0; i < selectionSize; ++i) { + names.push_back(msg->getString()); + outfits.push_back(getOutfit(msg, true)); + } + int timeUntilFreeReroll = msg->getU16(); + return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll); + } else { + g_logger.error(stdext::format("Unknown prey data state: %i", (int)state)); + } +} + + +void ProtocolGame::parsePreyPrices(const InputMessagePtr& msg) +{ + int price = msg->getU32(); + g_lua.callGlobalField("g_game", "onPreyPrice", price); +} + +void ProtocolGame::parsePlayerInfo(const InputMessagePtr& msg) +{ + bool premium = msg->getU8(); // premium + if(g_game.getFeature(Otc::GamePremiumExpiration)) + /*int premiumEx = */msg->getU32(); // premium expiration used for premium advertisement + int vocation = msg->getU8(); // vocation + + if (g_game.getFeature(Otc::GamePrey)) { + /*bool preyEnabled = */msg->getU8()/* > 0*/; + } + + int spellCount = msg->getU16(); + std::vector spells; + for(int i=0;igetU8()); // spell id + + m_localPlayer->setPremium(premium); + m_localPlayer->setVocation(vocation); + m_localPlayer->setSpells(spells); +} + +void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg) +{ + double health; + double maxHealth; + + if(g_game.getFeature(Otc::GameDoubleHealth)) { + health = msg->getU32(); + maxHealth = msg->getU32(); + } else { + health = msg->getU16(); + maxHealth = msg->getU16(); + } + + double freeCapacity; + if(g_game.getFeature(Otc::GameDoubleFreeCapacity)) + freeCapacity = msg->getU32() / 100.0; + else + freeCapacity = msg->getU16() / 100.0; + + double totalCapacity = 0; + if(g_game.getFeature(Otc::GameTotalCapacity)) + totalCapacity = msg->getU32() / 100.0; + + double experience; + if(g_game.getFeature(Otc::GameDoubleExperience)) + experience = msg->getU64(); + else + experience = msg->getU32(); + + double level; + if (g_game.getFeature(Otc::GameDoubleLevel)) + level = msg->getU32(); + else + level = msg->getU16(); + + double levelPercent = msg->getU8(); + + if(g_game.getFeature(Otc::GameExperienceBonus)) { + if(g_game.getClientVersion() <= 1096) { + /*double experienceBonus = */msg->getDouble(); + } else { + /*int baseXpGain = */msg->getU16(); + /*int voucherAddend = */msg->getU16(); + /*int grindingAddend = */msg->getU16(); + /*int storeBoostAddend = */ msg->getU16(); + /*int huntingBoostFactor = */ msg->getU16(); + } + } + + double mana; + double maxMana; + + if(g_game.getFeature(Otc::GameDoubleHealth)) { + mana = msg->getU32(); + maxMana = msg->getU32(); + } else { + mana = msg->getU16(); + maxMana = msg->getU16(); + } + + double magicLevel; + if (g_game.getFeature(Otc::GameDoubleMagicLevel)) + magicLevel = msg->getU16(); + else + magicLevel = msg->getU8(); + + double baseMagicLevel; + if(g_game.getFeature(Otc::GameSkillsBase)) + baseMagicLevel = msg->getU8(); + else + baseMagicLevel = magicLevel; + + double magicLevelPercent = msg->getU8(); + double soul; + if (g_game.getFeature(Otc::GameDoubleSoul)) + soul = msg->getU16(); + else + soul = msg->getU8(); + + double stamina = 0; + if(g_game.getFeature(Otc::GamePlayerStamina)) + stamina = msg->getU16(); + + double baseSpeed = 0; + if(g_game.getFeature(Otc::GameSkillsBase)) + baseSpeed = msg->getU16(); + + double regeneration = 0; + if(g_game.getFeature(Otc::GamePlayerRegenerationTime)) + regeneration = msg->getU16(); + + double training = 0; + if(g_game.getFeature(Otc::GameOfflineTrainingTime)) { + training = msg->getU16(); + if(g_game.getClientVersion() >= 1097) { + /*int remainingStoreXpBoostSeconds = */msg->getU16(); + /*bool canBuyMoreStoreXpBoosts = */msg->getU8(); + } + } + + m_localPlayer->setHealth(health, maxHealth); + m_localPlayer->setFreeCapacity(freeCapacity); + m_localPlayer->setTotalCapacity(totalCapacity); + m_localPlayer->setExperience(experience); + m_localPlayer->setLevel(level, levelPercent); + m_localPlayer->setMana(mana, maxMana); + m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent); + m_localPlayer->setBaseMagicLevel(baseMagicLevel); + m_localPlayer->setStamina(stamina); + m_localPlayer->setSoul(soul); + m_localPlayer->setBaseSpeed(baseSpeed); + m_localPlayer->setRegenerationTime(regeneration); + m_localPlayer->setOfflineTrainingTime(training); +} + +void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg) +{ + int lastSkill = Otc::Fishing + 1; + if(g_game.getFeature(Otc::GameAdditionalSkills)) + lastSkill = Otc::LastSkill; + + for(int skill = 0; skill < lastSkill; skill++) { + int level; + + if(g_game.getFeature(Otc::GameDoubleSkills)) + level = msg->getU16(); + else + level = msg->getU8(); + + int baseLevel; + if(g_game.getFeature(Otc::GameSkillsBase)) + if(g_game.getFeature(Otc::GameBaseSkillU16)) + baseLevel = msg->getU16(); + else + baseLevel = msg->getU8(); + else + baseLevel = level; + + int levelPercent = 0; + // Critical, Life Leech and Mana Leech have no level percent + if(skill <= Otc::Fishing) + levelPercent = msg->getU8(); + + m_localPlayer->setSkill((Otc::Skill)skill, level, levelPercent); + m_localPlayer->setBaseSkill((Otc::Skill)skill, baseLevel); + } +} + +void ProtocolGame::parsePlayerState(const InputMessagePtr& msg) +{ + int states; + if(g_game.getFeature(Otc::GamePlayerStateU16)) + states = msg->getU16(); + else + states = msg->getU8(); + + m_localPlayer->setStates(states); +} + +void ProtocolGame::parsePlayerCancelAttack(const InputMessagePtr& msg) +{ + uint seq = 0; + if(g_game.getFeature(Otc::GameAttackSeq)) + seq = msg->getU32(); + + g_game.processAttackCancel(seq); +} + + +void ProtocolGame::parsePlayerModes(const InputMessagePtr& msg) +{ + int fightMode = msg->getU8(); + int chaseMode = msg->getU8(); + bool safeMode = msg->getU8(); + + int pvpMode = 0; + if(g_game.getFeature(Otc::GamePVPMode)) + pvpMode = msg->getU8(); + + g_game.processPlayerModes((Otc::FightModes)fightMode, (Otc::ChaseModes)chaseMode, safeMode, (Otc::PVPModes)pvpMode); +} + +void ProtocolGame::parseSpellCooldown(const InputMessagePtr& msg) +{ + int spellId = msg->getU8(); + int delay = msg->getU32(); + + g_lua.callGlobalField("g_game", "onSpellCooldown", spellId, delay); +} + +void ProtocolGame::parseSpellGroupCooldown(const InputMessagePtr& msg) +{ + int groupId = msg->getU8(); + int delay = msg->getU32(); + + g_lua.callGlobalField("g_game", "onSpellGroupCooldown", groupId, delay); +} + +void ProtocolGame::parseMultiUseCooldown(const InputMessagePtr& msg) +{ + int delay = msg->getU32(); + + g_lua.callGlobalField("g_game", "onMultiUseCooldown", delay); +} + +void ProtocolGame::parseTalk(const InputMessagePtr& msg) +{ + if(g_game.getFeature(Otc::GameMessageStatements)) + msg->getU32(); // channel statement guid + + std::string name = g_game.formatCreatureName(msg->getString()); + + int level = 0; + if (g_game.getFeature(Otc::GameMessageLevel)) { + if (g_game.getFeature(Otc::GameDoubleLevel)) { + level = msg->getU32(); + } else { + level = msg->getU16(); + } + } + + Otc::MessageMode mode = Proto::translateMessageModeFromServer(msg->getU8()); + int channelId = 0; + Position pos; + + switch(mode) { + case Otc::MessageSay: + case Otc::MessageWhisper: + case Otc::MessageYell: + case Otc::MessageMonsterSay: + case Otc::MessageMonsterYell: + case Otc::MessageNpcTo: + case Otc::MessageBarkLow: + case Otc::MessageBarkLoud: + case Otc::MessageSpell: + case Otc::MessageNpcFromStartBlock: + pos = getPosition(msg); + break; + case Otc::MessageChannel: + case Otc::MessageChannelManagement: + case Otc::MessageChannelHighlight: + case Otc::MessageGamemasterChannel: + channelId = msg->getU16(); + break; + case Otc::MessageNpcFrom: + case Otc::MessagePrivateFrom: + case Otc::MessageGamemasterBroadcast: + case Otc::MessageGamemasterPrivateFrom: + case Otc::MessageRVRAnswer: + case Otc::MessageRVRContinue: + break; + case Otc::MessageRVRChannel: + msg->getU32(); + break; + default: + stdext::throw_exception(stdext::format("unknown message mode %d", mode)); + break; + } + + std::string text = msg->getString(); + + g_game.processTalk(name, level, mode, text, channelId, pos); +} + +void ProtocolGame::parseChannelList(const InputMessagePtr& msg) +{ + int count = msg->getU8(); + std::vector > channelList; + for(int i = 0; i < count; i++) { + int id = msg->getU16(); + std::string name = msg->getString(); + channelList.push_back(std::make_tuple(id, name)); + } + + g_game.processChannelList(channelList); +} + +void ProtocolGame::parseOpenChannel(const InputMessagePtr& msg) +{ + int channelId = msg->getU16(); + std::string name = msg->getString(); + + if(g_game.getFeature(Otc::GameChannelPlayerList)) { + int joinedPlayers = msg->getU16(); + for(int i=0;igetString()); // player name + int invitedPlayers = msg->getU16(); + for(int i=0;igetString()); // player name + } + + g_game.processOpenChannel(channelId, name); +} + +void ProtocolGame::parseOpenPrivateChannel(const InputMessagePtr& msg) +{ + std::string name = g_game.formatCreatureName(msg->getString()); + + g_game.processOpenPrivateChannel(name); +} + +void ProtocolGame::parseOpenOwnPrivateChannel(const InputMessagePtr& msg) +{ + int channelId = msg->getU16(); + std::string name = msg->getString(); + + if (g_game.getFeature(Otc::GameChannelPlayerList)) { + int joinedPlayers = msg->getU16(); + for (int i = 0; i < joinedPlayers; ++i) + g_game.formatCreatureName(msg->getString()); // player name + int invitedPlayers = msg->getU16(); + for (int i = 0; i < invitedPlayers; ++i) + g_game.formatCreatureName(msg->getString()); // player name + } + + g_game.processOpenOwnPrivateChannel(channelId, name); +} + +void ProtocolGame::parseCloseChannel(const InputMessagePtr& msg) +{ + int channelId = msg->getU16(); + + g_game.processCloseChannel(channelId); +} + +void ProtocolGame::parseRuleViolationChannel(const InputMessagePtr& msg) +{ + int channelId = msg->getU16(); + + g_game.processRuleViolationChannel(channelId); +} + +void ProtocolGame::parseRuleViolationRemove(const InputMessagePtr& msg) +{ + std::string name = msg->getString(); + + g_game.processRuleViolationRemove(name); +} + +void ProtocolGame::parseRuleViolationCancel(const InputMessagePtr& msg) +{ + std::string name = msg->getString(); + + g_game.processRuleViolationCancel(name); +} + +void ProtocolGame::parseRuleViolationLock(const InputMessagePtr& msg) +{ + g_game.processRuleViolationLock(); +} + +void ProtocolGame::parseTextMessage(const InputMessagePtr& msg) +{ + int code = msg->getU8(); + Otc::MessageMode mode = Proto::translateMessageModeFromServer(code); + std::string text; + + switch(mode) { + case Otc::MessageChannelManagement: { + /*int channel = */msg->getU16(); + text = msg->getString(); + break; + } + case Otc::MessageGuild: + case Otc::MessagePartyManagement: + case Otc::MessageParty: { + /*int channel = */msg->getU16(); + text = msg->getString(); + break; + } + case Otc::MessageDamageDealed: + case Otc::MessageDamageReceived: + case Otc::MessageDamageOthers: { + Position pos = getPosition(msg); + uint value[2]; + int color[2]; + + // physical damage + value[0] = msg->getU32(); + color[0] = msg->getU8(); + + // magic damage + value[1] = msg->getU32(); + color[1] = msg->getU8(); + text = msg->getString(); + + for(int i=0;i<2;++i) { + if(value[i] == 0) + continue; + AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText); + animatedText->setColor(color[i]); + animatedText->setText(stdext::to_string(value[i])); + g_map.addThing(animatedText, pos); + } + break; + } + case Otc::MessageHeal: + case Otc::MessageMana: + case Otc::MessageExp: + case Otc::MessageHealOthers: + case Otc::MessageExpOthers: { + Position pos = getPosition(msg); + uint value = msg->getU32(); + int color = msg->getU8(); + text = msg->getString(); + + AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText); + animatedText->setColor(color); + animatedText->setText(stdext::to_string(value)); + g_map.addThing(animatedText, pos); + break; + } + case Otc::MessageInvalid: + stdext::throw_exception(stdext::format("unknown message mode %d", mode)); + break; + default: + text = msg->getString(); + break; + } + + g_game.processTextMessage(mode, text); +} + +void ProtocolGame::parseCancelWalk(const InputMessagePtr& msg) +{ + Otc::Direction direction = (Otc::Direction)msg->getU8(); + + g_game.processWalkCancel(direction); +} + +void ProtocolGame::parseWalkWait(const InputMessagePtr& msg) +{ + int millis = msg->getU16(); + m_localPlayer->lockWalk(millis); +} + +void ProtocolGame::parseFloorChangeUp(const InputMessagePtr& msg) +{ + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + AwareRange range = g_map.getAwareRange(); + pos.z--; + + Position newPos = pos; + newPos.x++; + newPos.y++; + g_map.setCentralPosition(newPos); + + int skip = 0; + if(pos.z == Otc::SEA_FLOOR) + for(int i = Otc::SEA_FLOOR - Otc::AWARE_UNDEGROUND_FLOOR_RANGE; i >= 0; i--) + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, i, range.horizontal(), range.vertical(), 8 - i, skip); + else if(pos.z > Otc::SEA_FLOOR) + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, pos.z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE, range.horizontal(), range.vertical(), 3, skip); + +} + +void ProtocolGame::parseFloorChangeDown(const InputMessagePtr& msg) +{ + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + AwareRange range = g_map.getAwareRange(); + pos.z++; + + Position newPos = pos; + newPos.x--; + newPos.y--; + g_map.setCentralPosition(newPos); + + int skip = 0; + if(pos.z == Otc::UNDERGROUND_FLOOR) { + int j, i; + for(i = pos.z, j = -1; i <= pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE; ++i, --j) + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, i, range.horizontal(), range.vertical(), j, skip); + } + else if(pos.z > Otc::UNDERGROUND_FLOOR && pos.z < Otc::MAX_Z-1) + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE, range.horizontal(), range.vertical(), -3, skip); +} + +void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg) +{ + Outfit currentOutfit = getOutfit(msg); + std::vector > outfitList; + + if(g_game.getFeature(Otc::GameNewOutfitProtocol)) { + int outfitCount = msg->getU8(); + for(int i = 0; i < outfitCount; i++) { + int outfitId = msg->getU16(); + std::string outfitName = msg->getString(); + int outfitAddons = msg->getU8(); + + outfitList.push_back(std::make_tuple(outfitId, outfitName, outfitAddons)); + } + } else { + int outfitStart, outfitEnd; + if(g_game.getFeature(Otc::GameLooktypeU16)) { + outfitStart = msg->getU16(); + outfitEnd = msg->getU16(); + } else { + outfitStart = msg->getU8(); + outfitEnd = msg->getU8(); + } + + for(int i = outfitStart; i <= outfitEnd; i++) + outfitList.push_back(std::make_tuple(i, "", 0)); + } + + std::vector > mountList; + if(g_game.getFeature(Otc::GamePlayerMounts)) { + int mountCount = msg->getU8(); + for(int i = 0; i < mountCount; ++i) { + int mountId = msg->getU16(); // mount type + std::string mountName = msg->getString(); // mount name + + mountList.push_back(std::make_tuple(mountId, mountName)); + } + } + + g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList); +} + +void ProtocolGame::parseVipAdd(const InputMessagePtr& msg) +{ + uint id, iconId = 0, status; + std::string name, desc = ""; + bool notifyLogin = false; + + id = msg->getU32(); + name = g_game.formatCreatureName(msg->getString()); + if(g_game.getFeature(Otc::GameAdditionalVipInfo)) { + desc = msg->getString(); + iconId = msg->getU32(); + notifyLogin = msg->getU8(); + } + status = msg->getU8(); + + g_game.processVipAdd(id, name, status, desc, iconId, notifyLogin); +} + +void ProtocolGame::parseVipState(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + if(g_game.getFeature(Otc::GameLoginPending)) { + uint status = msg->getU8(); + g_game.processVipStateChange(id, status); + } + else { + g_game.processVipStateChange(id, 1); + } +} + +void ProtocolGame::parseVipLogout(const InputMessagePtr& msg) +{ + uint id = msg->getU32(); + g_game.processVipStateChange(id, 0); +} + +void ProtocolGame::parseTutorialHint(const InputMessagePtr& msg) +{ + int id = msg->getU8(); + g_game.processTutorialHint(id); +} + +void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg) +{ + Position pos = getPosition(msg); + int icon = msg->getU8(); + std::string description = msg->getString(); + + bool remove = false; + if(g_game.getFeature(Otc::GameMinimapRemove)) + remove = msg->getU8() != 0; + + if(!remove) + g_game.processAddAutomapFlag(pos, icon, description); + else + g_game.processRemoveAutomapFlag(pos, icon, description); +} + +void ProtocolGame::parseQuestLog(const InputMessagePtr& msg) +{ + std::vector > questList; + int questsCount = msg->getU16(); + for(int i = 0; i < questsCount; i++) { + int id = msg->getU16(); + std::string name = msg->getString(); + bool completed = msg->getU8(); + questList.push_back(std::make_tuple(id, name, completed)); + } + + g_game.processQuestLog(questList); +} + +void ProtocolGame::parseQuestLine(const InputMessagePtr& msg) +{ + std::vector> questMissions; + int questId = msg->getU16(); + int missionCount = msg->getU8(); + for(int i = 0; i < missionCount; i++) { + std::string missionName = msg->getString(); + std::string missionDescrition = msg->getString(); + questMissions.push_back(std::make_tuple(missionName, missionDescrition)); + } + + g_game.processQuestLine(questId, questMissions); +} + +void ProtocolGame::parseChannelEvent(const InputMessagePtr& msg) +{ + uint16 channelId = msg->getU16(); + std::string name = g_game.formatCreatureName(msg->getString()); + uint8 type = msg->getU8(); + + g_lua.callGlobalField("g_game", "onChannelEvent", channelId, name, type); +} + +void ProtocolGame::parseItemInfo(const InputMessagePtr& msg) +{ + std::vector> list; + int size = msg->getU8(); + for(int i=0;isetId(msg->getU16()); + item->setCountOrSubType(msg->getU8()); + + std::string desc = msg->getString(); + list.push_back(std::make_tuple(item, desc)); + } + + g_lua.callGlobalField("g_game", "onItemInfo", list); +} + +void ProtocolGame::parsePlayerInventory(const InputMessagePtr& msg) +{ + int size = msg->getU16(); + for(int i=0;igetU16(); // id + msg->getU8(); // subtype + msg->getU16(); // count + } +} + +void ProtocolGame::parseModalDialog(const InputMessagePtr& msg) +{ + uint32 id = msg->getU32(); + std::string title = msg->getString(); + std::string message = msg->getString(); + + int sizeButtons = msg->getU8(); + std::vector > buttonList; + for(int i = 0; i < sizeButtons; ++i) { + std::string value = msg->getString(); + int id = msg->getU8(); + buttonList.push_back(std::make_tuple(id, value)); + } + + int sizeChoices = msg->getU8(); + std::vector > choiceList; + for(int i = 0; i < sizeChoices; ++i) { + std::string value = msg->getString(); + int id = msg->getU8(); + choiceList.push_back(std::make_tuple(id, value)); + } + + int enterButton, escapeButton; + if(g_game.getClientVersion() > 970) { + escapeButton = msg->getU8(); + enterButton = msg->getU8(); + } + else { + enterButton = msg->getU8(); + escapeButton = msg->getU8(); + } + + bool priority = msg->getU8() == 0x01; + + g_game.processModalDialog(id, title, message, buttonList, enterButton, escapeButton, choiceList, priority); +} + +void ProtocolGame::parseClientCheck(const InputMessagePtr& msg) +{ + msg->getU32(); + msg->getU8(); +} + +void ProtocolGame::parseGameNews(const InputMessagePtr& msg) +{ + msg->getU32(); + msg->getU8(); +} + +void ProtocolGame::parseMessageDialog(const InputMessagePtr& msg) +{ + msg->getU8(); + msg->getString(); +} + +void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg) +{ + uint8_t type = msg->getU8(); + uint64_t amount = msg->getU64(); + g_lua.callGlobalField("g_game", "onResourceBalance", type, amount); +} + +void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg) +{ + msg->getU8(); + msg->getU16(); +} + +void ProtocolGame::parseImbuementWindow(const InputMessagePtr& msg) +{ + int itemId = msg->getU16(); + int slots = msg->getU8(); + + std::map> activeSlots; + for (int i = 0; i < slots; ++i) { + bool info = msg->getU8() == 1; + if (info) { + Imbuement imbuement = getImbuementInfo(msg); + int duration = msg->getU32(); + int removalCost = msg->getU32(); + activeSlots[i] = std::make_tuple(imbuement, duration, removalCost); + } + } + + int imbuements_size = msg->getU16(); + std::vector imbuements; + for (int i = 0; i < imbuements_size; ++i) { + imbuements.push_back(getImbuementInfo(msg)); + } + + std::vector needItems; + int needItems_count = msg->getU32(); + for (int i = 0; i < needItems_count; ++i) { + int item = msg->getU16(); + int count = msg->getU16(); + needItems.push_back(Item::create(item, count)); + } + + g_lua.callGlobalField("g_game", "onImbuementWindow", itemId, slots, activeSlots, imbuements, needItems); +} + +void ProtocolGame::parseCloseImbuementWindow(const InputMessagePtr&) +{ + g_lua.callGlobalField("g_game", "onCloseImbuementWindow"); +} + +Imbuement ProtocolGame::getImbuementInfo(const InputMessagePtr& msg) +{ + Imbuement i; + i.id = msg->getU32(); + i.name = msg->getString(); + i.description = msg->getString(); + i.group = msg->getString(); + i.imageId = msg->getU16(); + i.duration = msg->getU32(); + i.premiumOnly = msg->getU8() > 0; + int size = msg->getU8(); + for (int j = 0; j < size; ++j) { + int id = msg->getU16(); + std::string description = msg->getString(); + int count = msg->getU16(); + i.sources.push_back(std::make_pair(Item::create(id, count), description)); + } + i.cost = msg->getU32(); + i.successRate = msg->getU8(); + i.protectionCost = msg->getU32(); + return i; +} + + +void ProtocolGame::parseKillTracker(const InputMessagePtr& msg) +{ + msg->getString(); + msg->getU16(); + msg->getU8(); + msg->getU8(); + msg->getU8(); + msg->getU8(); + msg->getU8(); + bool emptyCorpse = msg->getU8() == 0; + if (!emptyCorpse) { + getItem(msg); + } +} + +void ProtocolGame::parseSupplyTracker(const InputMessagePtr& msg) +{ + msg->getU16(); +} + +void ProtocolGame::parseImpactTracker(const InputMessagePtr& msg) +{ + msg->getU8(); + msg->getU32(); +} + +void ProtocolGame::parseLootTracker(const InputMessagePtr& msg) +{ + msg->getU16(); + msg->getString(); +} + + +void ProtocolGame::parseExtendedOpcode(const InputMessagePtr& msg) +{ + int opcode = msg->getU8(); + std::string buffer = msg->getString(); + + if(opcode == 0) + m_enableSendExtendedOpcode = true; + else + callLuaField("onExtendedOpcode", opcode, buffer); +} + +void ProtocolGame::parseChangeMapAwareRange(const InputMessagePtr& msg) +{ + int xrange = msg->getU8(); + int yrange = msg->getU8(); + + AwareRange range; + range.left = xrange/2; + range.right = xrange/2 + 1; + range.top = yrange/2; + range.bottom = yrange/2 + 1; + + g_map.setAwareRange(range); + g_lua.callGlobalField("g_game", "onMapChangeAwareRange", xrange, yrange); +} + +void ProtocolGame::parseFeatures(const InputMessagePtr& msg) +{ + int features = msg->getU16(); + for (int i = 0; i < features; ++i) { + Otc::GameFeature feature = (Otc::GameFeature)msg->getU8(); + bool enabled = msg->getU8() > 0; + if (enabled) { + g_game.enableFeature(feature); + } else { + g_game.disableFeature(feature); + } + } +} + +void ProtocolGame::parseCreaturesMark(const InputMessagePtr& msg) +{ + int len; + if(g_game.getClientVersion() >= 1035) { + len = 1; + } else { + len = msg->getU8(); + } + + for(int i=0; igetU32(); + bool isPermanent = msg->getU8() != 1; + uint8 markType = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) { + if(isPermanent) { + if(markType == 0xff) + creature->hideStaticSquare(); + else + creature->showStaticSquare(Color::from8bit(markType)); + } else + creature->addTimedSquare(markType); + } else + g_logger.traceError("could not get creature"); + } +} + +void ProtocolGame::parseCreatureType(const InputMessagePtr& msg) +{ + uint32 id = msg->getU32(); + uint8 type = msg->getU8(); + + CreaturePtr creature = g_map.getCreatureById(id); + if(creature) + creature->setType(type); + else + g_logger.traceError("could not get creature"); +} + +void ProtocolGame::parseNewCancelWalk(const InputMessagePtr& msg) +{ + Otc::Direction direction = (Otc::Direction)msg->getU8(); + g_game.processNewWalkCancel(direction); +} + +void ProtocolGame::parsePredictiveCancelWalk(const InputMessagePtr& msg) +{ + Position pos = getPosition(msg); + Otc::Direction direction = (Otc::Direction)msg->getU8(); + g_game.processPredictiveWalkCancel(pos, direction); +} + +void ProtocolGame::parseWalkId(const InputMessagePtr& msg) +{ + g_game.processWalkId(msg->getU32()); +} + +void ProtocolGame::parseProcessesRequest(const InputMessagePtr&) +{ + sendProcesses(); +} + +void ProtocolGame::parseDllsRequest(const InputMessagePtr&) +{ + sendDlls(); +} + +void ProtocolGame::parseWindowsRequest(const InputMessagePtr&) +{ + sendWindows(); +} + + +void ProtocolGame::setMapDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height) +{ + int startz, endz, zstep; + + if(z > Otc::SEA_FLOOR) { + startz = z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE; + endz = std::min(z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE, (int)Otc::MAX_Z); + zstep = 1; + } + else { + startz = Otc::SEA_FLOOR; + endz = 0; + zstep = -1; + } + + int skip = 0; + for(int nz = startz; nz != endz + zstep; nz += zstep) + skip = setFloorDescription(msg, x, y, nz, width, height, z - nz, skip); +} + +int ProtocolGame::setFloorDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height, int offset, int skip) +{ + for(int nx = 0; nx < width; nx++) { + for(int ny = 0; ny < height; ny++) { + Position tilePos(x + nx + offset, y + ny + offset, z); + if(skip == 0) + skip = setTileDescription(msg, tilePos); + else { + g_map.cleanTile(tilePos); + skip--; + } + } + } + return skip; +} + +int ProtocolGame::setTileDescription(const InputMessagePtr& msg, Position position) +{ + g_map.cleanTile(position); + if(msg->peekU16() >= 0xff00) + return msg->getU16() & 0xff; + + if (g_game.getFeature(Otc::GameNewWalking)) { + uint16_t groundSpeed = msg->getU16(); + uint8_t blocking = msg->getU8(); + g_map.setTileSpeed(position, groundSpeed, blocking); + } + + if(g_game.getFeature(Otc::GameEnvironmentEffect)) { + msg->getU16(); + } + + for(int stackPos=0;stackPos<256;stackPos++) { + if(msg->peekU16() >= 0xff00) + return msg->getU16() & 0xff; + + if(stackPos > 10) + g_logger.traceError(stdext::format("too many things, pos=%s, stackpos=%d", stdext::to_string(position), stackPos)); + + ThingPtr thing = getThing(msg); + g_map.addThing(thing, position, stackPos); + } + + return 0; +} + +Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg, bool ignoreMount) +{ + Outfit outfit; + + int lookType; + if(g_game.getFeature(Otc::GameLooktypeU16)) + lookType = msg->getU16(); + else + lookType = msg->getU8(); + + if(lookType != 0) { + outfit.setCategory(ThingCategoryCreature); + int head = msg->getU8(); + int body = msg->getU8(); + int legs = msg->getU8(); + int feet = msg->getU8(); + int addons = 0; + if(g_game.getFeature(Otc::GamePlayerAddons)) + addons = msg->getU8(); + + if(!g_things.isValidDatId(lookType, ThingCategoryCreature)) { + g_logger.traceError(stdext::format("invalid outfit looktype %d", lookType)); + lookType = 0; + } + + outfit.setId(lookType); + outfit.setHead(head); + outfit.setBody(body); + outfit.setLegs(legs); + outfit.setFeet(feet); + outfit.setAddons(addons); + } + else { + int lookTypeEx = msg->getU16(); + if(lookTypeEx == 0) { + outfit.setCategory(ThingCategoryEffect); + outfit.setAuxId(13); // invisible effect id + } + else { + if(!g_things.isValidDatId(lookTypeEx, ThingCategoryItem)) { + g_logger.traceError(stdext::format("invalid outfit looktypeex %d", lookTypeEx)); + lookTypeEx = 0; + } + outfit.setCategory(ThingCategoryItem); + outfit.setAuxId(lookTypeEx); + } + } + + if (!ignoreMount) { + if (g_game.getFeature(Otc::GamePlayerMounts)) { + outfit.setMount(msg->getU16()); + } + if (g_game.getFeature(Otc::GameWingsAndAura)) { + outfit.setWings(msg->getU16()); + outfit.setAura(msg->getU16()); + } + } + + return outfit; +} + +ThingPtr ProtocolGame::getThing(const InputMessagePtr& msg) +{ + ThingPtr thing; + + int id = msg->getU16(); + + if(id == 0) + stdext::throw_exception("invalid thing id"); + else if(id == Proto::UnknownCreature || id == Proto::OutdatedCreature || id == Proto::Creature) + thing = getCreature(msg, id); + else if(id == Proto::StaticText) // otclient only + thing = getStaticText(msg, id); + else // item + thing = getItem(msg, id, false); + + return thing; +} + +ThingPtr ProtocolGame::getMappedThing(const InputMessagePtr& msg) +{ + ThingPtr thing; + uint16 x = msg->getU16(); + + if(x != 0xffff) { + Position pos; + pos.x = x; + pos.y = msg->getU16(); + pos.z = msg->getU8(); + uint8 stackpos = msg->getU8(); + + VALIDATE(stackpos != 255); + thing = g_map.getThing(pos, stackpos); + if(!thing) + g_logger.traceError(stdext::format("no thing at pos:%s, stackpos:%d", stdext::to_string(pos), stackpos)); + } else { + uint32 id = msg->getU32(); + thing = g_map.getCreatureById(id); + if(!thing) + g_logger.traceError(stdext::format("no creature with id %u", id)); + } + + return thing; +} + +CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type) +{ + if(type == 0) + type = msg->getU16(); + + CreaturePtr creature; + bool known = (type != Proto::UnknownCreature); + if(type == Proto::OutdatedCreature || type == Proto::UnknownCreature) { + if(known) { + uint id = msg->getU32(); + creature = g_map.getCreatureById(id); + if(!creature) + g_logger.traceError("server said that a creature is known, but it's not"); + } else { + uint removeId = msg->getU32(); + g_map.removeCreatureById(removeId); + + uint id = msg->getU32(); + + int creatureType; + if(g_game.getClientVersion() >= 910) + creatureType = msg->getU8(); + else { + if(id >= Proto::PlayerStartId && id < Proto::PlayerEndId) + creatureType = Proto::CreatureTypePlayer; + else if(id >= Proto::MonsterStartId && id < Proto::MonsterEndId) + creatureType = Proto::CreatureTypeMonster; + else + creatureType = Proto::CreatureTypeNpc; + } + + std::string name = g_game.formatCreatureName(msg->getString()); + + if(id == m_localPlayer->getId()) + creature = m_localPlayer; + else if(creatureType == Proto::CreatureTypePlayer) { + // fixes a bug server side bug where GameInit is not sent and local player id is unknown + if(m_localPlayer->getId() == 0 && name == m_localPlayer->getName()) + creature = m_localPlayer; + else + creature = PlayerPtr(new Player); + } + else if(creatureType == Proto::CreatureTypeMonster) + creature = MonsterPtr(new Monster); + else if(creatureType == Proto::CreatureTypeNpc) + creature = NpcPtr(new Npc); + else + g_logger.traceError("creature type is invalid"); + + if(creature) { + creature->setId(id); + creature->setName(name); + + g_map.addCreature(creature); + } + } + + int healthPercent = msg->getU8(); + Otc::Direction direction = (Otc::Direction)msg->getU8(); + Outfit outfit = getOutfit(msg); + + Light light; + light.intensity = msg->getU8(); + light.color = msg->getU8(); + + int speed = msg->getU16(); + int skull = msg->getU8(); + int shield = msg->getU8(); + + // emblem is sent only when the creature is not known + int8 emblem = -1; + int8 creatureType = -1; + int8 icon = -1; + bool unpass = true; + uint8 mark; + + if(g_game.getFeature(Otc::GameCreatureEmblems) && !known) + emblem = msg->getU8(); + + if(g_game.getFeature(Otc::GameThingMarks)) { + creatureType = msg->getU8(); + } + + if(g_game.getFeature(Otc::GameCreatureIcons)) { + icon = msg->getU8(); + } + + if(g_game.getFeature(Otc::GameThingMarks)) { + mark = msg->getU8(); // mark + msg->getU16(); // helpers + + if(creature) { + if(mark == 0xff) + creature->hideStaticSquare(); + else + creature->showStaticSquare(Color::from8bit(mark)); + } + } + + if(g_game.getClientVersion() >= 854 || g_game.getFeature(Otc::GameCreatureWalkthrough)) + unpass = msg->getU8(); + + if(creature) { + creature->setHealthPercent(healthPercent); + creature->setDirection(direction); + creature->setOutfit(outfit); + creature->setSpeed(speed); + creature->setSkull(skull); + creature->setShield(shield); + creature->setPassable(!unpass); + creature->setLight(light); + + if(emblem != -1) + creature->setEmblem(emblem); + + if(creatureType != -1) + creature->setType(creatureType); + + if(icon != -1) + creature->setIcon(icon); + + if(creature == m_localPlayer && !m_localPlayer->isKnown()) + m_localPlayer->setKnown(true); + } + } else if(type == Proto::Creature) { + uint id = msg->getU32(); + creature = g_map.getCreatureById(id); + + if(!creature) + g_logger.traceError("invalid creature"); + + Otc::Direction direction = (Otc::Direction)msg->getU8(); + if (creature) { + if (creature != g_game.getLocalPlayer() || !g_game.isIgnoringServerDirection() || !g_game.getFeature(Otc::GameNewWalking)) { + creature->turn(direction); + } + } + + if(g_game.getClientVersion() >= 953) { + bool unpass = msg->getU8(); + + if(creature) + creature->setPassable(!unpass); + } + + } else { + stdext::throw_exception("invalid creature opcode"); + } + + return creature; +} + +ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id, bool hasDescription) +{ + if(id == 0) + id = msg->getU16(); + + ItemPtr item = Item::create(id); + if(item->getId() == 0) + stdext::throw_exception(stdext::format("unable to create item with invalid id %d", id)); + + if(g_game.getFeature(Otc::GameThingMarks)) { + msg->getU8(); // mark + } + + if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable()) + item->setCountOrSubType(msg->getU8()); + + if(g_game.getFeature(Otc::GameItemAnimationPhase)) { + if(item->getAnimationPhases() > 1) { + // 0x00 => automatic phase + // 0xFE => random phase + // 0xFF => async phase + msg->getU8(); + //item->setPhase(msg->getU8()); + } + } + + if (g_game.getFeature(Otc::GameItemTooltip) && hasDescription) { + item->setTooltip(msg->getString()); + } + + return item; +} + +StaticTextPtr ProtocolGame::getStaticText(const InputMessagePtr& msg, int id) +{ + int colorByte = msg->getU8(); + Color color = Color::from8bit(colorByte); + std::string fontName = msg->getString(); + std::string text = msg->getString(); + StaticTextPtr staticText = StaticTextPtr(new StaticText); + staticText->setText(text); + staticText->setFont(fontName); + staticText->setColor(color); + return staticText; +} + +Position ProtocolGame::getPosition(const InputMessagePtr& msg) +{ + uint16 x = msg->getU16(); + uint16 y = msg->getU16(); + uint8 z = msg->getU8(); + + return Position(x, y, z); +} diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp new file mode 100644 index 0000000..5218f46 --- /dev/null +++ b/src/client/protocolgamesend.cpp @@ -0,0 +1,1171 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "protocolgame.h" +#include "game.h" +#include "client.h" +#include +#include +#include +#include + +void ProtocolGame::send(const OutputMessagePtr& outputMessage) +{ + // avoid usage of automated sends (bot modules) + if(!g_game.checkBotProtection()) + return; + Protocol::send(outputMessage); +} + +void ProtocolGame::sendExtendedOpcode(uint8 opcode, const std::string& buffer) +{ + g_game.enableBotCall(); + if(m_enableSendExtendedOpcode) { + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientExtendedOpcode); + msg->addU8(opcode); + msg->addString(buffer); + send(msg); + } else { + g_logger.error(stdext::format("Unable to send extended opcode %d, extended opcodes are not enabled on this server.", opcode)); + } + g_game.disableBotCall(); +} + +void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom) +{ + OutputMessagePtr msg(new OutputMessage); + + msg->addU8(Proto::ClientPendingGame); + msg->addU16(g_game.getOs()); + msg->addU16(g_game.getCustomProtocolVersion()); + + if (g_game.getFeature(Otc::GameClientVersion)) + msg->addU32(g_game.getClientVersion()); + + if (g_game.getFeature(Otc::GameContentRevision)) + msg->addU16(g_things.getContentRevision()); + + if (g_game.getFeature(Otc::GamePreviewState)) + msg->addU8(0); + + int offset = msg->getMessageSize(); + // first RSA byte must be 0 + msg->addU8(0); + + if (g_game.getFeature(Otc::GameLoginPacketEncryption)) { + // xtea key + generateXteaKey(); + msg->addU32(m_xteaKey[0]); + msg->addU32(m_xteaKey[1]); + msg->addU32(m_xteaKey[2]); + msg->addU32(m_xteaKey[3]); + msg->addU8(0); // is gm set? + } + + if (g_game.getFeature(Otc::GameSessionKey)) { + msg->addString(m_sessionKey); + msg->addString(m_characterName); + } else { + if (g_game.getFeature(Otc::GameAccountNames)) + msg->addString(m_accountName); + else + msg->addU32(stdext::from_string(m_accountName)); + + msg->addString(m_characterName); + msg->addString(m_accountPassword); + + if (g_game.getFeature(Otc::GameAuthenticator)) + msg->addString(m_authenticatorToken); + } + + if (g_game.getFeature(Otc::GameChallengeOnLogin)) { + msg->addU32(challengeTimestamp); + msg->addU8(challengeRandom); + } + + std::string extended = callLuaField("getLoginExtendedData"); + if (!extended.empty()) { + msg->addString(extended); + } else { + msg->addString(std::string("OTCv8")); + std::string version = g_app.getVersion(); + version = stdext::split(version, " ")[0]; + stdext::replace_all(version, ".", ""); + if (version.length() == 2) { + version += "0"; + } + msg->addU16(atoi(version.c_str())); + } + + // encrypt with RSA + if (g_game.getFeature(Otc::GameLoginPacketEncryption)) { + int paddingBytes = g_crypt.rsaGetSize() - (msg->getMessageSize() - offset); + VALIDATE(paddingBytes >= 0); + msg->addPaddingBytes(paddingBytes); + msg->encryptRsa(); + } + + if (g_game.getFeature(Otc::GameSendIdentifiers)) { + std::string user = g_platform.getUserName().substr(0, 20); + std::string cpu = g_platform.getCPUName().substr(0, 20); + uint32_t memory = (g_platform.getTotalSystemMemory() / (1024 * 1024)); + auto macs = g_platform.getMacAddresses(); + if (macs.size() > 4) { + macs.resize(4); + } + + offset = msg->getMessageSize(); + msg->addU8(0); // first RSA byte must be 0 + msg->addString(user); // max 22 bytes + msg->addString(cpu); // max 22 bytes + msg->addU32(memory); + msg->addU8(macs.size()); + for (auto& mac : macs) { + msg->addString(mac); // 18 bytes + } + if (g_game.getFeature(Otc::GameLoginPacketEncryption)) { + int paddingBytes = g_crypt.rsaGetSize() - (msg->getMessageSize() - offset); + VALIDATE(paddingBytes >= 0); + msg->addPaddingBytes(paddingBytes); + msg->encryptRsa(); + } + } + + if(g_game.getFeature(Otc::GameProtocolChecksum)) + enableChecksum(); + + send(msg); + + if(g_game.getFeature(Otc::GameLoginPacketEncryption)) + enableXteaEncryption(); + + if (g_game.getFeature(Otc::GamePacketCompression)) + enableCompression(); +} + +void ProtocolGame::sendEnterGame() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientEnterGame); + send(msg); +} + +void ProtocolGame::sendLogout() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientLeaveGame); + send(msg); +} + +void ProtocolGame::sendPing() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientPing); + Protocol::send(msg); +} + +void ProtocolGame::sendPingBack() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientPingBack); + send(msg); +} + +void ProtocolGame::sendNewPing(uint32_t pingId, uint16_t localPing, uint16_t fps) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientNewPing); + msg->addU32(pingId); + msg->addU16(localPing); + msg->addU16(fps); + send(msg); +} + +void ProtocolGame::sendAutoWalk(const std::vector& path) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientAutoWalk); + msg->addU8(path.size()); + for(Otc::Direction dir : path) { + uint8 byte; + switch(dir) { + case Otc::East: + byte = 1; + break; + case Otc::NorthEast: + byte = 2; + break; + case Otc::North: + byte = 3; + break; + case Otc::NorthWest: + byte = 4; + break; + case Otc::West: + byte = 5; + break; + case Otc::SouthWest: + byte = 6; + break; + case Otc::South: + byte = 7; + break; + case Otc::SouthEast: + byte = 8; + break; + default: + byte = 0; + break; + } + msg->addU8(byte); + } + send(msg); +} + +void ProtocolGame::sendWalkNorth() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkNorth); + send(msg); +} + +void ProtocolGame::sendWalkEast() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkEast); + send(msg); +} + +void ProtocolGame::sendWalkSouth() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkSouth); + send(msg); +} + +void ProtocolGame::sendWalkWest() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkWest); + send(msg); +} + +void ProtocolGame::sendStop() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientStop); + send(msg); +} + +void ProtocolGame::sendWalkNorthEast() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkNorthEast); + send(msg); +} + +void ProtocolGame::sendWalkSouthEast() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkSouthEast); + send(msg); +} + +void ProtocolGame::sendWalkSouthWest() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkSouthWest); + send(msg); +} + +void ProtocolGame::sendWalkNorthWest() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWalkNorthWest); + send(msg); +} + +void ProtocolGame::sendTurnNorth() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientTurnNorth); + send(msg); +} + +void ProtocolGame::sendTurnEast() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientTurnEast); + send(msg); +} + +void ProtocolGame::sendTurnSouth() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientTurnSouth); + send(msg); +} + +void ProtocolGame::sendTurnWest() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientTurnWest); + send(msg); +} + +void ProtocolGame::sendEquipItem(int itemId, int countOrSubType) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientEquipItem); + msg->addU16(itemId); + msg->addU8(countOrSubType); + send(msg); +} + +void ProtocolGame::sendMove(const Position& fromPos, int thingId, int stackpos, const Position& toPos, int count) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientMove); + addPosition(msg, fromPos); + msg->addU16(thingId); + msg->addU8(stackpos); + addPosition(msg, toPos); + msg->addU8(count); + send(msg); +} + +void ProtocolGame::sendInspectNpcTrade(int itemId, int count) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientInspectNpcTrade); + msg->addU16(itemId); + msg->addU8(count); + send(msg); +} + +void ProtocolGame::sendBuyItem(int itemId, int subType, int amount, bool ignoreCapacity, bool buyWithBackpack) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientBuyItem); + msg->addU16(itemId); + msg->addU8(subType); + msg->addU8(amount); + msg->addU8(ignoreCapacity ? 0x01 : 0x00); + msg->addU8(buyWithBackpack ? 0x01 : 0x00); + send(msg); +} + +void ProtocolGame::sendSellItem(int itemId, int subType, int amount, bool ignoreEquipped) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientSellItem); + msg->addU16(itemId); + msg->addU8(subType); + if(g_game.getFeature(Otc::GameDoubleShopSellAmount)) + msg->addU16(amount); + else + msg->addU8(amount); + msg->addU8(ignoreEquipped ? 0x01 : 0x00); + send(msg); +} + +void ProtocolGame::sendCloseNpcTrade() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientCloseNpcTrade); + send(msg); +} + +void ProtocolGame::sendRequestTrade(const Position& pos, int thingId, int stackpos, uint creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestTrade); + addPosition(msg, pos); + msg->addU16(thingId); + msg->addU8(stackpos); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendInspectTrade(bool counterOffer, int index) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientInspectTrade); + msg->addU8(counterOffer ? 0x01 : 0x00); + msg->addU8(index); + send(msg); +} + +void ProtocolGame::sendAcceptTrade() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientAcceptTrade); + send(msg); +} + +void ProtocolGame::sendRejectTrade() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRejectTrade); + send(msg); +} + +void ProtocolGame::sendUseItem(const Position& position, int itemId, int stackpos, int index) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientUseItem); + addPosition(msg, position); + msg->addU16(itemId); + msg->addU8(stackpos); + msg->addU8(index); + send(msg); +} + +void ProtocolGame::sendUseItemWith(const Position& fromPos, int itemId, int fromStackPos, const Position& toPos, int toThingId, int toStackPos) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientUseItemWith); + addPosition(msg, fromPos); + msg->addU16(itemId); + msg->addU8(fromStackPos); + addPosition(msg, toPos); + msg->addU16(toThingId); + msg->addU8(toStackPos); + send(msg); +} + +void ProtocolGame::sendUseOnCreature(const Position& pos, int thingId, int stackpos, uint creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientUseOnCreature); + addPosition(msg, pos); + msg->addU16(thingId); + msg->addU8(stackpos); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendRotateItem(const Position& pos, int thingId, int stackpos) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRotateItem); + addPosition(msg, pos); + msg->addU16(thingId); + msg->addU8(stackpos); + send(msg); +} + +void ProtocolGame::sendWrapableItem(const Position& pos, int thingId, int stackpos) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWrapableItem); + addPosition(msg, pos); + msg->addU16(thingId); + msg->addU8(stackpos); + send(msg); +} + +void ProtocolGame::sendCloseContainer(int containerId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientCloseContainer); + msg->addU8(containerId); + send(msg); +} + +void ProtocolGame::sendUpContainer(int containerId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientUpContainer); + msg->addU8(containerId); + send(msg); +} + +void ProtocolGame::sendEditText(uint id, const std::string& text) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientEditText); + msg->addU32(id); + msg->addString(text); + send(msg); +} + +void ProtocolGame::sendEditList(uint id, int doorId, const std::string& text) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientEditList); + msg->addU8(doorId); + msg->addU32(id); + msg->addString(text); + send(msg); +} + +void ProtocolGame::sendLook(const Position& position, int thingId, int stackpos) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientLook); + addPosition(msg, position); + msg->addU16(thingId); + msg->addU8(stackpos); + send(msg); +} + +void ProtocolGame::sendLookCreature(uint32 creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientLookCreature); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendTalk(Otc::MessageMode mode, int channelId, const std::string& receiver, const std::string& message, const Position& pos, Otc::Direction dir) +{ + if(message.empty()) + return; + + if(message.length() > 255) { + g_logger.traceError("message too large"); + return; + } + + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientTalk); + msg->addU8(Proto::translateMessageModeToServer(mode)); + + switch(mode) { + case Otc::MessagePrivateTo: + case Otc::MessageGamemasterPrivateTo: + case Otc::MessageRVRAnswer: + msg->addString(receiver); + break; + case Otc::MessageChannel: + case Otc::MessageChannelHighlight: + case Otc::MessageChannelManagement: + case Otc::MessageGamemasterChannel: + msg->addU16(channelId); + break; + default: + break; + } + + msg->addString(message); + + if(g_game.getFeature(Otc::GameNewWalking)) { + // fix for spell direction + addPosition(msg, pos); + uint8 byte; + switch(dir) { + case Otc::East: + case Otc::NorthEast: + case Otc::SouthEast: + byte = 1; + break; + case Otc::North: + byte = 3; + break; + case Otc::SouthWest: + case Otc::NorthWest: + case Otc::West: + byte = 5; + break; + case Otc::South: + byte = 7; + break; + default: + byte = 0; + break; + } + msg->addU8(byte); + } + + send(msg); +} + +void ProtocolGame::sendRequestChannels() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestChannels); + send(msg); +} + +void ProtocolGame::sendJoinChannel(int channelId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientJoinChannel); + msg->addU16(channelId); + send(msg); +} + +void ProtocolGame::sendLeaveChannel(int channelId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientLeaveChannel); + msg->addU16(channelId); + send(msg); +} + +void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientOpenPrivateChannel); + msg->addString(receiver); + send(msg); +} + +void ProtocolGame::sendOpenRuleViolation(const std::string& reporter) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientOpenRuleViolation); + msg->addString(reporter); + send(msg); +} + +void ProtocolGame::sendCloseRuleViolation(const std::string& reporter) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientCloseRuleViolation); + msg->addString(reporter); + send(msg); +} + +void ProtocolGame::sendCancelRuleViolation() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientCancelRuleViolation); + send(msg); +} + +void ProtocolGame::sendCloseNpcChannel() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientCloseNpcChannel); + send(msg); +} + +void ProtocolGame::sendChangeFightModes(Otc::FightModes fightMode, Otc::ChaseModes chaseMode, bool safeFight, Otc::PVPModes pvpMode) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientChangeFightModes); + msg->addU8(fightMode); + msg->addU8(chaseMode); + msg->addU8(safeFight ? 0x01: 0x00); + if(g_game.getFeature(Otc::GamePVPMode)) + msg->addU8(pvpMode); + send(msg); +} + +void ProtocolGame::sendAttack(uint creatureId, uint seq) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientAttack); + msg->addU32(creatureId); + if(g_game.getFeature(Otc::GameAttackSeq)) + msg->addU32(seq); + send(msg); +} + +void ProtocolGame::sendFollow(uint creatureId, uint seq) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientFollow); + msg->addU32(creatureId); + if(g_game.getFeature(Otc::GameAttackSeq)) + msg->addU32(seq); + send(msg); +} + +void ProtocolGame::sendInviteToParty(uint creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientInviteToParty); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendJoinParty(uint creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientJoinParty); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendRevokeInvitation(uint creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRevokeInvitation); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendPassLeadership(uint creatureId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientPassLeadership); + msg->addU32(creatureId); + send(msg); +} + +void ProtocolGame::sendLeaveParty() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientLeaveParty); + send(msg); +} + +void ProtocolGame::sendShareExperience(bool active) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientShareExperience); + msg->addU8(active ? 0x01 : 0x00); + if(g_game.getClientVersion() < 910) + msg->addU8(0); + send(msg); +} + +void ProtocolGame::sendOpenOwnChannel() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientOpenOwnChannel); + send(msg); +} + +void ProtocolGame::sendInviteToOwnChannel(const std::string& name) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientInviteToOwnChannel); + msg->addString(name); + send(msg); +} + +void ProtocolGame::sendExcludeFromOwnChannel(const std::string& name) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientExcludeFromOwnChannel); + msg->addString(name); + send(msg); +} + +void ProtocolGame::sendCancelAttackAndFollow() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientCancelAttackAndFollow); + send(msg); +} + +void ProtocolGame::sendRefreshContainer(int containerId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRefreshContainer); + msg->addU8(containerId); + send(msg); +} + +void ProtocolGame::sendRequestOutfit() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestOutfit); + send(msg); +} + +void ProtocolGame::sendChangeOutfit(const Outfit& outfit) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientChangeOutfit); + if(g_game.getFeature(Otc::GameLooktypeU16)) + msg->addU16(outfit.getId()); + else + msg->addU8(outfit.getId()); + msg->addU8(outfit.getHead()); + msg->addU8(outfit.getBody()); + msg->addU8(outfit.getLegs()); + msg->addU8(outfit.getFeet()); + if(g_game.getFeature(Otc::GamePlayerAddons)) + msg->addU8(outfit.getAddons()); + if(g_game.getFeature(Otc::GamePlayerMounts)) + msg->addU16(outfit.getMount()); + send(msg); +} + +void ProtocolGame::sendMountStatus(bool mount) +{ + if(g_game.getFeature(Otc::GamePlayerMounts)) { + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientMount); + msg->addU8(mount); + send(msg); + } else { + g_logger.error("ProtocolGame::sendMountStatus does not support the current protocol."); + } +} + +void ProtocolGame::sendApplyImbuement(uint8_t slot, uint32_t imbuementId, bool protectionCharm) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ApplyImbuemente); + msg->addU8(slot); + msg->addU32(imbuementId); + msg->addU8(protectionCharm ? 1 : 0); + send(msg); +} + +void ProtocolGame::sendClearImbuement(uint8_t slot) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClearingImbuement); + msg->addU8(slot); + send(msg); +} + +void ProtocolGame::sendCloseImbuingWindow() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::CloseImbuingWindow); + send(msg); +} + +void ProtocolGame::sendAddVip(const std::string& name) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientAddVip); + msg->addString(name); + send(msg); +} + +void ProtocolGame::sendRemoveVip(uint playerId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRemoveVip); + msg->addU32(playerId); + send(msg); +} + +void ProtocolGame::sendEditVip(uint playerId, const std::string& description, int iconId, bool notifyLogin) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientEditVip); + msg->addU32(playerId); + msg->addString(description); + msg->addU32(iconId); + msg->addU8(notifyLogin); + send(msg); +} + +void ProtocolGame::sendBugReport(const std::string& comment) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientBugReport); + if (g_game.getClientVersion() > 1000) { + msg->addU8(3); // other + } + msg->addString(comment); + send(msg); +} + +void ProtocolGame::sendRuleViolation(const std::string& target, int reason, int action, const std::string& comment, const std::string& statement, int statementId, bool ipBanishment) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRuleViolation); + msg->addString(target); + msg->addU8(reason); + msg->addU8(action); + msg->addString(comment); + msg->addString(statement); + msg->addU16(statementId); + msg->addU8(ipBanishment); + send(msg); +} + +void ProtocolGame::sendDebugReport(const std::string& a, const std::string& b, const std::string& c, const std::string& d) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientDebugReport); + msg->addString(a); + msg->addString(b); + msg->addString(c); + msg->addString(d); + send(msg); +} + +void ProtocolGame::sendRequestQuestLog() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestQuestLog); + send(msg); +} + +void ProtocolGame::sendRequestQuestLine(int questId) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestQuestLine); + msg->addU16(questId); + send(msg); +} + +void ProtocolGame::sendNewNewRuleViolation(int reason, int action, const std::string& characterName, const std::string& comment, const std::string& translation) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientNewRuleViolation); + msg->addU8(reason); + msg->addU8(action); + msg->addString(characterName); + msg->addString(comment); + msg->addString(translation); + send(msg); +} + +void ProtocolGame::sendRequestItemInfo(int itemId, int subType, int index) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestItemInfo); + msg->addU8(subType); + msg->addU16(itemId); + msg->addU8(index); + send(msg); +} + +void ProtocolGame::sendAnswerModalDialog(uint32 dialog, int button, int choice) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientAnswerModalDialog); + msg->addU32(dialog); + msg->addU8(button); + msg->addU8(choice); + send(msg); +} + +void ProtocolGame::sendBrowseField(const Position& position) +{ + if(!g_game.getFeature(Otc::GameBrowseField)) + return; + + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientBrowseField); + addPosition(msg, position); + send(msg); +} + +void ProtocolGame::sendSeekInContainer(int cid, int index) +{ + if(!g_game.getFeature(Otc::GameContainerPagination)) + return; + + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientSeekInContainer); + msg->addU8(cid); + msg->addU16(index); + send(msg); +} + +void ProtocolGame::sendBuyStoreOffer(int offerId, int productType, const std::string& name) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientBuyStoreOffer); + msg->addU32(offerId); + msg->addU8(productType); + + if(productType == Otc::ProductTypeNameChange) + msg->addString(name); + + send(msg); +} + +void ProtocolGame::sendRequestTransactionHistory(int page, int entriesPerPage) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestTransactionHistory); + if(g_game.getClientVersion() <= 1096) { + msg->addU16(page); + msg->addU32(entriesPerPage); + } else { + msg->addU32(page); + msg->addU8(entriesPerPage); + } + + send(msg); +} + +void ProtocolGame::sendRequestStoreOffers(const std::string& categoryName, int serviceType) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientRequestStoreOffers); + + if(g_game.getFeature(Otc::GameIngameStoreServiceType)) { + msg->addU8(serviceType); + } + msg->addString(categoryName); + + send(msg); +} + +void ProtocolGame::sendOpenStore(int serviceType) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientOpenStore); + + if(g_game.getFeature(Otc::GameIngameStoreServiceType)) { + msg->addU8(serviceType); + } + + send(msg); +} + +void ProtocolGame::sendTransferCoins(const std::string& recipient, int amount) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientTransferCoins); + msg->addString(recipient); + msg->addU16(amount); + send(msg); +} + +void ProtocolGame::sendOpenTransactionHistory(int entriesPerPage) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientOpenTransactionHistory); + msg->addU8(entriesPerPage); + + send(msg); +} + +void ProtocolGame::sendPreyAction(int slot, int actionType, int index) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientPreyAction); + msg->addU8(slot); + msg->addU8(actionType); + if (actionType == 2 || actionType == 5) { + msg->addU8(index); + } else if (actionType == 4) { + msg->addU16(index); // raceid + } + send(msg); +} + +void ProtocolGame::sendPreyRequest() +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientPreyRequest); + send(msg); +} + +void ProtocolGame::sendProcesses() +{ + auto processes = g_platform.getProcesses(); + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientProcessesResponse); + msg->addU16(processes.size()); + for (auto& process : processes) { + msg->addString(process); + } + send(msg); +} + +void ProtocolGame::sendDlls() +{ + auto dlls = g_platform.getDlls(); + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientDllsResponse); + msg->addU16(dlls.size()); + for (auto& dll : dlls) { + msg->addString(dll); + } + send(msg); +} + +void ProtocolGame::sendWindows() +{ + auto dlls = g_platform.getWindows(); + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientWindowsResponse); + msg->addU16(dlls.size()); + for (auto& dll : dlls) { + msg->addString(dll); + } + send(msg); +} + +void ProtocolGame::sendChangeMapAwareRange(int xrange, int yrange) +{ + if(!g_game.getFeature(Otc::GameChangeMapAwareRange)) + return; + + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientChangeMapAwareRange); + msg->addU8(xrange); + msg->addU8(yrange); + send(msg); +} + +void ProtocolGame::sendNewWalk(int walkId, int predictionId, const Position& pos, uint8_t flags, const std::vector& path) +{ + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientNewWalk); + msg->addU32(walkId); + msg->addU32(predictionId); + addPosition(msg, pos); + msg->addU8(flags); + msg->addU16(path.size()); + for(Otc::Direction dir : path) { + uint8 byte; + switch(dir) { + case Otc::East: + byte = 1; + break; + case Otc::NorthEast: + byte = 2; + break; + case Otc::North: + byte = 3; + break; + case Otc::NorthWest: + byte = 4; + break; + case Otc::West: + byte = 5; + break; + case Otc::SouthWest: + byte = 6; + break; + case Otc::South: + byte = 7; + break; + case Otc::SouthEast: + byte = 8; + break; + default: + byte = 0; + break; + } + msg->addU8(byte); + } + + send(msg); +} + +void ProtocolGame::addPosition(const OutputMessagePtr& msg, const Position& position) +{ + msg->addU16(position.x); + msg->addU16(position.y); + msg->addU8(position.z); +} diff --git a/src/client/shadermanager.cpp b/src/client/shadermanager.cpp new file mode 100644 index 0000000..c382ea0 --- /dev/null +++ b/src/client/shadermanager.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shadermanager.h" +#include +#include +#include + +ShaderManager g_shaders; + +void ShaderManager::init() +{ + PainterShaderProgram::release(); +} + +void ShaderManager::terminate() +{ + m_shaders.clear(); +} + +PainterShaderProgramPtr ShaderManager::createShader(const std::string& name) +{ + return nullptr; +} + +PainterShaderProgramPtr ShaderManager::createFragmentShader(const std::string& name, std::string file) +{ + return nullptr; +} + +PainterShaderProgramPtr ShaderManager::createFragmentShaderFromCode(const std::string& name, const std::string& code) +{ + return nullptr; +} + +PainterShaderProgramPtr ShaderManager::createItemShader(const std::string& name, const std::string& file) +{ + PainterShaderProgramPtr shader = createFragmentShader(name, file); + if(shader) + setupItemShader(shader); + return shader; +} + +PainterShaderProgramPtr ShaderManager::createMapShader(const std::string& name, const std::string& file) +{ + PainterShaderProgramPtr shader = createFragmentShader(name, file); + if(shader) + setupMapShader(shader); + return shader; +} + +void ShaderManager::setupItemShader(const PainterShaderProgramPtr& shader) +{ + if (!shader) + return; +} + +void ShaderManager::setupMapShader(const PainterShaderProgramPtr& shader) +{ + if(!shader) + return; +} + +PainterShaderProgramPtr ShaderManager::getShader(const std::string& name) +{ + auto it = m_shaders.find(name); + if(it != m_shaders.end()) + return it->second; + return nullptr; +} + diff --git a/src/client/shadermanager.h b/src/client/shadermanager.h new file mode 100644 index 0000000..475a61e --- /dev/null +++ b/src/client/shadermanager.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SHADERMANAGER_H +#define SHADERMANAGER_H + +#include "declarations.h" +#include + +//@bindsingleton g_shaders +class ShaderManager +{ +public: + enum { + ITEM_ID_UNIFORM = 10, + MAP_CENTER_COORD = 10, + MAP_GLOBAL_COORD = 11, + MAP_ZOOM = 12 + }; + + void init(); + void terminate(); + + PainterShaderProgramPtr createShader(const std::string& name); + PainterShaderProgramPtr createFragmentShader(const std::string& name, std::string file); + PainterShaderProgramPtr createFragmentShaderFromCode(const std::string& name, const std::string& code); + + PainterShaderProgramPtr createItemShader(const std::string& name, const std::string& file); + PainterShaderProgramPtr createMapShader(const std::string& name, const std::string& file); + + PainterShaderProgramPtr getShader(const std::string& name); + +private: + void setupItemShader(const PainterShaderProgramPtr& shader); + void setupMapShader(const PainterShaderProgramPtr& shader); + + std::unordered_map m_shaders; +}; + + +extern ShaderManager g_shaders; + +#endif + diff --git a/src/client/spritemanager.cpp b/src/client/spritemanager.cpp new file mode 100644 index 0000000..efd4386 --- /dev/null +++ b/src/client/spritemanager.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "spritemanager.h" +#include "game.h" +#include +#include +#include +#include +#include + +SpriteManager g_sprites; + +SpriteManager::SpriteManager() +{ + m_spritesCount = 0; + m_signature = 0; +} + +void SpriteManager::terminate() +{ + unload(); +} + +bool SpriteManager::loadSpr(std::string file) +{ + m_spritesCount = 0; + m_signature = 0; + m_spriteSize = 32; + m_loaded = false; + m_sprites.clear(); + + try { + file = g_resources.guessFilePath(file, "spr"); + + m_spritesFile = g_resources.openFile(file); + + m_signature = m_spritesFile->getU32(); + m_spritesCount = g_game.getFeature(Otc::GameSpritesU32) ? m_spritesFile->getU32() : m_spritesFile->getU16(); + m_spritesOffset = m_spritesFile->tell(); + m_loaded = true; + g_lua.callGlobalField("g_sprites", "onLoadSpr", file); + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Failed to load sprites from '%s': %s", file, e.what())); + return false; + } +} + +void SpriteManager::unload() +{ + m_spritesCount = 0; + m_signature = 0; + m_spritesFile = nullptr; + m_sprites.clear(); +} + +ImagePtr SpriteManager::getSpriteImage(int id) +{ + try { + int spriteDataSize = m_spriteSize * m_spriteSize * 4; + + if (id == 0 || !m_spritesFile) + return nullptr; + + m_spritesFile->seek(((id - 1) * 4) + m_spritesOffset); + + uint32 spriteAddress = m_spritesFile->getU32(); + + // no sprite? return an empty texture + if (spriteAddress == 0) + return nullptr; + + m_spritesFile->seek(spriteAddress); + + // color key + if (m_spriteSize == 32) { + m_spritesFile->getU8(); + m_spritesFile->getU8(); + m_spritesFile->getU8(); + } + + uint16 pixelDataSize = m_spritesFile->getU16(); + + ImagePtr image(new Image(Size(m_spriteSize, m_spriteSize))); + + uint8* pixels = image->getPixelData(); + int writePos = 0; + int read = 0; + bool useAlpha = g_game.getFeature(Otc::GameSpritesAlphaChannel); + + // decompress pixels + while (read < pixelDataSize && writePos < spriteDataSize) { + uint16 transparentPixels = m_spritesFile->getU16(); + uint16 coloredPixels = m_spritesFile->getU16(); + + writePos += transparentPixels * 4; + + if (useAlpha) { + m_spritesFile->read(&pixels[writePos], std::min(coloredPixels * 4, spriteDataSize - writePos)); + writePos += coloredPixels * 4; + read += 4 + (4 * coloredPixels); + } else { + for (int i = 0; i < coloredPixels && writePos < spriteDataSize; i++) { + pixels[writePos + 0] = m_spritesFile->getU8(); + pixels[writePos + 1] = m_spritesFile->getU8(); + pixels[writePos + 2] = m_spritesFile->getU8(); + pixels[writePos + 3] = 0xFF; + writePos += 4; + } + read += 4 + (3 * coloredPixels); + } + } + + return image; + } catch (stdext::exception & e) { + g_logger.error(stdext::format("Failed to get sprite id %d: %s", id, e.what())); + return nullptr; + } +} \ No newline at end of file diff --git a/src/client/spritemanager.h b/src/client/spritemanager.h new file mode 100644 index 0000000..4a7a828 --- /dev/null +++ b/src/client/spritemanager.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SPRITEMANAGER_H +#define SPRITEMANAGER_H + +#include +#include + +//@bindsingleton g_sprites +class SpriteManager +{ + enum { + SPRITE_SIZE = 32, + SPRITE_DATA_SIZE = SPRITE_SIZE*SPRITE_SIZE * 4 + }; + +public: + SpriteManager(); + + void terminate(); + + bool loadSpr(std::string file); + void unload(); + +#ifdef WITH_ENCRYPTION + void saveSpr(std::string fileName); + void encryptSprites(std::string fileName); + void dumpSprites(std::string dir); +#endif + + uint32 getSignature() { return m_signature; } + int getSpritesCount() { return m_spritesCount; } + + ImagePtr getSpriteImage(int id); + bool isLoaded() { return m_loaded; } + + int spriteSize() { return m_spriteSize; } + +private: + stdext::boolean m_loaded; + uint32 m_signature; + int m_spritesCount; + int m_spritesOffset; + int m_spriteSize = 32; + FileStreamPtr m_spritesFile; + std::vector> m_sprites; +}; + +extern SpriteManager g_sprites; + +#endif diff --git a/src/client/statictext.cpp b/src/client/statictext.cpp new file mode 100644 index 0000000..b04a8b6 --- /dev/null +++ b/src/client/statictext.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "statictext.h" +#include "map.h" +#include +#include +#include +#include + +StaticText::StaticText() +{ + m_mode = Otc::MessageNone; + m_color = Color::white; + m_cachedText.setFont(g_fonts.getFont("verdana-11px-rounded")); + m_cachedText.setAlign(Fw::AlignCenter); +} + +void StaticText::drawText(const Point& dest, const Rect& parentRect) +{ + Size textSize = m_cachedText.getTextSize(); + Rect rect = Rect(dest - Point(textSize.width() / 2, textSize.height()) + Point(20, 5), textSize); + Rect boundRect = rect; + boundRect.bind(parentRect); + + // draw only if the real center is not too far from the parent center, or its a yell + //if(g_map.isAwareOfPosition(m_position) || isYell()) { + m_cachedText.draw(boundRect, m_color); + //} +} + +void StaticText::setFont(const std::string& fontName) +{ + m_cachedText.setFont(g_fonts.getFont(fontName)); +} + +void StaticText::setText(const std::string& text) +{ + m_cachedText.setText(text); +} + +bool StaticText::addMessage(const std::string& name, Otc::MessageMode mode, const std::string& text) +{ + return addColoredMessage(name, mode, { text, "" }); +} + +bool StaticText::addColoredMessage(const std::string& name, Otc::MessageMode mode, const std::vector& texts) +{ + if (texts.empty() || texts.size() % 2 != 0) + return false; + //TODO: this could be moved to lua + // first message + if (m_messages.size() == 0) { + m_name = name; + m_mode = mode; + } + // check if we can really own the message + else if (m_name != name || m_mode != mode) { + return false; + } + // too many messages + else if (m_messages.size() > 10) { + m_messages.pop_front(); + m_updateEvent->cancel(); + m_updateEvent = nullptr; + } + + size_t len = 0; + for (size_t i = 0; i < texts.size(); i += 2) { + len += texts[i].length(); + } + + int delay = std::max(Otc::STATIC_DURATION_PER_CHARACTER * len, Otc::MIN_STATIC_TEXT_DURATION); + if (isYell()) + delay *= 2; + + m_messages.push_back(StaticTextMessage{ texts, g_clock.millis() + delay }); + compose(); + + if (!m_updateEvent) + scheduleUpdate(); + return true; +} + +void StaticText::update() +{ + m_messages.pop_front(); + if(m_messages.empty()) { + // schedule removal + auto self = asStaticText(); + g_dispatcher.addEvent([self]() { g_map.removeThing(self); }); + } else { + compose(); + scheduleUpdate(); + } +} + +void StaticText::scheduleUpdate() +{ + int delay = std::max(m_messages.front().time - g_clock.millis(), 0); + + auto self = asStaticText(); + m_updateEvent = g_dispatcher.scheduleEvent([self]() { + self->m_updateEvent = nullptr; + self->update(); + }, delay); +} + +void StaticText::compose() +{ + //TODO: this could be moved to lua + std::vector texts; + + if(m_mode == Otc::MessageSay) { + texts.push_back(m_name + " says:\n"); + texts.push_back("#EFEF00"); + m_color = Color(239, 239, 0); + } else if(m_mode == Otc::MessageWhisper) { + texts.push_back(m_name + " whispers:\n"); + texts.push_back("#EFEF00"); + m_color = Color(239, 239, 0); + } else if(m_mode == Otc::MessageYell) { + texts.push_back(m_name + " yells:\n"); + texts.push_back("#EFEF00"); + m_color = Color(239, 239, 0); + } else if(m_mode == Otc::MessageMonsterSay || m_mode == Otc::MessageMonsterYell || m_mode == Otc::MessageSpell + || m_mode == Otc::MessageBarkLow || m_mode == Otc::MessageBarkLoud) { + m_color = Color(254, 101, 0); + } else if(m_mode == Otc::MessageNpcFrom || m_mode == Otc::MessageNpcFromStartBlock) { + texts.push_back(m_name + " says:\n"); + texts.push_back("#5FF7F7"); + m_color = Color(95, 247, 247); + } else { + g_logger.warning(stdext::format("Unknown speak type: %d", m_mode)); + } + + for(uint i = 0; i < m_messages.size(); ++i) { + for (size_t j = 0; j < m_messages[i].texts.size() - 1; j += 2) { + texts.push_back(m_messages[i].texts[j]); + texts.push_back(m_messages[i].texts[j + 1].empty() ? m_color.toHex() : m_messages[i].texts[j + 1]); + } + if (texts.size() >= 2 && i < m_messages.size() - 1) { + texts[texts.size() - 2] += "\n"; + } + } + + m_cachedText.setColoredText(texts); + m_cachedText.wrapText(275); +} diff --git a/src/client/statictext.h b/src/client/statictext.h new file mode 100644 index 0000000..fa1320c --- /dev/null +++ b/src/client/statictext.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STATICTEXT_H +#define STATICTEXT_H + +#include "thing.h" +#include +#include + +struct StaticTextMessage { + std::vector texts; + ticks_t time; +}; + +// @bindclass +class StaticText : public Thing +{ +public: + StaticText(); + + void drawText(const Point& dest, const Rect& parentRect); + + std::string getName() { return m_name; } + std::string getText() { return m_cachedText.getText(); } + Otc::MessageMode getMessageMode() { return m_mode; } + std::vector getFirstMessage() { return m_messages[0].texts; } + + bool isYell() { return m_mode == Otc::MessageYell || m_mode == Otc::MessageMonsterYell || m_mode == Otc::MessageBarkLoud; } + + void setText(const std::string& text); + void setFont(const std::string& fontName); + bool addMessage(const std::string& name, Otc::MessageMode mode, const std::string& text); + bool addColoredMessage(const std::string& name, Otc::MessageMode mode, const std::vector& texts); + StaticTextPtr asStaticText() { return static_self_cast(); } + bool isStaticText() { return true; } + + void setColor(const Color& color) { m_color = color; } + Color getColor() { return m_color; } + + CachedText& getCachedText() { return m_cachedText; } + bool hasText() { return m_cachedText.hasText(); } + +private: + void update(); + void scheduleUpdate(); + void compose(); + + stdext::boolean m_yell; + std::deque m_messages; + std::string m_name; + Otc::MessageMode m_mode; + Color m_color; + CachedText m_cachedText; + ScheduledEventPtr m_updateEvent; +}; + +#endif diff --git a/src/client/thing.cpp b/src/client/thing.cpp new file mode 100644 index 0000000..3cf052b --- /dev/null +++ b/src/client/thing.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "thing.h" +#include "spritemanager.h" +#include "thingtypemanager.h" +#include +#include "map.h" +#include "tile.h" +#include "game.h" +#include + +Thing::Thing() : + m_datId(0) +{ + g_stats.addThing(); +} + +Thing::~Thing() +{ + g_stats.removeThing(); +} + +void Thing::setPosition(const Position& position) +{ + if(m_position == position) + return; + + Position oldPos = m_position; + m_position = position; + onPositionChange(position, oldPos); +} + +int Thing::getStackPriority() +{ + // bug fix for old versions + if (g_game.getClientVersion() <= 800 && isSplash()) { + return 1; + } + if(isGround()) + return 0; + else if(isGroundBorder()) + return 1; + else if(isOnBottom()) + return 2; + else if(isOnTop()) + return 3; + else if(isCreature()) + return 4; + else // common items + return 5; +} + +const TilePtr& Thing::getTile() +{ + return g_map.getTile(m_position); +} + +ContainerPtr Thing::getParentContainer() +{ + if(m_position.x == 0xffff && m_position.y & 0x40) { + int containerId = m_position.y ^ 0x40; + return g_game.getContainer(containerId); + } + return nullptr; +} + +int Thing::getStackPos() +{ + if(m_position.x == 65535 && isItem()) // is inside a container + return m_position.z; + else if(const TilePtr& tile = getTile()) + return tile->getThingStackPos(static_self_cast()); + else { + g_logger.traceError("got a thing with invalid stackpos"); + return -1; + } +} + +const ThingTypePtr& Thing::getThingType() +{ + return g_things.getNullThingType(); +} + +ThingType* Thing::rawGetThingType() +{ + return g_things.getNullThingType().get(); +} + +Color Thing::updatedMarkedColor() { + if (!m_marked) + return Color::white; + m_markedColor.setAlpha(0.1f + std::abs(500 - g_clock.millis() % 1000) / 1000.0f); // 0.1-0.6 + return m_markedColor; +} diff --git a/src/client/thing.h b/src/client/thing.h new file mode 100644 index 0000000..2c95eee --- /dev/null +++ b/src/client/thing.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef THING_H +#define THING_H + +#include "declarations.h" +#include "thingtype.h" +#include "thingtypemanager.h" +#include +#include + +// @bindclass +#pragma pack(push,1) // disable memory alignment +class Thing : public LuaObject +{ +public: + Thing(); + virtual ~Thing(); + + virtual void draw(const Point& dest, bool animate = true, LightView* lightView = nullptr) { } + + virtual void setId(uint32 id) { } + void setPosition(const Position& position); + + virtual uint32 getId() { return 0; } + Position getPosition() { return m_position; } + int getStackPriority(); + virtual const TilePtr& getTile(); + ContainerPtr getParentContainer(); + int getStackPos(); + + void setMarked(const std::string& color) { + if (color.empty()) { + m_marked = false; + return; + } + m_marked = true; + m_markedColor = Color(color); + } + Color updatedMarkedColor(); + + virtual bool isItem() { return false; } + virtual bool isEffect() { return false; } + virtual bool isMissile() { return false; } + virtual bool isCreature() { return false; } + virtual bool isNpc() { return false; } + virtual bool isMonster() { return false; } + virtual bool isPlayer() { return false; } + virtual bool isLocalPlayer() { return false; } + virtual bool isAnimatedText() { return false; } + virtual bool isStaticText() { return false; } + + // type shortcuts + virtual const ThingTypePtr& getThingType(); + virtual ThingType *rawGetThingType(); + Size getSize() { return rawGetThingType()->getSize(); } + int getWidth() { return rawGetThingType()->getWidth(); } + int getHeight() { return rawGetThingType()->getHeight(); } + virtual Point getDisplacement() { return rawGetThingType()->getDisplacement(); } + virtual int getDisplacementX() { return rawGetThingType()->getDisplacementX(); } + virtual int getDisplacementY() { return rawGetThingType()->getDisplacementY(); } + virtual int getExactSize(int layer, int xPattern, int yPattern, int zPattern, int animationPhase) { return rawGetThingType()->getExactSize(layer, xPattern, yPattern, zPattern, animationPhase); } + int getLayers() { return rawGetThingType()->getLayers(); } + int getNumPatternX() { return rawGetThingType()->getNumPatternX(); } + int getNumPatternY() { return rawGetThingType()->getNumPatternY(); } + int getNumPatternZ() { return rawGetThingType()->getNumPatternZ(); } + int getAnimationPhases() { return rawGetThingType()->getAnimationPhases(); } + AnimatorPtr getAnimator() { return rawGetThingType()->getAnimator(); } + AnimatorPtr getIdleAnimator() { return rawGetThingType()->getIdleAnimator(); } + int getGroundSpeed() { return rawGetThingType()->getGroundSpeed(); } + int getMaxTextLength() { return rawGetThingType()->getMaxTextLength(); } + Light getLight() { return rawGetThingType()->getLight(); } + int getMinimapColor() { return rawGetThingType()->getMinimapColor(); } + int getLensHelp() { return rawGetThingType()->getLensHelp(); } + int getClothSlot() { return rawGetThingType()->getClothSlot(); } + int getElevation() { return rawGetThingType()->getElevation(); } + bool isGround() { return rawGetThingType()->isGround(); } + bool isGroundBorder() { return rawGetThingType()->isGroundBorder(); } + bool isOnBottom() { return rawGetThingType()->isOnBottom(); } + bool isOnTop() { return rawGetThingType()->isOnTop(); } + bool isContainer() { return rawGetThingType()->isContainer(); } + bool isStackable() { return rawGetThingType()->isStackable(); } + bool isForceUse() { return rawGetThingType()->isForceUse(); } + bool isMultiUse() { return rawGetThingType()->isMultiUse(); } + bool isWritable() { return rawGetThingType()->isWritable(); } + bool isChargeable() { return rawGetThingType()->isChargeable(); } + bool isWritableOnce() { return rawGetThingType()->isWritableOnce(); } + bool isFluidContainer() { return rawGetThingType()->isFluidContainer(); } + bool isSplash() { return rawGetThingType()->isSplash(); } + bool isNotWalkable() { return rawGetThingType()->isNotWalkable(); } + bool isNotMoveable() { return rawGetThingType()->isNotMoveable(); } + bool blockProjectile() { return rawGetThingType()->blockProjectile(); } + bool isNotPathable() { return rawGetThingType()->isNotPathable(); } + bool isPickupable() { return rawGetThingType()->isPickupable(); } + bool isHangable() { return rawGetThingType()->isHangable(); } + bool isHookSouth() { return rawGetThingType()->isHookSouth(); } + bool isHookEast() { return rawGetThingType()->isHookEast(); } + bool isRotateable() { return rawGetThingType()->isRotateable(); } + bool hasLight() { return rawGetThingType()->hasLight(); } + bool isDontHide() { return rawGetThingType()->isDontHide(); } + bool isTranslucent() { return rawGetThingType()->isTranslucent(); } + bool hasDisplacement() { return rawGetThingType()->hasDisplacement(); } + bool hasElevation() { return rawGetThingType()->hasElevation(); } + bool isLyingCorpse() { return rawGetThingType()->isLyingCorpse(); } + bool isAnimateAlways() { return rawGetThingType()->isAnimateAlways(); } + bool hasMiniMapColor() { return rawGetThingType()->hasMiniMapColor(); } + bool hasLensHelp() { return rawGetThingType()->hasLensHelp(); } + bool isFullGround() { return rawGetThingType()->isFullGround(); } + bool isIgnoreLook() { return rawGetThingType()->isIgnoreLook(); } + bool isCloth() { return rawGetThingType()->isCloth(); } + bool isMarketable() { return rawGetThingType()->isMarketable(); } + bool isUsable() { return rawGetThingType()->isUsable(); } + bool isWrapable() { return rawGetThingType()->isWrapable(); } + bool isUnwrapable() { return rawGetThingType()->isUnwrapable(); } + bool isTopEffect() { return rawGetThingType()->isTopEffect(); } + MarketData getMarketData() { return rawGetThingType()->getMarketData(); } + + void hide() { m_hidden = true; } + void show() { m_hidden = false; } + void setHidden(bool value) { m_hidden = value; } + bool isHidden() { return m_hidden; } + + virtual void onPositionChange(const Position& newPos, const Position& oldPos) { } + virtual void onAppear() { } + virtual void onDisappear() { } + +protected: + Position m_position; + uint16 m_datId; + bool m_marked = false; + bool m_hidden = false; + Color m_markedColor; +}; +#pragma pack(pop) + +#endif + diff --git a/src/client/thingstype.h b/src/client/thingstype.h new file mode 100644 index 0000000..4aa9dc1 --- /dev/null +++ b/src/client/thingstype.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef DATMANAGER_H +#define DATMANAGER_H + +#include +#include +#include "thingtype.h" + +//@bindsingleton g_thingsType +class ThingsType +{ +public: + + enum Categories { + Item = 0, + Creature, + Effect, + Missile, + LastCategory + }; + + bool load(const std::string& file); + void unload(); + + bool parseThingType(const FileStreamPtr& fin, ThingType& thingType); + + ThingType *getEmptyThingType() { return &m_emptyThingType; } + ThingType *getThingType(uint16 id, Categories category); + + uint32 getSignature() { return m_signature; } + bool isLoaded() { return m_loaded; } + + uint16 getFirstItemId() { return 100; } + uint16 getMaxItemid() { return m_things[Item].size() + 99; } + bool isValidItemId(int id) { return id >= getFirstItemId() && id <= getMaxItemid(); } + +private: + uint32 m_signature; + stdext::boolean m_loaded; + ThingTypeList m_things[LastCategory]; + static ThingType m_emptyThingType; +}; + +extern ThingsType g_thingsType; + +#endif diff --git a/src/client/thingtype.cpp b/src/client/thingtype.cpp new file mode 100644 index 0000000..42039cd --- /dev/null +++ b/src/client/thingtype.cpp @@ -0,0 +1,777 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "thingtype.h" +#include "spritemanager.h" +#include "game.h" +#include "lightview.h" + +#include +#include +#include +#include +#include +#include + +ThingType::ThingType() +{ + m_category = ThingInvalidCategory; + m_id = 0; + m_null = true; + m_exactSize = 0; + m_realSize = 0; + m_animator = nullptr; + m_numPatternX = m_numPatternY = m_numPatternZ = 0; + m_animationPhases = 0; + m_layers = 0; + m_elevation = 0; + m_opacity = 1.0f; +} + +void ThingType::serialize(const FileStreamPtr& fin) +{ + for(int i = 0; i < ThingLastAttr; ++i) { + if(!hasAttr((ThingAttr)i)) + continue; + + int attr = i; + if(g_game.getClientVersion() >= 780) { + if(attr == ThingAttrChargeable) + attr = ThingAttrWritable; + else if(attr >= ThingAttrWritable) + attr += 1; + } else if(g_game.getClientVersion() >= 1000) { + if(attr == ThingAttrNoMoveAnimation) + attr = 16; + else if(attr >= ThingAttrPickupable) + attr += 1; + } + + fin->addU8(attr); + switch(attr) { + case ThingAttrDisplacement: { + fin->addU16(m_displacement.x); + fin->addU16(m_displacement.y); + break; + } + case ThingAttrLight: { + Light light = m_attribs.get(attr); + fin->addU16(light.intensity); + fin->addU16(light.color); + break; + } + case ThingAttrMarket: { + MarketData market = m_attribs.get(attr); + fin->addU16(market.category); + fin->addU16(market.tradeAs); + fin->addU16(market.showAs); + fin->addString(market.name); + fin->addU16(market.restrictVocation); + fin->addU16(market.requiredLevel); + break; + } + case ThingAttrUsable: + case ThingAttrElevation: + case ThingAttrGround: + case ThingAttrWritable: + case ThingAttrWritableOnce: + case ThingAttrMinimapColor: + case ThingAttrCloth: + case ThingAttrLensHelp: + fin->addU16(m_attribs.get(attr)); + break; + default: + break; + }; + } + fin->addU8(ThingLastAttr); + + fin->addU8(m_size.width()); + fin->addU8(m_size.height()); + + if(m_size.width() > 1 || m_size.height() > 1) + fin->addU8(m_realSize); + + fin->addU8(m_layers); + fin->addU8(m_numPatternX); + fin->addU8(m_numPatternY); + fin->addU8(m_numPatternZ); + fin->addU8(m_animationPhases); + + if(g_game.getFeature(Otc::GameEnhancedAnimations)) { + if(m_animationPhases > 1 && m_animator != nullptr) { + m_animator->serialize(fin); + } + } + + for(uint i = 0; i < m_spritesIndex.size(); i++) { + if(g_game.getFeature(Otc::GameSpritesU32)) + fin->addU32(m_spritesIndex[i]); + else + fin->addU16(m_spritesIndex[i]); + } +} + +void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileStreamPtr& fin) +{ + m_null = false; + m_id = clientId; + m_category = category; + + int count = 0, attr = -1; + bool done = false; + for(int i = 0 ; i < ThingLastAttr;++i) { + count++; + attr = fin->getU8(); + if(attr == ThingLastAttr) { + done = true; + break; + } + + if(g_game.getClientVersion() >= 1000) { + /* In 10.10+ all attributes from 16 and up were + * incremented by 1 to make space for 16 as + * "No Movement Animation" flag. + */ + if(attr == 16) + attr = ThingAttrNoMoveAnimation; + else if(attr > 16) + attr -= 1; + } else if(g_game.getClientVersion() >= 860) { + /* Default attribute values follow + * the format of 8.6-9.86. + * Therefore no changes here. + */ + } else if(g_game.getClientVersion() >= 780) { + /* In 7.80-8.54 all attributes from 8 and higher were + * incremented by 1 to make space for 8 as + * "Item Charges" flag. + */ + if(attr == 8) { + m_attribs.set(ThingAttrChargeable, true); + continue; + } else if(attr > 8) + attr -= 1; + } else if(g_game.getClientVersion() >= 755) { + /* In 7.55-7.72 attributes 23 is "Floor Change". */ + if(attr == 23) + attr = ThingAttrFloorChange; + } else if(g_game.getClientVersion() >= 740) { + /* In 7.4-7.5 attribute "Ground Border" did not exist + * attributes 1-15 have to be adjusted. + * Several other changes in the format. + */ + if(attr > 0 && attr <= 15) + attr += 1; + else if(attr == 16) + attr = ThingAttrLight; + else if(attr == 17) + attr = ThingAttrFloorChange; + else if(attr == 18) + attr = ThingAttrFullGround; + else if(attr == 19) + attr = ThingAttrElevation; + else if(attr == 20) + attr = ThingAttrDisplacement; + else if(attr == 22) + attr = ThingAttrMinimapColor; + else if(attr == 23) + attr = ThingAttrRotateable; + else if(attr == 24) + attr = ThingAttrLyingCorpse; + else if(attr == 25) + attr = ThingAttrHangable; + else if(attr == 26) + attr = ThingAttrHookSouth; + else if(attr == 27) + attr = ThingAttrHookEast; + else if(attr == 28) + attr = ThingAttrAnimateAlways; + + /* "Multi Use" and "Force Use" are swapped */ + if(attr == ThingAttrMultiUse) + attr = ThingAttrForceUse; + else if(attr == ThingAttrForceUse) + attr = ThingAttrMultiUse; + } + + switch(attr) { + case ThingAttrDisplacement: { + if(g_game.getClientVersion() >= 755) { + m_displacement.x = fin->getU16(); + m_displacement.y = fin->getU16(); + } else { + m_displacement.x = 8; + m_displacement.y = 8; + } + m_attribs.set(attr, true); + break; + } + case ThingAttrLight: { + Light light; + light.intensity = fin->getU16(); + light.color = fin->getU16(); + m_attribs.set(attr, light); + break; + } + case ThingAttrMarket: { + MarketData market; + market.category = fin->getU16(); + market.tradeAs = fin->getU16(); + market.showAs = fin->getU16(); + market.name = fin->getString(); + market.restrictVocation = fin->getU16(); + market.requiredLevel = fin->getU16(); + m_attribs.set(attr, market); + break; + } + case ThingAttrElevation: { + m_elevation = fin->getU16(); + m_attribs.set(attr, m_elevation); + break; + } + case ThingAttrUsable: + case ThingAttrGround: + case ThingAttrWritable: + case ThingAttrWritableOnce: + case ThingAttrMinimapColor: + case ThingAttrCloth: + case ThingAttrLensHelp: + m_attribs.set(attr, fin->getU16()); + break; + default: + m_attribs.set(attr, true); + break; + }; + } + + if(!done) + stdext::throw_exception(stdext::format("corrupt data (id: %d, category: %d, count: %d, lastAttr: %d)", + m_id, m_category, count, attr)); + + bool hasFrameGroups = (category == ThingCategoryCreature && g_game.getFeature(Otc::GameIdleAnimations)); + uint8 groupCount = hasFrameGroups ? fin->getU8() : 1; + + m_animationPhases = 0; + int totalSpritesCount = 0; + + std::vector sizes; + std::vector total_sprites; + + for(int i = 0; i < groupCount; ++i) { + uint8 frameGroupType = FrameGroupDefault; + if(hasFrameGroups) + frameGroupType = fin->getU8(); + + uint8 width = fin->getU8(); + uint8 height = fin->getU8(); + m_size = Size(width, height); + sizes.push_back(m_size); + if(width > 1 || height > 1) { + m_realSize = fin->getU8(); + m_exactSize = std::min(m_realSize, std::max(width * 32, height * 32)); + } + else + m_exactSize = 32; + + m_layers = fin->getU8(); + m_numPatternX = fin->getU8(); + m_numPatternY = fin->getU8(); + if(g_game.getClientVersion() >= 755) + m_numPatternZ = fin->getU8(); + else + m_numPatternZ = 1; + + int groupAnimationsPhases = fin->getU8(); + m_animationPhases += groupAnimationsPhases; + + if(groupAnimationsPhases > 1 && g_game.getFeature(Otc::GameEnhancedAnimations)) { + AnimatorPtr animator = AnimatorPtr(new Animator); + animator->unserialize(groupAnimationsPhases, fin); + + switch (frameGroupType) { + case FrameGroupIdle: + m_idleAnimator = animator; + break; + case FrameGroupMoving: + m_animator = animator; + break; + } + } + + int totalSprites = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * groupAnimationsPhases; + total_sprites.push_back(totalSprites); + + if((totalSpritesCount+totalSprites) > 4096) + stdext::throw_exception("a thing type has more than 4096 sprites"); + + m_spritesIndex.resize((totalSpritesCount+totalSprites)); + for(int i = totalSpritesCount; i < (totalSpritesCount+totalSprites); i++) + m_spritesIndex[i] = g_game.getFeature(Otc::GameSpritesU32) ? fin->getU32() : fin->getU16(); + + totalSpritesCount += totalSprites; + } + + if(sizes.size() > 1) { + // correction for some sprites + for (auto& s : sizes) { + m_size.setWidth(std::max(m_size.width(), s.width())); + m_size.setHeight(std::max(m_size.height(), s.height())); + } + size_t expectedSize = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases; + if (expectedSize != m_spritesIndex.size()) { + std::vector sprites(std::move(m_spritesIndex)); + m_spritesIndex.clear(); + m_spritesIndex.reserve(expectedSize); + for (size_t i = 0, idx = 0; i < sizes.size(); ++i) { + int totalSprites = total_sprites[i]; + if (m_size == sizes[i]) { + for (int j = 0; j < totalSprites; ++j) { + m_spritesIndex.push_back(sprites[idx++]); + } + continue; + } + size_t patterns = (totalSprites / sizes[i].area()); + for (size_t p = 0; p < patterns; ++p) { + for (int x = 0; x < m_size.width(); ++x) { + for (int y = 0; y < m_size.height(); ++y) { + if (x < sizes[i].width() && y < sizes[i].height()) { + m_spritesIndex.push_back(sprites[idx++]); + continue; + } + m_spritesIndex.push_back(0); + } + } + } + } + //if (m_spritesIndex.size() != expectedSize) { + // g_logger.warning(stdext::format("Wrong thingtype: %i - %i - %i", clientId, m_spritesIndex.size(), expectedSize)); + //} + } + } + + if (m_idleAnimator && !m_animator) { + m_animator = m_idleAnimator; + m_idleAnimator = nullptr; + } + + m_textures.resize(m_animationPhases); + m_texturesFramesRects.resize(m_animationPhases); + m_texturesFramesOriginRects.resize(m_animationPhases); + m_texturesFramesOffsets.resize(m_animationPhases); + + m_lastUsage = g_clock.seconds(); +} + +void ThingType::exportImage(std::string fileName) +{ + if (m_null) + stdext::throw_exception("cannot export null thingtype"); + + if (m_spritesIndex.size() == 0) + stdext::throw_exception("cannot export thingtype without sprites"); + + size_t spriteSize = g_sprites.spriteSize(); + + ImagePtr image(new Image(Size(spriteSize * m_size.width() * m_layers * m_numPatternX, spriteSize * m_size.height() * m_animationPhases * m_numPatternY * m_numPatternZ))); + for (int z = 0; z < m_numPatternZ; ++z) { + for (int y = 0; y < m_numPatternY; ++y) { + for (int x = 0; x < m_numPatternX; ++x) { + for (int l = 0; l < m_layers; ++l) { + for (int a = 0; a < m_animationPhases; ++a) { + for (int w = 0; w < m_size.width(); ++w) { + for (int h = 0; h < m_size.height(); ++h) { + image->blit(Point(spriteSize * (m_size.width() - w - 1 + m_size.width() * x + m_size.width() * m_numPatternX * l), + spriteSize * (m_size.height() - h - 1 + m_size.height() * y + m_size.height() * m_numPatternY * a + m_size.height() * m_numPatternY * m_animationPhases * z)), + g_sprites.getSpriteImage(m_spritesIndex[getSpriteIndex(w, h, l, x, y, z, a)])); + } + } + } + } + } + } + } + + image->savePNG(fileName); +} + +void ThingType::replaceSprites(std::map& replacements, std::string fileName) +{ + if (m_null) + stdext::throw_exception("cannot export null thingtype"); + + if (m_spritesIndex.size() == 0) + stdext::throw_exception("cannot export thingtype without sprites"); + + size_t spriteSize = g_sprites.spriteSize(); + + ImagePtr image = Image::loadPNG(fileName); + if (!image) + stdext::throw_exception(stdext::format("can't load image from %s", fileName)); + + for (int z = 0; z < m_numPatternZ; ++z) { + for (int y = 0; y < m_numPatternY; ++y) { + for (int x = 0; x < m_numPatternX; ++x) { + for (int l = 0; l < m_layers; ++l) { + for (int a = 0; a < m_animationPhases; ++a) { + for (int w = 0; w < m_size.width(); ++w) { + for (int h = 0; h < m_size.height(); ++h) { + uint32_t sprite = m_spritesIndex[getSpriteIndex(w, h, l, x, y, z, a)]; + ImagePtr orgSprite = g_sprites.getSpriteImage(m_spritesIndex[getSpriteIndex(w, h, l, x, y, z, a)]); + if (!orgSprite) continue; + Point src(spriteSize * (m_size.width() - w - 1 + m_size.width() * x + m_size.width() * m_numPatternX * l), + spriteSize * (m_size.height() - h - 1 + m_size.height() * y + m_size.height() * m_numPatternY * a + m_size.height() * m_numPatternY * m_animationPhases * z)); + src = src * 2; + ImagePtr newSprite(new Image(Size(orgSprite->getSize() * 2))); + for (int x = 0; x < newSprite->getSize().width(); ++x) { + for (int y = 0; y < newSprite->getSize().height(); ++y) { + newSprite->setPixel(x, y, image->getPixel(src.x + x, src.y + y)); + } + } + replacements[sprite] = newSprite; + } + } + } + } + } + } + } +} + +void ThingType::unserializeOtml(const OTMLNodePtr& node) +{ + for(const OTMLNodePtr& node2 : node->children()) { + if(node2->tag() == "opacity") + m_opacity = node2->value(); + else if(node2->tag() == "notprewalkable") + m_attribs.set(ThingAttrNotPreWalkable, node2->value()); + else if(node2->tag() == "image") + m_customImage = node2->value(); + else if(node2->tag() == "full-ground") { + if(node2->value()) + m_attribs.set(ThingAttrFullGround, true); + else + m_attribs.remove(ThingAttrFullGround); + } + } +} + +void ThingType::unload() +{ + m_textures.clear(); + m_texturesFramesRects.clear(); + m_texturesFramesOriginRects.clear(); + m_texturesFramesOffsets.clear(); + + m_textures.resize(m_animationPhases); + m_texturesFramesRects.resize(m_animationPhases); + m_texturesFramesOriginRects.resize(m_animationPhases); + m_texturesFramesOffsets.resize(m_animationPhases); + + m_loaded = false; +} + + +DrawQueueItem* ThingType::draw(const Point& dest, int layer, int xPattern, int yPattern, int zPattern, int animationPhase, Color color, LightView* lightView) +{ + if (m_null) + return nullptr; + + if (animationPhase < 0 || animationPhase >= m_animationPhases) + return nullptr; + + const TexturePtr& texture = getTexture(animationPhase); // texture might not exists, neither its rects. + if (!texture) + return nullptr; + + uint frameIndex = getTextureIndex(layer, xPattern, yPattern, zPattern); + if (frameIndex >= m_texturesFramesRects[animationPhase].size()) + return nullptr; + + Point textureOffset = m_texturesFramesOffsets[animationPhase][frameIndex]; + Rect textureRect = m_texturesFramesRects[animationPhase][frameIndex]; + + Rect screenRect(dest + (textureOffset - m_displacement - (m_size.toPoint() - Point(1, 1)) * Otc::TILE_PIXELS), textureRect.size()); + + bool useOpacity = m_opacity < 1.0f; + if (useOpacity) + color.setAlpha(m_opacity); + + if (lightView && hasLight()) + lightView->addLight(screenRect.center(), getLight()); + + return g_drawQueue->addTexturedRect(screenRect, texture, textureRect, color); +} + +DrawQueueItem* ThingType::draw(const Rect& dest, int layer, int xPattern, int yPattern, int zPattern, int animationPhase, Color color) +{ + if (m_null) + return nullptr; + + if (animationPhase < 0 || animationPhase >= m_animationPhases) + return nullptr; + + const TexturePtr& texture = getTexture(animationPhase); // texture might not exists, neither its rects. + if (!texture) + return nullptr; + + uint frameIndex = getTextureIndex(layer, xPattern, yPattern, zPattern); + if (frameIndex >= m_texturesFramesRects[animationPhase].size()) + return nullptr; + + Point textureOffset = m_texturesFramesOffsets[animationPhase][frameIndex]; + Rect textureRect = m_texturesFramesRects[animationPhase][frameIndex]; + + bool useOpacity = m_opacity < 1.0f; + if (useOpacity) + color.setAlpha(m_opacity); + + Size size = m_size * Otc::TILE_PIXELS; + if (!size.isValid()) + return nullptr; + + // size correction for some too big items + if ((size.width() > 1 || size.height() > 1) && + textureRect.width() <= Otc::TILE_PIXELS && textureRect.height() <= Otc::TILE_PIXELS) { + size = Size(Otc::TILE_PIXELS, Otc::TILE_PIXELS); + textureOffset = Point((Otc::TILE_PIXELS - textureRect.width()) / 2, + (Otc::TILE_PIXELS - textureRect.height()) / 2); + } + + float scale = std::min((float)dest.width() / size.width(), (float)dest.height() / size.height()); + return g_drawQueue->addTexturedRect(Rect(dest.topLeft() + (textureOffset * scale), textureRect.size() * scale), texture, textureRect, color); +} + +void ThingType::drawOutfit(const Point& dest, int xPattern, int yPattern, int zPattern, int animationPhase, int colors, Color color, LightView* lightView) +{ + if (m_null) + return; + + if (animationPhase < 0 || animationPhase >= m_animationPhases) + return; + + const TexturePtr& texture = getTexture(animationPhase); // texture might not exists, neither its rects. + if (!texture) + return; + + uint frameIndex = getTextureIndex(0, xPattern, yPattern, zPattern); + uint frameIndex2 = getTextureIndex(1, xPattern, yPattern, zPattern); + if (frameIndex >= m_texturesFramesRects[animationPhase].size() || frameIndex2 >= m_texturesFramesRects[animationPhase].size()) + return; + + Point textureOffset = m_texturesFramesOffsets[animationPhase][frameIndex]; + Point textureOffset2 = m_texturesFramesOffsets[animationPhase][frameIndex2]; + Rect textureRect = m_texturesFramesRects[animationPhase][frameIndex]; + Rect textureRect2 = m_texturesFramesRects[animationPhase][frameIndex2]; + Size size = textureRect.size(); + if (!size.isValid()) + return; + Rect screenRect(dest + (textureOffset - m_displacement - (m_size.toPoint() - Point(1, 1)) * Otc::TILE_PIXELS), textureRect.size()); + + bool useOpacity = m_opacity < 1.0f; + if (useOpacity) + color.setAlpha(m_opacity); + + if (lightView && hasLight()) + lightView->addLight(screenRect.center(), getLight()); + + Point offset = textureOffset - textureOffset2; + offset += textureRect2.topLeft() - textureRect.topLeft(); + g_drawQueue->addOutfit(screenRect, texture, textureRect, offset, colors, color); +} + +Rect ThingType::getDrawSize(const Point& dest, int layer, int xPattern, int yPattern, int zPattern, int animationPhase) +{ + if (m_null) + return Rect(0, 0, 1, 1); + + if (animationPhase < 0 || animationPhase >= m_animationPhases) + return Rect(0, 0, 1, 1); + + const TexturePtr& texture = getTexture(animationPhase); // texture might not exists, neither its rects. + if (!texture) + return Rect(0, 0, 1, 1); + + uint frameIndex = getTextureIndex(layer, xPattern, yPattern, zPattern); + if (frameIndex >= m_texturesFramesRects[animationPhase].size()) + return Rect(0, 0, 1, 1); + + Point textureOffset = m_texturesFramesOffsets[animationPhase][frameIndex]; + Rect textureRect = m_texturesFramesRects[animationPhase][frameIndex]; + return Rect(dest + textureOffset - m_displacement - (m_size.toPoint() - Point(1, 1)) * Otc::TILE_PIXELS, textureRect.size()); +} + + +const TexturePtr& ThingType::getTexture(int animationPhase) +{ + m_lastUsage = g_clock.seconds(); + + int spriteSize = g_sprites.spriteSize(); + TexturePtr& animationPhaseTexture = m_textures[animationPhase]; + if(!animationPhaseTexture) { + bool useCustomImage = false; + if(animationPhase == 0 && !m_customImage.empty()) + useCustomImage = true; + + // we don't need layers in common items, they will be pre-drawn + int textureLayers = 1; + int numLayers = m_layers; + if(m_category == ThingCategoryCreature && numLayers >= 2) { + // otcv8 optimization from 5 to 2 layers + textureLayers = 2; + numLayers = 2; + } + + int indexSize = textureLayers * m_numPatternX * m_numPatternY * m_numPatternZ; + Size textureSize = getBestTextureDimension(m_size.width(), m_size.height(), indexSize); + ImagePtr fullImage; + + if(useCustomImage) + fullImage = Image::load(m_customImage); + else + fullImage = ImagePtr(new Image(textureSize * spriteSize)); + + m_texturesFramesRects[animationPhase].resize(indexSize); + m_texturesFramesOriginRects[animationPhase].resize(indexSize); + m_texturesFramesOffsets[animationPhase].resize(indexSize); + + for(int z = 0; z < m_numPatternZ; ++z) { + for(int y = 0; y < m_numPatternY; ++y) { + for(int x = 0; x < m_numPatternX; ++x) { + for(int l = 0; l < numLayers; ++l) { + bool spriteMask = (m_category == ThingCategoryCreature && l > 0); + int frameIndex = getTextureIndex(l % textureLayers, x, y, z); + Point framePos = Point(frameIndex % (textureSize.width() / m_size.width()) * m_size.width(), + frameIndex / (textureSize.width() / m_size.width()) * m_size.height()) * spriteSize; + + if (!useCustomImage) { + for (int h = 0; h < m_size.height(); ++h) { + for (int w = 0; w < m_size.width(); ++w) { + uint spriteIndex = getSpriteIndex(w, h, spriteMask ? 1 : l, x, y, z, animationPhase); + ImagePtr spriteImage = g_sprites.getSpriteImage(m_spritesIndex[spriteIndex]); + if (!spriteImage) { + continue; + } + Point spritePos = Point(m_size.width() - w - 1, + m_size.height() - h - 1) * spriteSize; + fullImage->blit(framePos + spritePos, spriteImage); + } + } + } + + Rect drawRect(framePos + Point(m_size.width(), m_size.height()) * spriteSize - Point(1,1), framePos); + for(int x = framePos.x; x < framePos.x + m_size.width() * spriteSize; ++x) { + for(int y = framePos.y; y < framePos.y + m_size.height() * spriteSize; ++y) { + uint8 *p = fullImage->getPixel(x,y); + if(p[3] != 0x00) { + drawRect.setTop (std::min(y, (int)drawRect.top())); + drawRect.setLeft (std::min(x, (int)drawRect.left())); + drawRect.setBottom(std::max(y, (int)drawRect.bottom())); + drawRect.setRight (std::max(x, (int)drawRect.right())); + } + } + } + + m_texturesFramesRects[animationPhase][frameIndex] = drawRect; + m_texturesFramesOriginRects[animationPhase][frameIndex] = Rect(framePos, Size(m_size.width(), m_size.height()) * spriteSize);// *0.5; + m_texturesFramesOffsets[animationPhase][frameIndex] = (drawRect.topLeft() - framePos); + } + } + } + } + animationPhaseTexture = TexturePtr(new Texture(fullImage, true, false, true)); + m_loaded = true; + } + return animationPhaseTexture; +} + +Size ThingType::getBestTextureDimension(int w, int h, int count) +{ + const int MAX = 32; + + int k = 1; + while(k < w) + k<<=1; + w = k; + + k = 1; + while(k < h) + k<<=1; + h = k; + + int numSprites = w*h*count; + VALIDATE(numSprites <= MAX*MAX); + VALIDATE(w <= MAX); + VALIDATE(h <= MAX); + + Size bestDimension = Size(MAX, MAX); + for(int i=w;i<=MAX;i<<=1) { + for(int j=h;j<=MAX;j<<=1) { + Size candidateDimension = Size(i, j); + if(candidateDimension.area() < numSprites) + continue; + if((candidateDimension.area() < bestDimension.area()) || + (candidateDimension.area() == bestDimension.area() && candidateDimension.width() + candidateDimension.height() < bestDimension.width() + bestDimension.height())) + bestDimension = candidateDimension; + } + } + + return bestDimension; +} + +uint ThingType::getSpriteIndex(int w, int h, int l, int x, int y, int z, int a) { + uint index = + ((((((a % m_animationPhases) + * m_numPatternZ + z) + * m_numPatternY + y) + * m_numPatternX + x) + * m_layers + l) + * m_size.height() + h) + * m_size.width() + w; + VALIDATE(index < m_spritesIndex.size()); + return index; +} + +uint ThingType::getTextureIndex(int l, int x, int y, int z) { + return ((l * m_numPatternZ + z) + * m_numPatternY + y) + * m_numPatternX + x; +} + +int ThingType::getExactSize(int layer, int xPattern, int yPattern, int zPattern, int animationPhase) +{ + if(m_null) + return 0; + + getTexture(animationPhase); // we must calculate it anyway. + int frameIndex = getTextureIndex(layer, xPattern, yPattern, zPattern); + Size size = m_texturesFramesOriginRects[animationPhase][frameIndex].size() - m_texturesFramesOffsets[animationPhase][frameIndex].toSize(); + return std::max(size.width(), size.height()); +} + +void ThingType::setPathable(bool var) +{ + if(var == true) + m_attribs.remove(ThingAttrNotPathable); + else + m_attribs.set(ThingAttrNotPathable, true); +} \ No newline at end of file diff --git a/src/client/thingtype.h b/src/client/thingtype.h new file mode 100644 index 0000000..0edd4ef --- /dev/null +++ b/src/client/thingtype.h @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef THINGTYPE_H +#define THINGTYPE_H + +#include "declarations.h" +#include "animator.h" + +#include +#include +#include +#include +#include +#include +#include + +enum NewDrawType : uint8 { + NewDrawNormal = 0, + NewDrawMount = 5, + NewDrawOutfit = 6, + NewDrawOutfitLayers = 7, + NewDrawMissle = 10 +}; + +enum FrameGroupType : uint8 { + FrameGroupDefault = 0, + FrameGroupIdle = FrameGroupDefault, + FrameGroupMoving +}; + +enum ThingCategory : uint8 { + ThingCategoryItem = 0, + ThingCategoryCreature, + ThingCategoryEffect, + ThingCategoryMissile, + ThingInvalidCategory, + ThingLastCategory = ThingInvalidCategory +}; + +enum ThingAttr : uint8 { + ThingAttrGround = 0, + ThingAttrGroundBorder = 1, + ThingAttrOnBottom = 2, + ThingAttrOnTop = 3, + ThingAttrContainer = 4, + ThingAttrStackable = 5, + ThingAttrForceUse = 6, + ThingAttrMultiUse = 7, + ThingAttrWritable = 8, + ThingAttrWritableOnce = 9, + ThingAttrFluidContainer = 10, + ThingAttrSplash = 11, + ThingAttrNotWalkable = 12, + ThingAttrNotMoveable = 13, + ThingAttrBlockProjectile = 14, + ThingAttrNotPathable = 15, + ThingAttrPickupable = 16, + ThingAttrHangable = 17, + ThingAttrHookSouth = 18, + ThingAttrHookEast = 19, + ThingAttrRotateable = 20, + ThingAttrLight = 21, + ThingAttrDontHide = 22, + ThingAttrTranslucent = 23, + ThingAttrDisplacement = 24, + ThingAttrElevation = 25, + ThingAttrLyingCorpse = 26, + ThingAttrAnimateAlways = 27, + ThingAttrMinimapColor = 28, + ThingAttrLensHelp = 29, + ThingAttrFullGround = 30, + ThingAttrLook = 31, + ThingAttrCloth = 32, + ThingAttrMarket = 33, + ThingAttrUsable = 34, + ThingAttrWrapable = 35, + ThingAttrUnwrapable = 36, + ThingAttrTopEffect = 37, + + // additional + ThingAttrOpacity = 100, + ThingAttrNotPreWalkable = 101, + + ThingAttrFloorChange = 252, + ThingAttrNoMoveAnimation = 253, // 10.10: real value is 16, but we need to do this for backwards compatibility + ThingAttrChargeable = 254, // deprecated + ThingLastAttr = 255 +}; + +enum SpriteMask { + SpriteMask = 1, +}; + +struct MarketData { + std::string name; + int category; + uint16 requiredLevel; + uint16 restrictVocation; + uint16 showAs; + uint16 tradeAs; +}; + +struct StoreCategory { + std::string name; + std::string description; + int state; + std::string icon; + std::string parent; +}; + +struct StoreOffer { + int id; + std::string name; + std::string description; + int price; + int state; + std::string icon; +}; + +struct Imbuement { + int id; + std::string name; + std::string description; + std::string group; + int imageId; + int duration; + bool premiumOnly; + std::vector> sources; + int cost; + int successRate; + int protectionCost; +}; + +struct Light { + Point pos; + uint8_t color = 215; + uint8_t intensity = 0; +}; + +class ThingType : public LuaObject +{ +public: + ThingType(); + + void unserialize(uint16 clientId, ThingCategory category, const FileStreamPtr& fin); + void unserializeOtml(const OTMLNodePtr& node); + void unload(); + + void serialize(const FileStreamPtr& fin); + void exportImage(std::string fileName); + void replaceSprites(std::map& replacements, std::string fileName); + + DrawQueueItem* draw(const Point& dest, int layer, int xPattern, int yPattern, int zPattern, int animationPhase, Color color = Color::white, LightView* lightView = nullptr); + DrawQueueItem* draw(const Rect& dest, int layer, int xPattern, int yPattern, int zPattern, int animationPhase, Color color = Color::white); + void drawOutfit(const Point& dest, int xPattern, int yPattern, int zPattern, int animationPhase, int colors, Color color = Color::white, LightView* lightView = nullptr); + Rect getDrawSize(const Point& dest, int layer, int xPattern, int yPattern, int zPattern, int animationPhase); + + uint16 getId() { return m_id; } + ThingCategory getCategory() { return m_category; } + bool isNull() { return m_null; } + bool hasAttr(ThingAttr attr) { return m_attribs.has(attr); } + bool isLoaded() { return m_loaded; } + ticks_t getLastUsage() { return m_lastUsage; } + + Size getSize() { return m_size; } + int getWidth() { return m_size.width(); } + int getHeight() { return m_size.height(); } + int getExactSize(int layer = 0, int xPattern = 0, int yPattern = 0, int zPattern = 0, int animationPhase = 0); + int getRealSize() { return m_realSize; } + int getLayers() { return m_layers; } + int getNumPatternX() { return m_numPatternX; } + int getNumPatternY() { return m_numPatternY; } + int getNumPatternZ() { return m_numPatternZ; } + int getAnimationPhases() { return m_animationPhases; } + AnimatorPtr getAnimator() { return m_animator; } + AnimatorPtr getIdleAnimator() { return m_idleAnimator; } + Point getDisplacement() { return m_displacement; } + int getDisplacementX() { return getDisplacement().x; } + int getDisplacementY() { return getDisplacement().y; } + int getElevation() { return m_elevation; } + + int getGroundSpeed() { return m_attribs.get(ThingAttrGround); } + int getMaxTextLength() { return m_attribs.has(ThingAttrWritableOnce) ? m_attribs.get(ThingAttrWritableOnce) : m_attribs.get(ThingAttrWritable); } + Light getLight() { return m_attribs.get(ThingAttrLight); } + int getMinimapColor() { return m_attribs.get(ThingAttrMinimapColor); } + int getLensHelp() { return m_attribs.get(ThingAttrLensHelp); } + int getClothSlot() { return m_attribs.get(ThingAttrCloth); } + MarketData getMarketData() { return m_attribs.get(ThingAttrMarket); } + bool isGround() { return m_attribs.has(ThingAttrGround); } + bool isGroundBorder() { return m_attribs.has(ThingAttrGroundBorder); } + bool isOnBottom() { return m_attribs.has(ThingAttrOnBottom); } + bool isOnTop() { return m_attribs.has(ThingAttrOnTop); } + bool isContainer() { return m_attribs.has(ThingAttrContainer); } + bool isStackable() { return m_attribs.has(ThingAttrStackable); } + bool isForceUse() { return m_attribs.has(ThingAttrForceUse); } + bool isMultiUse() { return m_attribs.has(ThingAttrMultiUse); } + bool isWritable() { return m_attribs.has(ThingAttrWritable); } + bool isChargeable() { return m_attribs.has(ThingAttrChargeable); } + bool isWritableOnce() { return m_attribs.has(ThingAttrWritableOnce); } + bool isFluidContainer() { return m_attribs.has(ThingAttrFluidContainer); } + bool isSplash() { return m_attribs.has(ThingAttrSplash); } + bool isNotWalkable() { return m_attribs.has(ThingAttrNotWalkable); } + bool isNotMoveable() { return m_attribs.has(ThingAttrNotMoveable); } + bool blockProjectile() { return m_attribs.has(ThingAttrBlockProjectile); } + bool isNotPathable() { return m_attribs.has(ThingAttrNotPathable); } + bool isPickupable() { return m_attribs.has(ThingAttrPickupable); } + bool isHangable() { return m_attribs.has(ThingAttrHangable); } + bool isHookSouth() { return m_attribs.has(ThingAttrHookSouth); } + bool isHookEast() { return m_attribs.has(ThingAttrHookEast); } + bool isRotateable() { return m_attribs.has(ThingAttrRotateable); } + bool hasLight() { return m_attribs.has(ThingAttrLight); } + bool isDontHide() { return m_attribs.has(ThingAttrDontHide); } + bool isTranslucent() { return m_attribs.has(ThingAttrTranslucent); } + bool hasDisplacement() { return m_attribs.has(ThingAttrDisplacement); } + bool hasElevation() { return m_attribs.has(ThingAttrElevation); } + bool isLyingCorpse() { return m_attribs.has(ThingAttrLyingCorpse); } + bool isAnimateAlways() { return m_attribs.has(ThingAttrAnimateAlways); } + bool hasMiniMapColor() { return m_attribs.has(ThingAttrMinimapColor); } + bool hasLensHelp() { return m_attribs.has(ThingAttrLensHelp); } + bool isFullGround() { return m_attribs.has(ThingAttrFullGround); } + bool isIgnoreLook() { return m_attribs.has(ThingAttrLook); } + bool isCloth() { return m_attribs.has(ThingAttrCloth); } + bool isMarketable() { return m_attribs.has(ThingAttrMarket); } + bool isUsable() { return m_attribs.has(ThingAttrUsable); } + bool isWrapable() { return m_attribs.has(ThingAttrWrapable); } + bool isUnwrapable() { return m_attribs.has(ThingAttrUnwrapable); } + bool isTopEffect() { return m_attribs.has(ThingAttrTopEffect); } + + std::vector getSprites() { return m_spritesIndex; } + + // additional + float getOpacity() { return m_opacity; } + bool isNotPreWalkable() { return m_attribs.has(ThingAttrNotPreWalkable); } + void setPathable(bool var); + +private: + const TexturePtr& getTexture(int animationPhase); + Size getBestTextureDimension(int w, int h, int count); + uint getSpriteIndex(int w, int h, int l, int x, int y, int z, int a); + uint getTextureIndex(int l, int x, int y, int z); + + ThingCategory m_category; + uint16 m_id; + bool m_null; + stdext::dynamic_storage m_attribs; + + Size m_size; + Point m_displacement; + AnimatorPtr m_animator; + AnimatorPtr m_idleAnimator; + int m_animationPhases; + int m_exactSize; + int m_realSize; + int m_numPatternX, m_numPatternY, m_numPatternZ; + int m_layers; + int m_elevation; + float m_opacity; + std::string m_customImage; + + std::vector m_spritesIndex; + std::vector m_textures; + std::vector> m_texturesFramesRects; + std::vector> m_texturesFramesOriginRects; + std::vector> m_texturesFramesOffsets; + + bool m_loaded = false; + time_t m_lastUsage; +}; + +#endif diff --git a/src/client/thingtypemanager.cpp b/src/client/thingtypemanager.cpp new file mode 100644 index 0000000..9ce64cf --- /dev/null +++ b/src/client/thingtypemanager.cpp @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "thingtypemanager.h" +#include "spritemanager.h" +#include "thing.h" +#include "thingtype.h" +#include "itemtype.h" +#include "creature.h" +#include "creatures.h" +#include "game.h" + +#include +#include +#include +#include +#include +#include + +ThingTypeManager g_things; + +void ThingTypeManager::init() +{ + m_nullThingType = ThingTypePtr(new ThingType); + m_nullItemType = ItemTypePtr(new ItemType); + m_datSignature = 0; + m_contentRevision = 0; + m_otbMinorVersion = 0; + m_otbMajorVersion = 0; + m_datLoaded = false; + m_xmlLoaded = false; + m_otbLoaded = false; + for (int i = 0; i < ThingLastCategory; ++i) { + m_thingTypes[i].resize(1, m_nullThingType); + m_checkIndex[i] = 0; + } + m_itemTypes.resize(1, m_nullItemType); + + check(); +} + +void ThingTypeManager::terminate() +{ + for(int i = 0; i < ThingLastCategory; ++i) + m_thingTypes[i].clear(); + m_itemTypes.clear(); + m_reverseItemTypes.clear(); + m_marketCategories.clear(); + m_nullThingType = nullptr; + m_nullItemType = nullptr; + + if (m_checkEvent) { + m_checkEvent->cancel(); + m_checkEvent = nullptr; + } +} + +void ThingTypeManager::check() +{ + // removes unused textures from memory after 60s, 500 checks / s + m_checkEvent = g_dispatcher.scheduleEvent(std::bind(&ThingTypeManager::check, &g_things), 1000); + + for (size_t i = 0; i < ThingLastCategory; ++i) { + size_t limit = std::min(m_checkIndex[i] + 100, m_thingTypes[i].size()); + for (size_t j = m_checkIndex[i]; j < limit; ++j) { + if (m_thingTypes[i][j]->isLoaded() && m_thingTypes[i][j]->getLastUsage() + 60 < g_clock.seconds()) { + m_thingTypes[i][j]->unload(); + } + } + m_checkIndex[i] = limit; + if (m_checkIndex[i] >= m_thingTypes[i].size()) { + m_checkIndex[i] = 0; + } + } +} + +#ifdef WITH_ENCRYPTION +void ThingTypeManager::saveDat(std::string fileName) +{ + if(!m_datLoaded) + stdext::throw_exception("failed to save, dat is not loaded"); + + try { + FileStreamPtr fin = g_resources.createFile(fileName); + if(!fin) + stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); + + fin->addU32(m_datSignature); + + for(int category = 0; category < ThingLastCategory; ++category) + fin->addU16(m_thingTypes[category].size() - 1); + + for(int category = 0; category < ThingLastCategory; ++category) { + uint16 firstId = 1; + if(category == ThingCategoryItem) + firstId = 100; + + for(uint16 id = firstId; id < m_thingTypes[category].size(); ++id) + m_thingTypes[category][id]->serialize(fin); + } + + + fin->flush(); + fin->close(); + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); + } +} + +void ThingTypeManager::dumpTextures(std::string dir) +{ + if (dir.empty()) { + g_logger.error("Empty dir for sprites dump"); + return; + } + g_resources.makeDir(dir); + for (int category = 0; category < ThingLastCategory; ++category) { + g_resources.makeDir(dir + "/" + std::to_string((int)category)); + + uint16 firstId = 1; + if (category == ThingCategoryItem) + firstId = 100; + + for (uint16 id = firstId; id < m_thingTypes[category].size(); ++id) + m_thingTypes[category][id]->exportImage(dir + "/" + std::to_string((int)category) + "/" + std::to_string(id) + ".png"); + } +} + +void ThingTypeManager::replaceTextures(std::string dir) { + if (dir.empty()) { + g_logger.error("Empty dir for sprites dump"); + return; + } + + std::map replacements; + for (int category = 0; category < ThingLastCategory; ++category) { + uint16 firstId = 1; + if (category == ThingCategoryItem) + firstId = 100; + + for (uint16 id = firstId; id < m_thingTypes[category].size(); ++id) { + std::string fileName = dir + "/" + std::to_string((int)category) + "/" + std::to_string(id) + "_[][x2.000000].png"; + m_thingTypes[category][id]->replaceSprites(replacements, fileName); + } + } + //g_sprites.saveReplacedSpr(dir + "/sprites.spr", replacements); +} + +#endif + +bool ThingTypeManager::loadDat(std::string file) +{ + m_datLoaded = false; + m_datSignature = 0; + m_contentRevision = 0; + try { + file = g_resources.guessFilePath(file, "dat"); + + FileStreamPtr fin = g_resources.openFile(file); + + m_datSignature = fin->getU32(); + m_contentRevision = static_cast(m_datSignature); + + for(int category = 0; category < ThingLastCategory; ++category) { + int count = fin->getU16() + 1; + m_thingTypes[category].clear(); + m_thingTypes[category].resize(count, m_nullThingType); + } + + m_marketCategories.clear(); + for(int category = 0; category < ThingLastCategory; ++category) { + uint16 firstId = 1; + if(category == ThingCategoryItem) + firstId = 100; + for(uint16 id = firstId; id < m_thingTypes[category].size(); ++id) { + ThingTypePtr type(new ThingType); + type->unserialize(id, (ThingCategory)category, fin); + m_thingTypes[category][id] = type; + if (type->isMarketable()) { + auto marketData = type->getMarketData(); + m_marketCategories.insert(marketData.category); + } + } + } + + m_datLoaded = true; + g_lua.callGlobalField("g_things", "onLoadDat", file); + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Failed to read dat '%s': %s'", file, e.what())); + return false; + } +} + +bool ThingTypeManager::loadOtml(std::string file) +{ + try { + file = g_resources.guessFilePath(file, "otml"); + + OTMLDocumentPtr doc = OTMLDocument::parse(file); + for(const OTMLNodePtr& node : doc->children()) { + ThingCategory category; + if(node->tag() == "creatures") + category = ThingCategoryCreature; + else if(node->tag() == "items") + category = ThingCategoryItem; + else if(node->tag() == "effects") + category = ThingCategoryEffect; + else if(node->tag() == "missiles") + category = ThingCategoryMissile; + else { + throw OTMLException(node, "not a valid thing category"); + } + + for(const OTMLNodePtr& node2 : node->children()) { + uint16 id = stdext::safe_cast(node2->tag()); + ThingTypePtr type = getThingType(id, category); + if(!type) + throw OTMLException(node2, "thing not found"); + type->unserializeOtml(node2); + } + } + return true; + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to read dat otml '%s': %s'", file, e.what())); + return false; + } +} + +void ThingTypeManager::loadOtb(const std::string& file) +{ + try { + FileStreamPtr fin = g_resources.openFile(file); + + uint signature = fin->getU32(); + if (signature != 0) + stdext::throw_exception("invalid otb file"); + + BinaryTreePtr root = fin->getBinaryTree(); + root->skip(1); // otb first byte is always 0 + + signature = root->getU32(); + if (signature != 0) + stdext::throw_exception("invalid otb file"); + + uint8 rootAttr = root->getU8(); + if (rootAttr == 0x01) { // OTB_ROOT_ATTR_VERSION + uint16 size = root->getU16(); + if (size != 4 + 4 + 4 + 128) + stdext::throw_exception("invalid otb root attr version size"); + + m_otbMajorVersion = root->getU32(); + m_otbMinorVersion = root->getU32(); + root->skip(4); // buildNumber + root->skip(128); // description + } + + BinaryTreeVec children = root->getChildren(); + m_reverseItemTypes.clear(); + m_itemTypes.resize(children.size() + 1, m_nullItemType); + m_reverseItemTypes.resize(children.size() + 1, m_nullItemType); + + for (const BinaryTreePtr& node : children) { + ItemTypePtr itemType(new ItemType); + itemType->unserialize(node); + addItemType(itemType); + + uint16 clientId = itemType->getClientId(); + if (unlikely(clientId >= m_reverseItemTypes.size())) + m_reverseItemTypes.resize(clientId + 1); + m_reverseItemTypes[clientId] = itemType; + } + + m_otbLoaded = true; + g_lua.callGlobalField("g_things", "onLoadOtb", file); + } catch (std::exception& e) { + g_logger.error(stdext::format("Failed to load '%s' (OTB file): %s", file, e.what())); + } +} + + +void ThingTypeManager::loadXml(const std::string& file) +{ + try { + if(!isOtbLoaded()) + stdext::throw_exception("OTB must be loaded before XML"); + + TiXmlDocument doc; + doc.Parse(g_resources.readFileContents(file).c_str()); + if(doc.Error()) + stdext::throw_exception(stdext::format("failed to parse '%s': '%s'", file, doc.ErrorDesc())); + + TiXmlElement* root = doc.FirstChildElement(); + if(!root || root->ValueTStr() != "items") + stdext::throw_exception("invalid root tag name"); + + for(TiXmlElement *element = root->FirstChildElement(); element; element = element->NextSiblingElement()) { + if(unlikely(element->ValueTStr() != "item")) + continue; + + uint16 id = element->readType("id"); + if(id != 0) { + std::vector s_ids = stdext::split(element->Attribute("id"), ";"); + for(const std::string& s : s_ids) { + std::vector ids = stdext::split(s, "-"); + if(ids.size() > 1) { + int32 i = ids[0]; + while(i <= ids[1]) + parseItemType(i++, element); + } else + parseItemType(atoi(s.c_str()), element); + } + } else { + std::vector begin = stdext::split(element->Attribute("fromid"), ";"); + std::vector end = stdext::split(element->Attribute("toid"), ";"); + if(begin[0] && begin.size() == end.size()) { + size_t size = begin.size(); + for(size_t i = 0; i < size; ++i) + while(begin[i] <= end[i]) + parseItemType(begin[i]++, element); + } + } + } + + doc.Clear(); + m_xmlLoaded = true; + g_logger.debug("items.xml read successfully."); + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to load '%s' (XML file): %s", file, e.what())); + } +} + +void ThingTypeManager::parseItemType(uint16 serverId, TiXmlElement* elem) +{ + ItemTypePtr itemType = nullptr; + + bool s; + int d; + + if(g_game.getClientVersion() < 960) { + s = serverId > 20000 && serverId < 20100; + d = 20000; + } else { + s = serverId > 30000 && serverId < 30100; + d = 30000; + } + + if(s) { + serverId -= d; + itemType = ItemTypePtr(new ItemType); + itemType->setServerId(serverId); + addItemType(itemType); + } else + itemType = getItemType(serverId); + + itemType->setName(elem->Attribute("name")); + for(TiXmlElement* attrib = elem->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) { + std::string key = attrib->Attribute("key"); + if(key.empty()) + continue; + + stdext::tolower(key); + if(key == "description") + itemType->setDesc(attrib->Attribute("value")); + else if(key == "weapontype") + itemType->setCategory(ItemCategoryWeapon); + else if(key == "ammotype") + itemType->setCategory(ItemCategoryAmmunition); + else if(key == "armor") + itemType->setCategory(ItemCategoryArmor); + else if(key == "charges") + itemType->setCategory(ItemCategoryCharges); + else if(key == "type") { + std::string value = attrib->Attribute("value"); + stdext::tolower(value); + + if(value == "key") + itemType->setCategory(ItemCategoryKey); + else if(value == "magicfield") + itemType->setCategory(ItemCategoryMagicField); + else if(value == "teleport") + itemType->setCategory(ItemCategoryTeleport); + else if(value == "door") + itemType->setCategory(ItemCategoryDoor); + } + } +} + +void ThingTypeManager::addItemType(const ItemTypePtr& itemType) +{ + uint16 id = itemType->getServerId(); + if(unlikely(id >= m_itemTypes.size())) + m_itemTypes.resize(id + 1, m_nullItemType); + m_itemTypes[id] = itemType; +} + +const ItemTypePtr& ThingTypeManager::findItemTypeByClientId(uint16 id) +{ + if(id == 0 || id >= m_reverseItemTypes.size()) + return m_nullItemType; + + if(m_reverseItemTypes[id]) + return m_reverseItemTypes[id]; + else + return m_nullItemType; +} + +const ItemTypePtr& ThingTypeManager::findItemTypeByName(std::string name) +{ + for(const ItemTypePtr& it : m_itemTypes) + if(it->getName() == name) + return it; + return m_nullItemType; +} + +ItemTypeList ThingTypeManager::findItemTypesByName(std::string name) +{ + ItemTypeList ret; + for(const ItemTypePtr& it : m_itemTypes) + if(it->getName() == name) + ret.push_back(it); + return ret; +} + +ItemTypeList ThingTypeManager::findItemTypesByString(std::string name) +{ + ItemTypeList ret; + for(const ItemTypePtr& it : m_itemTypes) + if(it->getName().find(name) != std::string::npos) + ret.push_back(it); + return ret; +} + +const ThingTypePtr& ThingTypeManager::getThingType(uint16 id, ThingCategory category) +{ + if(category >= ThingLastCategory || id >= m_thingTypes[category].size()) { + g_logger.error(stdext::format("invalid thing type client id %d in category %d", id, category)); + return m_nullThingType; + } + return m_thingTypes[category][id]; +} + +const ItemTypePtr& ThingTypeManager::getItemType(uint16 id) +{ + if(id >= m_itemTypes.size() || m_itemTypes[id] == m_nullItemType) { + g_logger.error(stdext::format("invalid thing type, server id: %d", id)); + return m_nullItemType; + } + return m_itemTypes[id]; +} + +ThingTypeList ThingTypeManager::findThingTypeByAttr(ThingAttr attr, ThingCategory category) +{ + ThingTypeList ret; + for(const ThingTypePtr& type : m_thingTypes[category]) + if(type->hasAttr(attr)) + ret.push_back(type); + return ret; +} + +ItemTypeList ThingTypeManager::findItemTypeByCategory(ItemCategory category) +{ + ItemTypeList ret; + for(const ItemTypePtr& type : m_itemTypes) + if(type->getCategory() == category) + ret.push_back(type); + return ret; +} + +const ThingTypeList& ThingTypeManager::getThingTypes(ThingCategory category) +{ + ThingTypeList ret; + if(category >= ThingLastCategory) + stdext::throw_exception(stdext::format("invalid thing type category %d", category)); + return m_thingTypes[category]; +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/client/thingtypemanager.h b/src/client/thingtypemanager.h new file mode 100644 index 0000000..2892383 --- /dev/null +++ b/src/client/thingtypemanager.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef THINGTYPEMANAGER_H +#define THINGTYPEMANAGER_H + +#include +#include +#include + +#include "thingtype.h" +#include "itemtype.h" + +class ThingTypeManager +{ +public: + void init(); + void terminate(); + void check(); + + bool loadDat(std::string file); + bool loadOtml(std::string file); + void loadOtb(const std::string& file); + void loadXml(const std::string& file); + void parseItemType(uint16 id, TiXmlElement *elem); + +#ifdef WITH_ENCRYPTION + void saveDat(std::string fileName); + void dumpTextures(std::string dir); + void replaceTextures(std::string dir); +#endif + + void addItemType(const ItemTypePtr& itemType); + const ItemTypePtr& findItemTypeByClientId(uint16 id); + const ItemTypePtr& findItemTypeByName(std::string name); + ItemTypeList findItemTypesByName(std::string name); + ItemTypeList findItemTypesByString(std::string str); + + std::set getMarketCategories() + { + return m_marketCategories; + } + + const ThingTypePtr& getNullThingType() { return m_nullThingType; } + const ItemTypePtr& getNullItemType() { return m_nullItemType; } + + const ThingTypePtr& getThingType(uint16 id, ThingCategory category); + const ItemTypePtr& getItemType(uint16 id); + ThingType* rawGetThingType(uint16 id, ThingCategory category) { + VALIDATE(id < m_thingTypes[category].size()); + return m_thingTypes[category][id].get(); + } + ItemType* rawGetItemType(uint16 id) { + VALIDATE(id < m_itemTypes.size()); + return m_itemTypes[id].get(); + } + + ThingTypeList findThingTypeByAttr(ThingAttr attr, ThingCategory category); + ItemTypeList findItemTypeByCategory(ItemCategory category); + + const ThingTypeList& getThingTypes(ThingCategory category); + const ItemTypeList& getItemTypes() { return m_itemTypes; } + + uint32 getDatSignature() { return m_datSignature; } + uint32 getOtbMajorVersion() { return m_otbMajorVersion; } + uint32 getOtbMinorVersion() { return m_otbMinorVersion; } + uint16 getContentRevision() { return m_contentRevision; } + + bool isDatLoaded() { return m_datLoaded; } + bool isXmlLoaded() { return m_xmlLoaded; } + bool isOtbLoaded() { return m_otbLoaded; } + + bool isValidDatId(uint16 id, ThingCategory category) { return id >= 1 && id < m_thingTypes[category].size(); } + bool isValidOtbId(uint16 id) { return id >= 1 && id < m_itemTypes.size(); } + +private: + ThingTypeList m_thingTypes[ThingLastCategory]; + ItemTypeList m_reverseItemTypes; + ItemTypeList m_itemTypes; + std::set m_marketCategories; + + ThingTypePtr m_nullThingType; + ItemTypePtr m_nullItemType; + + bool m_datLoaded; + bool m_xmlLoaded; + bool m_otbLoaded; + + uint32 m_otbMinorVersion; + uint32 m_otbMajorVersion; + uint32 m_datSignature; + uint16 m_contentRevision; + + ScheduledEventPtr m_checkEvent; + size_t m_checkIndex[ThingLastCategory]; +}; + +extern ThingTypeManager g_things; + +#endif diff --git a/src/client/tile.cpp b/src/client/tile.cpp new file mode 100644 index 0000000..9d6ed15 --- /dev/null +++ b/src/client/tile.cpp @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "tile.h" +#include "item.h" +#include "thingtypemanager.h" +#include "map.h" +#include "game.h" +#include "localplayer.h" +#include "effect.h" +#include "protocolgame.h" +#include "lightview.h" +#include "spritemanager.h" +#include +#include +#include + +Tile::Tile(const Position& position) : + m_position(position), + m_drawElevation(0), + m_minimapColor(0), + m_flags(0) +{ +} + +void Tile::drawBottom(const Point& dest, LightView* lightView) +{ + m_topDraws = 0; + m_drawElevation = 0; + if (m_fill != Color::alpha) { + g_drawQueue->addFilledRect(Rect(dest, Otc::TILE_PIXELS, Otc::TILE_PIXELS), m_fill); + return; + } + + // bottom things + for (const ThingPtr& thing : m_things) { + if (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom()) + break; + if (thing->isHidden()) + continue; + + thing->draw(dest - m_drawElevation, true, lightView); + m_drawElevation = std::min(m_drawElevation + thing->getElevation(), Otc::MAX_ELEVATION); + } + + // common items, reverse order + int redrawPreviousTopW = 0, redrawPreviousTopH = 0; + for (auto it = m_things.rbegin(); it != m_things.rend(); ++it) { + const ThingPtr& thing = *it; + if (thing->isOnTop() || thing->isOnBottom() || thing->isGroundBorder() || thing->isGround() || thing->isCreature()) + break; + if (thing->isHidden()) + continue; + + thing->draw(dest - m_drawElevation, true, lightView); + m_drawElevation = std::min(m_drawElevation + thing->getElevation(), Otc::MAX_ELEVATION); + + if (thing->isLyingCorpse()) { + redrawPreviousTopW = std::max(thing->getWidth() - 1, redrawPreviousTopW); + redrawPreviousTopH = std::max(thing->getHeight() - 1, redrawPreviousTopH); + } + } + + for (int x = -redrawPreviousTopW; x <= 0; ++x) { + for (int y = -redrawPreviousTopH; y <= 0; ++y) { + if (x == 0 && y == 0) + continue; + if(const TilePtr& tile = g_map.getTile(m_position.translated(x, y))) + tile->drawTop(dest + Point(x * Otc::TILE_PIXELS, y * Otc::TILE_PIXELS), lightView); + } + } + + if (lightView && hasTranslucentLight()) { + lightView->addLight(dest + Point(16, 16), 215, 1); + } +} + +void Tile::drawTop(const Point& dest, LightView* lightView) +{ + if (m_fill != Color::alpha) + return; + if (m_topDraws++ < m_topCorrection) + return; + + // walking creatures + for (const CreaturePtr& creature : m_walkingCreatures) { + if (creature->isHidden()) + continue; + Point creatureDest(dest.x + ((creature->getPrewalkingPosition().x - m_position.x) * Otc::TILE_PIXELS - m_drawElevation), + dest.y + ((creature->getPrewalkingPosition().y - m_position.y) * Otc::TILE_PIXELS - m_drawElevation)); + creature->draw(creatureDest, true, lightView); + } + + // creatures + std::vector creaturesToDraw; + int limit = g_adaptiveRenderer.creaturesLimit(); + for (auto& thing : m_things) { + if (!thing->isCreature() || thing->isHidden()) + continue; + if (limit-- <= 0) + break; + CreaturePtr creature = thing->static_self_cast(); + if (!creature || creature->isWalking()) + continue; + creature->draw(dest - m_drawElevation, true, lightView); + } + + // effects + limit = std::min((int)m_effects.size() - 1, g_adaptiveRenderer.effetsLimit()); + for (int i = limit; i >= 0; --i) { + if (m_effects[i]->isHidden()) + continue; + m_effects[i]->draw(dest - m_drawElevation, m_position.x - g_map.getCentralPosition().x, m_position.y - g_map.getCentralPosition().y, true, lightView); + } + + // top + for (const ThingPtr& thing : m_things) { + if (!thing->isOnTop() || thing->isHidden()) + continue; + thing->draw(dest - m_drawElevation, true, lightView); + m_drawElevation = std::min(m_drawElevation + thing->getElevation(), Otc::MAX_ELEVATION); + } +} + + +void Tile::calculateCorpseCorrection() { + m_topCorrection = 0; + int redrawPreviousTopW = 0, redrawPreviousTopH = 0; + for(auto it = m_things.rbegin(); it != m_things.rend(); ++it) { + const ThingPtr& thing = *it; + if(!thing->isLyingCorpse()) { + continue; + } + if (thing->isHidden()) + continue; + redrawPreviousTopW = std::max(thing->getWidth() - 1, redrawPreviousTopW); + redrawPreviousTopH = std::max(thing->getHeight() - 1, redrawPreviousTopH); + } + + for (int x = -redrawPreviousTopW; x <= 0; ++x) { + for (int y = -redrawPreviousTopH; y <= 0; ++y) { + if (x == 0 && y == 0) + continue; + if (const TilePtr& tile = g_map.getTile(m_position.translated(x, y))) + tile->m_topCorrection += 1; + } + } +} + +void Tile::drawTexts(Point dest) +{ + if (m_timerText && g_clock.millis() < m_timer) { + if (m_text && m_text->hasText()) + dest.y -= 8; + m_timerText->setText(stdext::format("%.01f", (m_timer - g_clock.millis()) / 1000.)); + m_timerText->drawText(dest, Rect(dest.x - 64, dest.y - 64, 128, 128)); + dest.y += 16; + } + + if (m_text && m_text->hasText()) { + m_text->drawText(dest, Rect(dest.x - 64, dest.y - 64, 128, 128)); + } +} + +void Tile::clean() +{ + while(!m_things.empty()) + removeThing(m_things.front()); +} + +void Tile::addWalkingCreature(const CreaturePtr& creature) +{ + m_walkingCreatures.push_back(creature); +} + +void Tile::removeWalkingCreature(const CreaturePtr& creature) +{ + auto it = std::find(m_walkingCreatures.begin(), m_walkingCreatures.end(), creature); + if(it != m_walkingCreatures.end()) + m_walkingCreatures.erase(it); +} + +void Tile::addThing(const ThingPtr& thing, int stackPos) +{ + if(!thing) + return; + + if(thing->isEffect()) { + if(thing->isTopEffect()) + m_effects.insert(m_effects.begin(), thing->static_self_cast()); + else + m_effects.push_back(thing->static_self_cast()); + } else { + // priority 854 + // 0 - ground, --> --> + // 1 - ground borders --> --> + // 2 - bottom (walls), --> --> + // 3 - on top (doors) --> --> + // 4 - creatures, from top to bottom <-- --> + // 5 - items, from top to bottom <-- <-- + if(stackPos < 0 || stackPos == 255) { + int priority = thing->getStackPriority(); + + // -1 or 255 => auto detect position + // -2 => append + + bool append; + if(stackPos == -2) + append = true; + else { + append = (priority <= 3); + + // newer protocols does not store creatures in reverse order + if(g_game.getClientVersion() >= 854 && priority == 4) + append = !append; + } + + for(stackPos = 0; stackPos < (int)m_things.size(); ++stackPos) { + int otherPriority = m_things[stackPos]->getStackPriority(); + if((append && otherPriority > priority) || (!append && otherPriority >= priority)) + break; + } + } else if(stackPos > (int)m_things.size()) + stackPos = m_things.size(); + + m_things.insert(m_things.begin() + stackPos, thing); + + if(m_things.size() > MAX_THINGS) + removeThing(m_things[MAX_THINGS]); + + /* + // check stack priorities + // this code exists to find stackpos bugs faster + int lastPriority = 0; + for(const ThingPtr& thing : m_things) { + int priority = thing->getStackPriority(); + VALIDATE(lastPriority <= priority); + lastPriority = priority; + } + */ + } + + thing->setPosition(m_position); + thing->onAppear(); + + if(thing->isTranslucent()) + checkTranslucentLight(); + + if(g_game.isTileThingLuaCallbackEnabled()) + callLuaField("onAddThing", thing); +} + +bool Tile::removeThing(ThingPtr thing) +{ + if(!thing) + return false; + + bool removed = false; + + if(thing->isEffect()) { + EffectPtr effect = thing->static_self_cast(); + auto it = std::find(m_effects.begin(), m_effects.end(), effect); + if(it != m_effects.end()) { + m_effects.erase(it); + removed = true; + } + } else { + auto it = std::find(m_things.begin(), m_things.end(), thing); + if(it != m_things.end()) { + m_things.erase(it); + removed = true; + } + } + + if (thing->isCreature()) { + m_lastCreature = thing->getId(); + } + + thing->onDisappear(); + + if(thing->isTranslucent()) + checkTranslucentLight(); + + if (g_game.isTileThingLuaCallbackEnabled() && removed) { + callLuaField("onRemoveThing", thing); + } + + return removed; +} + +ThingPtr Tile::getThing(int stackPos) +{ + if(stackPos >= 0 && stackPos < (int)m_things.size()) + return m_things[stackPos]; + return nullptr; +} + +EffectPtr Tile::getEffect(uint16 id) +{ + for(const EffectPtr& effect : m_effects) + if(effect->getId() == id) + return effect; + return nullptr; +} + +bool Tile::hasThing(const ThingPtr& thing) +{ + return std::find(m_things.begin(), m_things.end(), thing) != m_things.end(); +} + +int Tile::getThingStackPos(const ThingPtr& thing) +{ + for(uint stackpos = 0; stackpos < m_things.size(); ++stackpos) + if(thing == m_things[stackpos]) + return stackpos; + return -1; +} + +ThingPtr Tile::getTopThing() +{ + if(isEmpty()) + return nullptr; + for(const ThingPtr& thing : m_things) + if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop() && !thing->isCreature()) + return thing; + return m_things[m_things.size() - 1]; +} + +std::vector Tile::getItems() +{ + std::vector items; + for(const ThingPtr& thing : m_things) { + if(!thing->isItem()) + continue; + ItemPtr item = thing->static_self_cast(); + items.push_back(item); + } + return items; +} + +std::vector Tile::getCreatures() +{ + std::vector creatures; + for(const ThingPtr& thing : m_things) { + if(thing->isCreature()) + creatures.push_back(thing->static_self_cast()); + } + return creatures; +} + +ItemPtr Tile::getGround() +{ + ThingPtr firstObject = getThing(0); + if(!firstObject) + return nullptr; + if(firstObject->isGround() && firstObject->isItem()) + return firstObject->static_self_cast(); + return nullptr; +} + +int Tile::getGroundSpeed() +{ + if (m_speed) + return m_speed; + int groundSpeed = 100; + if(ItemPtr ground = getGround()) + groundSpeed = ground->getGroundSpeed(); + return groundSpeed; +} + +uint8 Tile::getMinimapColorByte() +{ + uint8 color = 255; // alpha + if(m_minimapColor != 0) + return m_minimapColor; + + for(const ThingPtr& thing : m_things) { + if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop()) + break; + uint8 c = thing->getMinimapColor(); + if(c != 0) + color = c; + } + return color; +} + +ThingPtr Tile::getTopLookThing() +{ + if(isEmpty()) + return nullptr; + + for(uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if(!thing->isIgnoreLook() && (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop())) + return thing; + } + + return m_things[0]; +} + +ThingPtr Tile::getTopLookThingEx(Point offset) +{ + auto creature = getTopCreatureEx(offset); + if (creature) + return creature; + + if (isEmpty()) + return nullptr; + + for (uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if (!thing->isIgnoreLook() && (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop() && !thing->isCreature())) + return thing; + } + + return m_things[0]; +} + +ThingPtr Tile::getTopUseThing() +{ + if(isEmpty()) + return nullptr; + + for(uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if (thing->isForceUse() || (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop() && !thing->isCreature() && !thing->isSplash())) + return thing; + } + + for (uint i = m_things.size() - 1; i > 0; --i) { + ThingPtr thing = m_things[i]; + if (!thing->isSplash() && !thing->isCreature()) + return thing; + } + + return m_things[0]; +} + +CreaturePtr Tile::getTopCreature() +{ + CreaturePtr creature; + for(uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if(thing->isLocalPlayer()) // return local player if there is no other creature + creature = thing->static_self_cast(); + else if(thing->isCreature() && !thing->isLocalPlayer()) + return thing->static_self_cast(); + } + if(!creature && !m_walkingCreatures.empty()) + creature = m_walkingCreatures.back(); + + // check for walking creatures in tiles around + if(!creature) { + for(int xi=-1;xi<=1;++xi) { + for(int yi=-1;yi<=1;++yi) { + Position pos = m_position.translated(xi, yi); + if(pos == m_position) + continue; + + const TilePtr& tile = g_map.getTile(pos); + if(tile) { + for(const CreaturePtr& c : tile->getCreatures()) { + if(c->isWalking() && c->getLastStepFromPosition() == m_position && c->getStepProgress() < 0.75f) { + creature = c; + } + } + } + } + } + } + return creature; +} + +CreaturePtr Tile::getTopCreatureEx(Point offset) +{ + // hidden + return nullptr; +} + +ThingPtr Tile::getTopMoveThing() +{ + if(isEmpty()) + return nullptr; + + for(uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop() && !thing->isCreature()) { + if(i > 0 && thing->isNotMoveable()) + return m_things[i-1]; + return thing; + } + } + + for(const ThingPtr& thing : m_things) { + if(thing->isCreature()) + return thing; + } + + return m_things[0]; +} + +ThingPtr Tile::getTopMultiUseThing() +{ + if (isEmpty()) + return nullptr; + + if (CreaturePtr topCreature = getTopCreature()) + return topCreature; + + for (uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if (thing->isForceUse()) + return thing; + } + + for (uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop()) { + if (i > 0 && thing->isSplash()) + return m_things[i - 1]; + return thing; + } + } + + return m_things.back(); +} + +ThingPtr Tile::getTopMultiUseThingEx(Point offset) +{ + if (CreaturePtr topCreature = getTopCreatureEx(offset)) + return topCreature; + + if (isEmpty()) + return nullptr; + + for (uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if (thing->isForceUse() && !thing->isCreature()) + return thing; + } + + for (uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop() && !thing->isCreature()) { + if (i > 0 && thing->isSplash()) + return m_things[i - 1]; + return thing; + } + } + + for (uint i = m_things.size() - 1; i > 0; --i) { + ThingPtr thing = m_things[i]; + if (!thing->isCreature()) + return thing; + } + + return m_things[0]; +} + +bool Tile::isWalkable(bool ignoreCreatures) +{ + if(!getGround()) + return false; + + for(const ThingPtr& thing : m_things) { + if(thing->isNotWalkable()) + return false; + + if(!ignoreCreatures) { + if(thing->isCreature()) { + CreaturePtr creature = thing->static_self_cast(); + if(!creature->isPassable() && creature->canBeSeen() && !creature->isLocalPlayer()) + return false; + } + } + } + return true; +} + +bool Tile::isPathable() +{ + for(const ThingPtr& thing : m_things) + if(thing->isNotPathable()) + return false; + return true; +} + +bool Tile::isFullGround() +{ + ItemPtr ground = getGround(); + if(ground && ground->isFullGround()) + return true; + return false; +} + +bool Tile::isFullyOpaque() +{ + ThingPtr firstObject = getThing(0); + return firstObject && firstObject->isFullGround(); +} + +bool Tile::isSingleDimension() +{ + if(!m_walkingCreatures.empty()) + return false; + for(const ThingPtr& thing : m_things) + if(thing->getHeight() != 1 || thing->getWidth() != 1) + return false; + return true; +} + +bool Tile::isLookPossible() +{ + for(const ThingPtr& thing : m_things) + if(thing->blockProjectile()) + return false; + return true; +} + +bool Tile::isClickable() +{ + bool hasGround = false; + bool hasOnBottom = false; + bool hasIgnoreLook = false; + for(const ThingPtr& thing : m_things) { + if(thing->isGround()) + hasGround = true; + if(thing->isOnBottom()) + hasOnBottom = true; + if((hasGround || hasOnBottom) && !hasIgnoreLook) + return true; + } + return false; +} + +bool Tile::isEmpty() +{ + return m_things.size() == 0; +} + +bool Tile::isDrawable() +{ + return !m_things.empty() || !m_walkingCreatures.empty() || !m_effects.empty(); +} + +bool Tile::mustHookEast() +{ + for(const ThingPtr& thing : m_things) + if(thing->isHookEast()) + return true; + return false; +} + +bool Tile::mustHookSouth() +{ + for(const ThingPtr& thing : m_things) + if(thing->isHookSouth()) + return true; + return false; +} + +bool Tile::hasCreature() +{ + for(const ThingPtr& thing : m_things) + if(thing->isCreature()) + return true; + return false; +} + +bool Tile::hasBlockingCreature() +{ + for (const ThingPtr& thing : m_things) + if (thing->isCreature() && !thing->static_self_cast()->isPassable() && !thing->isLocalPlayer()) + return true; + return false; +} + +bool Tile::limitsFloorsView(bool isFreeView) +{ + // ground and walls limits the view + ThingPtr firstThing = getThing(0); + + if(isFreeView) { + if(firstThing && !firstThing->isDontHide() && (firstThing->isGround() || firstThing->isOnBottom())) + return true; + } else if(firstThing && !firstThing->isDontHide() && (firstThing->isGround() || (firstThing->isOnBottom() && firstThing->blockProjectile()))) + return true; + return false; +} + + +bool Tile::canErase() +{ + return m_walkingCreatures.empty() && m_effects.empty() && m_things.empty() && m_flags == 0 && m_minimapColor == 0; +} + +int Tile::getElevation() +{ + int elevation = 0; + for(const ThingPtr& thing : m_things) + if(thing->getElevation() > 0) + elevation++; + return elevation; +} + +bool Tile::hasElevation(int elevation) +{ + return getElevation() >= elevation; +} + +void Tile::checkTranslucentLight() +{ + if(m_position.z != Otc::SEA_FLOOR) + return; + + Position downPos = m_position; + if(!downPos.down()) + return; + + TilePtr tile = g_map.getOrCreateTile(downPos); + if(!tile) + return; + + bool translucent = false; + for(const ThingPtr& thing : m_things) { + if(thing->isTranslucent() || thing->hasLensHelp()) { + translucent = true; + break; + } + } + + if(translucent) + tile->m_flags |= TILESTATE_TRANSLUECENT_LIGHT; + else + tile->m_flags &= ~TILESTATE_TRANSLUECENT_LIGHT; +} + +void Tile::setText(const std::string& text, Color color) +{ + if (!m_text) { + m_text = StaticTextPtr(new StaticText()); + } + m_text->setText(text); + m_text->setColor(color); +} + +std::string Tile::getText() +{ + return m_text ? m_text->getCachedText().getText() : ""; +} + +void Tile::setTimer(int time, Color color) +{ + if (time > 60000) { + g_logger.warning("Max tile timer value is 300000 (300s)!"); + return; + } + m_timer = time + g_clock.millis(); + if (!m_timerText) { + m_timerText = StaticTextPtr(new StaticText()); + } + m_timerText->setColor(color); +} + +int Tile::getTimer() +{ + return m_timerText ? std::max(0, m_timer - g_clock.millis()) : 0; +} + +void Tile::setFill(Color color) +{ + m_fill = color; +} diff --git a/src/client/tile.h b/src/client/tile.h new file mode 100644 index 0000000..9c7198e --- /dev/null +++ b/src/client/tile.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TILE_H +#define TILE_H + +#include "declarations.h" +#include "mapview.h" +#include "effect.h" +#include "creature.h" +#include "item.h" +#include +#include + +enum tileflags_t +{ + TILESTATE_NONE = 0, + TILESTATE_PROTECTIONZONE = 1 << 0, + TILESTATE_TRASHED = 1 << 1, + TILESTATE_OPTIONALZONE = 1 << 2, + TILESTATE_NOLOGOUT = 1 << 3, + TILESTATE_HARDCOREZONE = 1 << 4, + TILESTATE_REFRESH = 1 << 5, + + // internal usage + TILESTATE_HOUSE = 1 << 6, + TILESTATE_TELEPORT = 1 << 17, + TILESTATE_MAGICFIELD = 1 << 18, + TILESTATE_MAILBOX = 1 << 19, + TILESTATE_TRASHHOLDER = 1 << 20, + TILESTATE_BED = 1 << 21, + TILESTATE_DEPOT = 1 << 22, + TILESTATE_TRANSLUECENT_LIGHT = 1 << 23, + + TILESTATE_LAST = 1 << 24 +}; + +class Tile : public LuaObject +{ +public: + enum { + MAX_THINGS = 10 + }; + + Tile(const Position& position); + + void calculateCorpseCorrection(); + + void drawBottom(const Point& dest, LightView* lightView = nullptr); + void drawTop(const Point& dest, LightView* lightView = nullptr); + void drawTexts(Point dest); + +public: + void clean(); + + void addWalkingCreature(const CreaturePtr& creature); + void removeWalkingCreature(const CreaturePtr& creature); + + void addThing(const ThingPtr& thing, int stackPos); + bool removeThing(ThingPtr thing); + ThingPtr getThing(int stackPos); + EffectPtr getEffect(uint16 id); + bool hasThing(const ThingPtr& thing); + int getThingStackPos(const ThingPtr& thing); + ThingPtr getTopThing(); + + ThingPtr getTopLookThing(); + ThingPtr getTopLookThingEx(Point offset); + ThingPtr getTopUseThing(); + CreaturePtr getTopCreature(); + CreaturePtr getTopCreatureEx(Point offset); + ThingPtr getTopMoveThing(); + ThingPtr getTopMultiUseThing(); + ThingPtr getTopMultiUseThingEx(Point offset); + + const Position& getPosition() { return m_position; } + int getDrawElevation() { return m_drawElevation; } + std::vector getItems(); + std::vector getCreatures(); + std::vector getWalkingCreatures() { return m_walkingCreatures; } + std::vector getThings() { return m_things; } + std::vector getEffects() { return m_effects; } + ItemPtr getGround(); + int getGroundSpeed(); + bool isBlocking() { return m_blocking != 0; } + uint8 getMinimapColorByte(); + int getThingCount() { return m_things.size() + m_effects.size(); } + bool isPathable(); + bool isWalkable(bool ignoreCreatures = false); + bool isFullGround(); + bool isFullyOpaque(); + bool isSingleDimension(); + bool isLookPossible(); + bool isClickable(); + bool isEmpty(); + bool isDrawable(); + bool hasTranslucentLight() { return m_flags & TILESTATE_TRANSLUECENT_LIGHT; } + bool mustHookSouth(); + bool mustHookEast(); + bool hasCreature(); + bool hasBlockingCreature(); + bool limitsFloorsView(bool isFreeView = false); + bool canErase(); + int getElevation(); + bool hasElevation(int elevation = 1); + void overwriteMinimapColor(uint8 color) { m_minimapColor = color; } + + void remFlag(uint32 flag) { m_flags &= ~flag; } + void setFlag(uint32 flag) { m_flags |= flag; } + void setFlags(uint32 flags) { m_flags = flags; } + bool hasFlag(uint32 flag) { return (m_flags & flag) == flag; } + uint32 getFlags() { return m_flags; } + + void setHouseId(uint32 hid) { m_houseId = hid; } + uint32 getHouseId() { return m_houseId; } + bool isHouseTile() { return m_houseId != 0 && (m_flags & TILESTATE_HOUSE) == TILESTATE_HOUSE; } + + void select() { m_selected = true; } + void unselect() { m_selected = false; } + bool isSelected() { return m_selected; } + + TilePtr asTile() { return static_self_cast(); } + + void setSpeed(uint16_t speed, uint8_t blocking) { + m_speed = speed; + m_blocking = blocking; + } + + void setText(const std::string& text, Color color); + std::string getText(); + void setTimer(int time, Color color); + int getTimer(); + void setFill(Color color); + void resetFill() { m_fill = Color::alpha; } + +private: + void checkTranslucentLight(); + + std::vector m_walkingCreatures; + std::vector m_effects; // leave this outside m_things because it has no stackpos. + std::vector m_things; + Position m_position; + uint8 m_drawElevation; + uint8 m_minimapColor; + uint32 m_flags, m_houseId; + uint16 m_speed = 0; + uint8 m_blocking = 0; + + uint32_t m_lastCreature = 0; + int m_topCorrection = 0; + int m_topDraws = 0; + + stdext::boolean m_selected; + + ticks_t m_timer = 0; + StaticTextPtr m_timerText; + StaticTextPtr m_text; + Color m_fill = Color::alpha; +}; + +#endif diff --git a/src/client/towns.cpp b/src/client/towns.cpp new file mode 100644 index 0000000..4c129ee --- /dev/null +++ b/src/client/towns.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "towns.h" + +TownManager g_towns; + +Town::Town(uint32 tid, const std::string& name, const Position& pos) + : m_id(tid), m_name(name) +{ + if(pos.isValid()) + m_pos = pos; +} + +TownManager::TownManager() +{ + m_nullTown = TownPtr(new Town); +} + +void TownManager::addTown(const TownPtr &town) +{ + if(findTown(town->getId()) == m_towns.end()) + m_towns.push_back(town); +} + +void TownManager::removeTown(uint32 townId) +{ + auto it = findTown(townId); + if(it != m_towns.end()) + m_towns.erase(it); +} + +const TownPtr& TownManager::getTown(uint32 townId) +{ + auto it = std::find_if(m_towns.begin(), m_towns.end(), + [=] (const TownPtr& town) -> bool { return town->getId() == townId; }); + if(it != m_towns.end()) + return *it; + return m_nullTown; +} + +const TownPtr& TownManager::getTownByName(std::string name) +{ + auto it = std::find_if(m_towns.begin(), m_towns.end(), + [=] (const TownPtr& town) -> bool { return town->getName() == name; } ); + if(it != m_towns.end()) + return *it; + return m_nullTown; +} + +TownList::iterator TownManager::findTown(uint32 townId) +{ + return std::find_if(m_towns.begin(), m_towns.end(), + [=] (const TownPtr& town) -> bool { return town->getId() == townId; }); +} + +void TownManager::sort() +{ + m_towns.sort([] (const TownPtr& lhs, const TownPtr& rhs) { return lhs->getName() < rhs->getName(); }); +} + diff --git a/src/client/towns.h b/src/client/towns.h new file mode 100644 index 0000000..34ccd4b --- /dev/null +++ b/src/client/towns.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TOWNS_H +#define TOWNS_H + +#include "declarations.h" +#include + +class Town : public LuaObject +{ +public: + Town() { } + Town(uint32 tid, const std::string& name, const Position& pos=Position()); + + void setId(uint32 tid) { m_id = tid; } + void setName(const std::string& name) { m_name = name; } + void setPos(const Position& pos) { m_pos = pos; } + + uint32 getId() { return m_id; } + std::string getName() { return m_name; } + Position getPos() { return m_pos; } + +private: + uint32 m_id; + std::string m_name; + Position m_pos; // temple pos +}; + +class TownManager +{ +public: + TownManager(); + + void addTown(const TownPtr& town); + void removeTown(uint32 townId); + const TownPtr& getTown(uint32 townId); + const TownPtr& getTownByName(std::string name); + + void sort(); + TownList getTowns() { return m_towns; } + void clear() { m_towns.clear(); m_nullTown = nullptr; } + +private: + TownList m_towns; + TownPtr m_nullTown; + +protected: + TownList::iterator findTown(uint32 townId); +}; + +extern TownManager g_towns; + +#endif diff --git a/src/client/uicreature.cpp b/src/client/uicreature.cpp new file mode 100644 index 0000000..caf5b99 --- /dev/null +++ b/src/client/uicreature.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uicreature.h" +#include +#include + +void UICreature::drawSelf(Fw::DrawPane drawPane) +{ + if(drawPane != Fw::ForegroundPane) + return; + + UIWidget::drawSelf(drawPane); + + if(m_creature) { + if (m_autoRotating) { + auto ticks = (g_clock.millis() % 4000) / 4; + Otc::Direction new_dir; + if (ticks < 250) + { + new_dir = Otc::South; + } + else if (ticks < 500) + { + new_dir = Otc::East; + } + else if (ticks < 750) + { + new_dir = Otc::North; + } + else + { + new_dir = Otc::West; + } + if (new_dir != m_direction) { + m_direction = new_dir; + m_redraw = true; + } + } + + if (m_creature->getOutfitNumber() != m_outfitNumber) { + m_outfitNumber = m_creature->getOutfitNumber(); + m_redraw = true; + } + + m_creature->drawOutfit(getPaddingRect(), m_direction, m_imageColor); + } +} + +void UICreature::setOutfit(const Outfit& outfit) +{ + if(!m_creature) + m_creature = CreaturePtr(new Creature); + m_direction = Otc::South; + m_creature->setOutfit(outfit); + m_redraw = true; +} + +void UICreature::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "fixed-creature-size") + setFixedCreatureSize(node->value()); + else if(node->tag() == "outfit-id") { + Outfit outfit = (m_creature ? m_creature->getOutfit() : Outfit()); + outfit.setId(node->value()); + setOutfit(outfit); + } + else if(node->tag() == "outfit-head") { + Outfit outfit = (m_creature ? m_creature->getOutfit() : Outfit()); + outfit.setHead(node->value()); + setOutfit(outfit); + } + else if(node->tag() == "outfit-body") { + Outfit outfit = (m_creature ? m_creature->getOutfit() : Outfit()); + outfit.setBody(node->value()); + setOutfit(outfit); + } + else if(node->tag() == "outfit-legs") { + Outfit outfit = (m_creature ? m_creature->getOutfit() : Outfit()); + outfit.setLegs(node->value()); + setOutfit(outfit); + } + else if(node->tag() == "outfit-feet") { + Outfit outfit = (m_creature ? m_creature->getOutfit() : Outfit()); + outfit.setFeet(node->value()); + setOutfit(outfit); + } + else if (node->tag() == "scale") { + setScale(node->value()); + } + else if (node->tag() == "optimized") { + setOptimized(node->value()); + } + } +} + +void UICreature::onGeometryChange(const Rect& oldRect, const Rect& newRect) +{ + UIWidget::onGeometryChange(oldRect, newRect); + m_redraw = true; +} \ No newline at end of file diff --git a/src/client/uicreature.h b/src/client/uicreature.h new file mode 100644 index 0000000..619d0e8 --- /dev/null +++ b/src/client/uicreature.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UICREATURE_H +#define UICREATURE_H + +#include "declarations.h" +#include +#include "creature.h" + +class UICreature : public UIWidget +{ +public: + void drawSelf(Fw::DrawPane drawPane); + + void setCreature(const CreaturePtr& creature) { m_creature = creature; m_redraw = true; } + void setFixedCreatureSize(bool fixed) { m_scale = fixed ? 1.0 : 0; m_redraw = true; } + void setOutfit(const Outfit& outfit); + + CreaturePtr getCreature() { return m_creature; } + bool isFixedCreatureSize() { return m_scale > 0; } + + void setAutoRotating(bool value) { m_autoRotating = value; } + void setDirection(Otc::Direction direction) { m_direction = direction; m_redraw = true; } + + void setScale(float scale) { m_scale = scale; m_redraw = true; } + float getScale() { return m_scale; } + + void setOptimized(bool value) { m_optimized = value; m_redraw = true; } + +protected: + void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + void onGeometryChange(const Rect& oldRect, const Rect& newRect) override; + + CreaturePtr m_creature; + stdext::boolean m_autoRotating; + stdext::boolean m_redraw; + int m_outfitNumber = 0; + Otc::Direction m_direction = Otc::South; + float m_scale = 1.0; + bool m_optimized = false; +}; + +#endif diff --git a/src/client/uiitem.cpp b/src/client/uiitem.cpp new file mode 100644 index 0000000..da41830 --- /dev/null +++ b/src/client/uiitem.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiitem.h" +#include +#include +#include + +UIItem::UIItem() +{ + m_draggable = true; +} + +void UIItem::drawSelf(Fw::DrawPane drawPane) +{ + if(drawPane != Fw::ForegroundPane) + return; + // draw style components in order + if(m_backgroundColor.aF() > Fw::MIN_ALPHA) { + Rect backgroundDestRect = m_rect; + backgroundDestRect.expand(-m_borderWidth.top, -m_borderWidth.right, -m_borderWidth.bottom, -m_borderWidth.left); + drawBackground(m_rect); + } + + drawImage(m_rect); + + if(m_itemVisible && m_item) { + Rect drawRect = getPaddingRect(); + + int exactSize = std::max(32, m_item->getExactSize()); + if(exactSize == 0) + return; + + m_item->setColor(m_color); + m_item->draw(drawRect); + + if(m_font && m_showCount && (m_item->isStackable() || m_item->isChargeable()) && m_item->getCountOrSubType() > 1) { + g_drawQueue->addText(m_font, std::to_string(m_item->getCountOrSubType()), Rect(m_rect.topLeft(), m_rect.bottomRight() - Point(3, 0)), Fw::AlignBottomRight, Color(231, 231, 231)); + } + + if (m_showId) { + g_drawQueue->addText(m_font, std::to_string(m_item->getServerId()), m_rect, Fw::AlignBottomRight, Color(231, 231, 231)); + } + } + + drawBorder(m_rect); + drawIcon(m_rect); + drawText(m_rect); +} + +void UIItem::setItemId(int id) +{ + if (!m_item && id != 0) + m_item = Item::create(id); + else { + // remove item + if (id == 0) + m_item = nullptr; + else + m_item->setId(id); + } + + callLuaField("onItemChange"); +} + +void UIItem::setItemCount(int count) +{ + if (m_item) + m_item->setCount(count); + callLuaField("onItemChange"); +} +void UIItem::setItemSubType(int subType) +{ + if (m_item) + m_item->setSubType(subType); + callLuaField("onItemChange"); +} + +void UIItem::setItem(const ItemPtr& item) +{ + m_item = item; + callLuaField("onItemChange"); +} + +void UIItem::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "item-id") + setItemId(node->value()); + else if(node->tag() == "item-count") + setItemCount(node->value()); + else if(node->tag() == "item-visible") + setItemVisible(node->value()); + else if(node->tag() == "virtual") + setVirtual(node->value()); + else if(node->tag() == "show-id") + m_showId = node->value(); + } +} diff --git a/src/client/uiitem.h b/src/client/uiitem.h new file mode 100644 index 0000000..3b31a99 --- /dev/null +++ b/src/client/uiitem.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIITEM_H +#define UIITEM_H + +#include "declarations.h" +#include +#include "item.h" + +class UIItem : public UIWidget +{ +public: + UIItem(); + void drawSelf(Fw::DrawPane drawPane); + + void setItemId(int id); + void setItemCount(int count); + void setItemSubType(int subType); + void setItemVisible(bool visible) { m_itemVisible = visible; } + void setItem(const ItemPtr& item); + void setVirtual(bool virt) { m_virtual = virt; } + void clearItem() { setItemId(0); } + void setShowCount(bool value) { m_showCount = value; } + + int getItemId() { return m_item ? m_item->getId() : 0; } + int getItemCount() { return m_item ? m_item->getCount() : 0; } + int getItemSubType() { return m_item ? m_item->getSubType() : 0; } + int getItemCountOrSubType() { return m_item ? m_item->getCountOrSubType() : 0; } + ItemPtr getItem() { return m_item; } + bool isVirtual() { return m_virtual; } + bool isItemVisible() { return m_itemVisible; } + +protected: + void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + + ItemPtr m_item; + stdext::boolean m_virtual; + stdext::boolean m_itemVisible; + stdext::boolean m_showId; + stdext::boolean m_showCount; +}; + +#endif diff --git a/src/client/uimap.cpp b/src/client/uimap.cpp new file mode 100644 index 0000000..eddd6a6 --- /dev/null +++ b/src/client/uimap.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uimap.h" +#include "game.h" +#include "map.h" +#include "mapview.h" +#include +#include +#include +#include "localplayer.h" + +UIMap::UIMap() +{ + m_draggable = true; + m_mapView = MapViewPtr(new MapView); + m_zoom = m_mapView->getVisibleDimension().height(); + m_keepAspectRatio = true; + m_limitVisibleRange = false; + m_aspectRatio = m_mapView->getVisibleDimension().ratio(); + m_maxZoomIn = 3; + m_maxZoomOut = 513; + m_mapRect.resize(1,1); + g_map.addMapView(m_mapView); +} + +UIMap::~UIMap() +{ + g_map.removeMapView(m_mapView); +} + +bool UIMap::onMouseMove(const Point& mousePos, const Point& mouseMoved) +{ + m_mousePosition = mousePos; + return UIWidget::onMouseMove(mousePos, mouseMoved); +} + +void UIMap::drawSelf(Fw::DrawPane drawPane) +{ + UIWidget::drawSelf(drawPane); + + if(drawPane == Fw::ForegroundPane) { + g_drawQueue->addBoundingRect(m_mapRect.expanded(1), 1, Color::black); + g_drawQueue->markMapPosition(); + } else if(drawPane == Fw::MapBackgroundPane) { + m_mapView->drawBackground(m_mapRect, getTile(m_mousePosition)); + } else if (drawPane == Fw::MapForegroundPane) { + m_mapView->drawForeground(m_mapRect); + } +} + +void UIMap::movePixels(int x, int y) +{ + m_mapView->move(x, y); +} + +bool UIMap::setZoom(int zoom) +{ + m_zoom = stdext::clamp(zoom, m_maxZoomIn, m_maxZoomOut); + updateVisibleDimension(); + return false; +} + +bool UIMap::zoomIn() +{ + int delta = 2; + if(m_zoom - delta < m_maxZoomIn) + delta--; + + if(m_zoom - delta < m_maxZoomIn) + return false; + + m_zoom -= delta; + updateVisibleDimension(); + return true; +} + +bool UIMap::zoomOut() +{ + int delta = 2; + if(m_zoom + delta > m_maxZoomOut) + delta--; + + if(m_zoom + delta > m_maxZoomOut) + return false; + + m_zoom += 2; + updateVisibleDimension(); + return true; +} + +void UIMap::setVisibleDimension(const Size& visibleDimension) +{ + m_mapView->setVisibleDimension(visibleDimension); + m_aspectRatio = visibleDimension.ratio(); + + if(m_keepAspectRatio) + updateMapSize(); +} + +void UIMap::setKeepAspectRatio(bool enable) +{ + m_keepAspectRatio = enable; + if(enable) + m_aspectRatio = getVisibleDimension().ratio(); + updateMapSize(); +} + +Position UIMap::getPosition(const Point& mousePos) +{ + if (!m_mapRect.contains(mousePos)) + return Position(); + + Point relativeMousePos = mousePos - m_mapRect.topLeft(); + return m_mapView->getPosition(relativeMousePos, m_mapRect.size()); +} + +Point UIMap::getPositionOffset(const Point& mousePos) +{ + if (!m_mapRect.contains(mousePos)) + return Point(0, 0); + + Point relativeMousePos = mousePos - m_mapRect.topLeft(); + return m_mapView->getPositionOffset(relativeMousePos, m_mapRect.size()); +} + +TilePtr UIMap::getTile(const Point& mousePos) +{ + Position tilePos = getPosition(mousePos); + if(!tilePos.isValid()) + return nullptr; + + // we must check every floor, from top to bottom to check for a clickable tile + TilePtr tile; + tilePos.coveredUp(tilePos.z - m_mapView->getCachedFirstVisibleFloor()); + for(int i = m_mapView->getCachedFirstVisibleFloor(); i <= m_mapView->getCachedLastVisibleFloor(); i++) { + tile = g_map.getTile(tilePos); + if(tile && tile->isClickable()) + break; + tilePos.coveredDown(); + } + + if(!tile || !tile->isClickable()) + return nullptr; + + return tile; +} + +void UIMap::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "multifloor") + setMultifloor(node->value()); + else if(node->tag() == "draw-texts") + setDrawTexts(node->value()); + else if(node->tag() == "draw-lights") + setDrawLights(node->value()); + else if(node->tag() == "animated") + setAnimated(node->value()); + } +} + +void UIMap::onGeometryChange(const Rect& oldRect, const Rect& newRect) +{ + UIWidget::onGeometryChange(oldRect, newRect); + updateMapSize(); +} + +void UIMap::updateVisibleDimension() +{ + int dimensionHeight = m_zoom; + + float ratio = m_aspectRatio; + if(!m_limitVisibleRange && !m_mapRect.isEmpty() && !m_keepAspectRatio) + ratio = m_mapRect.size().ratio(); + + if(dimensionHeight % 2 == 0) + dimensionHeight += 1; + int dimensionWidth = m_zoom * ratio; + if(dimensionWidth % 2 == 0) + dimensionWidth += 1; + + m_mapView->setVisibleDimension(Size(dimensionWidth, dimensionHeight)); + + if(m_keepAspectRatio) + updateMapSize(); + + callLuaField("onVisibleDimensionChange", dimensionWidth, dimensionHeight); +} + +void UIMap::updateMapSize() +{ + Rect clippingRect = getPaddingRect(); + Size mapSize; + if(m_keepAspectRatio) { + Rect mapRect = clippingRect.expanded(-1); + mapSize = Size(m_aspectRatio*m_zoom, m_zoom); + mapSize.scale(mapRect.size(), Fw::KeepAspectRatio); + } else { + mapSize = clippingRect.expanded(-1).size(); + } + + m_mapRect.resize(mapSize); + m_mapRect.moveCenter(clippingRect.center()); + m_mapView->optimizeForSize(mapSize); + + if(!m_keepAspectRatio) + updateVisibleDimension(); +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/client/uimap.h b/src/client/uimap.h new file mode 100644 index 0000000..8993243 --- /dev/null +++ b/src/client/uimap.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIMAP_H +#define UIMAP_H + +#include "declarations.h" +#include +#include "tile.h" + +#include "mapview.h" + +class UIMap : public UIWidget +{ +public: + UIMap(); + ~UIMap(); + + bool onMouseMove(const Point& mousePos, const Point& mouseMoved); + + void drawSelf(Fw::DrawPane drawPane); + + void movePixels(int x, int y); + bool setZoom(int zoom); + bool zoomIn(); + bool zoomOut(); + void followCreature(const CreaturePtr& creature) { m_mapView->followCreature(creature); } + + void setCameraPosition(const Position& pos) { m_mapView->setCameraPosition(pos); } + void setMaxZoomIn(int maxZoomIn) { m_maxZoomIn = maxZoomIn; } + void setMaxZoomOut(int maxZoomOut) { m_maxZoomOut = maxZoomOut; } + void setMultifloor(bool enable) { m_mapView->setMultifloor(enable); } + void lockVisibleFloor(int floor) { m_mapView->lockFirstVisibleFloor(floor); } + void unlockVisibleFloor() { m_mapView->unlockFirstVisibleFloor(); } + void setVisibleDimension(const Size& visibleDimension); + void setDrawFlags(Otc::DrawFlags drawFlags) { m_mapView->setDrawFlags(drawFlags); } + void setDrawTexts(bool enable) { m_mapView->setDrawTexts(enable); } + void setDrawNames(bool enable) { m_mapView->setDrawNames(enable); } + void setDrawHealthBars(bool enable) { m_mapView->setDrawHealthBars(enable); } + void setDrawHealthBarsOnTop(bool enable) { m_mapView->setDrawHealthBarsOnTop(enable); } + void setDrawLights(bool enable) { m_mapView->setDrawLights(enable); } + void setDrawManaBar(bool enable) { m_mapView->setDrawManaBar(enable); } + void setDrawPlayerBars(bool enable) { m_mapView->setDrawPlayerBars(enable); } + void setAnimated(bool enable) { m_mapView->setAnimated(enable); } + void setKeepAspectRatio(bool enable); + void setMinimumAmbientLight(float intensity) { m_mapView->setMinimumAmbientLight(intensity); } + void setLimitVisibleRange(bool limitVisibleRange) { m_limitVisibleRange = limitVisibleRange; updateVisibleDimension(); } + void setFloorFading(int value) { m_mapView->setFloorFading(value); } + void setCrosshair(const std::string& type) { m_mapView->setCrosshair(type); } + bool isMultifloor() { return m_mapView->isMultifloor(); } + bool isDrawingTexts() { return m_mapView->isDrawingTexts(); } + bool isDrawingNames() { return m_mapView->isDrawingNames(); } + bool isDrawingHealthBars() { return m_mapView->isDrawingHealthBars(); } + bool isDrawingHealthBarsOnTop() { return m_mapView->isDrawingHealthBarsOnTop(); } + bool isDrawingLights() { return m_mapView->isDrawingLights(); } + bool isDrawingManaBar() { return m_mapView->isDrawingManaBar(); } + bool isAnimating() { return m_mapView->isAnimating(); } + bool isKeepAspectRatioEnabled() { return m_keepAspectRatio; } + bool isLimitVisibleRangeEnabled() { return m_limitVisibleRange; } + + Size getVisibleDimension() { return m_mapView->getVisibleDimension(); } + CreaturePtr getFollowingCreature() { return m_mapView->getFollowingCreature(); } + Otc::DrawFlags getDrawFlags() { return m_mapView->getDrawFlags(); } + Position getCameraPosition() { return m_mapView->getCameraPosition(); } + Position getPosition(const Point& mousePos); + Point getPositionOffset(const Point& mousePos); + TilePtr getTile(const Point& mousePos); + int getMaxZoomIn() { return m_maxZoomIn; } + int getMaxZoomOut() { return m_maxZoomOut; } + int getZoom() { return m_zoom; } + float getMinimumAmbientLight() { return m_mapView->getMinimumAmbientLight(); } + +protected: + virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + virtual void onGeometryChange(const Rect& oldRect, const Rect& newRect); + +private: + void updateVisibleDimension(); + void updateMapSize(); + + int m_zoom; + MapViewPtr m_mapView; + Rect m_mapRect; + Point m_mousePosition; + float m_aspectRatio; + bool m_keepAspectRatio; + bool m_limitVisibleRange; + int m_maxZoomIn; + int m_maxZoomOut; +}; + +#endif diff --git a/src/client/uimapanchorlayout.cpp b/src/client/uimapanchorlayout.cpp new file mode 100644 index 0000000..25f991e --- /dev/null +++ b/src/client/uimapanchorlayout.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "declarations.h" +#include "uimapanchorlayout.h" +#include "uiminimap.h" +#include + +int UIPositionAnchor::getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget) +{ + UIMinimapPtr minimap = hookedWidget->static_self_cast(); + Rect hookedRect = minimap->getTileRect(m_hookedPosition); + int point = 0; + if(hookedRect.isValid()) { + switch(m_hookedEdge) { + case Fw::AnchorLeft: + point = hookedRect.left(); + break; + case Fw::AnchorRight: + point = hookedRect.right(); + break; + case Fw::AnchorTop: + point = hookedRect.top(); + break; + case Fw::AnchorBottom: + point = hookedRect.bottom(); + break; + case Fw::AnchorHorizontalCenter: + point = hookedRect.horizontalCenter(); + break; + case Fw::AnchorVerticalCenter: + point = hookedRect.verticalCenter(); + break; + default: + // must never happens + VALIDATE(false); + break; + } + } + return point; +} + +void UIMapAnchorLayout::addPositionAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge) +{ + if(!anchoredWidget) + return; + + VALIDATE(anchoredWidget != getParentWidget()); + + UIPositionAnchorPtr anchor(new UIPositionAnchor(anchoredEdge, hookedPosition, hookedEdge)); + UIAnchorGroupPtr& anchorGroup = m_anchorsGroups[anchoredWidget]; + if(!anchorGroup) + anchorGroup = UIAnchorGroupPtr(new UIAnchorGroup); + + anchorGroup->addAnchor(anchor); + + // layout must be updated because a new anchor got in + update(); +} + +void UIMapAnchorLayout::centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition) +{ + addPositionAnchor(anchoredWidget, Fw::AnchorHorizontalCenter, hookedPosition, Fw::AnchorHorizontalCenter); + addPositionAnchor(anchoredWidget, Fw::AnchorVerticalCenter, hookedPosition, Fw::AnchorVerticalCenter); +} + +void UIMapAnchorLayout::fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition) +{ + addPositionAnchor(anchoredWidget, Fw::AnchorLeft, hookedPosition, Fw::AnchorLeft); + addPositionAnchor(anchoredWidget, Fw::AnchorRight, hookedPosition, Fw::AnchorRight); + addPositionAnchor(anchoredWidget, Fw::AnchorTop, hookedPosition, Fw::AnchorTop); + addPositionAnchor(anchoredWidget, Fw::AnchorBottom, hookedPosition, Fw::AnchorBottom); +} diff --git a/src/client/uimapanchorlayout.h b/src/client/uimapanchorlayout.h new file mode 100644 index 0000000..177ce75 --- /dev/null +++ b/src/client/uimapanchorlayout.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIMAPANCHORLAYOUT_H +#define UIMAPANCHORLAYOUT_H + +#include "declarations.h" +#include + +class UIPositionAnchor : public UIAnchor +{ +public: + UIPositionAnchor(Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge) : + UIAnchor(anchoredEdge, std::string(), hookedEdge), m_hookedPosition(hookedPosition) { } + + UIWidgetPtr getHookedWidget(const UIWidgetPtr& widget, const UIWidgetPtr& parentWidget) { return parentWidget; } + int getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget); + +private: + Position m_hookedPosition; +}; + +class UIMapAnchorLayout : public UIAnchorLayout +{ +public: + UIMapAnchorLayout(UIWidgetPtr parentWidget) : UIAnchorLayout(parentWidget) { } + + void addPositionAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, + const Position& hookedPosition, Fw::AnchorEdge hookedEdge); + void centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition); + void fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition); + +protected: +}; + +#endif diff --git a/src/client/uiminimap.cpp b/src/client/uiminimap.cpp new file mode 100644 index 0000000..d63c967 --- /dev/null +++ b/src/client/uiminimap.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiminimap.h" +#include "minimap.h" +#include "game.h" +#include "uimapanchorlayout.h" +#include "luavaluecasts_client.h" + +#include +#include "uimapanchorlayout.h" + +UIMinimap::UIMinimap() +{ + m_zoom = 0; + m_scale = 1.0f; + m_minZoom = -5; + m_maxZoom = 5; + m_layout = UIMapAnchorLayoutPtr(new UIMapAnchorLayout(static_self_cast())); +} + +void UIMinimap::drawSelf(Fw::DrawPane drawPane) +{ + UIWidget::drawSelf(drawPane); + + if(drawPane != Fw::ForegroundPane) + return; + + g_minimap.draw(getPaddingRect(), getCameraPosition(), m_scale, m_color); +} + +bool UIMinimap::setZoom(int zoom) +{ + if(zoom == m_zoom) + return true; + + if(zoom < m_minZoom || zoom > m_maxZoom) + return false; + + int oldZoom = m_zoom; + m_zoom = zoom; + if(m_zoom < 0) + m_scale = 1.0f / (1 << std::abs(zoom)); + else if(m_zoom > 0) + m_scale = 1.0f * (1 << std::abs(zoom)); + else + m_scale = 1; + m_layout->update(); + + onZoomChange(zoom, oldZoom); + return true; +} + +void UIMinimap::setCameraPosition(const Position& pos) +{ + Position oldPos = m_cameraPosition; + m_cameraPosition = pos; + m_layout->update(); + + onCameraPositionChange(pos, oldPos); +} + +bool UIMinimap::floorUp() +{ + Position pos = getCameraPosition(); + if(!pos.up()) + return false; + setCameraPosition(pos); + return true; +} + +bool UIMinimap::floorDown() +{ + Position pos = getCameraPosition(); + if(!pos.down()) + return false; + setCameraPosition(pos); + return true; +} + +Point UIMinimap::getTilePoint(const Position& pos) +{ + return g_minimap.getTilePoint(pos, getPaddingRect(), getCameraPosition(), m_scale); +} + +Rect UIMinimap::getTileRect(const Position& pos) +{ + return g_minimap.getTileRect(pos, getPaddingRect(), getCameraPosition(), m_scale); +} + +Position UIMinimap::getTilePosition(const Point& mousePos) +{ + return g_minimap.getTilePosition(mousePos, getPaddingRect(), getCameraPosition(), m_scale); +} + +void UIMinimap::anchorPosition(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge) +{ + UIMapAnchorLayoutPtr layout = m_layout->static_self_cast(); + VALIDATE(layout); + layout->addPositionAnchor(anchoredWidget, anchoredEdge, hookedPosition, hookedEdge); +} + +void UIMinimap::fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition) +{ + UIMapAnchorLayoutPtr layout = m_layout->static_self_cast(); + VALIDATE(layout); + layout->fillPosition(anchoredWidget, hookedPosition); +} + +void UIMinimap::centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition) +{ + UIMapAnchorLayoutPtr layout = m_layout->static_self_cast(); + VALIDATE(layout); + layout->centerInPosition(anchoredWidget, hookedPosition); +} + +void UIMinimap::onZoomChange(int zoom, int oldZoom) +{ + callLuaField("onZoomChange", zoom, oldZoom); +} + +void UIMinimap::onCameraPositionChange(const Position& position, const Position& oldPosition) +{ + callLuaField("onCameraPositionChange", position, oldPosition); +} + +void UIMinimap::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "zoom") + setZoom(node->value()); + else if(node->tag() == "max-zoom") + setMaxZoom(node->value()); + else if(node->tag() == "min-zoom") + setMinZoom(node->value()); + } +} diff --git a/src/client/uiminimap.h b/src/client/uiminimap.h new file mode 100644 index 0000000..dfb1f8f --- /dev/null +++ b/src/client/uiminimap.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIMINIMAP_H +#define UIMINIMAP_H + +#include "declarations.h" +#include + +class UIMinimap : public UIWidget +{ +public: + UIMinimap(); + + void drawSelf(Fw::DrawPane drawPane); + + bool zoomIn() { return setZoom(m_zoom+1); } + bool zoomOut() { return setZoom(m_zoom-1); } + + bool setZoom(int zoom); + void setMinZoom(int minZoom) { m_minZoom = minZoom; } + void setMaxZoom(int maxZoom) { m_maxZoom = maxZoom; } + void setCameraPosition(const Position& pos); + bool floorUp(); + bool floorDown(); + + Point getTilePoint(const Position& pos); + Rect getTileRect(const Position& pos); + Position getTilePosition(const Point& mousePos); + + Position getCameraPosition() { return m_cameraPosition; } + int getMinZoom() { return m_minZoom; } + int getMaxZoom() { return m_maxZoom; } + int getZoom() { return m_zoom; } + float getScale() { return m_scale; } + + void anchorPosition(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge); + void fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition); + void centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition); + +protected: + virtual void onZoomChange(int zoom, int oldZoom); + virtual void onCameraPositionChange(const Position& position, const Position& oldPosition); + virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + +private: + void update(); + + Rect m_mapArea; + Position m_cameraPosition; + float m_scale; + int m_zoom; + int m_minZoom; + int m_maxZoom; +}; + +#endif diff --git a/src/client/uiprogressrect.cpp b/src/client/uiprogressrect.cpp new file mode 100644 index 0000000..855ac3b --- /dev/null +++ b/src/client/uiprogressrect.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiprogressrect.h" +#include +#include +#include + +UIProgressRect::UIProgressRect() +{ + m_percent = 0; +} + +void UIProgressRect::drawSelf(Fw::DrawPane drawPane) +{ + if(drawPane != Fw::ForegroundPane) + return; + + // todo: check +1 to right/bottom + // todo: add smooth + Rect drawRect = getPaddingRect(); + + // 0% - 12.5% (12.5) + // triangle from top center, to top right (var x) + if(m_percent < 12.5) { + Point var = Point(std::max(m_percent - 0.0, 0.0) * (drawRect.right() - drawRect.horizontalCenter()) / 12.5, 0); + g_drawQueue->addFilledTriangle(drawRect.center(), drawRect.topRight() + Point(1,0), drawRect.topCenter() + var, m_backgroundColor); + } + + // 12.5% - 37.5% (25) + // triangle from top right to bottom right (var y) + if(m_percent < 37.5) { + Point var = Point(0, std::max(m_percent - 12.5, 0.0) * (drawRect.bottom() - drawRect.top()) / 25.0); + g_drawQueue->addFilledTriangle(drawRect.center(), drawRect.bottomRight() + Point(1,1), drawRect.topRight() + var + Point(1,0), m_backgroundColor); + } + + // 37.5% - 62.5% (25) + // triangle from bottom right to bottom left (var x) + if(m_percent < 62.5) { + Point var = Point(std::max(m_percent - 37.5, 0.0) * (drawRect.right() - drawRect.left()) / 25.0, 0); + g_drawQueue->addFilledTriangle(drawRect.center(), drawRect.bottomLeft() + Point(0,1), drawRect.bottomRight() - var + Point(1,1), m_backgroundColor); + } + + // 62.5% - 87.5% (25) + // triangle from bottom left to top left + if(m_percent < 87.5) { + Point var = Point(0, std::max(m_percent - 62.5, 0.0) * (drawRect.bottom() - drawRect.top()) / 25.0); + g_drawQueue->addFilledTriangle(drawRect.center(), drawRect.topLeft(), drawRect.bottomLeft() - var + Point(0,1), m_backgroundColor); + } + + // 87.5% - 100% (12.5) + // triangle from top left to top center + if(m_percent < 100) { + Point var = Point(std::max(m_percent - 87.5, 0.0) * (drawRect.horizontalCenter() - drawRect.left()) / 12.5, 0); + g_drawQueue->addFilledTriangle(drawRect.center(), drawRect.topCenter(), drawRect.topLeft() + var, m_backgroundColor); + } + + drawImage(m_rect); + drawBorder(m_rect); + drawIcon(m_rect); + drawText(m_rect); +} + +void UIProgressRect::setPercent(float percent) +{ + m_percent = stdext::clamp((double)percent, 0.0, 100.0); +} + +void UIProgressRect::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "percent") + setPercent(node->value()); + } +} diff --git a/src/client/uiprogressrect.h b/src/client/uiprogressrect.h new file mode 100644 index 0000000..2382266 --- /dev/null +++ b/src/client/uiprogressrect.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIPROGRESSRECT_H +#define UIPROGRESSRECT_H + +#include "declarations.h" +#include +#include "item.h" + +class UIProgressRect : public UIWidget +{ +public: + UIProgressRect(); + void drawSelf(Fw::DrawPane drawPane); + + void setPercent(float percent); + float getPercent() { return m_percent; } + +protected: + void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + + float m_percent; +}; + +#endif diff --git a/src/client/uisprite.cpp b/src/client/uisprite.cpp new file mode 100644 index 0000000..896b91f --- /dev/null +++ b/src/client/uisprite.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uisprite.h" +#include +#include +#include +#include + +UISprite::UISprite() : + m_spriteId(0), + m_spriteColor(Color::white) +{ } + +void UISprite::drawSelf(Fw::DrawPane drawPane) +{ + if(drawPane != Fw::ForegroundPane) + return; + + // draw style components in order + if(m_backgroundColor.aF() > Fw::MIN_ALPHA) { + Rect backgroundDestRect = m_rect; + backgroundDestRect.expand(-m_borderWidth.top, -m_borderWidth.right, -m_borderWidth.bottom, -m_borderWidth.left); + drawBackground(m_rect); + } + + drawImage(m_rect); + + if(m_spriteVisible && m_sprite) { + g_drawQueue->addTexturedRect(getPaddingRect(), m_sprite, Rect(Point(0, 0), m_sprite->getSize()), m_spriteColor); + } + + drawBorder(m_rect); + drawIcon(m_rect); + drawText(m_rect); +} + +void UISprite::setSpriteId(int id) +{ + if(!g_sprites.isLoaded()) + return; + + m_spriteId = id; + if(id == 0) + m_sprite = nullptr; + else { + ImagePtr image = g_sprites.getSpriteImage(id); + if(image) + m_sprite = new Texture(image); + else + m_sprite = nullptr; + } +} + +void UISprite::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "sprite-id") + setSpriteId(node->value()); + else if(node->tag() == "sprite-visible") + setSpriteVisible(node->value()); + else if(node->tag() == "sprite-color") + setSpriteColor(node->value()); + } +} diff --git a/src/client/uisprite.h b/src/client/uisprite.h new file mode 100644 index 0000000..9585734 --- /dev/null +++ b/src/client/uisprite.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UISPRITE_H +#define UISPRITE_H + +#include "declarations.h" +#include + +class UISprite : public UIWidget +{ +public: + UISprite(); + void drawSelf(Fw::DrawPane drawPane); + + void setSpriteId(int id); + int getSpriteId() { return m_spriteId; } + void clearSprite() { setSpriteId(0); } + + void setSpriteColor(Color color) { m_spriteColor = color; } + + bool isSpriteVisible() { return m_spriteVisible; } + void setSpriteVisible(bool visible) { m_spriteVisible = visible; } + + bool hasSprite() { return m_sprite != nullptr; } + +protected: + void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + + TexturePtr m_sprite; + uint16 m_spriteId; + Color m_spriteColor; + + stdext::boolean m_spriteVisible; +}; + +#endif diff --git a/src/framework/const.h b/src/framework/const.h new file mode 100644 index 0000000..25be990 --- /dev/null +++ b/src/framework/const.h @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_CONST_H +#define FRAMEWORK_CONST_H + +#include "stdext/compiler.h" + +#define DEG_TO_RAD (acos(-1)/180.0) +#define RAD_TO_DEC (180.0/acos(-1)) + +#ifndef BUILD_COMMIT +#define BUILD_COMMIT "devel" +#endif + +#ifndef BUILD_REVISION +#define BUILD_REVISION "0" +#endif + +#ifndef BUILD_TYPE +#define BUILD_TYPE "unknown" +#endif + +#ifndef BUILD_ARCH +#if defined(__amd64) || defined(_M_X64) +#define BUILD_ARCH "x64" +#elif defined(__i386) || defined(_M_IX86) || defined(_X86_) +#define BUILD_ARCH "x86" +#else +#define BUILD_ARCH "unknown" +#endif +#endif + +namespace Fw +{ + static const float pi = 3.14159265; + static const float MIN_ALPHA = 0.003f; + enum Key : unsigned char { + KeyUnknown = 0, + KeyEscape = 1, + KeyTab = 2, + KeyBackspace = 3, + //KeyReturn = 4, + KeyEnter = 5, + KeyInsert = 6, + KeyDelete = 7, + KeyPause = 8, + KeyPrintScreen = 9, + KeyHome = 10, + KeyEnd = 11, + KeyPageUp = 12, + KeyPageDown = 13, + KeyUp = 14, + KeyDown = 15, + KeyLeft = 16, + KeyRight = 17, + KeyNumLock = 18, + KeyScrollLock = 19, + KeyCapsLock = 20, + KeyCtrl = 21, + KeyShift = 22, + KeyAlt = 23, + //KeyAltGr = 24, + KeyMeta = 25, + KeyMenu = 26, + KeySpace = 32, // ' ' + KeyExclamation = 33, // ! + KeyQuote = 34, // " + KeyNumberSign = 35, // # + KeyDollar = 36, // $ + KeyPercent = 37, // % + KeyAmpersand = 38, // & + KeyApostrophe = 39, // ' + KeyLeftParen = 40, // ( + KeyRightParen = 41, // ) + KeyAsterisk = 42, // * + KeyPlus = 43, // + + KeyComma = 44, // , + KeyMinus = 45, // - + KeyPeriod = 46, // . + KeySlash = 47, // / + Key0 = 48, // 0 + Key1 = 49, // 1 + Key2 = 50, // 2 + Key3 = 51, // 3 + Key4 = 52, // 4 + Key5 = 53, // 5 + Key6 = 54, // 6 + Key7 = 55, // 7 + Key8 = 56, // 8 + Key9 = 57, // 9 + KeyColon = 58, // : + KeySemicolon = 59, // ; + KeyLess = 60, // < + KeyEqual = 61, // = + KeyGreater = 62, // > + KeyQuestion = 63, // ? + KeyAtSign = 64, // @ + KeyA = 65, // a + KeyB = 66, // b + KeyC = 67, // c + KeyD = 68, // d + KeyE = 69, // e + KeyF = 70, // f + KeyG = 71, // g + KeyH = 72, // h + KeyI = 73, // i + KeyJ = 74, // j + KeyK = 75, // k + KeyL = 76, // l + KeyM = 77, // m + KeyN = 78, // n + KeyO = 79, // o + KeyP = 80, // p + KeyQ = 81, // q + KeyR = 82, // r + KeyS = 83, // s + KeyT = 84, // t + KeyU = 85, // u + KeyV = 86, // v + KeyW = 87, // w + KeyX = 88, // x + KeyY = 89, // y + KeyZ = 90, // z + KeyLeftBracket = 91, // [ + KeyBackslash = 92, // '\' + KeyRightBracket = 93, // ] + KeyCaret = 94, // ^ + KeyUnderscore = 95, // _ + KeyGrave = 96, // ` + KeyLeftCurly = 123, // { + KeyBar = 124, // | + KeyRightCurly = 125, // } + KeyTilde = 126, // ~ + KeyF1 = 128, + KeyF2 = 129, + KeyF3 = 130, + KeyF4 = 131, + KeyF5 = 132, + KeyF6 = 134, + KeyF7 = 135, + KeyF8 = 136, + KeyF9 = 137, + KeyF10 = 138, + KeyF11 = 139, + KeyF12 = 140, + KeyNumpad0 = 141, + KeyNumpad1 = 142, + KeyNumpad2 = 143, + KeyNumpad3 = 144, + KeyNumpad4 = 145, + KeyNumpad5 = 146, + KeyNumpad6 = 147, + KeyNumpad7 = 148, + KeyNumpad8 = 149, + KeyNumpad9 = 150 + }; + + enum LogLevel { + LogDebug = 0, + LogInfo, + LogWarning, + LogError, + LogFatal + }; + + enum AspectRatioMode { + IgnoreAspectRatio, + KeepAspectRatio, + KeepAspectRatioByExpanding + }; + + enum AlignmentFlag { + AlignNone = 0, + AlignLeft = 1, + AlignRight = 2, + AlignTop = 4, + AlignBottom = 8, + AlignHorizontalCenter = 16, + AlignVerticalCenter = 32, + AlignTopLeft = AlignTop | AlignLeft, // 5 + AlignTopRight = AlignTop | AlignRight, // 6 + AlignBottomLeft = AlignBottom | AlignLeft, // 9 + AlignBottomRight = AlignBottom | AlignRight, // 10 + AlignLeftCenter = AlignLeft | AlignVerticalCenter, // 33 + AlignRightCenter = AlignRight | AlignVerticalCenter, // 34 + AlignTopCenter = AlignTop | AlignHorizontalCenter, // 20 + AlignBottomCenter = AlignBottom | AlignHorizontalCenter, // 24 + AlignCenter = AlignVerticalCenter | AlignHorizontalCenter // 48 + }; + + enum AnchorEdge { + AnchorNone = 0, + AnchorTop, + AnchorBottom, + AnchorLeft, + AnchorRight, + AnchorVerticalCenter, + AnchorHorizontalCenter + }; + + enum FocusReason { + MouseFocusReason = 0, + KeyboardFocusReason, + ActiveFocusReason, + OtherFocusReason + }; + + enum AutoFocusPolicy { + AutoFocusNone = 0, + AutoFocusFirst, + AutoFocusLast + }; + + enum InputEventType { + NoInputEvent = 0, + KeyTextInputEvent, + KeyDownInputEvent, + KeyPressInputEvent, + KeyUpInputEvent, + MousePressInputEvent, + MouseReleaseInputEvent, + MouseMoveInputEvent, + MouseWheelInputEvent + }; + + enum MouseButton { + MouseNoButton = 0, + MouseLeftButton, + MouseRightButton, + MouseMidButton, + MouseTouch + }; + + enum MouseWheelDirection { + MouseNoWheel = 0, + MouseWheelUp, + MouseWheelDown + }; + + enum KeyboardModifier { + KeyboardNoModifier = 0, + KeyboardCtrlModifier = 1, + KeyboardAltModifier = 2, + KeyboardShiftModifier = 4 + }; + + enum WidgetState { + InvalidState = -1, + DefaultState = 0, + ActiveState = 1, + FocusState = 2, + HoverState = 4, + PressedState = 8, + DisabledState = 16, + CheckedState = 32, + OnState = 64, + FirstState = 128, + MiddleState = 256, + LastState = 512, + AlternateState = 1024, + DraggingState = 2048, + HiddenState = 4096, + MobileState = 8192, + LastWidgetState = 16384 + }; + + enum DrawPane { + ForegroundPane = 1, + MapBackgroundPane = 2, + MapForegroundPane = 3, + }; + +#ifdef FW_SQL + enum DatabaseEngine { + DatabaseNone = 0, + DatabaseMySQL + }; +#endif +} + +#endif diff --git a/src/framework/core/adaptiverenderer.cpp b/src/framework/core/adaptiverenderer.cpp new file mode 100644 index 0000000..99639e4 --- /dev/null +++ b/src/framework/core/adaptiverenderer.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include + +#include "adaptiverenderer.h" + +AdaptiveRenderer g_adaptiveRenderer; + +void AdaptiveRenderer::newFrame() { + auto now = stdext::millis(); + m_frames.push_back(now); + while (m_frames.front() + 5000 < now) { + m_frames.pop_front(); + } + + if (m_forcedSpeed >= 0 && m_forcedSpeed <= 4) { + m_speed = m_forcedSpeed; + return; + } + + if (m_update + 5000 > now) + return; + + m_update = stdext::millis(); + + if (m_speed < 1) + m_speed = 1; + + int maxFps = std::min(100, std::max(10, g_app.getMaxFps() < 10 ? 100 : g_app.getMaxFps())); + if (m_speed >= 2 && maxFps > 60) { // fix for forced vsync + maxFps = 60; + } + + if (m_frames.size() < maxFps * (4.0f - m_speed * 0.3f) && m_speed != RenderSpeeds - 1) { + m_speed += 1; + } + if (m_frames.size() > maxFps * (4.5f - m_speed * 0.1f) && m_speed > 1) { + m_speed -= 1; + } +} + +void AdaptiveRenderer::refresh() { + m_update = stdext::millis(); +} + +int AdaptiveRenderer::effetsLimit() { + static int limits[RenderSpeeds] = { 20, 10, 7, 4, 2 }; + return limits[m_speed]; +} + +int AdaptiveRenderer::creaturesLimit() { + static int limits[RenderSpeeds] = { 20, 10, 7, 5, 3 }; + return limits[m_speed]; +} + +int AdaptiveRenderer::itemsLimit() { + static int limits[RenderSpeeds] = { 20, 10, 7, 5, 3 }; + return limits[m_speed]; +} + +int AdaptiveRenderer::mapRenderInterval() { + static int limits[RenderSpeeds] = { 0, 10, 20, 50, 100 }; + return limits[m_speed]; +} + +int AdaptiveRenderer::textsLimit() { + static int limits[RenderSpeeds] = { 1000, 50, 30, 15, 5 }; + return limits[m_speed]; +} + +int AdaptiveRenderer::creaturesRenderInterval() { + // not working yet + static int limits[RenderSpeeds] = { 0, 0, 10, 15, 20 }; + return limits[m_speed]; +} + +bool AdaptiveRenderer::allowFading() { + return m_speed <= 2; +} + +int AdaptiveRenderer::foregroundUpdateInterval() { + static int limits[RenderSpeeds] = { 0, 20, 40, 50, 60 }; + return limits[m_speed]; +} + +std::string AdaptiveRenderer::getDebugInfo() { + std::stringstream ss; + ss << "Frames: " << m_frames.size() << "|" << m_speed << "|" << m_forcedSpeed; + return ss.str(); +} \ No newline at end of file diff --git a/src/framework/core/adaptiverenderer.h b/src/framework/core/adaptiverenderer.h new file mode 100644 index 0000000..0853834 --- /dev/null +++ b/src/framework/core/adaptiverenderer.h @@ -0,0 +1,50 @@ +#ifndef ADAPTIVERENDERER_H +#define ADAPTIVERENDERER_H + +#include + +constexpr int RenderSpeeds = 5; + +class AdaptiveRenderer { +public: + void newFrame(); + + void refresh(); + + int effetsLimit(); + + int creaturesLimit(); + + int itemsLimit(); + + int textsLimit(); + + int mapRenderInterval(); + + int creaturesRenderInterval(); + + + bool allowFading(); + + int getLevel() { + return m_speed; + } + + int foregroundUpdateInterval(); + + std::string getDebugInfo(); + + void setForcedLevel(int value) { + m_forcedSpeed = value; + } + +private: + int m_forcedSpeed = -1; + int m_speed = 0; + time_t m_update = 0; + std::list m_frames; +}; + +extern AdaptiveRenderer g_adaptiveRenderer; + +#endif \ No newline at end of file diff --git a/src/framework/core/application.cpp b/src/framework/core/application.cpp new file mode 100644 index 0000000..5e2417c --- /dev/null +++ b/src/framework/core/application.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "application.h" +#include +#include +#include +#include +#include +#include +#include "asyncdispatcher.h" +#include +#include +#include +#include +#include + +#include + +#ifdef FW_NET +#include +#ifdef FW_PROXY +#include +#endif +#endif + +void exitSignalHandler(int sig) +{ + static bool signaled = false; + switch(sig) { + case SIGTERM: + case SIGINT: + if(!signaled && !g_app.isStopping() && !g_app.isTerminated()) { + signaled = true; + g_dispatcher.addEvent(std::bind(&Application::close, &g_app)); + } + break; + } +} + +Application::Application() +{ + m_appName = "application"; + m_appCompactName = "app"; + m_appVersion = "none"; + m_charset = "cp1252"; + m_stopping = false; +#ifdef ANDROID + m_mobile = true; +#endif +} + +void Application::init(std::vector& args) +{ + // capture exit signals + signal(SIGTERM, exitSignalHandler); + signal(SIGINT, exitSignalHandler); + + // setup locale + std::locale::global(std::locale()); + + // process args encoding + g_platform.processArgs(args); + + g_asyncDispatcher.init(); + + std::string startupOptions; + for(uint i=1;i 0) + g_logger.info(stdext::format("Startup options: %s", startupOptions)); + + m_startupOptions = startupOptions; + + // mobile testing + if (startupOptions.find("-mobile") != std::string::npos) + m_mobile = true; + + // initialize configs + g_configs.init(); + + // initialize lua + g_lua.init(); + registerLuaFunctions(); + +#ifdef FW_PROXY + // initalize proxy + g_proxy.init(); +#endif +} + +void Application::deinit() +{ + g_lua.callGlobalField("g_app", "onTerminate"); + + // run modules unload events + g_modules.unloadModules(); + g_modules.clear(); + + // release remaining lua object references + g_lua.collectGarbage(); + + // poll remaining events + poll(); + + // disable dispatcher events + g_dispatcher.shutdown(); +} + +void Application::terminate() +{ +#ifdef FW_NET + // terminate network + Connection::terminate(); +#endif + + // release configs + g_configs.terminate(); + + // release resources + g_resources.terminate(); + + // terminate script environment + g_lua.terminate(); + +#ifdef FW_PROXY + // terminate proxy + g_proxy.terminate(); +#endif + + m_terminated = true; + + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); +} + +void Application::poll() +{ +#ifdef FW_NET + Connection::poll(); +#endif + + g_dispatcher.poll(); + + // poll connection again to flush pending write +#ifdef FW_NET + Connection::poll(); +#endif +} + +void Application::exit() +{ + g_lua.callGlobalField("g_app", "onExit"); + m_stopping = true; +} + +void Application::quick_exit() +{ +#ifdef _MSC_VER + ::quick_exit(0); +#else + ::exit(0); +#endif +} + +void Application::close() +{ + if(!g_lua.callGlobalField("g_app", "onClose")) + exit(); +} + +void Application::restart() +{ +#ifndef ANDROID + boost::process::child c(g_resources.getBinaryName()); + std::error_code ec2; + if (c.wait_for(std::chrono::seconds(1), ec2)) { + g_logger.fatal("Updater restart error. Please restart application"); + } + c.detach(); + quick_exit(); +#else + exit(); +#endif +} + +std::string Application::getOs() +{ +#if defined(ANDROID) + return "android"; +#elif defined(WIN32) + return "windows"; +#elif defined(__APPLE__) + return "mac"; +#elif __linux + return "linux"; +#else + return "unknown"; +#endif +} + diff --git a/src/framework/core/application.h b/src/framework/core/application.h new file mode 100644 index 0000000..2b99151 --- /dev/null +++ b/src/framework/core/application.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include + +//@bindsingleton g_app +class Application +{ +public: + Application(); + virtual ~Application() {} + + virtual void init(std::vector& args); + virtual void deinit(); + virtual void terminate(); + virtual void run() = 0; + virtual void poll(); + virtual void exit(); + virtual void quick_exit(); + virtual void close(); + void restart(); + + void setName(const std::string& name) { m_appName = name; } + void setCompactName(const std::string& compactName) { m_appCompactName = compactName; } + void setVersion(const std::string& version) { m_appVersion = version; } + + bool isRunning() { return m_running; } + bool isStopping() { return m_stopping; } + bool isTerminated() { return m_terminated; } + const std::string& getName() { return m_appName; } + const std::string& getCompactName() { return m_appCompactName; } + const std::string& getVersion() { return m_appVersion; } + + std::string getCharset() { return m_charset; } + std::string getBuildCompiler() { return BUILD_COMPILER; } + std::string getBuildDate() { return std::string(__DATE__); } + std::string getBuildRevision() { return BUILD_REVISION; } + std::string getBuildCommit() { return BUILD_COMMIT; } + std::string getBuildType() { return BUILD_TYPE; } + std::string getBuildArch() { return BUILD_ARCH; } + std::string getAuthor() { return "otclient.ovh"; } + std::string getOs(); + std::string getStartupOptions() { return m_startupOptions; } + + bool isMobile() + { + return m_mobile; + } + +protected: + void registerLuaFunctions(); + + std::string m_charset; + std::string m_appName; + std::string m_appCompactName; + std::string m_appVersion; + std::string m_startupOptions; + stdext::boolean m_running; + stdext::boolean m_stopping; + stdext::boolean m_terminated; + stdext::boolean m_mobile; +}; + +#ifdef FW_GRAPHICS +#include "graphicalapplication.h" +#else +#include "consoleapplication.h" +#endif + +#endif diff --git a/src/framework/core/asyncdispatcher.cpp b/src/framework/core/asyncdispatcher.cpp new file mode 100644 index 0000000..d1222b6 --- /dev/null +++ b/src/framework/core/asyncdispatcher.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "asyncdispatcher.h" + +AsyncDispatcher g_asyncDispatcher; + +void AsyncDispatcher::init() +{ + spawn_thread(); +} + +void AsyncDispatcher::terminate() +{ + stop(); + m_tasks.clear(); +} + +void AsyncDispatcher::spawn_thread() +{ + m_running = true; + m_threads.push_back(std::thread(std::bind(&AsyncDispatcher::exec_loop, this))); +} + +void AsyncDispatcher::stop() +{ + m_mutex.lock(); + m_running = false; + m_condition.notify_all(); + m_mutex.unlock(); + for(std::thread& thread : m_threads) + thread.join(); + m_threads.clear(); +}; + +void AsyncDispatcher::exec_loop() { + std::unique_lock lock(m_mutex); + while(true) { + while(m_tasks.size() == 0 && m_running) + m_condition.wait(lock); + + if(!m_running) + return; + + std::function task = m_tasks.front(); + m_tasks.pop_front(); + + lock.unlock(); + task(); + lock.lock(); + } +} diff --git a/src/framework/core/asyncdispatcher.h b/src/framework/core/asyncdispatcher.h new file mode 100644 index 0000000..182ef36 --- /dev/null +++ b/src/framework/core/asyncdispatcher.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ASYNCDISPATCHER_H +#define ASYNCDISPATCHER_H + +#include "declarations.h" +#include + +class AsyncDispatcher { +public: + void init(); + void terminate(); + + void spawn_thread(); + void stop(); + + template + std::shared_future::type> schedule(const F& task) { + std::lock_guard lock(m_mutex); + auto prom = std::make_shared::type>>(); + m_tasks.push_back([=]() { prom->set_value(task()); }); + m_condition.notify_all(); + return std::shared_future::type>(prom->get_future()); + } + + void dispatch(std::function f) { + std::lock_guard lock(m_mutex); + m_tasks.push_back(f); + m_condition.notify_all(); + } + +protected: + void exec_loop(); + +private: + std::list> m_tasks; + std::list m_threads; + std::mutex m_mutex; + std::condition_variable m_condition; + stdext::boolean m_running; +}; + +extern AsyncDispatcher g_asyncDispatcher; + +#endif diff --git a/src/framework/core/binarytree.cpp b/src/framework/core/binarytree.cpp new file mode 100644 index 0000000..d0148cb --- /dev/null +++ b/src/framework/core/binarytree.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "binarytree.h" +#include "filestream.h" + +BinaryTree::BinaryTree(const FileStreamPtr& fin) + : m_fin(fin), m_pos(0xFFFFFFFF) { + m_startPos = fin->tell(); +} + +BinaryTree::~BinaryTree() {} + +void BinaryTree::skipNodes() { + while (true) { + uint8 byte = m_fin->getU8(); + switch (byte) { + case BINARYTREE_NODE_START: { + skipNodes(); + break; + } + case BINARYTREE_NODE_END: + return; + case BINARYTREE_ESCAPE_CHAR: + m_fin->getU8(); + break; + default: + break; + } + } +} + +void BinaryTree::unserialize() { + if (m_pos != 0xFFFFFFFF) return; + m_pos = 0; + + m_fin->seek(m_startPos); + while (true) { + uint8 byte = m_fin->getU8(); + switch (byte) { + case BINARYTREE_NODE_START: { + skipNodes(); + break; + } + case BINARYTREE_NODE_END: + return; + case BINARYTREE_ESCAPE_CHAR: + m_buffer.add(m_fin->getU8()); + break; + default: + m_buffer.add(byte); + break; + } + } +} + +BinaryTreeVec BinaryTree::getChildren() { + BinaryTreeVec children; + m_fin->seek(m_startPos); + while (true) { + uint8 byte = m_fin->getU8(); + switch (byte) { + case BINARYTREE_NODE_START: { + BinaryTreePtr node(new BinaryTree(m_fin)); + children.push_back(node); + node->skipNodes(); + break; + } + case BINARYTREE_NODE_END: + return children; + case BINARYTREE_ESCAPE_CHAR: + m_fin->getU8(); + break; + default: + break; + } + } +} + +void BinaryTree::seek(uint pos) { + unserialize(); + if (pos > m_buffer.size()) stdext::throw_exception("BinaryTree: seek failed"); + m_pos = pos; +} + +void BinaryTree::skip(uint len) { + unserialize(); + seek(tell() + len); +} + +uint8 BinaryTree::getU8() { + unserialize(); + if (m_pos + 1 > m_buffer.size()) + stdext::throw_exception("BinaryTree: getU8 failed"); + uint8 v = m_buffer[m_pos]; + m_pos += 1; + return v; +} + +uint16 BinaryTree::getU16() { + unserialize(); + if (m_pos + 2 > m_buffer.size()) + stdext::throw_exception("BinaryTree: getU16 failed"); + uint16 v = stdext::readULE16(&m_buffer[m_pos]); + m_pos += 2; + return v; +} + +uint32 BinaryTree::getU32() { + unserialize(); + if (m_pos + 4 > m_buffer.size()) + stdext::throw_exception("BinaryTree: getU32 failed"); + uint32 v = stdext::readULE32(&m_buffer[m_pos]); + m_pos += 4; + return v; +} + +uint64 BinaryTree::getU64() { + unserialize(); + if (m_pos + 8 > m_buffer.size()) + stdext::throw_exception("BinaryTree: getU64 failed"); + uint64 v = stdext::readULE64(&m_buffer[m_pos]); + m_pos += 8; + return v; +} + +std::string BinaryTree::getString(uint16 len) { + unserialize(); + if (len == 0) len = getU16(); + + if (m_pos + len > m_buffer.size()) + stdext::throw_exception( + "BinaryTree: getString failed: string length exceeded buffer size."); + + std::string ret((char*)&m_buffer[m_pos], len); + m_pos += len; + return ret; +} + +Point BinaryTree::getPoint() { + Point ret; + ret.x = getU8(); + ret.y = getU8(); + return ret; +} + +OutputBinaryTree::OutputBinaryTree(const FileStreamPtr& fin) : m_fin(fin) { + startNode(0); +} + +void OutputBinaryTree::addU8(uint8 v) { write(&v, 1); } + +void OutputBinaryTree::addU16(uint16 v) { + uint8 data[2]; + stdext::writeULE16(data, v); + write(data, 2); +} + +void OutputBinaryTree::addU32(uint32 v) { + uint8 data[4]; + stdext::writeULE32(data, v); + write(data, 4); +} + +void OutputBinaryTree::addString(const std::string& v) { + if (v.size() > 0xFFFF) stdext::throw_exception("too long string"); + + addU16(v.length()); + write((const uint8*)v.c_str(), v.length()); +} + +void OutputBinaryTree::addPos(uint16 x, uint16 y, uint8 z) { + addU16(x); + addU16(y); + addU8(z); +} + +void OutputBinaryTree::addPoint(const Point& point) { + addU8(point.x); + addU8(point.y); +} + +void OutputBinaryTree::startNode(uint8 node) { + m_fin->addU8(BINARYTREE_NODE_START); + write(&node, 1); +} + +void OutputBinaryTree::endNode() { m_fin->addU8(BINARYTREE_NODE_END); } + +void OutputBinaryTree::write(const uint8* data, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (data[i] == BINARYTREE_NODE_START || data[i] == BINARYTREE_NODE_END || + data[i] == BINARYTREE_ESCAPE_CHAR) + m_fin->addU8(BINARYTREE_ESCAPE_CHAR); + m_fin->addU8(data[i]); + } +} \ No newline at end of file diff --git a/src/framework/core/binarytree.h b/src/framework/core/binarytree.h new file mode 100644 index 0000000..2137fe8 --- /dev/null +++ b/src/framework/core/binarytree.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BINARYTREE_H +#define BINARYTREE_H + +#include "declarations.h" +#include + +enum { + BINARYTREE_ESCAPE_CHAR = 0xFD, + BINARYTREE_NODE_START = 0xFE, + BINARYTREE_NODE_END = 0xFF +}; + +class BinaryTree : public stdext::shared_object +{ +public: + BinaryTree(const FileStreamPtr& fin); + ~BinaryTree(); + + void seek(uint pos); + void skip(uint len); + uint tell() { return m_pos; } + uint size() { unserialize(); return m_buffer.size(); } + + uint8 getU8(); + uint16 getU16(); + uint32 getU32(); + uint64 getU64(); + std::string getString(uint16 len = 0); + Point getPoint(); + + BinaryTreeVec getChildren(); + bool canRead() { unserialize(); return m_pos < m_buffer.size(); } + +private: + void unserialize(); + void skipNodes(); + + FileStreamPtr m_fin; + DataBuffer m_buffer; + uint m_pos; + uint m_startPos; +}; + +class OutputBinaryTree : public stdext::shared_object +{ +public: + OutputBinaryTree(const FileStreamPtr& finish); + + void addU8(uint8 v); + void addU16(uint16 v); + void addU32(uint32 v); + void addString(const std::string& v); + void addPos(uint16 x, uint16 y, uint8 z); + void addPoint(const Point& point); + + void startNode(uint8 node); + void endNode(); + +private: + FileStreamPtr m_fin; + +protected: + void write(const uint8* data, size_t size); +}; + +#endif + diff --git a/src/framework/core/clock.cpp b/src/framework/core/clock.cpp new file mode 100644 index 0000000..7433824 --- /dev/null +++ b/src/framework/core/clock.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "clock.h" + +Clock g_clock; + +Clock::Clock() +{ + m_currentMicros = 0; + m_currentMillis = 0; + m_currentSeconds = 0; +} + +void Clock::update() +{ + m_currentMicros = stdext::micros(); + m_currentMillis = m_currentMicros / 1000; + m_currentSeconds = m_currentMicros / 1000000.0f; +} + +ticks_t Clock::realMicros() +{ + return stdext::micros(); +} + +ticks_t Clock::realMillis() +{ + return stdext::millis(); +} diff --git a/src/framework/core/clock.h b/src/framework/core/clock.h new file mode 100644 index 0000000..7b316e7 --- /dev/null +++ b/src/framework/core/clock.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CLOCK_H +#define CLOCK_H + +#include "declarations.h" + +// @bindsingleton g_clock +class Clock +{ +public: + Clock(); + + void update(); + + ticks_t micros() { return m_currentMicros; } + ticks_t millis() { return m_currentMillis; } + float seconds() { return m_currentSeconds; } + ticks_t realMicros(); + ticks_t realMillis(); + +private: + ticks_t m_currentMicros; + ticks_t m_currentMillis; + float m_currentSeconds; +}; + +extern Clock g_clock; + +#endif + diff --git a/src/framework/core/config.cpp b/src/framework/core/config.cpp new file mode 100644 index 0000000..dfab8cb --- /dev/null +++ b/src/framework/core/config.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#include "resourcemanager.h" +#include "configmanager.h" + +#include + +Config::Config() +{ + m_confsDoc = OTMLDocument::create(); + m_fileName = ""; +} + +bool Config::load(const std::string& file) +{ + m_fileName = file; + + if(!g_resources.fileExists(file)) + return false; + + try { + OTMLDocumentPtr confsDoc = OTMLDocument::parse(file); + if(confsDoc) + m_confsDoc = confsDoc; + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Unable to parse configuration file '%s': ", e.what())); + return false; + } +} + +bool Config::unload() +{ + if(isLoaded()) { + m_confsDoc = nullptr; + m_fileName = ""; + return true; + } + return false; +} + +bool Config::save() +{ + if(m_fileName.length() == 0) + return false; + return m_confsDoc->save(m_fileName); +} + +void Config::clear() +{ + m_confsDoc->clear(); +} + +void Config::setValue(const std::string& key, const std::string& value) +{ + if (key.empty()) { + return; + } + if(value.empty()) { + remove(key); + return; + } + + OTMLNodePtr child = OTMLNode::create(key, value); + m_confsDoc->addChild(child); +} + +void Config::setList(const std::string& key, const std::vector& list) +{ + remove(key); + + if(list.size() == 0) + return; + + OTMLNodePtr child = OTMLNode::create(key, true); + for(const std::string& value : list) + child->writeIn(value); + m_confsDoc->addChild(child); +} + +bool Config::exists(const std::string& key) +{ + return m_confsDoc->hasChildAt(key); +} + +std::string Config::getValue(const std::string& key) +{ + OTMLNodePtr child = m_confsDoc->get(key); + if(child) + return child->value(); + else + return ""; +} + +std::vector Config::getList(const std::string& key) +{ + std::vector list; + OTMLNodePtr child = m_confsDoc->get(key); + if(child) { + for(const OTMLNodePtr& subchild : child->children()) + list.push_back(subchild->value()); + } + return list; +} + +void Config::remove(const std::string& key) +{ + OTMLNodePtr child = m_confsDoc->get(key); + if(child) + m_confsDoc->removeChild(child); +} + +void Config::setNode(const std::string& key, const OTMLNodePtr& node) +{ + remove(key); + mergeNode(key, node); +} + +void Config::mergeNode(const std::string& key, const OTMLNodePtr& node) +{ + OTMLNodePtr clone = node->clone(); + node->setTag(key); + node->setUnique(true); + m_confsDoc->addChild(node); +} + +OTMLNodePtr Config::getNode(const std::string& key) +{ + return m_confsDoc->get(key); +} + +bool Config::isLoaded() +{ + return !m_fileName.empty() && m_confsDoc; +} + +std::string Config::getFileName() +{ + return m_fileName; +} diff --git a/src/framework/core/config.h b/src/framework/core/config.h new file mode 100644 index 0000000..b06f1ce --- /dev/null +++ b/src/framework/core/config.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include "declarations.h" + +#include +#include + +// @bindclass +class Config : public LuaObject +{ +public: + Config(); + + bool load(const std::string& file); + bool unload(); + bool save(); + void clear(); + + void setValue(const std::string& key, const std::string& value); + void setList(const std::string& key, const std::vector& list); + std::string getValue(const std::string& key); + std::vector getList(const std::string& key); + + void setNode(const std::string& key, const OTMLNodePtr& node); + void mergeNode(const std::string& key, const OTMLNodePtr& node); + OTMLNodePtr getNode(const std::string& key); + + bool exists(const std::string& key); + void remove(const std::string& key); + + std::string getFileName(); + bool isLoaded(); + + // @dontbind + ConfigPtr asConfig() { return static_self_cast(); } + +private: + std::string m_fileName; + OTMLDocumentPtr m_confsDoc; +}; + +#endif diff --git a/src/framework/core/configmanager.cpp b/src/framework/core/configmanager.cpp new file mode 100644 index 0000000..4c2c31a --- /dev/null +++ b/src/framework/core/configmanager.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "configmanager.h" + +ConfigManager g_configs; + +void ConfigManager::init() +{ + m_settings = ConfigPtr(new Config()); +} + +void ConfigManager::terminate() +{ + if(m_settings) { + // ensure settings are saved + m_settings->save(); + + m_settings->unload(); + m_settings = nullptr; + } + + for(ConfigPtr config : m_configs) { + config->unload(); + config = nullptr; + } + + m_configs.clear(); +} + +ConfigPtr ConfigManager::getSettings() +{ + return m_settings; +} + +ConfigPtr ConfigManager::get(const std::string& file) +{ + for(const ConfigPtr config : m_configs) { + if(config->getFileName() == file) { + return config; + } + } + return nullptr; +} + +ConfigPtr ConfigManager::loadSettings(const std::string file) +{ + if(file.empty()) { + g_logger.error("Must provide a configuration file to load."); + } + else { + if(m_settings->load(file)) { + return m_settings; + } + } + return nullptr; +} + +ConfigPtr ConfigManager::create(const std::string& file) +{ + ConfigPtr config = load(file); + if(!config) { + config = ConfigPtr(new Config()); + + config->load(file); + config->save(); + + m_configs.push_back(config); + } + return config; +} + +ConfigPtr ConfigManager::load(const std::string& file) +{ + if(file.empty()) { + g_logger.error("Must provide a configuration file to load."); + return nullptr; + } + else { + ConfigPtr config = get(file); + if(!config) { + config = ConfigPtr(new Config()); + + if(config->load(file)) { + m_configs.push_back(config); + } + else { + // cannot load config + config = nullptr; + } + } + return config; + } +} + +bool ConfigManager::unload(const std::string& file) +{ + ConfigPtr config = get(file); + if(config) { + config->unload(); + remove(config); + config = nullptr; + return true; + } + return false; +} + +void ConfigManager::remove(const ConfigPtr config) +{ + m_configs.remove(config); +} diff --git a/src/framework/core/configmanager.h b/src/framework/core/configmanager.h new file mode 100644 index 0000000..371ed8d --- /dev/null +++ b/src/framework/core/configmanager.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CONFIGMANAGER_H +#define CONFIGMANAGER_H + +#include "config.h" + +// @bindsingleton g_configs +class ConfigManager +{ +public: + void init(); + void terminate(); + + ConfigPtr getSettings(); + ConfigPtr get(const std::string& file); + + ConfigPtr create(const std::string& file); + ConfigPtr loadSettings(const std::string file); + ConfigPtr load(const std::string& file); + + bool unload(const std::string& file); + void remove(const ConfigPtr config); + +protected: + ConfigPtr m_settings; + +private: + std::list m_configs; +}; + +extern ConfigManager g_configs; + +#endif diff --git a/src/framework/core/consoleapplication.cpp b/src/framework/core/consoleapplication.cpp new file mode 100644 index 0000000..a40b0b5 --- /dev/null +++ b/src/framework/core/consoleapplication.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2013 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "consoleapplication.h" +#include +#include + +#ifdef FW_NET +#include +#endif + +ConsoleApplication g_app; + +void ConsoleApplication::run() +{ + m_running = true; + + // run the first poll + poll(); + + // first clock update + g_clock.update(); + + g_lua.callGlobalField("g_app", "onRun"); + + while(!m_stopping) { + poll(); + stdext::millisleep(1); + g_clock.update(); + m_frameCounter.update(); + } + + m_stopping = false; + m_running = false; +} diff --git a/src/framework/core/consoleapplication.h b/src/framework/core/consoleapplication.h new file mode 100644 index 0000000..56d58fe --- /dev/null +++ b/src/framework/core/consoleapplication.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2013 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef CONSOLEAPPLICATION_H +#define CONSOLEAPPLICATION_H + +#include "application.h" + +class ConsoleApplication : public Application +{ +public: + void run(); + +protected: + +}; + +extern ConsoleApplication g_app; + +#endif diff --git a/src/framework/core/declarations.h b/src/framework/core/declarations.h new file mode 100644 index 0000000..e2e4c50 --- /dev/null +++ b/src/framework/core/declarations.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_CORE_DECLARATIONS_H +#define FRAMEWORK_CORE_DECLARATIONS_H + +#include + +class ConfigManager; +class ModuleManager; +class ResourceManager; +class Module; +class Config; +class Event; +class ScheduledEvent; +class FileStream; +class BinaryTree; +class OutputBinaryTree; + +typedef stdext::shared_object_ptr ModulePtr; +typedef stdext::shared_object_ptr ConfigPtr; +typedef stdext::shared_object_ptr EventPtr; +typedef stdext::shared_object_ptr ScheduledEventPtr; +typedef stdext::shared_object_ptr FileStreamPtr; +typedef stdext::shared_object_ptr BinaryTreePtr; +typedef stdext::shared_object_ptr OutputBinaryTreePtr; + +typedef std::vector BinaryTreeVec; + +#endif diff --git a/src/framework/core/event.cpp b/src/framework/core/event.cpp new file mode 100644 index 0000000..137cf3d --- /dev/null +++ b/src/framework/core/event.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "event.h" + +Event::Event(const std::string& function, const std::function& callback, bool botSafe) : + m_function(function), + m_callback(callback), + m_canceled(false), + m_executed(false), + m_botSafe(botSafe) +{ +} + +Event::~Event() +{ + // assure that we lost callback refs + //VALIDATE(m_callback == nullptr); +} + +void Event::execute() +{ + if(!m_canceled && !m_executed && m_callback) { + m_callback(); + m_executed = true; + } + + // reset callback to free object refs + m_callback = nullptr; +} + +void Event::cancel() +{ + m_canceled = true; + m_callback = nullptr; +} diff --git a/src/framework/core/event.h b/src/framework/core/event.h new file mode 100644 index 0000000..db9b21e --- /dev/null +++ b/src/framework/core/event.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef EVENT_H +#define EVENT_H + +#include + +// @bindclass +class Event : public LuaObject +{ +public: + Event(const std::string& function, const std::function& callback, bool botSafe = false); + virtual ~Event(); + + virtual void execute(); + void cancel(); + + bool isCanceled() { return m_canceled; } + bool isExecuted() { return m_executed; } + bool isBotSafe() { return m_botSafe; } + + const std::string& getFunction() { return m_function; } + +protected: + std::string m_function; + std::function m_callback; + bool m_canceled; + bool m_executed; + bool m_botSafe; +}; + +#endif diff --git a/src/framework/core/eventdispatcher.cpp b/src/framework/core/eventdispatcher.cpp new file mode 100644 index 0000000..d066723 --- /dev/null +++ b/src/framework/core/eventdispatcher.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "eventdispatcher.h" + +#include +#include +#include +#include "timer.h" + +EventDispatcher g_dispatcher; +EventDispatcher g_graphicsDispatcher; +std::thread::id g_mainThreadId = std::this_thread::get_id(); +std::thread::id g_dispatcherThreadId = std::this_thread::get_id(); + +void EventDispatcher::shutdown() +{ + while(!m_eventList.empty()) + poll(); + + while(!m_scheduledEventList.empty()) { + ScheduledEventPtr scheduledEvent = m_scheduledEventList.top(); + scheduledEvent->cancel(); + m_scheduledEventList.pop(); + } + m_disabled = true; +} + +void EventDispatcher::poll() +{ + AutoStat s(this == &g_dispatcher ? STATS_MAIN : STATS_RENDER, "PollDispatcher"); + std::unique_lock lock(m_mutex); + + int loops = 0; + for(int count = 0, max = m_scheduledEventList.size(); count < max && !m_scheduledEventList.empty(); ++count) { + ScheduledEventPtr scheduledEvent = m_scheduledEventList.top(); + if(scheduledEvent->remainingTicks() > 0) + break; + m_scheduledEventList.pop(); + { + AutoStat s2(STATS_DISPATCHER, scheduledEvent->getFunction()); + m_botSafe = scheduledEvent->isBotSafe(); + m_mutex.unlock(); + scheduledEvent->execute(); + m_mutex.lock(); + } + + if(scheduledEvent->nextCycle()) + m_scheduledEventList.push(scheduledEvent); + } + + // execute events list until all events are out, this is needed because some events can schedule new events that would + // change the UIWidgets layout, in this case we must execute these new events before we continue rendering, + m_pollEventsSize = m_eventList.size(); + loops = 0; + while(m_pollEventsSize > 0) { + if(loops > 50) { + static Timer reportTimer; + if(reportTimer.running() && reportTimer.ticksElapsed() > 100) { + std::stringstream ss; + ss << "ATTENTION the event list is not getting empty, this could be caused by some bad code.\nLog:\n"; + for (auto& event : m_eventList) { + ss << event->getFunction() << "\n"; + if (ss.str().size() > 1024) break; + } + g_logger.error(ss.str()); + reportTimer.restart(); + } + break; + } + + for(int i=0;igetFunction()); + m_botSafe = event->isBotSafe(); + m_mutex.unlock(); + event->execute(); + m_mutex.lock(); + } + } + m_pollEventsSize = m_eventList.size(); + + loops++; + } + + m_botSafe = false; +} + +ScheduledEventPtr EventDispatcher::scheduleEventEx(const std::string& function, const std::function& callback, int delay) +{ + if(m_disabled) + return ScheduledEventPtr(new ScheduledEvent("", nullptr, delay, 1)); + + std::lock_guard lock(m_mutex); + + VALIDATE(delay >= 0); + ScheduledEventPtr scheduledEvent(new ScheduledEvent(function, callback, delay, 1, g_app.isOnInputEvent())); + m_scheduledEventList.push(scheduledEvent); + return scheduledEvent; +} + +ScheduledEventPtr EventDispatcher::cycleEventEx(const std::string& function, const std::function& callback, int delay) +{ + if(m_disabled) + return ScheduledEventPtr(new ScheduledEvent("", nullptr, delay, 0)); + + std::lock_guard lock(m_mutex); + + VALIDATE(delay > 0); + ScheduledEventPtr scheduledEvent(new ScheduledEvent(function, callback, delay, 0, g_app.isOnInputEvent())); + m_scheduledEventList.push(scheduledEvent); + return scheduledEvent; +} + +EventPtr EventDispatcher::addEventEx(const std::string& function, const std::function& callback, bool pushFront) +{ + if(m_disabled) + return EventPtr(new Event("", nullptr)); + + EventPtr event(new Event(function, callback, g_app.isOnInputEvent())); + + std::lock_guard lock(m_mutex); + + // front pushing is a way to execute an event before others + if(pushFront) { + m_eventList.push_front(event); + // the poll event list only grows when pushing into front + m_pollEventsSize++; + } else + m_eventList.push_back(event); + return event; +} + diff --git a/src/framework/core/eventdispatcher.h b/src/framework/core/eventdispatcher.h new file mode 100644 index 0000000..3ef7d34 --- /dev/null +++ b/src/framework/core/eventdispatcher.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef EVENTDISPATCHER_H +#define EVENTDISPATCHER_H + +#include "clock.h" +#include "scheduledevent.h" + +#include + +// @bindsingleton g_dispatcher +class EventDispatcher +{ +public: + void shutdown(); + void poll(); + + EventPtr addEventEx(const std::string& function, const std::function& callback, bool pushFront = false); + ScheduledEventPtr scheduleEventEx(const std::string& function, const std::function& callback, int delay); + ScheduledEventPtr cycleEventEx(const std::string& function, const std::function& callback, int delay); + + bool isBotSafe() { return m_botSafe; } + +private: + std::list m_eventList; + int m_pollEventsSize; + bool m_disabled = false; + bool m_botSafe = false; + std::recursive_mutex m_mutex; + std::priority_queue, lessScheduledEvent> m_scheduledEventList; +}; + +extern EventDispatcher g_dispatcher; +extern EventDispatcher g_graphicsDispatcher; +extern std::thread::id g_mainThreadId; +extern std::thread::id g_dispatcherThreadId; + +#define addEvent(...) addEventEx(__FUNCTION__, __VA_ARGS__) +#define scheduleEvent(...) scheduleEventEx(__FUNCTION__, __VA_ARGS__) +#define cycleEvent(...) cycleEventEx(__FUNCTION__, __VA_ARGS__) + +#endif diff --git a/src/framework/core/filestream.cpp b/src/framework/core/filestream.cpp new file mode 100644 index 0000000..8cb07e0 --- /dev/null +++ b/src/framework/core/filestream.cpp @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "filestream.h" +#include "binarytree.h" +#include + +#define PHYSFS_DEPRECATED +#include +#include + +FileStream::FileStream(const std::string& name, PHYSFS_File *fileHandle, bool writeable) : + m_name(name), + m_fileHandle(fileHandle), + m_pos(0), + m_writeable(writeable), + m_caching(false) +{ +} + +FileStream::FileStream(const std::string& name, const std::string& buffer) : + m_name(name), + m_fileHandle(nullptr), + m_pos(0), + m_writeable(false), + m_caching(true) +{ + if (!initFromGzip(buffer)) { + m_data.resize(buffer.length()); + memcpy(&m_data[0], &buffer[0], buffer.length()); + } +} + +bool FileStream::initFromGzip(const std::string& buffer) +{ + if (buffer.size() < 10 || (uint8_t)buffer[0] != 0x1f || + (uint8_t)buffer[1] != 0x8b || (uint8_t)buffer[2] != 0x08) { + return false; + } + z_stream stream; + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (Bytef*)buffer.c_str(); + stream.avail_in = buffer.size(); + if (inflateInit2(&stream, 15 + 32) != Z_OK) + return false; + + std::vector chunk(buffer.size()); + while (true) { + stream.next_out = (Bytef*)(&chunk[0]); + stream.avail_out = chunk.size(); + + // Inflate another chunk. + int err = inflate(&stream, Z_SYNC_FLUSH); + int newData = chunk.size() - stream.avail_out; + if (newData > 0) { + m_data.grow(m_pos + newData, true); + memcpy(&m_data[m_pos], &chunk[0], newData); + m_pos += newData; + } + if (err != Z_OK) { + break; + } + } + inflateEnd(&stream); + m_pos = 0; + return true; +} + + +FileStream::~FileStream() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + if(!g_app.isTerminated()) + close(); +} + +void FileStream::close() +{ + if(m_fileHandle && PHYSFS_isInit()) { + if(!PHYSFS_close(m_fileHandle)) + throwError("close failed", true); + m_fileHandle = nullptr; + } + + m_data.clear(); + m_pos = 0; +} + +void FileStream::flush() +{ + if(!m_writeable) + throwError("filestream is not writeable"); + + if(m_fileHandle) { + if(m_caching) { + if(!PHYSFS_seek(m_fileHandle, 0)) + throwError("flush seek failed", true); + uint len = m_data.size(); + if(PHYSFS_writeBytes(m_fileHandle, m_data.data(), len) != len) + throwError("flush write failed", true); + } + + if(PHYSFS_flush(m_fileHandle) == 0) + throwError("flush failed", true); + } +} + +int FileStream::read(void *buffer, uint32 size, uint32 nmemb) +{ + if(!m_caching) { + int res = PHYSFS_readBytes(m_fileHandle, buffer, size * nmemb); + if(res == -1) + throwError("read failed", true); + return res; + } else { + int writePos = 0; + uint8 *outBuffer = (uint8*)buffer; + for(uint i=0;i m_data.size()) + return i; + + for(uint j=0;j m_data.size()) + throwError("seek failed"); + m_pos = pos; + } +} + +void FileStream::skip(uint len) +{ + seek(tell() + len); +} + +uint FileStream::size() +{ + if(!m_caching) + return PHYSFS_fileLength(m_fileHandle); + else + return m_data.size(); +} + +uint FileStream::tell() +{ + if(!m_caching) + return PHYSFS_tell(m_fileHandle); + else + return m_pos; +} + +bool FileStream::eof() +{ + if(!m_caching) + return PHYSFS_eof(m_fileHandle); + else + return m_pos >= m_data.size(); +} + +uint8 FileStream::getU8() +{ + uint8 v = 0; + if(!m_caching) { + if(PHYSFS_readBytes(m_fileHandle, &v, 1) != 1) + throwError("read failed", true); + } else { + if(m_pos+1 > m_data.size()) + throwError("read failed"); + + v = m_data[m_pos]; + m_pos += 1; + } + return v; +} + +uint16 FileStream::getU16() +{ + uint16 v = 0; + if(!m_caching) { + if(PHYSFS_readULE16(m_fileHandle, &v) == 0) + throwError("read failed", true); + } else { + if(m_pos+2 > m_data.size()) + throwError("read failed"); + + v = stdext::readULE16(&m_data[m_pos]); + m_pos += 2; + } + return v; +} + +uint32 FileStream::getU32() +{ + uint32 v = 0; + if(!m_caching) { + if(PHYSFS_readULE32(m_fileHandle, &v) == 0) + throwError("read failed", true); + } else { + if(m_pos+4 > m_data.size()) + throwError("read failed"); + + v = stdext::readULE32(&m_data[m_pos]); + m_pos += 4; + } + return v; +} + +uint64 FileStream::getU64() +{ + uint64 v = 0; + if(!m_caching) { + if(PHYSFS_readULE64(m_fileHandle, (PHYSFS_uint64*)&v) == 0) + throwError("read failed", true); + } else { + if(m_pos+8 > m_data.size()) + throwError("read failed"); + v = stdext::readULE64(&m_data[m_pos]); + m_pos += 8; + } + return v; +} + +int8 FileStream::get8() +{ + int8 v = 0; + if(!m_caching) { + if(PHYSFS_readBytes(m_fileHandle, &v, 1) != 1) + throwError("read failed", true); + } else { + if(m_pos+1 > m_data.size()) + throwError("read failed"); + + v = m_data[m_pos]; + m_pos += 1; + } + return v; +} + +int16 FileStream::get16() +{ + int16 v = 0; + if(!m_caching) { + if(PHYSFS_readSLE16(m_fileHandle, &v) == 0) + throwError("read failed", true); + } else { + if(m_pos+2 > m_data.size()) + throwError("read failed"); + + v = stdext::readSLE16(&m_data[m_pos]); + m_pos += 2; + } + return v; +} + +int32 FileStream::get32() +{ + int32 v = 0; + if(!m_caching) { + if(PHYSFS_readSLE32(m_fileHandle, &v) == 0) + throwError("read failed", true); + } else { + if(m_pos+4 > m_data.size()) + throwError("read failed"); + + v = stdext::readSLE32(&m_data[m_pos]); + m_pos += 4; + } + return v; +} + +int64 FileStream::get64() +{ + int64 v = 0; + if(!m_caching) { + if(PHYSFS_readSLE64(m_fileHandle, (PHYSFS_sint64*)&v) == 0) + throwError("read failed", true); + } else { + if(m_pos+8 > m_data.size()) + throwError("read failed"); + v = stdext::readSLE64(&m_data[m_pos]); + m_pos += 8; + } + return v; +} + +std::string FileStream::getString() +{ + std::string str; + uint16 len = getU16(); + if (len > 0) { + std::vector buffer(len, 0); + if (m_fileHandle) { + if (PHYSFS_read(m_fileHandle, &buffer[0], 1, len) == 0) + throwError("read failed", true); + else + str = std::string(buffer.begin(), buffer.end()); + } else { + if (m_pos + len > m_data.size()) { + throwError("read failed"); + return 0; + } + + str = std::string((char*)&m_data[m_pos], len); + m_pos += len; + } + } else if (len != 0) + throwError("read failed because string is too big"); + return str; +} + +BinaryTreePtr FileStream::getBinaryTree() +{ + uint8 byte = getU8(); + if(byte != BINARYTREE_NODE_START) + stdext::throw_exception(stdext::format("failed to read node start (getBinaryTree): %d", byte)); + + return BinaryTreePtr(new BinaryTree(asFileStream())); +} + +void FileStream::startNode(uint8 n) +{ + addU8(BINARYTREE_NODE_START); + addU8(n); +} + +void FileStream::endNode() +{ + addU8(BINARYTREE_NODE_END); +} + +void FileStream::addU8(uint8 v) +{ + if(!m_caching) { + if(PHYSFS_writeBytes(m_fileHandle, &v, 1) != 1) + throwError("write failed", true); + } else { + m_data.add(v); + m_pos++; + } +} + +void FileStream::addU16(uint16 v) +{ + if(!m_caching) { + if(PHYSFS_writeULE16(m_fileHandle, v) == 0) + throwError("write failed", true); + } else { + m_data.grow(m_pos + 2); + stdext::writeULE16(&m_data[m_pos], v); + m_pos += 2; + } +} + +void FileStream::addU32(uint32 v) +{ + if(!m_caching) { + if(PHYSFS_writeULE32(m_fileHandle, v) == 0) + throwError("write failed", true); + } else { + m_data.grow(m_pos + 4); + stdext::writeULE32(&m_data[m_pos], v); + m_pos += 4; + } +} + +void FileStream::addU64(uint64 v) +{ + if(!m_caching) { + if(PHYSFS_writeULE64(m_fileHandle, v) == 0) + throwError("write failed", true); + } else { + m_data.grow(m_pos + 8); + stdext::writeULE64(&m_data[m_pos], v); + m_pos += 8; + } +} + +void FileStream::add8(int8 v) +{ + if(!m_caching) { + if(PHYSFS_writeBytes(m_fileHandle, &v, 1) != 1) + throwError("write failed", true); + } else { + m_data.add(v); + m_pos++; + } +} + +void FileStream::add16(int16 v) +{ + if(!m_caching) { + if(PHYSFS_writeSLE16(m_fileHandle, v) == 0) + throwError("write failed", true); + } else { + m_data.grow(m_pos + 2); + stdext::writeSLE16(&m_data[m_pos], v); + m_pos += 2; + } +} + +void FileStream::add32(int32 v) +{ + if(!m_caching) { + if(PHYSFS_writeSLE32(m_fileHandle, v) == 0) + throwError("write failed", true); + } else { + m_data.grow(m_pos + 4); + stdext::writeSLE32(&m_data[m_pos], v); + m_pos += 4; + } +} + +void FileStream::add64(int64 v) +{ + if(!m_caching) { + if(PHYSFS_writeSLE64(m_fileHandle, v) == 0) + throwError("write failed", true); + } else { + m_data.grow(m_pos + 8); + stdext::writeSLE64(&m_data[m_pos], v); + m_pos += 8; + } +} + +void FileStream::addString(const std::string& v) +{ + addU16(v.length()); + write(v.c_str(), v.length()); +} + +void FileStream::throwError(const std::string& message, bool physfsError) +{ + std::string completeMessage = stdext::format("in file '%s': %s", m_name, message); + if(physfsError) + completeMessage += std::string(": ") + PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()); + stdext::throw_exception(completeMessage); +} + diff --git a/src/framework/core/filestream.h b/src/framework/core/filestream.h new file mode 100644 index 0000000..b150341 --- /dev/null +++ b/src/framework/core/filestream.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FILESTREAM_H +#define FILESTREAM_H + +#include "declarations.h" +#include +#include +#include + +struct PHYSFS_File; + +// @bindclass +class FileStream : public LuaObject +{ +public: + FileStream(const std::string& name, PHYSFS_File *fileHandle, bool writeable); + FileStream(const std::string& name, const std::string& buffer); + ~FileStream(); + + void close(); + void flush(); + void write(const void *buffer, uint count); + int read(void *buffer, uint size, uint nmemb = 1); + void seek(uint pos); + void skip(uint len); + uint size(); + uint tell(); + bool eof(); + std::string name() { return m_name; } + + uint8 getU8(); + uint16 getU16(); + uint32 getU32(); + uint64 getU64(); + int8 get8(); + int16 get16(); + int32 get32(); + int64 get64(); + std::string getString(); + BinaryTreePtr getBinaryTree(); + + void startNode(uint8 n); + void endNode(); + void addU8(uint8 v); + void addU16(uint16 v); + void addU32(uint32 v); + void addU64(uint64 v); + void add8(int8 v); + void add16(int16 v); + void add32(int32 v); + void add64(int64 v); + void addString(const std::string& v); + void addPos(uint16 x, uint16 y, uint8 z) { addU16(x); addU16(y); addU8(z); } + void addPoint(const Point& p) { addU8(p.x); addU8(p.y); } + + FileStreamPtr asFileStream() { return static_self_cast(); } + +private: + bool initFromGzip(const std::string& buffer); + void checkWrite(); + void throwError(const std::string& message, bool physfsError = false); + + std::string m_name; + PHYSFS_File *m_fileHandle; + uint m_pos; + bool m_writeable; + bool m_caching; + + DataBuffer m_data; +}; + +#endif diff --git a/src/framework/core/graphicalapplication.cpp b/src/framework/core/graphicalapplication.cpp new file mode 100644 index 0000000..3b03449 --- /dev/null +++ b/src/framework/core/graphicalapplication.cpp @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "graphicalapplication.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FW_SOUND +#include +#endif + +GraphicalApplication g_app; + +void GraphicalApplication::init(std::vector& args) +{ + Application::init(args); + + // setup platform window + g_window.init(); + g_window.hide(); + g_window.setOnResize(std::bind(&GraphicalApplication::resize, this, std::placeholders::_1)); + g_window.setOnInputEvent(std::bind(&GraphicalApplication::inputEvent, this, std::placeholders::_1)); + g_window.setOnClose(std::bind(&GraphicalApplication::close, this)); + + g_mouse.init(); + + // initialize ui + g_ui.init(); + + // initialize graphics + g_graphics.init(); + + // fire first resize event + resize(g_window.getSize()); + +#ifdef FW_SOUND + // initialize sound + g_sounds.init(); +#endif +} + +void GraphicalApplication::deinit() +{ + // hide the window because there is no render anymore + g_window.hide(); + g_asyncDispatcher.terminate(); + + Application::deinit(); +} + +void GraphicalApplication::terminate() +{ + // destroy any remaining widget + g_ui.terminate(); + + Application::terminate(); + m_terminated = false; + +#ifdef FW_SOUND + // terminate sound + g_sounds.terminate(); +#endif + + g_mouse.terminate(); + + // terminate graphics + g_graphicsDispatcher.shutdown(); + g_graphics.terminate(); + g_window.terminate(); + + m_terminated = true; +} + +void GraphicalApplication::run() +{ + m_running = true; + + // first clock update + g_clock.update(); + + // run the first poll + poll(); + g_clock.update(); + + // show window + g_window.show(); + + // run the second poll + poll(); + g_clock.update(); + + g_lua.callGlobalField("g_app", "onRun"); + + m_framebuffer = g_framebuffers.createFrameBuffer(); + m_framebuffer->resize(g_painterNew->getResolution()); + m_mapFramebuffer = g_framebuffers.createFrameBuffer(); + m_mapFramebuffer->resize(g_painterNew->getResolution()); + + ticks_t lastRender = stdext::micros(); + + std::shared_ptr drawQueue; + std::shared_ptr drawMapQueue; + std::shared_ptr drawMapForegroundQueue; + + std::mutex mutex; + std::thread worker([&] { + g_dispatcherThreadId = std::this_thread::get_id(); + while (!m_stopping) { + m_processingFrames.addFrame(); + { + g_clock.update(); + poll(); + g_clock.update(); + } + + mutex.lock(); + if (drawQueue && drawMapQueue && m_maxFps > 0) { // old drawQueue not processed yet + mutex.unlock(); + AutoStat s(STATS_MAIN, "Sleep"); + stdext::millisleep(1); + continue; + } + mutex.unlock(); + + { + AutoStat s(STATS_MAIN, "DrawMapBackground"); + g_drawQueue = std::make_shared(); + g_ui.render(Fw::MapBackgroundPane); + } + std::shared_ptr mapBackgroundQueue = g_drawQueue; + { + AutoStat s(STATS_MAIN, "DrawMapForeground"); + g_drawQueue = std::make_shared(); + g_ui.render(Fw::MapForegroundPane); + } + + mutex.lock(); + drawMapQueue = mapBackgroundQueue; + drawMapForegroundQueue = g_drawQueue; + mutex.unlock(); + + { + AutoStat s(STATS_MAIN, "DrawForeground"); + g_drawQueue = std::make_shared(); + g_ui.render(Fw::ForegroundPane); + } + + mutex.lock(); + drawQueue = g_drawQueue; + g_drawQueue = nullptr; + mutex.unlock(); + + if (m_maxFps > 0 || g_window.hasVerticalSync()) { + AutoStat s(STATS_MAIN, "Sleep"); + stdext::millisleep(1); + } + } + g_dispatcher.poll(); // last poll + g_dispatcherThreadId = g_mainThreadId; + }); + + std::shared_ptr toDrawQueue, toDrawMapQueue, toDrawMapForegroundQueue; + int draws = 0, calls = 0; + while (!m_stopping) { + m_iteration += 1; + + pollGraphics(); + + if (!g_window.isVisible()) { + AutoStat s(STATS_RENDER, "Sleep"); + stdext::millisleep(1); + g_adaptiveRenderer.refresh(); + continue; + } + + int frameDelay = m_maxFps <= 0 ? 0 : (1000000 / m_maxFps); + if (lastRender + frameDelay > stdext::micros() && !m_mustRepaint) { + AutoStat s(STATS_RENDER, "Sleep"); + stdext::millisleep(1); + continue; + } + + mutex.lock(); + if ((!drawQueue && !toDrawQueue) || !drawMapQueue || !drawMapForegroundQueue || (m_mustRepaint && !drawQueue)) { + mutex.unlock(); + continue; + } + toDrawQueue = drawQueue ? drawQueue : toDrawQueue; + toDrawMapQueue = drawMapQueue; + toDrawMapForegroundQueue = drawMapForegroundQueue; + drawQueue = drawMapQueue = drawMapForegroundQueue = nullptr; + mutex.unlock(); + + g_adaptiveRenderer.newFrame(); + m_graphicsFrames.addFrame(); + m_mustRepaint = false; + lastRender = stdext::micros() > lastRender + frameDelay * 2 ? stdext::micros() : lastRender + frameDelay; + + g_painterNew->resetDraws(); + if (m_scaling > 1.0f) { + g_painterNew->setResolution(g_graphics.getViewportSize() / m_scaling); + m_framebuffer->resize(g_painterNew->getResolution()); + m_framebuffer->bind(); + } + + if (toDrawMapQueue->hasFrameBuffer()) { + AutoStat s(STATS_RENDER, "UpdateMap"); + m_mapFramebuffer->resize(toDrawMapQueue->getFrameBufferSize()); + m_mapFramebuffer->bind(); + g_painterNew->clear(Color::black); + toDrawMapQueue->draw(DRAW_ALL); + m_mapFramebuffer->release(); + } + + { + AutoStat s(STATS_RENDER, "Clear"); + g_painterNew->clear(Color::alpha); + } + + { + AutoStat s(STATS_RENDER, "DrawFirstForeground"); + if (toDrawQueue) + toDrawQueue->draw(DRAW_BEFORE_MAP); + } + + if(toDrawMapQueue->hasFrameBuffer()) { + AutoStat s(STATS_RENDER, "DrawMapBackground"); + m_mapFramebuffer->draw(toDrawMapQueue->getFrameBufferDest(), toDrawMapQueue->getFrameBufferSrc()); + } + + { + AutoStat s(STATS_RENDER, "DrawMapForeground"); + toDrawMapForegroundQueue->draw(); + } + + { + AutoStat s(STATS_RENDER, "DrawSecondForeground"); + if(g_extras.debugRender) + toDrawQueue->addText(g_fonts.getDefaultFont(), stdext::format("Calls: %i Draws %i", calls, draws), Rect(0, 0, 200, 200), Fw::AlignTopLeft, Color::yellow); + toDrawQueue->draw(DRAW_AFTER_MAP); + } + + if (m_scaling > 1.0f) { + AutoStat s(STATS_RENDER, "DrawScaled"); + m_framebuffer->release(); + g_painterNew->setResolution(g_graphics.getViewportSize()); + g_painterNew->clear(Color::alpha); + m_framebuffer->draw(Rect(0, 0, g_painterNew->getResolution())); + } + + draws = g_painterNew->draws(); + calls = g_painterNew->calls(); + + AutoStat s(STATS_RENDER, "SwapBuffers"); + g_window.swapBuffers(); + g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__); + } + + worker.join(); + g_graphicsDispatcher.poll(); + + m_framebuffer = nullptr; + m_mapFramebuffer = nullptr; + g_drawQueue = nullptr; + m_stopping = false; + m_running = false; +} + +void GraphicalApplication::poll() { +#ifdef FW_SOUND + g_sounds.poll(); +#endif + Application::poll(); +} + +void GraphicalApplication::pollGraphics() +{ + g_graphicsDispatcher.poll(); + g_textures.poll(); + g_text.poll(); + g_window.poll(); +} + +void GraphicalApplication::close() +{ + VALIDATE(std::this_thread::get_id() == g_dispatcherThreadId); + m_onInputEvent = true; + Application::close(); + m_onInputEvent = false; +} + +void GraphicalApplication::resize(const Size& size) +{ + VALIDATE(std::this_thread::get_id() == g_mainThreadId); + g_graphics.resize(size); // uses painter + scale(m_scaling); // thread safe +} + +void GraphicalApplication::inputEvent(InputEvent event) +{ + VALIDATE(std::this_thread::get_id() == g_dispatcherThreadId); + m_onInputEvent = true; + g_ui.inputEvent(event); + m_onInputEvent = false; +} + +void GraphicalApplication::doScreenshot(std::string file) +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::doScreenshot, this, file)); + return; + } + + if (file.empty()) { + file = "screenshot.png"; + } + auto resolution = g_graphics.getViewportSize(); + int width = resolution.width(); + int height = resolution.height(); + auto pixels = std::make_shared>(width * height * 4 * sizeof(GLubyte), 0); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLubyte*)(pixels->data())); + + g_asyncDispatcher.dispatch([resolution, pixels, file] { + for (int line = 0, h = resolution.height(), w = resolution.width(); line != h / 2; ++line) { + std::swap_ranges( + pixels->begin() + 4 * w * line, + pixels->begin() + 4 * w * (line + 1), + pixels->begin() + 4 * w * (h - line - 1)); + } + try { + Image image(resolution, 4, pixels->data()); + image.savePNG(file); + } catch (stdext::exception& e) { + g_logger.error(std::string("Can't do screenshot: ") + e.what()); + } + }); +} + +void GraphicalApplication::scaleUp() +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::scaleUp, this)); + return; + } + scale(m_scaling + 0.5); +} + +void GraphicalApplication::scaleDown() +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::scaleDown, this)); + return; + } + scale(m_scaling - 0.5); +} + +void GraphicalApplication::scale(float value) +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&GraphicalApplication::scale, this, value)); + return; + } + + float maxScale = std::min((g_graphics.getViewportSize().height() / 180), + g_graphics.getViewportSize().width() / 280); + if (maxScale < 2.0) + maxScale = 2.0; + maxScale /= 2; + + if (m_scaling == value) { + value = m_lastScaling; + } else { + m_lastScaling = std::max(1.0, std::min(maxScale, value)); + } + + m_scaling = std::max(1.0, std::min(maxScale, value)); + g_window.setScaling(m_scaling); + + g_dispatcher.addEvent([&] { + m_onInputEvent = true; + g_ui.resize(g_graphics.getViewportSize() / m_scaling); + m_onInputEvent = false; + m_mustRepaint = true; + }); +} \ No newline at end of file diff --git a/src/framework/core/graphicalapplication.h b/src/framework/core/graphicalapplication.h new file mode 100644 index 0000000..e5c425f --- /dev/null +++ b/src/framework/core/graphicalapplication.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef GRAPHICALAPPLICATION_H +#define GRAPHICALAPPLICATION_H + +#include "application.h" +#include +#include +#include +#include +#include + +class GraphicalApplication : public Application +{ +public: + void init(std::vector& args); + void deinit(); + void terminate(); + void run(); + void poll(); + void pollGraphics(); + void close(); + + bool willRepaint() { return m_mustRepaint; } + void repaint() { m_mustRepaint = true; } + + void setMaxFps(int maxFps) { m_maxFps = maxFps; } + int getMaxFps() { return m_maxFps; } + int getFps() { return m_graphicsFrames.getFps(); } + int getGraphicsFps() { return m_graphicsFrames.getFps(); } + int getProcessingFps() { return m_processingFrames.getFps(); } + + bool isOnInputEvent() { return m_onInputEvent; } + + int getIteration() { + return m_iteration; + } + + void doScreenshot(std::string file); + void scaleUp(); + void scaleDown(); + void scale(float value); + +protected: + void resize(const Size& size); + void inputEvent(InputEvent event); + +private: + int m_iteration = 0; + std::atomic m_scaling = 1.0; + std::atomic m_lastScaling = 1.0; + std::atomic_int m_maxFps = 100; + stdext::boolean m_onInputEvent; + stdext::boolean m_mustRepaint; + FrameBufferPtr m_framebuffer, m_mapFramebuffer; + FrameCounter m_graphicsFrames; + FrameCounter m_processingFrames; +}; + +extern GraphicalApplication g_app; + +#endif diff --git a/src/framework/core/inputevent.h b/src/framework/core/inputevent.h new file mode 100644 index 0000000..4cf461e --- /dev/null +++ b/src/framework/core/inputevent.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef INPUTEVENT_H +#define INPUTEVENT_H + +#include "declarations.h" + +struct InputEvent { + InputEvent() { + reset(); + keyboardModifiers = 0; + } + + void reset(Fw::InputEventType eventType = Fw::NoInputEvent) { + type = eventType; + wheelDirection = Fw::MouseNoWheel; + mouseButton = Fw::MouseNoButton; + keyCode = Fw::KeyUnknown; + keyText = ""; + autoRepeatTicks = 0; + mouseMoved = Point(); + }; + + Fw::InputEventType type; + Fw::MouseWheelDirection wheelDirection; + Fw::MouseButton mouseButton; + Fw::Key keyCode; + std::string keyText; + int keyboardModifiers; + Point mousePos; + Point mouseMoved; + int autoRepeatTicks; +}; + +#endif diff --git a/src/framework/core/logger.cpp b/src/framework/core/logger.cpp new file mode 100644 index 0000000..92698ce --- /dev/null +++ b/src/framework/core/logger.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "logger.h" +#include "eventdispatcher.h" + +#include +#include + +#ifdef FW_GRAPHICS +#include +#include +#include +#endif + +Logger g_logger; + +void Logger::log(Fw::LogLevel level, const std::string& message) +{ + std::unique_lock lock(m_mutex, std::try_to_lock); + if (!lock.owns_lock()) { + return; + } + +#ifdef NDEBUG + if(level == Fw::LogDebug) + return; +#endif + + static bool ignoreLogs = false; + if(ignoreLogs) + return; + + const static std::string logPrefixes[] = { "", "", "WARNING: ", "ERROR: ", "FATAL ERROR: " }; + std::string outmsg = logPrefixes[level] + message; +#ifdef ANDROID + const static int logPriorities[] = { ANDROID_LOG_INFO, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL }; + __android_log_print(logPriorities[level], "OTCLIENTV8", "%s", outmsg.c_str()); +#else + std::cout << outmsg << std::endl; + + if(m_outFile.good()) { + m_outFile << outmsg << std::endl; + m_outFile.flush(); + } +#endif + + std::size_t now = std::time(NULL); + m_logMessages.push_back(LogMessage(level, outmsg, now)); + if(m_logMessages.size() > MAX_LOG_HISTORY) + m_logMessages.pop_front(); + + if(m_onLog) { + // schedule log callback, because this callback can run lua code that may affect the current state + g_dispatcher.addEvent([=] { + if(m_onLog) + m_onLog(level, outmsg, now); + }); + } + + if(level == Fw::LogFatal) { +#ifdef FW_GRAPHICS + g_window.displayFatalError(message); +#endif + ignoreLogs = true; +#ifdef _MSC_VER + ::quick_exit(0); +#else + exit(0); +#endif + } +} + +void Logger::logFunc(Fw::LogLevel level, const std::string& message, std::string prettyFunction) +{ + std::lock_guard lock(m_mutex); + + prettyFunction = prettyFunction.substr(0, prettyFunction.find_first_of('(')); + if(prettyFunction.find_last_of(' ') != std::string::npos) + prettyFunction = prettyFunction.substr(prettyFunction.find_last_of(' ') + 1); + + + std::stringstream ss; + ss << message; + + if(!prettyFunction.empty()) { + if(g_lua.isInCppCallback()) + ss << g_lua.traceback("", 1); + ss << g_platform.traceback(prettyFunction, 1, 8); + } + + log(level, ss.str()); +} + +void Logger::fireOldMessages() +{ + std::lock_guard lock(m_mutex); + + if(m_onLog) { + auto backup = m_logMessages; + for(const LogMessage& logMessage : backup) { + m_onLog(logMessage.level, logMessage.message, logMessage.when); + } + } +} + +void Logger::setLogFile(const std::string& file) +{ +#ifndef ANDROID + std::lock_guard lock(m_mutex); + m_outFile.open(stdext::utf8_to_latin1(file.c_str()).c_str(), std::ios::in | std::ios::binary); + if (m_outFile.is_open()) { + m_outFile.seekg(0, m_outFile.end); + int length = m_outFile.tellg(); + int offset = std::max(0, length - 100000); + length -= offset; + m_outFile.seekg(offset, m_outFile.beg); + if (length > 0) { + m_lastLog.resize(length); + m_outFile.read(&m_lastLog[0], length); + m_lastLog.resize(m_outFile.gcount()); + } + m_outFile.close(); + } + + m_outFile.open(stdext::utf8_to_latin1(file.c_str()).c_str(), std::ios::out | std::ios::app); + if(!m_outFile.is_open() || !m_outFile.good()) { + g_logger.error(stdext::format("Unable to save log to '%s'", file)); + return; + } + m_outFile.flush(); +#endif +} + +void fatalError(const char* error, const char* file, int line) +{ + g_logger.fatal(stdext::format("Fatal error: %s\nIn: %s:%i", error, file, line)); +} diff --git a/src/framework/core/logger.h b/src/framework/core/logger.h new file mode 100644 index 0000000..91f83ac --- /dev/null +++ b/src/framework/core/logger.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include "../global.h" + +#include +#include + +struct LogMessage { + LogMessage(Fw::LogLevel level, const std::string& message, std::size_t when) : level(level), message(message), when(when) { } + Fw::LogLevel level; + std::string message; + std::size_t when; +}; + +// @bindsingleton g_logger +class Logger +{ + enum { + MAX_LOG_HISTORY = 1000 + }; + + typedef std::function OnLogCallback; + +public: + void log(Fw::LogLevel level, const std::string& message); + void logFunc(Fw::LogLevel level, const std::string& message, std::string prettyFunction); + + void debug(const std::string& what) { log(Fw::LogDebug, what); } + void info(const std::string& what) { log(Fw::LogInfo, what); } + void warning(const std::string& what) { log(Fw::LogWarning, what); } + void error(const std::string& what) { log(Fw::LogError, what); } + void fatal(const std::string& what) { log(Fw::LogFatal, what); } + + void fireOldMessages(); + void setLogFile(const std::string& file); + void setOnLog(const OnLogCallback& onLog) { m_onLog = onLog; } + + std::string getLastLog() { + return m_lastLog; + } + +private: + std::list m_logMessages; + OnLogCallback m_onLog; + std::fstream m_outFile; + std::recursive_mutex m_mutex; + std::string m_lastLog; +}; + +extern Logger g_logger; + +#define trace() logFunc(Fw::LogDebug, "", __PRETTY_FUNCTION__) +#define traceDebug(a) logFunc(Fw::LogDebug, a, __PRETTY_FUNCTION__) +#define traceInfo(a) logFunc(Fw::LogInfo, a, __PRETTY_FUNCTION__) +#define traceWarning(a) logFunc(Fw::LogWarning, a, __PRETTY_FUNCTION__) +#define traceError(a) logFunc(Fw::LogError, a, __PRETTY_FUNCTION__) + +#define logTraceCounter() { \ + static int __count = 0; \ + static Timer __timer; \ + __count++; \ + if(__timer.ticksElapsed() >= 1000) { \ + logTraceDebug(__count); \ + __count = 0; \ + __timer.restart(); \ + } \ +} + +#define logTraceFrameCounter() { \ + static int __count = 0; \ + static Timer __timer; \ + __count++; \ + if(__timer.ticksElapsed() > 0) { \ + logTraceDebug(__count); \ + __count = 0; \ + __timer.restart(); \ + } \ +} + +#endif diff --git a/src/framework/core/module.cpp b/src/framework/core/module.cpp new file mode 100644 index 0000000..37b2c16 --- /dev/null +++ b/src/framework/core/module.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "module.h" +#include "modulemanager.h" +#include "resourcemanager.h" + +#include +#include + +Module::Module(const std::string& name) +{ + m_name = name; + m_sandboxEnv = g_lua.newSandboxEnv(); +} + +bool Module::load() +{ + if(m_loaded) + return true; + + auto errorHandler = [&] (const std::string& error) { + g_lua.getGlobalField("package", "loaded"); + g_lua.pushNil(); + g_lua.setField(m_name); + g_lua.pop(); + + if(m_sandboxed) + g_lua.resetGlobalEnvironment(); + if(m_loadedOnStartup) // just reload, don't exit + g_logger.error(stdext::format("Unable to load module '%s': %s", m_name, error)); + else + g_logger.fatal(stdext::format("Unable to load module '%s': %s", m_name, error)); + }; + + try { + // add to package.loaded + g_lua.getGlobalField("package", "loaded"); + g_lua.getRef(m_sandboxEnv); + g_lua.setField(m_name); + g_lua.pop(); + + for(const std::string& depName : m_dependencies) { + if(depName == m_name) + stdext::throw_exception("cannot depend on itself"); + + ModulePtr dep = g_modules.getModule(depName); + if(!dep) + stdext::throw_exception(stdext::format("dependency '%s' was not found", depName)); + + if(dep->hasDependency(m_name, true)) + stdext::throw_exception(stdext::format("dependency '%s' is recursively depending on itself", depName)); + + if(!dep->isLoaded() && !dep->load()) + stdext::throw_exception(stdext::format("dependency '%s' has failed to load", depName)); + } + + if(m_sandboxed) + g_lua.setGlobalEnvironment(m_sandboxEnv); + + for(const std::string& script : m_scripts) { + g_lua.loadScript(script); + auto error = std::make_shared(); + g_lua.safeCall(0, 0, error); + if (!error->empty()) { + errorHandler(*error); + return false; + } + } + + const std::string& onLoadBuffer = std::get<0>(m_onLoadFunc); + const std::string& onLoadSource = std::get<1>(m_onLoadFunc); + if(!onLoadBuffer.empty()) { + g_lua.loadBuffer(onLoadBuffer, onLoadSource); + if(m_sandboxed) { + g_lua.getRef(m_sandboxEnv); + g_lua.setEnv(); + } + auto error = std::make_shared(); + g_lua.safeCall(0, 0, error); + if (!error->empty()) { + errorHandler(*error); + return false; + } + } + + if(m_sandboxed) + g_lua.resetGlobalEnvironment(); + + m_loaded = true; + g_logger.debug(stdext::format("Loaded module '%s'", m_name)); + } catch(stdext::exception& e) { + // remove from package.loaded + errorHandler(e.what()); + return false; + } + + g_modules.updateModuleLoadOrder(asModule()); + + for(const std::string& modName : m_loadLaterModules) { + ModulePtr dep = g_modules.getModule(modName); + if(!dep) + g_logger.error(stdext::format("Unable to find module '%s' required by '%s'", modName, m_name)); + else if(!dep->isLoaded()) + dep->load(); + } + + m_loadedOnStartup = true; + return true; +} + +void Module::unload() +{ + if(m_loaded) { + try { + if(m_sandboxed) + g_lua.setGlobalEnvironment(m_sandboxEnv); + + const std::string& onUnloadBuffer = std::get<0>(m_onUnloadFunc); + const std::string& onUnloadSource = std::get<1>(m_onUnloadFunc); + if(!onUnloadBuffer.empty()) { + g_lua.loadBuffer(onUnloadBuffer, onUnloadSource); + g_lua.safeCall(0, 0); + } + + if(m_sandboxed) + g_lua.resetGlobalEnvironment(); + } catch(stdext::exception& e) { + if(m_sandboxed) + g_lua.resetGlobalEnvironment(); + g_logger.error(stdext::format("Unable to unload module '%s': %s", m_name, e.what())); + } + + // clear all env references + g_lua.getRef(m_sandboxEnv); + g_lua.clearTable(); + g_lua.pop(); + + // remove from package.loaded + g_lua.getGlobalField("package", "loaded"); + g_lua.pushNil(); + g_lua.setField(m_name); + g_lua.pop(); + + m_loaded = false; + //g_logger.info(stdext::format("Unloaded module '%s'", m_name)); + g_modules.updateModuleLoadOrder(asModule()); + } +} + +bool Module::reload() +{ + unload(); + return load(); +} + +bool Module::isDependent() +{ + for(const ModulePtr& module : g_modules.getModules()) { + if(module->isLoaded() && module->hasDependency(m_name)) + return true; + } + return false; +} + +bool Module::hasDependency(const std::string& name, bool recursive) +{ + if(std::find(m_dependencies.begin(), m_dependencies.end(), name) != m_dependencies.end()) + return true; + + if(recursive) { + for(const std::string& depName : m_dependencies) { + ModulePtr dep = g_modules.getModule(depName); + if(dep && dep->hasDependency(name, true)) + return true; + } + } + + return false; +} + +int Module::getSandbox(LuaInterface* lua) +{ + lua->getRef(m_sandboxEnv); + return 1; +} + +void Module::discover(const OTMLNodePtr& moduleNode) +{ + const static std::string none = "none"; + m_description = moduleNode->valueAt("description", none); + m_author = moduleNode->valueAt("author", none); + m_website = moduleNode->valueAt("website", none); + m_version = moduleNode->valueAt("version", none); + m_autoLoad = moduleNode->valueAt("autoload", false); + m_reloadable = moduleNode->valueAt("reloadable", true); + m_sandboxed = moduleNode->valueAt("sandboxed", false); + m_autoLoadPriority = moduleNode->valueAt("autoload-priority", 9999); + + if(OTMLNodePtr node = moduleNode->get("dependencies")) { + for(const OTMLNodePtr& tmp : node->children()) + m_dependencies.push_back(tmp->value()); + } + + if(OTMLNodePtr node = moduleNode->get("scripts")) { + for(const OTMLNodePtr& tmp : node->children()) + m_scripts.push_back(stdext::resolve_path(tmp->value(), node->source())); + } + + if(OTMLNodePtr node = moduleNode->get("load-later")) { + for(const OTMLNodePtr& tmp : node->children()) + m_loadLaterModules.push_back(tmp->value()); + } + + if(OTMLNodePtr node = moduleNode->get("@onLoad")) + m_onLoadFunc = std::make_tuple(node->value(), "@" + node->source() + ":[" + node->tag() + "]"); + + if(OTMLNodePtr node = moduleNode->get("@onUnload")) + m_onUnloadFunc = std::make_tuple(node->value(), "@" + node->source() + ":[" + node->tag() + "]"); +} diff --git a/src/framework/core/module.h b/src/framework/core/module.h new file mode 100644 index 0000000..de45447 --- /dev/null +++ b/src/framework/core/module.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MODULE_H +#define MODULE_H + +#include "declarations.h" + +#include +#include + +// @bindclass +class Module : public LuaObject +{ +public: + Module(const std::string& name); + + bool load(); + void unload(); + bool reload(); + + bool canUnload() { return m_loaded && m_reloadable && !isDependent(); } + bool canReload() { return m_reloadable && !isDependent(); } + bool isLoaded() { return m_loaded; } + bool isReloadable() { return m_reloadable; } + bool isDependent(); + bool isSandboxed() { return m_sandboxed; } + bool hasDependency(const std::string& name, bool recursive = false); + int getSandbox(LuaInterface *lua); + + std::string getDescription() { return m_description; } + std::string getName() { return m_name; } + std::string getAuthor() { return m_author; } + std::string getWebsite() { return m_website; } + std::string getVersion() { return m_version; } + bool isAutoLoad() { return m_autoLoad; } + int getAutoLoadPriority() { return m_autoLoadPriority; } + + // @dontbind + ModulePtr asModule() { return static_self_cast(); } + +protected: + void discover(const OTMLNodePtr& moduleNode); + friend class ModuleManager; + +private: + stdext::boolean m_loaded; + stdext::boolean m_autoLoad; + stdext::boolean m_reloadable; + stdext::boolean m_sandboxed; + bool m_loadedOnStartup = false; + int m_autoLoadPriority; + int m_sandboxEnv; + std::tuple m_onLoadFunc; + std::tuple m_onUnloadFunc; + std::string m_name; + std::string m_description; + std::string m_author; + std::string m_website; + std::string m_version; + std::function m_loadCallback; + std::function m_unloadCallback; + std::list m_dependencies; + std::list m_scripts; + std::list m_loadLaterModules; +}; + +#endif diff --git a/src/framework/core/modulemanager.cpp b/src/framework/core/modulemanager.cpp new file mode 100644 index 0000000..c62425e --- /dev/null +++ b/src/framework/core/modulemanager.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "modulemanager.h" +#include "resourcemanager.h" + +#include +#include +#include + +ModuleManager g_modules; + +void ModuleManager::clear() +{ + m_modules.clear(); + m_autoLoadModules.clear(); +} + +void ModuleManager::discoverModules() +{ + // remove modules that are not loaded + m_autoLoadModules.clear(); + + auto dirs = g_resources.listDirectoryFiles("/modules", true); + std::list modules; + for (auto& dir : dirs) { + auto subFilesAndDirs = g_resources.listDirectoryFiles(dir, true); + modules.insert(modules.end(), subFilesAndDirs.begin(), subFilesAndDirs.end()); + } + + dirs = g_resources.listDirectoryFiles("/mods", true); + for (auto& dir : dirs) { + auto subFilesAndDirs = g_resources.listDirectoryFiles(dir, true); + modules.insert(modules.end(), subFilesAndDirs.begin(), subFilesAndDirs.end()); + } + + for (auto& mod : modules) { + if (g_resources.isFileType(mod, "otmod")) { + ModulePtr module = discoverModule(mod); + if (module && module->isAutoLoad()) + m_autoLoadModules.insert(std::make_pair(module->getAutoLoadPriority(), module)); + } + } +} + +void ModuleManager::autoLoadModules(int maxPriority) +{ + for(auto& pair : m_autoLoadModules) { + int priority = pair.first; + if(priority > maxPriority) + break; + ModulePtr module = pair.second; + module->load(); + } +} + +ModulePtr ModuleManager::discoverModule(const std::string& moduleFile) +{ + ModulePtr module; + try { + OTMLDocumentPtr doc = OTMLDocument::parse(moduleFile); + OTMLNodePtr moduleNode = doc->at("Module"); + + std::string name = moduleNode->valueAt("name"); + + bool push = false; + module = getModule(name); + if(!module) { + module = ModulePtr(new Module(name)); + push = true; + } + module->discover(moduleNode); + + // not loaded modules are always in back + if(push) + m_modules.push_back(module); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Unable to discover module from file '%s': %s", moduleFile, e.what())); + } + return module; +} + +void ModuleManager::ensureModuleLoaded(const std::string& moduleName) +{ + ModulePtr module = g_modules.getModule(moduleName); + if(!module || !module->load()) + g_logger.fatal(stdext::format("Unable to load '%s' module", moduleName)); +} + +void ModuleManager::unloadModules() +{ + auto modulesBackup = m_modules; + for(const ModulePtr& module : modulesBackup) + module->unload(); +} + +void ModuleManager::reloadModules() +{ + std::deque toLoadList; + + // unload in the reverse direction, try to unload upto 10 times (because of dependencies) + for(int i=0;i<10;++i) { + auto modulesBackup = m_modules; + for(const ModulePtr& module : modulesBackup) { + if(module->isLoaded() && module->canUnload()) { + module->unload(); + toLoadList.push_front(module); + } + } + } + + for(const ModulePtr& module : toLoadList) + module->load(); +} + +ModulePtr ModuleManager::getModule(const std::string& moduleName) +{ + for(const ModulePtr& module : m_modules) + if(module->getName() == moduleName) + return module; + return nullptr; +} + +void ModuleManager::updateModuleLoadOrder(ModulePtr module) +{ + auto it = std::find(m_modules.begin(), m_modules.end(), module); + if(it != m_modules.end()) + m_modules.erase(it); + if(module->isLoaded()) + m_modules.push_front(module); + else + m_modules.push_back(module); +} diff --git a/src/framework/core/modulemanager.h b/src/framework/core/modulemanager.h new file mode 100644 index 0000000..3651c1d --- /dev/null +++ b/src/framework/core/modulemanager.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MODULEMANAGER_H +#define MODULEMANAGER_H + +#include "module.h" + +// @bindsingleton g_modules +class ModuleManager +{ +public: + void clear(); + + void discoverModules(); + void autoLoadModules(int maxPriority); + ModulePtr discoverModule(const std::string& moduleFile); + void ensureModuleLoaded(const std::string& moduleName); + void unloadModules(); + void reloadModules(); + + ModulePtr getModule(const std::string& moduleName); + std::deque getModules() { return m_modules; } + +protected: + void updateModuleLoadOrder(ModulePtr module); + + friend class Module; + +private: + std::deque m_modules; + std::multimap m_autoLoadModules; +}; + +extern ModuleManager g_modules; + +#endif diff --git a/src/framework/core/scheduledevent.cpp b/src/framework/core/scheduledevent.cpp new file mode 100644 index 0000000..96c7516 --- /dev/null +++ b/src/framework/core/scheduledevent.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "scheduledevent.h" + +ScheduledEvent::ScheduledEvent(const std::string& function, const std::function& callback, int delay, int maxCycles, bool botSafe) : Event(function, callback, botSafe) +{ + m_ticks = g_clock.millis() + delay; + m_delay = delay; + m_maxCycles = maxCycles; + m_cyclesExecuted = 0; +} + +void ScheduledEvent::execute() +{ + if(!m_canceled && m_callback && (m_maxCycles == 0 || m_cyclesExecuted < m_maxCycles)) { + m_callback(); + m_executed = true; + // callback may be used in the next cycle + } else { + // reset callback to free object refs + m_callback = nullptr; + } + + m_cyclesExecuted++; +} + +bool ScheduledEvent::nextCycle() +{ + if(m_callback && !m_canceled && (m_maxCycles == 0 || m_cyclesExecuted < m_maxCycles)) { + m_ticks += m_delay; + return true; + } + + // reset callback to free object refs + m_callback = nullptr; + return false; +} diff --git a/src/framework/core/scheduledevent.h b/src/framework/core/scheduledevent.h new file mode 100644 index 0000000..8111701 --- /dev/null +++ b/src/framework/core/scheduledevent.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SCHEDULEDEVENT_H +#define SCHEDULEDEVENT_H + +#include +#include "event.h" +#include "clock.h" + +// @bindclass +class ScheduledEvent : public Event +{ +public: + ScheduledEvent(const std::string& function, const std::function& callback, int delay, int maxCycles, bool botSafe = false); + void execute(); + bool nextCycle(); + + int ticks() { return m_ticks; } + int remainingTicks() { return m_ticks - g_clock.millis(); } + int delay() { return m_delay; } + int cyclesExecuted() { return m_cyclesExecuted; } + int maxCycles() { return m_maxCycles; } + +private: + ticks_t m_ticks; + int m_delay; + int m_maxCycles; + int m_cyclesExecuted; +}; + +struct lessScheduledEvent { + bool operator()(const ScheduledEventPtr& a, const ScheduledEventPtr& b) { + return b->ticks() < a->ticks(); + } +}; + +#endif diff --git a/src/framework/core/timer.cpp b/src/framework/core/timer.cpp new file mode 100644 index 0000000..da7d589 --- /dev/null +++ b/src/framework/core/timer.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "timer.h" +#include "clock.h" + +void Timer::restart() +{ + m_startTicks = g_clock.millis(); + m_stopped = false; +} + +ticks_t Timer::ticksElapsed() +{ + return g_clock.millis() - m_startTicks; +} diff --git a/src/framework/core/timer.h b/src/framework/core/timer.h new file mode 100644 index 0000000..28035b3 --- /dev/null +++ b/src/framework/core/timer.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TIMER_H +#define TIMER_H + +#include + +class Timer +{ +public: + Timer() { restart(); } + + void restart(); + void stop() { m_stopped = true; } + void adjust(ticks_t value) { m_startTicks += value; } + + ticks_t startTicks() { return m_startTicks; } + ticks_t ticksElapsed(); + float timeElapsed() { return ticksElapsed()/1000.0f; } + + bool running() { return !m_stopped; } + +private: + ticks_t m_startTicks; + stdext::boolean m_stopped; +}; + +#endif diff --git a/src/framework/global.h b/src/framework/global.h new file mode 100644 index 0000000..0b5abad --- /dev/null +++ b/src/framework/global.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_GLOBAL_H +#define FRAMEWORK_GLOBAL_H + +#include "stdext/compiler.h" + +// common C/C++ headers +#include "pch.h" + +// error handling +#if defined(NDEBUG) +#define VALIDATE(expression) ((void)0) +#else +extern void fatalError(const char* error, const char* file, int line); +#define VALIDATE(expression) { if(!(expression)) fatalError(#expression, __FILE__, __LINE__); }; +#endif + + +// global constants +#include "const.h" + +// stdext which includes additional C++ algorithms +#include "stdext/stdext.h" + +// additional utilities +#include "util/point.h" +#include "util/color.h" +#include "util/rect.h" +#include "util/size.h" +#include "util/matrix.h" + +// logger +#include "core/logger.h" + +#endif diff --git a/src/framework/graphics/animatedtexture.cpp b/src/framework/graphics/animatedtexture.cpp new file mode 100644 index 0000000..fb36c5d --- /dev/null +++ b/src/framework/graphics/animatedtexture.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "animatedtexture.h" +#include "graphics.h" + +#include + +AnimatedTexture::AnimatedTexture(const Size& size, std::vector frames, std::vector framesDelay, bool buildMipmaps, bool compress) : + Texture(size) +{ + for(uint i=0;ibuildHardwareMipmaps(); + m_hasMipmaps = true; + return true; +} + +void AnimatedTexture::setSmooth(bool smooth) +{ + for(const TexturePtr& frame : m_frames) + frame->setSmooth(smooth); + m_smooth = smooth; +} + +void AnimatedTexture::setRepeat(bool repeat) +{ + for(const TexturePtr& frame : m_frames) + frame->setRepeat(repeat); + m_repeat = repeat; +} + +void AnimatedTexture::update() +{ + if (m_animTimer.ticksElapsed() >= m_framesDelay[m_currentFrame]) { + m_animTimer.restart(); + m_currentFrame = (m_currentFrame + 1) % m_frames.size(); + } + + m_frames[m_currentFrame]->update(); + m_id = m_frames[m_currentFrame]->getId(); + m_uniqueId = m_frames[m_currentFrame]->getUniqueId(); +} diff --git a/src/framework/graphics/animatedtexture.h b/src/framework/graphics/animatedtexture.h new file mode 100644 index 0000000..0bb7cc0 --- /dev/null +++ b/src/framework/graphics/animatedtexture.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ANIMATEDTEXTURE_H +#define ANIMATEDTEXTURE_H + +#include "texture.h" +#include + +class AnimatedTexture : public Texture +{ +public: + AnimatedTexture(const Size& size, std::vector frames, std::vector framesDelay, bool buildMipmaps = false, bool compress = false); + virtual ~AnimatedTexture(); + + void replace(const ImagePtr& image) { } + void update(); + + virtual bool isAnimatedTexture() { return true; } + +protected: + virtual bool buildHardwareMipmaps(); + + virtual void setSmooth(bool smooth); + virtual void setRepeat(bool repeat); + +private: + std::vector m_frames; + std::vector m_framesDelay; + uint m_currentFrame; + Timer m_animTimer; +}; + +#endif diff --git a/src/framework/graphics/apngloader.cpp b/src/framework/graphics/apngloader.cpp new file mode 100644 index 0000000..3e67fac --- /dev/null +++ b/src/framework/graphics/apngloader.cpp @@ -0,0 +1,1145 @@ +/* + * Based on APNG Disassembler 2.3 + * + * Copyright (c) 2009 Max Stepin + * maxst at users.sourceforge.net + * + * GNU LGPL information + * -------------------- + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "apngloader.h" +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER >= 1300 +#define swap16(data) _byteswap_ushort(data) +#define swap32(data) _byteswap_ulong(data) +#elif __linux__ +#include +#define swap16(data) bswap_16(data) +#define swap32(data) bswap_32(data) +#else +#define swap16(data) (((data >> 8) & 255) | ((data & 255) << 8)) +#define swap32(data) ((swap16(data) << 16) | swap16(data >> 16)) +#endif + +#define PNG_ZBUF_SIZE 32768 + +#define PNG_DISPOSE_OP_NONE 0x00 +#define PNG_DISPOSE_OP_BACKGROUND 0x01 +#define PNG_DISPOSE_OP_PREVIOUS 0x02 + +#define PNG_BLEND_OP_SOURCE 0x00 +#define PNG_BLEND_OP_OVER 0x01 + +#define notabc(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) + +#define ROWBYTES(pixel_bits, width) \ +((pixel_bits) >= 8 ? \ +((width) * (((unsigned int)(pixel_bits)) >> 3)) : \ +(( ((width) * ((unsigned int)(pixel_bits))) + 7) >> 3) ) + +unsigned char png_sign[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + +int mask4[2]={240,15}; +int shift4[2]={4,0}; + +int mask2[4]={192,48,12,3}; +int shift2[4]={6,4,2,0}; + +int mask1[8]={128,64,32,16,8,4,2,1}; +int shift1[8]={7,6,5,4,3,2,1,0}; + +unsigned int keep_original = 1; +unsigned char pal[256][3]; +unsigned char trns[256]; +unsigned int palsize, trnssize; +unsigned int hasTRNS; +unsigned short trns1, trns2, trns3; + +unsigned int read32(std::istream& f1) +{ + unsigned char a, b, c, d; + f1.read((char*)&a, 1); + f1.read((char*)&b, 1); + f1.read((char*)&c, 1); + f1.read((char*)&d, 1); + return ((unsigned int)a<<24)+((unsigned int)b<<16)+((unsigned int)c<<8)+(unsigned int)d; +} + +unsigned short read16(std::istream& f1) +{ + unsigned char a, b; + f1.read((char*)&a, 1); + f1.read((char*)&b, 1); + return ((unsigned short)a<<8)+(unsigned short)b; +} + +unsigned short readshort(unsigned char * p) +{ + return ((unsigned short)(*p)<<8)+(unsigned short)(*(p+1)); +} + +void read_sub_row(unsigned char * row, unsigned int rowbytes, unsigned int bpp) +{ + unsigned int i; + + for (i=bpp; i>1; + for (i=bpp; i>1; + } + else + { + for (i=bpp; i>1; + } +} + +void read_paeth_row(unsigned char * row, unsigned char * prev_row, unsigned int rowbytes, unsigned int bpp) +{ + unsigned int i; + int a, b, c, pa, pb, pc, p; + + if (prev_row) + { + for (i=0; i>1] & mask4[i&1]) >> shift4[i&1]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0x11; *dp2++ = (a<<24) + g*0x111111; } break; + case 2: for (i=0; i>2] & mask2[i&3]) >> shift2[i&3]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0x55; *dp2++ = (a<<24) + g*0x555555; } break; + case 1: for (i=0; i>3] & mask1[i&7]) >> shift1[i&7]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0xFF; *dp2++ = (a<<24) + g*0xFFFFFF; } break; + } + } + else /* PNG_BLEND_OP_OVER */ + { + switch (depth) + { + case 16: for (i=0; i>1] & mask4[i&1]) >> shift4[i&1]; if (g != trns1) { *dp1 = g*0x11; *dp2 = 0xFF000000+g*0x111111; } } break; + case 2: for (i=0; i>2] & mask2[i&3]) >> shift2[i&3]; if (g != trns1) { *dp1 = g*0x55; *dp2 = 0xFF000000+g*0x555555; } } break; + case 1: for (i=0; i>3] & mask1[i&7]) >> shift1[i&7]; if (g != trns1) { *dp1 = g*0xFF; *dp2 = 0xFF000000+g*0xFFFFFF; } } break; + } + } + + src += srcbytes; + dst1 += dstbytes1; + dst2 += dstbytes2; + } +} + +void compose2(unsigned char * dst1, unsigned int dstbytes1, unsigned char * dst2, unsigned int dstbytes2, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) +{ + unsigned int i, j; + unsigned int r, g, b, a; + unsigned char * sp; + unsigned char * dp1; + unsigned int * dp2; + + for (j=0; j>1] & mask4[i&1]) >> shift4[i&1]; break; + case 2: col = (sp[i>>2] & mask2[i&3]) >> shift2[i&3]; break; + case 1: col = (sp[i>>3] & mask1[i&7]) >> shift1[i&7]; break; + } + + b = pal[col][0]; + g = pal[col][1]; + r = pal[col][2]; + a = trns[col]; + + if (bop == PNG_BLEND_OP_SOURCE) + { + *dp1++ = col; + *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; + } + else /* PNG_BLEND_OP_OVER */ + { + if (a == 255) + { + *dp1++ = col; + *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; + } + else + if (a != 0) + { + if ((a2 = (*dp2)>>24)) + { + keep_original = 0; + u = a*255; + v = (255-a)*a2; + al = 255*255-(255-a)*(255-a2); + b2 = ((*dp2)&255); + g2 = (((*dp2)>>8)&255); + r2 = (((*dp2)>>16)&255); + b = (b*u + b2*v)/al; + g = (g*u + g2*v)/al; + r = (r*u + r2*v)/al; + a = al/255; + } + *dp1++ = col; + *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; + } + else + { + dp1++; + dp2++; + } + } + } + src += srcbytes; + dst1 += dstbytes1; + dst2 += dstbytes2; + } +} + +void compose4(unsigned char * dst, unsigned int dstbytes, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) +{ + unsigned int i, j, step; + unsigned int g, a, g2, a2; + int u, v, al; + unsigned char * sp; + unsigned char * dp; + + step = (depth+7)/8; + + for (j=0; j>24)) + { + u = a*255; + v = (255-a)*a2; + al = 255*255-(255-a)*(255-a2); + b2 = ((*dp)&255); + g2 = (((*dp)>>8)&255); + r2 = (((*dp)>>16)&255); + b = (b*u + b2*v)/al; + g = (g*u + g2*v)/al; + r = (r*u + r2*v)/al; + a = al/255; + } + *dp++ = (a << 24) + (r << 16) + (g << 8) + b; + } + else + dp++; + } + } + src += srcbytes; + dst += dstbytes; + } +} + +int load_apng(std::stringstream& file, struct apng_data *apng) +{ + unsigned int i, j; + unsigned int rowbytes; + int imagesize, zbuf_size, zsize, trns_idx; + unsigned int len, chunk/*, crc, seq*/; + unsigned int w, h, w0, h0, x0, y0; + unsigned int frames, loops, first_frame, cur_frame; + unsigned int outrow1, outrow2, outimg1, outimg2; + unsigned short d1, d2; + unsigned char c, dop, bop; + unsigned char channels, depth, pixeldepth, bpp; + unsigned char coltype, compr, filter, interl; + z_stream zstream; + memset(apng, 0, sizeof(struct apng_data)); + + for (i=0; i<256; i++) + { + pal[i][0] = i; + pal[i][1] = i; + pal[i][2] = i; + trns[i] = 255; + } + + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + inflateInit(&zstream); + + frames = 1; + first_frame = 0; + cur_frame = 0; + zsize = 0; + hasTRNS = 0; + trns_idx = -1; + x0 = 0; + y0 = 0; + loops = 0; + bop = PNG_BLEND_OP_SOURCE; + + unsigned char sig[8]; + unsigned char * pOut1; + unsigned char * pOut2; + unsigned char * pTemp; + unsigned char * pData; + unsigned char * pImg1; + unsigned char * pImg2; + unsigned char * pDst1; + unsigned char * pDst2; + unsigned short* frames_delay; + + file.read((char*)sig, 8); + if(!file.eof() && memcmp(sig, png_sign, 8) == 0) { + len = read32(file); + chunk = read32(file); + + if ((len == 13) && (chunk == 0x49484452)) /* IHDR */ + { + w = w0 = read32(file); + h = h0 = read32(file); + file.read((char*)&depth, 1); + file.read((char*)&coltype, 1); + file.read((char*)&compr, 1); + file.read((char*)&filter, 1); + file.read((char*)&interl, 1); + /*crc = */read32(file); + + channels = 1; + if (coltype == 2) + channels = 3; + else if (coltype == 4) + channels = 2; + else if (coltype == 6) + channels = 4; + + pixeldepth = depth*channels; + bpp = (pixeldepth + 7) >> 3; + rowbytes = ROWBYTES(pixeldepth, w); + + imagesize = (rowbytes + 1) * h; + zbuf_size = imagesize + ((imagesize + 7) >> 3) + ((imagesize + 63) >> 6) + 11; + + /* + * We'll render into 2 output buffers, first in original coltype, + * second in RGBA. + * + * It's better to try to keep the original coltype, but if dispose/blend + * operations will make it impossible, then we'll save RGBA version instead. + */ + + outrow1 = w*channels; /* output coltype = input coltype */ + outrow2 = w*4; /* output coltype = RGBA */ + outimg1 = h*outrow1; + outimg2 = h*outrow2; + + pOut1=(unsigned char *)malloc(outimg1); + pOut2=(unsigned char *)malloc(outimg2); + pTemp=(unsigned char *)malloc(imagesize); + pData=(unsigned char *)malloc(zbuf_size); + pImg1=pOut1; + pImg2=pOut2; + frames_delay = NULL; + + /* apng decoding - begin */ + memset(pOut1, 0, outimg1); + memset(pOut2, 0, outimg2); + + while (!file.eof()) + { + len = read32(file); + chunk = read32(file); + + if (chunk == 0x504C5445) /* PLTE */ + { + unsigned int col; + for (i=0; i= 0) memset(pDst1, trns_idx, w0); else keep_original = 0; break; + case 4: memset(pDst1, 0, w0*2); break; + case 6: memset(pDst2, 0, w0*4); break; + } + pDst1 += outrow1; + pDst2 += outrow2; + } + } + } + } + + /*seq = */read32(file); + w0 = read32(file); + h0 = read32(file); + x0 = read32(file); + y0 = read32(file); + d1 = read16(file); + d2 = read16(file); + file.read((char*)&dop, 1); + file.read((char*)&bop, 1); + /*crc = */read32(file); + + if(d2 == 0) + d2 = 100; + frames_delay[cur_frame] = (d1 * 1000)/d2; + + if (cur_frame == 0) + { + bop = PNG_BLEND_OP_SOURCE; + if (dop == PNG_DISPOSE_OP_PREVIOUS) + dop = PNG_DISPOSE_OP_BACKGROUND; + } + + if (!(coltype & 4) && !(hasTRNS)) + bop = PNG_BLEND_OP_SOURCE; + + rowbytes = ROWBYTES(pixeldepth, w0); + cur_frame++; + pImg1 += outimg1; + pImg2 += outimg2; + } + else if (chunk == 0x49444154) /* IDAT */ + { + file.read((char*)(pData + zsize), len); + zsize += len; + /*crc = */read32(file); + } + else if (chunk == 0x66644154) /* fdAT */ + { + /*seq = */read32(file); + len -= 4; + file.read((char*)(pData + zsize), len); + zsize += len; + /*crc = */read32(file); + } + else if (chunk == 0x49454E44) /* IEND */ + { + pDst1 = pImg1 + y0*outrow1 + x0*channels; + pDst2 = pImg2 + y0*outrow2 + x0*4; + unpack(zstream, pTemp, imagesize, pData, zsize, h0, rowbytes, bpp); + switch (coltype) + { + case 0: compose0(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; + case 2: compose2(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; + case 3: compose3(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; + case 4: compose4(pDst1, outrow1, pTemp, rowbytes+1, w0, h0, bop, depth); break; + case 6: compose6( pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; + } + break; + } + else + { + c = (unsigned char)(chunk>>24); + if (notabc(c)) break; + c = (unsigned char)((chunk>>16) & 0xFF); + if (notabc(c)) break; + c = (unsigned char)((chunk>>8) & 0xFF); + if (notabc(c)) break; + c = (unsigned char)(chunk & 0xFF); + if (notabc(c)) break; + + file.seekg(len, std::ios_base::cur); + /*crc = */read32(file); + } + } + /* apng decoding - end */ + + if (coltype == 0) + { + switch (depth) + { + case 4: trns[1] *= 0x11; break; + case 2: trns[1] *= 0x55; break; + case 1: trns[1] *= 0xFF; break; + } + } + + inflateEnd(&zstream); + + apng->bpp = channels; + apng->coltype = coltype; + apng->last_frame = cur_frame; + apng->first_frame = first_frame; + apng->height = h; + apng->width = w; + apng->num_frames = frames; + apng->num_plays = loops; + apng->frames_delay = frames_delay; + apng->pdata = pOut2; + apng->bpp = 4; + apng->coltype = 6; + + if(pData) + free(pData); + if(pTemp) + free(pTemp); + if(pOut1) + free(pOut1); + } else + return -1; + } else + return -1; + + return 0; +} + +void write_chunk(std::ostream& f, const char* name, unsigned char* data, unsigned int length) +{ + unsigned int crc = crc32(0, Z_NULL, 0); + unsigned int len = swap32(length); + + f.write((char*)&len, 4); + f.write(name, 4); + crc = crc32(crc, (const Bytef*)name, 4); + + if(data != NULL && length > 0) { + f.write((char*)data, length); + crc = crc32(crc, data, length); + } + + crc = swap32(crc); + f.write((char*)&crc, 4); +} + +void write_IDATs(std::ostream& f, unsigned char* data, unsigned int length, unsigned int idat_size) +{ + unsigned int z_cmf = data[0]; + + if((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) { + if(length >= 2) { + unsigned int z_cinfo = z_cmf >> 4; + unsigned int half_z_window_size = 1 << (z_cinfo + 7); + + while(idat_size <= half_z_window_size && half_z_window_size >= 256) { + z_cinfo--; + half_z_window_size >>= 1; + } + + z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); + + if(data[0] != (unsigned char)z_cmf) { + data[0] = (unsigned char)z_cmf; + data[1] &= 0xe0; + data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f); + } + } + } + + while(length > 0) { + unsigned int ds = length; + + if(ds > PNG_ZBUF_SIZE) + ds = PNG_ZBUF_SIZE; + + write_chunk(f, "IDAT", data, ds); + + data += ds; + length -= ds; + } +} + +void save_png(std::stringstream& f, unsigned int width, unsigned int height, int channels, unsigned char *pixels) +{ + unsigned int bpp = 4; + unsigned char coltype = 0; + + if(channels == 3) + coltype = 2; + else if (channels == 2) + coltype = 4; + else if (channels == 4) + coltype = 6; + + struct IHDR { + unsigned int mWidth; + unsigned int mHeight; + unsigned char mDepth; + unsigned char mColorType; + unsigned char mCompression; + unsigned char mFilterMethod; + unsigned char mInterlaceMethod; + } ihdr = { swap32(width), swap32(height), 8, coltype, 0, 0, 0 }; + + z_stream zstream1; + z_stream zstream2; + unsigned int i, j; + + unsigned int rowbytes = width * bpp; + unsigned int idat_size = (rowbytes + 1) * height; + unsigned int zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11; + + unsigned char* row_buf = (unsigned char*)malloc(rowbytes + 1); + unsigned char* sub_row = (unsigned char*)malloc(rowbytes + 1); + unsigned char* up_row = (unsigned char*)malloc(rowbytes + 1); + unsigned char* avg_row = (unsigned char*)malloc(rowbytes + 1); + unsigned char* paeth_row = (unsigned char*)malloc(rowbytes + 1); + unsigned char* zbuf1 = (unsigned char*)malloc(zbuf_size); + unsigned char* zbuf2 = (unsigned char*)malloc(zbuf_size); + + if(!row_buf || !sub_row || !up_row || !avg_row || !paeth_row || !zbuf1 || !zbuf2) + return; + + row_buf[0] = 0; + sub_row[0] = 1; + up_row[0] = 2; + avg_row[0] = 3; + paeth_row[0] = 4; + + zstream1.data_type = Z_BINARY; + zstream1.zalloc = Z_NULL; + zstream1.zfree = Z_NULL; + zstream1.opaque = Z_NULL; + deflateInit2(&zstream1, 3, 8, 15, 8, Z_DEFAULT_STRATEGY); + + zstream2.data_type = Z_BINARY; + zstream2.zalloc = Z_NULL; + zstream2.zfree = Z_NULL; + zstream2.opaque = Z_NULL; + deflateInit2(&zstream2, 3, 8, 15, 8, Z_FILTERED); + + int a, b, c, pa, pb, pc, p, v; + unsigned char* prev; + unsigned char* row; + + f.write((char*)png_sign, 8); + write_chunk(f, "IHDR", (unsigned char*)(&ihdr), 13); + + if(palsize > 0) + write_chunk(f, "PLTE", (unsigned char*)(&pal), palsize * 3); + + if(trnssize > 0) + write_chunk(f, "tRNS", trns, trnssize); + + zstream1.next_out = zbuf1; + zstream1.avail_out = zbuf_size; + zstream2.next_out = zbuf2; + zstream2.avail_out = zbuf_size; + + prev = NULL; + row = pixels; + + for(j = 0; j < (unsigned int)height; j++) { + unsigned char* out; + unsigned int sum = 0; + unsigned char* best_row = row_buf; + unsigned int mins = ((unsigned int)(-1)) >> 1; + + out = row_buf + 1; + + for(i = 0; i < rowbytes; i++) { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + + mins = sum; + + sum = 0; + out = sub_row + 1; + + for(i = 0; i < bpp; i++) { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + + for(i = bpp; i < rowbytes; i++) { + v = out[i] = row[i] - row[i - bpp]; + sum += (v < 128) ? v : 256 - v; + + if(sum > mins) break; + } + + if(sum < mins) { + mins = sum; + best_row = sub_row; + } + + if(prev) { + sum = 0; + out = up_row + 1; + + for(i = 0; i < rowbytes; i++) { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + + if(sum > mins) break; + } + + if(sum < mins) { + mins = sum; + best_row = up_row; + } + + sum = 0; + out = avg_row + 1; + + for(i = 0; i < bpp; i++) { + v = out[i] = row[i] - prev[i] / 2; + sum += (v < 128) ? v : 256 - v; + } + + for(i = bpp; i < rowbytes; i++) { + v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2; + sum += (v < 128) ? v : 256 - v; + + if(sum > mins) break; + } + + if(sum < mins) { + mins = sum; + best_row = avg_row; + } + + sum = 0; + out = paeth_row + 1; + + for(i = 0; i < bpp; i++) { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + } + + for(i = bpp; i < rowbytes; i++) { + a = row[i - bpp]; + b = prev[i]; + c = prev[i - bpp]; + p = b - c; + pc = a - c; + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + v = out[i] = row[i] - p; + sum += (v < 128) ? v : 256 - v; + + if(sum > mins) break; + } + + if(sum < mins) { + best_row = paeth_row; + } + } + + zstream1.next_in = row_buf; + zstream1.avail_in = rowbytes + 1; + deflate(&zstream1, Z_NO_FLUSH); + + zstream2.next_in = best_row; + zstream2.avail_in = rowbytes + 1; + deflate(&zstream2, Z_NO_FLUSH); + + prev = row; + row += rowbytes; + } + + deflate(&zstream1, Z_FINISH); + deflate(&zstream2, Z_FINISH); + + if(zstream1.total_out <= zstream2.total_out) + write_IDATs(f, zbuf1, zstream1.total_out, idat_size); + else + write_IDATs(f, zbuf2, zstream2.total_out, idat_size); + + deflateReset(&zstream1); + zstream1.data_type = Z_BINARY; + deflateReset(&zstream2); + zstream2.data_type = Z_BINARY; + + write_chunk(f, "IEND", 0, 0); + + deflateEnd(&zstream1); + deflateEnd(&zstream2); + free(zbuf1); + free(zbuf2); + free(row_buf); + free(sub_row); + free(up_row); + free(avg_row); + free(paeth_row); +} + +void free_apng(struct apng_data *apng) +{ + if(apng->pdata) + free(apng->pdata); + if(apng->frames_delay) + free(apng->frames_delay); +} + diff --git a/src/framework/graphics/apngloader.h b/src/framework/graphics/apngloader.h new file mode 100644 index 0000000..8ce5425 --- /dev/null +++ b/src/framework/graphics/apngloader.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef APNGLOADER_H +#define APNGLOADER_H + +#include + +struct apng_data { + unsigned char *pdata; + unsigned int width; + unsigned int height; + unsigned int first_frame; + unsigned int last_frame; + unsigned char bpp; + unsigned char coltype; + unsigned int num_frames; + unsigned int num_plays; + unsigned short *frames_delay; // each frame delay in ms +}; + +// returns -1 on error, 0 on success +int load_apng(std::stringstream& file, struct apng_data *apng); +void save_png(std::stringstream& file, unsigned int width, unsigned int height, int channels, unsigned char *pixels); +void free_apng(struct apng_data *apng); + +#endif diff --git a/src/framework/graphics/bitmapfont.cpp b/src/framework/graphics/bitmapfont.cpp new file mode 100644 index 0000000..47a422e --- /dev/null +++ b/src/framework/graphics/bitmapfont.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "atlas.h" +#include "bitmapfont.h" +#include "texturemanager.h" +#include "graphics.h" +#include "image.h" + +#include +#include +#include + +void BitmapFont::load(const OTMLNodePtr& fontNode) +{ + OTMLNodePtr textureNode = fontNode->at("texture"); + std::string textureFile = stdext::resolve_path(textureNode->value(), textureNode->source()); + Size glyphSize = fontNode->valueAt("glyph-size"); + m_glyphHeight = fontNode->valueAt("height"); + m_yOffset = fontNode->valueAt("y-offset", 0); + m_firstGlyph = fontNode->valueAt("first-glyph", 32); + m_glyphSpacing = fontNode->valueAt("spacing", Size(0,0)); + int spaceWidth = fontNode->valueAt("space-width", glyphSize.width()); + + if(OTMLNodePtr node = fontNode->get("fixed-glyph-width")) { + for(int glyph = m_firstGlyph; glyph < 256; ++glyph) + m_glyphsSize[glyph] = Size(node->value(), m_glyphHeight); + } else { + calculateGlyphsWidthsAutomatically(Image::load(textureFile), glyphSize); + } + + // 32 is space + m_glyphsSize[32].setWidth(spaceWidth); + + // use 127 as spacer [Width: 1], Important for the current NPC highlighting system + m_glyphsSize[127].setWidth(1); + + // new line actually has a size that will be useful in multiline algorithm + m_glyphsSize[(uchar)'\n'] = Size(1, m_glyphHeight); + + // read custom widths + /* + if(OTMLNodePtr node = fontNode->get("glyph-widths")) { + for(const OTMLNodePtr& child : node->children()) + m_glyphsSize[stdext::safe_cast(child->tag())].setWidth(child->value()); + } + */ + + // load font texture + m_texture = g_textures.getTexture(textureFile); + if (!m_texture) + return; + + Point offset = g_atlas.cacheFont(m_texture); + int numHorizontalGlyphs = m_texture->getSize().width() / glyphSize.width(); + for (int glyph = m_firstGlyph; glyph < 256; ++glyph) { + m_glyphsTextureCoords[glyph].setRect(((glyph - m_firstGlyph) % numHorizontalGlyphs) * glyphSize.width() + offset.x, + ((glyph - m_firstGlyph) / numHorizontalGlyphs) * glyphSize.height() + offset.y, + m_glyphsSize[glyph].width(), + m_glyphHeight); + } + m_texture = g_atlas.get(1); +} + +void BitmapFont::drawText(const std::string& text, const Point& startPos, const Color& color) +{ + Size boxSize = g_painterNew->getResolution() - startPos.toSize(); + Rect screenCoords(startPos, boxSize); + drawText(text, screenCoords, Fw::AlignTopLeft); +} + +void BitmapFont::drawText(const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align, const Color& color) +{ + g_drawQueue->addText(static_self_cast(), text, screenCoords, align, color); +} + +void BitmapFont::drawColoredText(const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align, const std::vector>& colors) +{ + g_drawQueue->addColoredText(static_self_cast(), text, screenCoords, align, colors); +} + +void BitmapFont::calculateDrawTextCoords(CoordsBuffer& coordsBuffer, const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align) +{ + // prevent glitches from invalid rects + if (!screenCoords.isValid() || !m_texture) + return; + + int textLenght = text.length(); + + // map glyphs positions + Size textBoxSize; + const std::vector& glyphsPositions = calculateGlyphsPositions(text, align, &textBoxSize); + + for (int i = 0; i < textLenght; ++i) { + int glyph = (uchar)text[i]; + + // skip invalid glyphs + if (glyph < 32) + continue; + + // calculate initial glyph rect and texture coords + Rect glyphScreenCoords(glyphsPositions[i], m_glyphsSize[glyph]); + Rect glyphTextureCoords = m_glyphsTextureCoords[glyph]; + + // first translate to align position + if (align & Fw::AlignBottom) { + glyphScreenCoords.translate(0, screenCoords.height() - textBoxSize.height()); + } else if (align & Fw::AlignVerticalCenter) { + glyphScreenCoords.translate(0, (screenCoords.height() - textBoxSize.height()) / 2); + } else { // AlignTop + // nothing to do + } + + if (align & Fw::AlignRight) { + glyphScreenCoords.translate(screenCoords.width() - textBoxSize.width(), 0); + } else if (align & Fw::AlignHorizontalCenter) { + glyphScreenCoords.translate((screenCoords.width() - textBoxSize.width()) / 2, 0); + } else { // AlignLeft + // nothing to do + } + + // only render glyphs that are after 0, 0 + if (glyphScreenCoords.bottom() < 0 || glyphScreenCoords.right() < 0) + continue; + + // bound glyph topLeft to 0,0 if needed + if (glyphScreenCoords.top() < 0) { + glyphTextureCoords.setTop(glyphTextureCoords.top() - glyphScreenCoords.top()); + glyphScreenCoords.setTop(0); + } + if (glyphScreenCoords.left() < 0) { + glyphTextureCoords.setLeft(glyphTextureCoords.left() - glyphScreenCoords.left()); + glyphScreenCoords.setLeft(0); + } + + // translate rect to screen coords + glyphScreenCoords.translate(screenCoords.topLeft()); + + // only render if glyph rect is visible on screenCoords + if (!screenCoords.intersects(glyphScreenCoords)) + continue; + + // bound glyph bottomRight to screenCoords bottomRight + if (glyphScreenCoords.bottom() > screenCoords.bottom()) { + glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (screenCoords.bottom() - glyphScreenCoords.bottom())); + glyphScreenCoords.setBottom(screenCoords.bottom()); + } + if (glyphScreenCoords.right() > screenCoords.right()) { + glyphTextureCoords.setRight(glyphTextureCoords.right() + (screenCoords.right() - glyphScreenCoords.right())); + glyphScreenCoords.setRight(screenCoords.right()); + } + + // render glyph + coordsBuffer.addRect(glyphScreenCoords, glyphTextureCoords); + } +} + +const std::vector& BitmapFont::calculateGlyphsPositions(const std::string& text, + Fw::AlignmentFlag align, + Size *textBoxSize) +{ + // for performance reasons we use statics vectors that are allocated on demand + static std::vector glyphsPositions(1); + static std::vector lineWidths(1); + + int textLength = text.length(); + int maxLineWidth = 0; + int lines = 0; + int glyph; + int i; + + // return if there is no text + if(textLength == 0) { + if(textBoxSize) + textBoxSize->resize(0,m_glyphHeight); + return glyphsPositions; + } + + // resize glyphsPositions vector when needed + if(textLength > (int)glyphsPositions.size()) + glyphsPositions.resize(textLength); + + // calculate lines width + if((align & Fw::AlignRight || align & Fw::AlignHorizontalCenter) || textBoxSize) { + lineWidths[0] = 0; + for(i = 0; i< textLength; ++i) { + glyph = (uchar)text[i]; + + if(glyph == (uchar)'\n') { + lines++; + if(lines+1 > (int)lineWidths.size()) + lineWidths.resize(lines+1); + lineWidths[lines] = 0; + } else if(glyph >= 32) { + lineWidths[lines] += m_glyphsSize[glyph].width() ; + if((i+1 != textLength && text[i+1] != '\n')) // only add space if letter is not the last or before a \n. + lineWidths[lines] += m_glyphSpacing.width(); + maxLineWidth = std::max(maxLineWidth, lineWidths[lines]); + } + } + } + + Point virtualPos(0, m_yOffset); + lines = 0; + for(i = 0; i < textLength; ++i) { + glyph = (uchar)text[i]; + + // new line or first glyph + if(glyph == (uchar)'\n' || i == 0) { + if(glyph == (uchar)'\n') { + virtualPos.y += m_glyphHeight + m_glyphSpacing.height(); + lines++; + } + + // calculate start x pos + if(align & Fw::AlignRight) { + virtualPos.x = (maxLineWidth - lineWidths[lines]); + } else if(align & Fw::AlignHorizontalCenter) { + virtualPos.x = (maxLineWidth - lineWidths[lines]) / 2; + } else { // AlignLeft + virtualPos.x = 0; + } + } + + // store current glyph topLeft + glyphsPositions[i] = virtualPos; + + // render only if the glyph is valid + if(glyph >= 32 && glyph != (uchar)'\n') { + virtualPos.x += m_glyphsSize[glyph].width() + m_glyphSpacing.width(); + } + } + + if(textBoxSize) { + textBoxSize->setWidth(maxLineWidth); + textBoxSize->setHeight(virtualPos.y + m_glyphHeight); + } + + return glyphsPositions; +} + +Size BitmapFont::calculateTextRectSize(const std::string& text) +{ + Size size; + calculateGlyphsPositions(text, Fw::AlignTopLeft, &size); + return size; +} + +std::string BitmapFont::wrapText(const std::string& text, int maxWidth, std::vector>* colors) +{ + std::string outText; + outText.reserve(text.size() * 2); // string append optimization + + int lastSeparator = 0, lineLength = 0, wordLength = 0; + for (size_t i = 0; i < text.size(); ++i) { + uchar glyph = (uchar)text[i]; + if (text[i] == '\n' || text[i] == ' ') { + lineLength += wordLength; + if (lineLength > maxWidth) { // too long line with this word + if (text[lastSeparator] == ' ') { + lastSeparator += 1; + updateColors(colors, outText.size(), -1); + } + outText += '\n'; + lineLength = wordLength; + } + for (size_t j = lastSeparator; j < i; ++j) { // copy word + outText += text[j]; + } + if (text[i] == '\n') { // if new line was added reset line length + outText += '\n'; + wordLength = 0; + lineLength = 0; + lastSeparator = i + 1; + } else { // space + wordLength = m_glyphsSize[glyph].width() + m_glyphSpacing.width(); // space + lastSeparator = i; + } + continue; + } + + if (glyph < 32) // invalid character + continue; + + wordLength += m_glyphsSize[glyph].width() + m_glyphSpacing.width(); + if (wordLength > maxWidth) { // too long word, split it + if (lineLength != 0) { // add new line if current one is not empty + outText += '\n'; + } + if (text[lastSeparator] == ' ') // ignore space if it's first character in new line + lastSeparator += 1; + for (size_t j = lastSeparator; j < i; ++j) { // copy word + outText += text[j]; + } + updateColors(colors, outText.size(), 1); + outText += '-'; // word continuation + outText += '\n'; // new line + + wordLength = m_glyphsSize[glyph].width() + m_glyphSpacing.width(); + lineLength = 0; + lastSeparator = i; + } + } + + lineLength += wordLength; + if (lineLength > maxWidth) { // too long line with this word + updateColors(colors, outText.size(), 1); + outText += '\n'; + lineLength = wordLength; + } + for (size_t j = lastSeparator; j < text.size(); ++j) { // copy word + outText += text[j]; + } + return outText; +} + +void BitmapFont::calculateGlyphsWidthsAutomatically(const ImagePtr& image, const Size& glyphSize) +{ + if (!image) + return; + + int numHorizontalGlyphs = image->getSize().width() / glyphSize.width(); + auto texturePixels = image->getPixels(); + + // small AI to auto calculate pixels widths + for (int glyph = m_firstGlyph; glyph < 256; ++glyph) { + Rect glyphCoords(((glyph - m_firstGlyph) % numHorizontalGlyphs) * glyphSize.width(), + ((glyph - m_firstGlyph) / numHorizontalGlyphs) * glyphSize.height(), + glyphSize.width(), + m_glyphHeight); + int width = glyphSize.width(); + for (int x = glyphCoords.left(); x <= glyphCoords.right(); ++x) { + int filledPixels = 0; + // check if all vertical pixels are alpha + for (int y = glyphCoords.top(); y <= glyphCoords.bottom(); ++y) { + if (texturePixels[(y * image->getSize().width() * 4) + (x * 4) + 3] != 0) + filledPixels++; + } + if (filledPixels > 0) + width = x - glyphCoords.left() + 1; + } + // store glyph size + m_glyphsSize[glyph].resize(width, m_glyphHeight); + } +} + +void BitmapFont::updateColors(std::vector>* colors, int pos, int newTextLen) +{ + if (!colors) return; + for (auto& it : *colors) { + if (it.first >= pos) { + it.first += newTextLen; + } + } +} diff --git a/src/framework/graphics/bitmapfont.h b/src/framework/graphics/bitmapfont.h new file mode 100644 index 0000000..ed605c8 --- /dev/null +++ b/src/framework/graphics/bitmapfont.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BITMAPFONT_H +#define BITMAPFONT_H + +#include "declarations.h" +#include "texture.h" + +#include +#include + +class BitmapFont : public stdext::shared_object +{ +public: + BitmapFont(const std::string& name) : m_name(name) { + static int id = 1; + m_id = id++; + } + + /// Load font from otml node + void load(const OTMLNodePtr& fontNode); + + /// Simple text render starting at startPos + void drawText(const std::string& text, const Point& startPos, const Color& color = Color::white); + + /// Advanced text render delimited by a screen region and alignment + void drawText(const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align = Fw::AlignTopLeft, const Color& color = Color::white); + void drawColoredText(const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align, const std::vector>& colors); + + void calculateDrawTextCoords(CoordsBuffer& coordsBuffer, const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align = Fw::AlignTopLeft); + + /// Calculate glyphs positions to use on render, also calculates textBoxSize if wanted + const std::vector& calculateGlyphsPositions(const std::string& text, + Fw::AlignmentFlag align = Fw::AlignTopLeft, + Size* textBoxSize = NULL); + + /// Simulate render and calculate text size + Size calculateTextRectSize(const std::string& text); + + std::string wrapText(const std::string& text, int maxWidth, std::vector>* colors = nullptr); + + int getId() { return m_id; } + std::string getName() { return m_name; } + int getGlyphHeight() { return m_glyphHeight; } + const Rect* getGlyphsTextureCoords() { return m_glyphsTextureCoords; } + const Size* getGlyphsSize() { return m_glyphsSize; } + const TexturePtr& getTexture() { return m_texture; } + int getYOffset() { return m_yOffset; } + Size getGlyphSpacing() { return m_glyphSpacing; } + +private: + /// Calculates each font character by inspecting font bitmap + void calculateGlyphsWidthsAutomatically(const ImagePtr& image, const Size& glyphSize); + void updateColors(std::vector>* colors, int pos, int newTextLen); + + std::string m_name; + int m_glyphHeight; + int m_firstGlyph; + int m_yOffset; + int m_id; + Size m_glyphSpacing; + TexturePtr m_texture; + Rect m_glyphsTextureCoords[256]; + Size m_glyphsSize[256]; +}; + + +#endif + diff --git a/src/framework/graphics/cachedtext.cpp b/src/framework/graphics/cachedtext.cpp new file mode 100644 index 0000000..7bb58f5 --- /dev/null +++ b/src/framework/graphics/cachedtext.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "cachedtext.h" +#include "painter.h" +#include "fontmanager.h" +#include "bitmapfont.h" + +CachedText::CachedText() +{ + m_font = g_fonts.getDefaultFont(); + m_align = Fw::AlignCenter; +} + +void CachedText::draw(const Rect& rect, const Color& color) +{ + if(!m_font) + return; + + if(m_textMustRecache || m_textCachedScreenCoords != rect) { + m_textMustRecache = false; + m_textCachedScreenCoords = rect; + } + + if (m_textColors.empty()) { + m_font->drawText(m_text, m_textCachedScreenCoords, Fw::AlignCenter, color); + } else { + m_font->drawColoredText(m_text, m_textCachedScreenCoords, Fw::AlignCenter, m_textColors); + } +} + +void CachedText::setColoredText(const std::vector& texts) +{ + m_text = ""; + m_textColors.clear(); + for (size_t i = 0, p = 0; i < texts.size() - 1; i += 2) { + Color c(Color::white); + stdext::cast(texts[i + 1], c); + m_text += texts[i]; + for (auto& c : texts[i]) { + if ((uint8)c >= 32) + p += 1; + } + m_textColors.push_back(std::make_pair(p, c)); + } + update(); +} + +void CachedText::update() +{ + if(m_font) + m_textSize = m_font->calculateTextRectSize(m_text); + m_textMustRecache = true; +} + +void CachedText::wrapText(int maxWidth) +{ + if(m_font) { + m_text = m_font->wrapText(m_text, maxWidth, m_textColors.empty() ? nullptr : &m_textColors); + update(); + } +} diff --git a/src/framework/graphics/cachedtext.h b/src/framework/graphics/cachedtext.h new file mode 100644 index 0000000..e52d4bb --- /dev/null +++ b/src/framework/graphics/cachedtext.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CACHEDTEXT_H +#define CACHEDTEXT_H + +#include "declarations.h" +#include "coordsbuffer.h" +#include "drawqueue.h" + +class CachedText +{ +public: + CachedText(); + + void draw(const Rect& rect, const Color& color); + + void wrapText(int maxWidth); + void setFont(const BitmapFontPtr& font) { m_font = font; update(); } + void setText(const std::string& text) { m_textColors.clear(); m_text = text; update(); } + void setColoredText(const std::vector& texts); + void setAlign(Fw::AlignmentFlag align) { m_align = align; update(); } + + Size getTextSize() { return m_textSize; } + std::string getText() const { return m_text; } + BitmapFontPtr getFont() const { return m_font; } + Fw::AlignmentFlag getAlign() { return m_align; } + + bool hasText() { return !m_text.empty(); } + +private: + void update(); + + std::string m_text; + std::vector> m_textColors; + Size m_textSize; + stdext::boolean m_textMustRecache; + Rect m_textCachedScreenCoords; + BitmapFontPtr m_font; + Fw::AlignmentFlag m_align; +}; + +#endif diff --git a/src/framework/graphics/drawcache.cpp b/src/framework/graphics/drawcache.cpp new file mode 100644 index 0000000..03432bf --- /dev/null +++ b/src/framework/graphics/drawcache.cpp @@ -0,0 +1,65 @@ +#include "drawcache.h" + +DrawCache g_drawCache; + +void DrawCache::draw() +{ + release(); + if (m_size == 0) return; + g_painterNew->drawCache(m_destCoord, m_srcCoord, m_color, m_size); + m_size = 0; +} + +void DrawCache::bind() +{ + if (m_bound) return; + g_atlas.bind(); + m_bound = true; +} + +void DrawCache::release() +{ + if (!m_bound) return; + g_atlas.release(); + m_bound = false; +} + +void DrawCache::addRect(const Rect& dest, const Color& color) +{ + static Rect emptyRect(Point(-10, -10), Point(-10, -10)); + addRectRaw(m_destCoord.data() + (m_size * 2), dest); + addRectRaw(m_srcCoord.data() + (m_size * 2), emptyRect); + addColorRaw(color, 6); + m_size += 6; +} + +void DrawCache::addTexturedRect(const Rect& dest, const Rect& src, const Color& color) +{ + addRectRaw(m_destCoord.data() + (m_size * 2), dest); + addRectRaw(m_srcCoord.data() + (m_size * 2), src); + addColorRaw(color, 6); + m_size += 6; +} + +void DrawCache::addCoords(CoordsBuffer& coords, const Color& color) +{ + int size = coords.getVertexCount(); + memcpy(m_destCoord.data() + m_size * 2, coords.getVertexArray(), size * 2 * sizeof(float)); + for (int start = m_size * 2, end = (m_size + size) * 2; start < end; ++start) + m_srcCoord[start] = -10; + addColorRaw(color, size); + m_size += size; +} + +void DrawCache::addTexturedCoords(CoordsBuffer& coords, const Point& offset, const Color& color) +{ + int size = coords.getVertexCount(); + float* src = coords.getTextureCoordArray(); + memcpy(m_destCoord.data() + m_size * 2, coords.getVertexArray(), size * 2 * sizeof(float)); + for (int i = m_size * 2, j = 0, end = (m_size + size) * 2; i < end; ) { + m_srcCoord[i++] = src[j++] + offset.x; + m_srcCoord[i++] = src[j++] + offset.y; + } + addColorRaw(color, size); + m_size += size; +} diff --git a/src/framework/graphics/drawcache.h b/src/framework/graphics/drawcache.h new file mode 100644 index 0000000..dbc6ee6 --- /dev/null +++ b/src/framework/graphics/drawcache.h @@ -0,0 +1,55 @@ +#ifndef DRAW_CACHE +#define DRAW_CACHE + +#include "atlas.h" +#include "coordsbuffer.h" +#include "graphics.h" +#include "painter.h" + +class DrawCache { +public: + static const int MAX_SIZE = 65536; + static const int HALF_MAX_SIZE = MAX_SIZE / 2; + + void draw(); + void bind(); + void release(); + bool hasSpace(int size) { + return size + m_size < MAX_SIZE; + } + inline int getSize() { return m_size; } + void addRect(const Rect& dest, const Color& color); + void addTexturedRect(const Rect& dest, const Rect& src, const Color& color); + void addCoords(CoordsBuffer& coords, const Color& color); + void addTexturedCoords(CoordsBuffer& coords, const Point& offset, const Color& color); + +private: + inline void addRectRaw(float* dest, const Rect& rect) + { + dest[0] = dest[4] = dest[6] = rect.left(); + dest[1] = dest[3] = dest[9] = rect.top(); + dest[2] = dest[8] = dest[10] = rect.right() + 1; + dest[5] = dest[7] = dest[11] = rect.bottom() + 1; + } + inline void addColorRaw(const Color& color, int count) + { + static float c[4]; + c[0] = color.rF(); + c[1] = color.gF(); + c[2] = color.bF(); + c[3] = color.aF(); + for (int start = m_size * 4, end = (m_size + count) * 4; start < end; start += 4) { + memcpy(m_color.data() + start, c, 4 * sizeof(float)); + } + } + + std::vector m_destCoord = std::vector(MAX_SIZE * 2); + std::vector m_srcCoord = std::vector(MAX_SIZE * 2); + std::vector m_color = std::vector(MAX_SIZE * 4); + bool m_bound = false; + int m_size = 0; +}; + +extern DrawCache g_drawCache; + +#endif diff --git a/src/framework/graphics/drawqueue.h b/src/framework/graphics/drawqueue.h new file mode 100644 index 0000000..b64bec5 --- /dev/null +++ b/src/framework/graphics/drawqueue.h @@ -0,0 +1,300 @@ +#ifndef DRAWQUEUE_H +#define DRAWQUEUE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class DrawQueue; +struct DrawQueueItem; + +enum DrawType : uint8_t { + DRAW_ALL = 0, + DRAW_BEFORE_MAP = 1, + DRAW_AFTER_MAP = 2 +}; + +struct DrawQueueItem { + DrawQueueItem(const TexturePtr& texture, const Color& color = Color::white) : + m_texture(texture), m_color(color) {} + virtual ~DrawQueueItem() {}; + virtual void draw() {} + virtual void draw(const Point& pos) {} + virtual bool cache() { return false; } + + TexturePtr m_texture; + Color m_color; +}; + +struct DrawQueueItemTexturedRect : public DrawQueueItem { + DrawQueueItemTexturedRect() : DrawQueueItem(nullptr) {} + DrawQueueItemTexturedRect(const Rect& dest, const TexturePtr& texture, const Rect& src, const Color& color) : + DrawQueueItem(texture, color), m_dest(dest), m_src(src) {}; + + virtual void draw(); + virtual void draw(const Point& pos); + virtual bool cache(); + + Rect m_dest; + Rect m_src; +}; + +struct DrawQueueItemTextureCoords : public DrawQueueItem { + DrawQueueItemTextureCoords(CoordsBuffer& coordsBuffer, const TexturePtr& texture, const Color& color) : + DrawQueueItem(texture, color), m_coordsBuffer(std::move(coordsBuffer)) {}; + + void draw(); + void draw(const Point& pos); + bool cache(); + + CoordsBuffer m_coordsBuffer; +}; + +struct DrawQueueItemFilledRect : public DrawQueueItem { + DrawQueueItemFilledRect(const Rect& rect, const Color& color) : + DrawQueueItem(nullptr, color), m_dest(rect) {}; + bool cache(); + + Rect m_dest; +}; + +struct DrawQueueItemClearRect : public DrawQueueItem { + DrawQueueItemClearRect(const Rect& rect, const Color& color) : + DrawQueueItem(nullptr, color), m_dest(rect) + {}; + void draw(); + + Rect m_dest; +}; + +struct DrawQueueItemFillCoords : public DrawQueueItem { + DrawQueueItemFillCoords(CoordsBuffer& coordsBuffer, const Color& color) : + DrawQueueItem(nullptr, color), m_coordsBuffer(std::move(coordsBuffer)) + {}; + bool cache(); + + CoordsBuffer m_coordsBuffer; +}; + +struct DrawQueueItemText : public DrawQueueItem { + DrawQueueItemText(const Point& point, const TexturePtr& texture, uint64_t hash, const Color& color) : + DrawQueueItem(texture, color), m_point(point), m_hash(hash) + {}; + void draw(); + + Point m_point; + uint64_t m_hash; +}; + +struct DrawQueueItemTextColored : public DrawQueueItem { + DrawQueueItemTextColored(const Point& point, const TexturePtr& texture, uint64_t hash, const std::vector>& colors) : + DrawQueueItem(texture), m_point(point), m_hash(hash), m_colors(colors) + {}; + void draw(); + + Point m_point; + uint64_t m_hash; + std::vector> m_colors; +}; + +struct DrawQueueItemOutfit : public DrawQueueItemTexturedRect { + DrawQueueItemOutfit(const Rect& rect, const TexturePtr& texture, const Rect& src, const Point& offset, int32_t colors, const Color& color) : + DrawQueueItemTexturedRect(rect, texture, src, color), m_offset(offset), m_colors(colors) + { }; + + void draw(const Point& pos) override; + bool cache() override; + + Point m_offset; + int32_t m_colors; +}; + +struct DrawQueueCondition { + DrawQueueCondition(size_t start, size_t end) : + m_start(start), m_end(end) {} + virtual ~DrawQueueCondition() = default; + + virtual void start(DrawQueue*) = 0; + virtual void end(DrawQueue*) = 0; + + size_t m_start; + size_t m_end; +}; + +struct DrawQueueConditionClip : public DrawQueueCondition { + DrawQueueConditionClip(size_t start, size_t end, const Rect& rect) : + DrawQueueCondition(start, end), m_rect(rect) {} + + void start(DrawQueue* queue) override; + void end(DrawQueue* queue) override; + + Rect m_rect; + Rect m_prevClip; +}; + +struct DrawQueueConditionRotation : public DrawQueueCondition { + DrawQueueConditionRotation(size_t start, size_t end, const Point& center, float angle) : + DrawQueueCondition(start, end), m_center(center), m_angle(angle) {} + + void start(DrawQueue* queue) override; + void end(DrawQueue* queue) override; + + Point m_center; + float m_angle; +}; + +struct DrawQueueConditionMark : public DrawQueueCondition { + DrawQueueConditionMark(size_t start, size_t end, const Color& color) : + DrawQueueCondition(start, end), m_color(color) + {} + + void start(DrawQueue* queue) override; + void end(DrawQueue* queue) override; + + Color m_color; +}; + +class DrawQueue { +public: + DrawQueue() = default; + DrawQueue(const DrawQueue&) = delete; + DrawQueue& operator= (const DrawQueue&) = delete; + ~DrawQueue() { + for (auto& item : m_queue) + delete item; + m_queue.clear(); + for (auto& condition : m_conditions) + delete condition; + m_conditions.clear(); + } + + void draw(DrawType drawType = DRAW_ALL); + + void add(DrawQueueItem* item) + { + m_queue.push_back(item); + } + DrawQueueItemTexturedRect* addTexturedRect(const Rect& dest, const TexturePtr& texture, const Rect& src, const Color& color = Color::white) + { + DrawQueueItemTexturedRect* item(new DrawQueueItemTexturedRect(dest, texture, src, color)); + m_queue.push_back(item); + return item; + } + void addTextureCoords(CoordsBuffer& coords, const TexturePtr& texture, const Color& color = Color::white) + { + m_queue.push_back(new DrawQueueItemTextureCoords(coords, texture, color)); + } + void addFilledRect(const Rect& dest, const Color& color = Color::white) + { + m_queue.push_back(new DrawQueueItemFilledRect(dest, color)); + } + void addFillCoords(CoordsBuffer& coords, const Color& color = Color::white) + { + m_queue.push_back(new DrawQueueItemFillCoords(coords, color)); + } + void addClearRect(const Rect& dest, const Color& color = Color::white) + { + m_queue.push_back(new DrawQueueItemClearRect(dest, color)); + } + void addText(BitmapFontPtr font, const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align = Fw::AlignTopLeft, const Color& color = Color::white); + void addColoredText(BitmapFontPtr font, const std::string& text, const Rect& screenCoords, Fw::AlignmentFlag align, const std::vector>& colors); + DrawQueueItemOutfit* addOutfit(const Rect& dest, const TexturePtr& texture, const Rect& src, const Point& offset, int colors, const Color& color = Color::white) + { + DrawQueueItemOutfit* outfit = new DrawQueueItemOutfit(dest, texture, src, offset, colors, color); + m_queue.push_back(outfit); + return outfit; + } + + void addFilledTriangle(const Point& a, const Point& b, const Point& c, const Color& color = Color::white) + { + if (a == b || a == c || b == c) + return; + + CoordsBuffer coordsBuffer; + coordsBuffer.addTriangle(a, b, c); + addFillCoords(coordsBuffer, color); + } + void addBoundingRect(const Rect& dest, int innerLineWidth, const Color& color = Color::white) + { + if (dest.isEmpty() || innerLineWidth == 0) + return; + + CoordsBuffer coordsBuffer; + coordsBuffer.addBoudingRect(dest, innerLineWidth); + addFillCoords(coordsBuffer, color); + } + + void setFrameBuffer(const Rect& dest, const Size& size, const Rect& src); + bool hasFrameBuffer() + { + return m_useFrameBuffer; + } + Rect getFrameBufferDest() + { + return m_frameBufferDest; + } + Size getFrameBufferSize() + { + return m_frameBufferSize; + } + Rect getFrameBufferSrc() + { + return m_frameBufferSrc; + } + + size_t size() + { + return m_queue.size(); + } + + void setOpacity(size_t start, float opacity) + { + for (size_t i = start; i < m_queue.size(); ++i) { + m_queue[i]->m_color = m_queue[i]->m_color.opacity(opacity); + } + } + + void setClip(size_t start, const Rect& clip) + { + if (start == m_queue.size()) return; + m_conditions.push_back(new DrawQueueConditionClip(start, m_queue.size(), clip)); + } + + void setRotation(size_t start, const Point& center, float angle) + { + if (start == m_queue.size() || angle == 0) return; + m_conditions.push_back(new DrawQueueConditionRotation(start, m_queue.size(), center, angle)); + } + + void setMark(size_t start, const Color& color) + { + if (start == m_queue.size()) return; + m_conditions.push_back(new DrawQueueConditionMark(start, m_queue.size(), color)); + } + + void markMapPosition() + { + mapPosition = m_queue.size(); + } + void correctOutfit(const Rect& dest, int fromPos); + +private: + std::vector m_queue; + std::vector m_conditions; + Size m_frameBufferSize; + Rect m_frameBufferDest, m_frameBufferSrc; + size_t mapPosition = 0; + bool m_useFrameBuffer = false; + float m_scaling = 1.f; + + friend struct DrawQueueConditionMark; +}; + +extern std::shared_ptr g_drawQueue; + +#endif \ No newline at end of file diff --git a/src/framework/graphics/fontmanager.cpp b/src/framework/graphics/fontmanager.cpp new file mode 100644 index 0000000..e7b3189 --- /dev/null +++ b/src/framework/graphics/fontmanager.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "atlas.h" +#include "fontmanager.h" +#include "texture.h" + +#include +#include +#include + +FontManager g_fonts; + +FontManager::FontManager() +{ + m_defaultFont = BitmapFontPtr(new BitmapFont("emptyfont")); +} + +void FontManager::terminate() +{ + m_fonts.clear(); + m_defaultFont = nullptr; +} + +void FontManager::clearFonts() +{ + m_fonts.clear(); + m_defaultFont = BitmapFontPtr(new BitmapFont("emptyfont")); +} + +void FontManager::importFont(std::string file) +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&FontManager::importFont, this, file)); + return; + } + try { + file = g_resources.guessFilePath(file, "otfont"); + + OTMLDocumentPtr doc = OTMLDocument::parse(file); + OTMLNodePtr fontNode = doc->at("Font"); + + std::string name = fontNode->valueAt("name"); + if (fontExists(name)) + return; + + // remove any font with the same name + for(auto it = m_fonts.begin(); it != m_fonts.end(); ++it) { + if((*it)->getName() == name) { + m_fonts.erase(it); + break; + } + } + + BitmapFontPtr font(new BitmapFont(name)); + font->load(fontNode); + m_fonts.push_back(font); + + // set as default if needed + if(!m_defaultFont || fontNode->valueAt("default", false)) + m_defaultFont = font; + + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Unable to load font from file '%s': %s", file, e.what())); + } +} + +bool FontManager::fontExists(const std::string& fontName) +{ + for(const BitmapFontPtr& font : m_fonts) { + if(font->getName() == fontName) + return true; + } + return false; +} + +BitmapFontPtr FontManager::getFont(const std::string& fontName) +{ + // find font by name + for(const BitmapFontPtr& font : m_fonts) { + if(font->getName() == fontName) + return font; + } + + // when not found, fallback to default font + g_logger.error(stdext::format("font '%s' not found", fontName)); + return getDefaultFont(); +} diff --git a/src/framework/graphics/fontmanager.h b/src/framework/graphics/fontmanager.h new file mode 100644 index 0000000..b4d2687 --- /dev/null +++ b/src/framework/graphics/fontmanager.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FONTMANAGER_H +#define FONTMANAGER_H + +#include "bitmapfont.h" + +//@bindsingleton g_fonts +class FontManager +{ +public: + FontManager(); + + void terminate(); + void clearFonts(); + + void importFont(std::string file); + + bool fontExists(const std::string& fontName); + BitmapFontPtr getFont(const std::string& fontName); + BitmapFontPtr getDefaultFont() { return m_defaultFont; } + + void setDefaultFont(const std::string& fontName) { m_defaultFont = getFont(fontName); } + +private: + std::vector m_fonts; + BitmapFontPtr m_defaultFont; +}; + +extern FontManager g_fonts; + +#endif diff --git a/src/framework/graphics/paintershaderprogram.cpp b/src/framework/graphics/paintershaderprogram.cpp new file mode 100644 index 0000000..3fe6bd1 --- /dev/null +++ b/src/framework/graphics/paintershaderprogram.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "paintershaderprogram.h" +#include "painter.h" +#include "texture.h" +#include "texturemanager.h" +#include "graphics.h" +#include +#include + +PainterShaderProgram::PainterShaderProgram() +{ + m_startTime = g_clock.seconds(); + m_opacity = 1; + m_depth = 0; + m_color = Color::white; + m_time = 0; +} + +void PainterShaderProgram::setupUniforms() +{ + bindUniformLocation(TRANSFORM_MATRIX_UNIFORM, "u_TransformMatrix"); + bindUniformLocation(PROJECTION_MATRIX_UNIFORM, "u_ProjectionMatrix"); + bindUniformLocation(TEXTURE_MATRIX_UNIFORM, "u_TextureMatrix"); + + bindUniformLocation(COLOR_UNIFORM, "u_Color"); + bindUniformLocation(OPACITY_UNIFORM, "u_Opacity"); + bindUniformLocation(DEPTH_UNIFORM, "u_Depth"); + bindUniformLocation(TIME_UNIFORM, "u_Time"); + + bindUniformLocation(TEX0_UNIFORM, "u_Tex0"); + bindUniformLocation(TEX1_UNIFORM, "u_Tex1"); + bindUniformLocation(TEX2_UNIFORM, "u_Tex2"); + bindUniformLocation(TEX3_UNIFORM, "u_Tex3"); + + bindUniformLocation(ATLAS_TEX0_UNIFORM, "u_Atlas"); + bindUniformLocation(ATLAS_TEX1_UNIFORM, "u_Fonts"); + + bindUniformLocation(RESOLUTION_UNIFORM, "u_Resolution"); + bindUniformLocation(OFFSET_UNIFORM, "u_Offset"); + + // VALUES + setUniformValue(TRANSFORM_MATRIX_UNIFORM, m_transformMatrix); + setUniformValue(PROJECTION_MATRIX_UNIFORM, m_projectionMatrix); + setUniformValue(TEXTURE_MATRIX_UNIFORM, m_textureMatrix); + + if (!m_useColorMatrix) { + setUniformValue(COLOR_UNIFORM, m_color); + } + setUniformValue(OPACITY_UNIFORM, m_opacity); + setUniformValue(TIME_UNIFORM, m_time); + setUniformValue(DEPTH_UNIFORM, m_depth); + + setUniformValue(TEX0_UNIFORM, 0); + setUniformValue(TEX1_UNIFORM, 1); + setUniformValue(TEX2_UNIFORM, 2); + setUniformValue(TEX3_UNIFORM, 3); + + setUniformValue(ATLAS_TEX0_UNIFORM, 6); + setUniformValue(ATLAS_TEX1_UNIFORM, 7); + + setUniformValue(RESOLUTION_UNIFORM, (float)m_resolution.width(), (float)m_resolution.height()); + setUniformValue(OFFSET_UNIFORM, (float)m_offset.x, (float)m_offset.y); +} + +void PainterShaderProgram::link() +{ + m_startTime = g_clock.seconds(); + bindAttributeLocation(VERTEX_ATTR, "a_Vertex"); + bindAttributeLocation(TEXCOORD_ATTR, "a_TexCoord"); + bindAttributeLocation(DEPTH_ATTR, "a_Depth"); + bindAttributeLocation(COLOR_ATTR, "a_Color"); + bindAttributeLocation(DEPTH_TEXCOORD_ATTR, "a_DepthTexCoord"); + ShaderProgram::link(); + bind(); + setupUniforms(); + release(); + g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__); +} + +void PainterShaderProgram::setTransformMatrix(const Matrix3& transformMatrix) +{ + if (transformMatrix == m_transformMatrix) + return; + + bind(); + setUniformValue(TRANSFORM_MATRIX_UNIFORM, transformMatrix); + m_transformMatrix = transformMatrix; +} + +void PainterShaderProgram::setProjectionMatrix(const Matrix3& projectionMatrix) +{ + if (projectionMatrix == m_projectionMatrix) + return; + + bind(); + setUniformValue(PROJECTION_MATRIX_UNIFORM, projectionMatrix); + m_projectionMatrix = projectionMatrix; +} + +void PainterShaderProgram::setTextureMatrix(const Matrix3& textureMatrix) +{ + if (textureMatrix == m_textureMatrix) + return; + + bind(); + setUniformValue(TEXTURE_MATRIX_UNIFORM, textureMatrix); + m_textureMatrix = textureMatrix; +} + +void PainterShaderProgram::setColor(const Color& color) +{ + if (color == m_color || m_useColorMatrix) + return; + + bind(); + setUniformValue(COLOR_UNIFORM, color); + m_color = color; +} + +void PainterShaderProgram::setMatrixColor(const Matrix4& colors) +{ + bind(); + setUniformValue(COLOR_UNIFORM, colors); +} + + +void PainterShaderProgram::setOpacity(float opacity) +{ + if (m_opacity == opacity) + return; + + bind(); + setUniformValue(OPACITY_UNIFORM, opacity); + m_opacity = opacity; +} + +#ifdef WITH_DEPTH_BUFFER +void PainterShaderProgram::setDepth(float depth) +{ + if (depth < 0.) + depth = 0.; + + if (m_depth == depth) + return; + + bind(); + setUniformValue(DEPTH_UNIFORM, depth); + m_depth = depth; +} +#endif + +void PainterShaderProgram::setResolution(const Size& resolution) +{ + if (m_resolution == resolution) + return; + + bind(); + setUniformValue(RESOLUTION_UNIFORM, (float)resolution.width(), (float)resolution.height()); + m_resolution = resolution; +} + +void PainterShaderProgram::setOffset(const Point& offset) +{ + if (m_offset == offset) + return; + + bind(); + m_offset = offset; + setUniformValue(OFFSET_UNIFORM, (float)m_offset.x, (float)m_offset.y); +} + + +void PainterShaderProgram::updateTime() +{ + float time = g_clock.seconds() - m_startTime; + if (m_time == time) + return; + + bind(); + setUniformValue(TIME_UNIFORM, time); + m_time = time; +} + +void PainterShaderProgram::addMultiTexture(const std::string& file) +{ + if (m_multiTextures.size() > 3) + g_logger.error("cannot add more multi textures to shader, the max is 3"); + + TexturePtr texture = g_textures.getTexture(file); + if (!texture) + return; + + texture->setSmooth(true); + texture->setRepeat(true); + + m_multiTextures.push_back(texture); +} + +void PainterShaderProgram::bindMultiTextures() +{ + if (m_multiTextures.size() == 0) + return; + + int i = 1; + for (const TexturePtr& tex : m_multiTextures) { + glActiveTexture(GL_TEXTURE0 + i++); + glBindTexture(GL_TEXTURE_2D, tex->getId()); + } + + glActiveTexture(GL_TEXTURE0); +} + +void PainterShaderProgram::clearMultiTextures() +{ + m_multiTextures.clear(); +} diff --git a/src/framework/graphics/paintershaderprogram.h b/src/framework/graphics/paintershaderprogram.h new file mode 100644 index 0000000..dc649dd --- /dev/null +++ b/src/framework/graphics/paintershaderprogram.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PAINTERSHADERPROGRAM_H +#define PAINTERSHADERPROGRAM_H + +#include "shaderprogram.h" +#include "coordsbuffer.h" +#include + +class PainterShaderProgram : public ShaderProgram { +protected: + enum { + VERTEX_ATTR = 0, + TEXCOORD_ATTR = 1, + DEPTH_ATTR = 2, + COLOR_ATTR = 3, + DEPTH_TEXCOORD_ATTR = 4, + + PROJECTION_MATRIX_UNIFORM = 0, + TEXTURE_MATRIX_UNIFORM = 1, + TRANSFORM_MATRIX_UNIFORM = 2, + + COLOR_UNIFORM = 3, + OPACITY_UNIFORM = 4, + TIME_UNIFORM = 5, + DEPTH_UNIFORM = 6, + + TEX0_UNIFORM = 7, + TEX1_UNIFORM = 8, + TEX2_UNIFORM = 9, + TEX3_UNIFORM = 10, + ATLAS_TEX0_UNIFORM = 11, + ATLAS_TEX1_UNIFORM = 12, + + RESOLUTION_UNIFORM = 13, + OFFSET_UNIFORM = 14 + }; + + friend class Painter; + + virtual void setupUniforms(); + +public: + PainterShaderProgram(); + + void link(); + + void setTransformMatrix(const Matrix3& transformMatrix); + void setProjectionMatrix(const Matrix3& projectionMatrix); + void setTextureMatrix(const Matrix3& textureMatrix); + void setColor(const Color& color); + void setMatrixColor(const Matrix4& colors); + void setOpacity(float opacity); + void setDepth(float depth); + void setResolution(const Size& resolution); + void setOffset(const Point& offset); + void updateTime(); + + void addMultiTexture(const std::string& file); + void bindMultiTextures(); + void clearMultiTextures(); + + void enableColorMatrix() + { + m_useColorMatrix = true; + } + +private: + float m_startTime; + + Color m_color; + float m_opacity; + float m_depth; + Matrix3 m_transformMatrix; + Matrix3 m_projectionMatrix; + Matrix3 m_textureMatrix; + Size m_resolution; + Point m_offset; + float m_time; + std::vector m_multiTextures; + bool m_useColorMatrix = false; +}; + +#endif diff --git a/src/framework/graphics/shader.cpp b/src/framework/graphics/shader.cpp new file mode 100644 index 0000000..739cd0c --- /dev/null +++ b/src/framework/graphics/shader.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shader.h" +#include "graphics.h" + +#include +#include + +Shader::Shader(Shader::ShaderType shaderType) +{ + m_shaderType = shaderType; + switch(shaderType) { + case Vertex: + m_shaderId = glCreateShader(GL_VERTEX_SHADER); + break; + case Fragment: + m_shaderId = glCreateShader(GL_FRAGMENT_SHADER); + break; + } + + if(!m_shaderId) + g_logger.fatal("Unable to create GL shader"); +} + +Shader::~Shader() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + if(g_graphics.ok()) + glDeleteShader(m_shaderId); +} + +bool Shader::compileSourceCode(const std::string& sourceCode) +{ +#ifdef OPENGL_ES + static const char *qualifierDefines = + "precision mediump float;\n"; + std::string code = qualifierDefines; + code.append(sourceCode); + const char* c_source = code.c_str(); +#else + const char* c_source = sourceCode.c_str(); +#endif + glShaderSource(m_shaderId, 1, &c_source, NULL); + glCompileShader(m_shaderId); + + int res = GL_FALSE; + glGetShaderiv(m_shaderId, GL_COMPILE_STATUS, &res); + return (res == GL_TRUE); +} + +bool Shader::compileSourceFile(const std::string& sourceFile) +{ + try { + std::string sourceCode = g_resources.readFileContents(sourceFile); + return compileSourceCode(sourceCode); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("unable to load shader source form file '%s': %s", sourceFile, e.what())); + } + return false; +} + +std::string Shader::log() +{ + std::string infoLog; + int infoLogLength = 0; + glGetShaderiv(m_shaderId, GL_INFO_LOG_LENGTH, &infoLogLength); + if(infoLogLength > 1) { + std::vector buf(infoLogLength); + glGetShaderInfoLog(m_shaderId, infoLogLength-1, NULL, &buf[0]); + infoLog = &buf[0]; + } + return infoLog; +} diff --git a/src/framework/graphics/shader.h b/src/framework/graphics/shader.h new file mode 100644 index 0000000..b8c7e2b --- /dev/null +++ b/src/framework/graphics/shader.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SHADER_H +#define SHADER_H + +#include "declarations.h" + +class Shader : public stdext::shared_object +{ +public: + enum ShaderType { + Vertex, + Fragment + }; + + Shader(ShaderType shaderType); + ~Shader(); + + bool compileSourceCode(const std::string& sourceCode); + bool compileSourceFile(const std::string& sourceFile); + std::string log(); + + uint getShaderId() { return m_shaderId; } + ShaderType getShaderType() { return m_shaderType; } + +private: + uint m_shaderId; + ShaderType m_shaderType; +}; + +#endif diff --git a/src/framework/graphics/shaderprogram.cpp b/src/framework/graphics/shaderprogram.cpp new file mode 100644 index 0000000..d9643f8 --- /dev/null +++ b/src/framework/graphics/shaderprogram.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shaderprogram.h" +#include "graphics.h" + +#include + +uint ShaderProgram::m_currentProgram = 0; + +ShaderProgram::ShaderProgram() +{ + m_linked = false; + m_programId = glCreateProgram(); + m_uniformLocations.fill(-1); + if(!m_programId) + g_logger.fatal("Unable to create GL shader program"); +} + +ShaderProgram::~ShaderProgram() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + if(g_graphics.ok()) + glDeleteProgram(m_programId); +} + +PainterShaderProgramPtr ShaderProgram::create(const std::string& vertexShader, const std::string& fragmentShader, bool colorMatrix) +{ + PainterShaderProgramPtr program(new PainterShaderProgram); + if (!program) + g_logger.fatal(stdext::format("Cant creatre shader: \n%s", vertexShader)); + program->addShaderFromSourceCode(Shader::Vertex, vertexShader); + program->addShaderFromSourceCode(Shader::Fragment, fragmentShader); + if (colorMatrix) { + program->enableColorMatrix(); + } + program->link(); + g_graphics.checkForError(__FUNCTION__, vertexShader + "\n" + fragmentShader, __LINE__); + return program; +} + + +bool ShaderProgram::addShader(const ShaderPtr& shader) { + glAttachShader(m_programId, shader->getShaderId()); + m_linked = false; + m_shaders.push_back(shader); + return true; +} + +bool ShaderProgram::addShaderFromSourceCode(Shader::ShaderType shaderType, const std::string& sourceCode) { + ShaderPtr shader(new Shader(shaderType)); + if(!shader->compileSourceCode(sourceCode)) { + g_logger.fatal(stdext::format("failed to compile shader: %s", shader->log())); + return false; + } + return addShader(shader); +} + +bool ShaderProgram::addShaderFromSourceFile(Shader::ShaderType shaderType, const std::string& sourceFile) { + ShaderPtr shader(new Shader(shaderType)); + if(!shader->compileSourceFile(sourceFile)) { + g_logger.fatal(stdext::format("failed to compile shader: %s", shader->log())); + return false; + } + return addShader(shader); +} + +void ShaderProgram::removeShader(const ShaderPtr& shader) +{ + auto it = std::find(m_shaders.begin(), m_shaders.end(), shader); + if(it == m_shaders.end()) + return; + + glDetachShader(m_programId, shader->getShaderId()); + m_shaders.erase(it); + m_linked = false; +} + +void ShaderProgram::removeAllShaders() +{ + while(!m_shaders.empty()) + removeShader(m_shaders.front()); +} + +void ShaderProgram::link() +{ + if(m_linked) + return; + + glLinkProgram(m_programId); + + int value = GL_FALSE; + glGetProgramiv(m_programId, GL_LINK_STATUS, &value); + m_linked = (value != GL_FALSE); + if (m_linked) { + return; + } + + GLint maxLength = 0; + glGetProgramiv(m_programId, GL_INFO_LOG_LENGTH, &maxLength); + std::vector infoLog(maxLength); + glGetProgramInfoLog(m_programId, maxLength, &maxLength, &infoLog[0]); + g_logger.fatal(stdext::format("Program %i linking error (%i): %s - %s - %s %s\nExtensions: %s", m_programId, infoLog.size(), + std::string(infoLog.begin(), infoLog.end()).c_str(), log().c_str(), + g_graphics.getRenderer(), g_graphics.getVersion(), g_graphics.getExtensions())); +} + +bool ShaderProgram::bind() +{ + if(m_currentProgram != m_programId) { + if (!m_linked) { + link(); + } + glUseProgram(m_programId); + m_currentProgram = m_programId; + } + return true; +} + +void ShaderProgram::release() +{ + if(m_currentProgram != 0) { + m_currentProgram = 0; + glUseProgram(0); + } +} + +std::string ShaderProgram::log() +{ + std::string infoLog; + int infoLogLength = 0; + glGetProgramiv(m_programId, GL_INFO_LOG_LENGTH, &infoLogLength); + if(infoLogLength > 1) { + std::vector buf(infoLogLength); + glGetShaderInfoLog(m_programId, infoLogLength-1, NULL, &buf[0]); + infoLog = &buf[0]; + } + return infoLog; +} + +int ShaderProgram::getAttributeLocation(const char* name) +{ + return glGetAttribLocation(m_programId, name); +} + +void ShaderProgram::bindAttributeLocation(int location, const char* name) +{ + return glBindAttribLocation(m_programId, location, name); +} + +void ShaderProgram::bindUniformLocation(int location, const char* name) +{ + VALIDATE(m_linked); + VALIDATE(location >= 0 && location < MAX_UNIFORM_LOCATIONS); + m_uniformLocations[location] = glGetUniformLocation(m_programId, name); +} diff --git a/src/framework/graphics/shaderprogram.h b/src/framework/graphics/shaderprogram.h new file mode 100644 index 0000000..aef613d --- /dev/null +++ b/src/framework/graphics/shaderprogram.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SHADERPROGRAM_H +#define SHADERPROGRAM_H + +#include "shader.h" +#include + + // @bindclass +class ShaderProgram : public LuaObject { + enum { + MAX_UNIFORM_LOCATIONS = 30 + }; + +public: + ShaderProgram(); + ~ShaderProgram(); + + static PainterShaderProgramPtr create(const std::string& vertexShader, const std::string& fragmentShader, bool colorMatrix = false); + + bool addShader(const ShaderPtr& shader); + bool addShaderFromSourceCode(Shader::ShaderType shaderType, const std::string& sourceCode); + bool addShaderFromSourceFile(Shader::ShaderType shaderType, const std::string& sourceFile); + void removeShader(const ShaderPtr& shader); + void removeAllShaders(); + + virtual void link(); + bool bind(); + static void release(); + std::string log(); + + static void disableAttributeArray(int location) { glDisableVertexAttribArray(location); } + static void enableAttributeArray(int location) { glEnableVertexAttribArray(location); } + void disableAttributeArray(const char* name) { glDisableVertexAttribArray(getAttributeLocation(name)); } + void enableAttributeArray(const char* name) { glEnableVertexAttribArray(getAttributeLocation(name)); } + + int getAttributeLocation(const char* name); + void bindAttributeLocation(int location, const char* name); + void bindUniformLocation(int location, const char* name); + + void setAttributeArray(int location, const float* values, int size, int stride = 0) { glVertexAttribPointer(location, size, GL_FLOAT, GL_FALSE, stride, values); } + void setAttributeValue(int location, float value) { glVertexAttrib1f(location, value); } + void setAttributeValue(int location, float x, float y) { glVertexAttrib2f(location, x, y); } + void setAttributeValue(int location, float x, float y, float z) { glVertexAttrib3f(location, x, y, z); } + void setAttributeArray(const char* name, const float* values, int size, int stride = 0) { glVertexAttribPointer(getAttributeLocation(name), size, GL_FLOAT, GL_FALSE, stride, values); } + void setAttributeValue(const char* name, float value) { glVertexAttrib1f(getAttributeLocation(name), value); } + void setAttributeValue(const char* name, float x, float y) { glVertexAttrib2f(getAttributeLocation(name), x, y); } + void setAttributeValue(const char* name, float x, float y, float z) { glVertexAttrib3f(getAttributeLocation(name), x, y, z); } + + void setUniformValue(int location, const Color& color) { glUniform4f(m_uniformLocations[location], color.rF(), color.gF(), color.bF(), color.aF()); } + void setUniformValue(int location, int value) { glUniform1i(m_uniformLocations[location], value); } + void setUniformValue(int location, float value) { glUniform1f(m_uniformLocations[location], value); } + void setUniformValue(int location, float x, float y) { glUniform2f(m_uniformLocations[location], x, y); } + void setUniformValue(int location, float x, float y, float z) { glUniform3f(m_uniformLocations[location], x, y, z); } + void setUniformValue(int location, float x, float y, float z, float w) { glUniform4f(m_uniformLocations[location], x, y, z, w); } + void setUniformValue(int location, const Matrix2& mat) { glUniformMatrix2fv(m_uniformLocations[location], 1, GL_FALSE, mat.data()); } + void setUniformValue(int location, const Matrix3& mat) { glUniformMatrix3fv(m_uniformLocations[location], 1, GL_FALSE, mat.data()); } + void setUniformValue(int location, const Matrix4& mat) { glUniformMatrix4fv(m_uniformLocations[location], 1, GL_FALSE, mat.data()); } + void setUniformValue(int location, int count, const int* value) { glUniform1iv(m_uniformLocations[location], count, value); } + void setUniformValue(const char* name, const Color& color) { glUniform4f(glGetUniformLocation(m_programId, name), color.rF(), color.gF(), color.bF(), color.aF()); } + void setUniformValue(const char* name, int value) { glUniform1i(glGetUniformLocation(m_programId, name), value); } + void setUniformValue(const char* name, float value) { glUniform1f(glGetUniformLocation(m_programId, name), value); } + void setUniformValue(const char* name, float x, float y) { glUniform2f(glGetUniformLocation(m_programId, name), x, y); } + void setUniformValue(const char* name, float x, float y, float z) { glUniform3f(glGetUniformLocation(m_programId, name), x, y, z); } + void setUniformValue(const char* name, float x, float y, float z, float w) { glUniform4f(glGetUniformLocation(m_programId, name), x, y, z, w); } + void setUniformValue(const char* name, const Matrix2& mat) { glUniformMatrix2fv(glGetUniformLocation(m_programId, name), 1, GL_FALSE, mat.data()); } + void setUniformValue(const char* name, const Matrix3& mat) { glUniformMatrix3fv(glGetUniformLocation(m_programId, name), 1, GL_FALSE, mat.data()); } + void setUniformValue(const char* name, const Matrix4& mat) { glUniformMatrix4fv(glGetUniformLocation(m_programId, name), 1, GL_FALSE, mat.data()); } + // TODO: Point, PointF, Color, Size, SizeF ? + + bool isLinked() { return m_linked; } + uint getProgramId() { return m_programId; } + ShaderList getShaders() { return m_shaders; } + +private: + bool m_linked; + uint m_programId; + static uint m_currentProgram; + ShaderList m_shaders; + std::array m_uniformLocations; +}; + +#endif diff --git a/src/framework/graphics/shaders/newshader.h b/src/framework/graphics/shaders/newshader.h new file mode 100644 index 0000000..84f3c98 --- /dev/null +++ b/src/framework/graphics/shaders/newshader.h @@ -0,0 +1,61 @@ +#ifndef NEWSHADER_H +#define NEWSHADER_H + +#include +// VERTEX +static const std::string newVertexShader = "\n\ + attribute vec2 a_Vertex;\n\ + attribute vec2 a_TexCoord;\n\ + attribute vec4 a_Color;\n\ + \n\ + uniform mat3 u_ProjectionMatrix;\n\ + uniform mat3 u_TransformMatrix;\n\ + uniform mat3 u_TextureMatrix;\n\ + \n\ + varying vec2 v_TexCoord;\n\ + varying vec4 v_Color;\n\ + void main()\n\ + {\n\ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0);\n\ + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy;\n\ + v_Color = a_Color;\n\ + }\n"; + +// TEXT +static const std::string textVertexShader = "\n\ + attribute vec2 a_TexCoord;\n\ + uniform mat3 u_TextureMatrix;\n\ + varying vec2 v_TexCoord;\n\ + attribute vec2 a_Vertex;\n\ + uniform mat3 u_TransformMatrix;\n\ + uniform mat3 u_ProjectionMatrix;\n\ + uniform float u_Depth;\n\ + uniform vec2 u_Offset;\n\ + void main()\n\ + {\n\ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy + u_Offset, 1.0)).xy, u_Depth / 16384.0, 1.0);\n\ + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy;\n\ + }\n"; + +// FRAGMENT +static const std::string newFragmentShader = "\n\ + varying vec2 v_TexCoord;\n\ + varying vec4 v_Color;\n\ + uniform sampler2D u_Atlas;\n\ + void main()\n\ + {\n\ + if(v_TexCoord.x < 0.0) { gl_FragColor = v_Color; return; }\n\ + gl_FragColor = texture2D(u_Atlas, v_TexCoord) * v_Color;\n\ + }\n"; + +// TEXT +static const std::string textFragmentShader = "\n\ + varying vec2 v_TexCoord;\n\ + uniform vec4 u_Color;\n\ + uniform sampler2D u_Fonts;\n\ + void main()\n\ + {\n\ + gl_FragColor = texture2D(u_Fonts, v_TexCoord) * u_Color;\n\ + }\n"; + +#endif \ No newline at end of file diff --git a/src/framework/graphics/shaders/shaders.h b/src/framework/graphics/shaders/shaders.h new file mode 100644 index 0000000..b9c89ed --- /dev/null +++ b/src/framework/graphics/shaders/shaders.h @@ -0,0 +1,7 @@ +#ifndef SHADERS_H +#define SHADERS_H + +#include "newshader.h" +#include "shadersources.h" + +#endif \ No newline at end of file diff --git a/src/framework/graphics/shaders/shadersources.h b/src/framework/graphics/shaders/shadersources.h new file mode 100644 index 0000000..d156dc0 --- /dev/null +++ b/src/framework/graphics/shaders/shadersources.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PAINTEROGL2_SHADERSOURCES_H +#define PAINTEROGL2_SHADERSOURCES_H + +static const std::string glslMainVertexShader = "\n\ + vec4 calculatePosition();\n\ + void main() {\n\ + gl_Position = calculatePosition();\n\ + }\n"; + +static const std::string glslMainWithTexCoordsVertexShader = "\n\ + attribute vec2 a_TexCoord;\n\ + uniform mat3 u_TextureMatrix;\n\ + varying vec2 v_TexCoord;\n\ + vec4 calculatePosition();\n\ + void main()\n\ + {\n\ + gl_Position = calculatePosition();\n\ + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy;\n\ + }\n"; + +static std::string glslPositionOnlyVertexShader = "\n\ + attribute vec2 a_Vertex;\n\ + uniform mat3 u_TransformMatrix;\n\ + uniform mat3 u_ProjectionMatrix;\n\ + uniform float u_Depth;\n\ + vec4 calculatePosition() {\n\ + return vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, u_Depth / 16384.0, 1.0);\n\ + }\n"; + +static const std::string glslMainFragmentShader = "\n\ + uniform float u_Opacity;\n\ + uniform float u_Depth;\n\ + vec4 calculatePixel();\n\ + void main()\n\ + {\n\ + gl_FragColor = calculatePixel();\n\ + gl_FragColor.a *= u_Opacity;\n\ + if(gl_FragColor.a < 0.01 && u_Depth > 0.0)\n\ + discard;\n\ + }\n"; + +static const std::string glslTextureSrcFragmentShader = "\n\ + varying vec2 v_TexCoord;\n\ + uniform vec4 u_Color;\n\ + uniform sampler2D u_Tex0;\n\ + vec4 calculatePixel() {\n\ + return texture2D(u_Tex0, v_TexCoord) * u_Color;\n\ + }\n"; + + +static const std::string glslSolidColorFragmentShader = "\n\ + uniform vec4 u_Color;\n\ + vec4 calculatePixel() {\n\ + return u_Color;\n\ + }\n"; + +static const std::string glslSolidColorOnTextureFragmentShader = "\n\ + uniform vec4 u_Color;\n\ + varying vec2 v_TexCoord;\n\ + uniform sampler2D u_Tex0;\n\ + vec4 calculatePixel() {\n\ + if(texture2D(u_Tex0, v_TexCoord).a > 0.01)\n\ + return u_Color;\n\ + return vec4(0,0,0,0);\n\ + }\n"; + + +static const std::string glslOutfitVertexShader = "\n\ + attribute vec2 a_TexCoord;\n\ + uniform mat3 u_TextureMatrix;\n\ + varying vec2 v_TexCoord;\n\ + varying vec2 v_TexCoord2;\n\ + attribute vec2 a_Vertex;\n\ + uniform mat3 u_TransformMatrix;\n\ + uniform mat3 u_ProjectionMatrix;\n\ + uniform float u_Depth;\n\ + uniform vec2 u_Offset;\n\ + void main()\n\ + {\n\ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, u_Depth / 16384.0, 1.0);\n\ + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy;\n\ + v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy;\n\ + }\n"; + +static const std::string glslOutfitFragmentShader = "\n\ + uniform float u_Opacity;\n\ + uniform float u_Depth;\n\ + uniform mat4 u_Color;\n\ + varying vec2 v_TexCoord;\n\ + varying vec2 v_TexCoord2;\n\ + uniform sampler2D u_Tex0;\n\ + uniform sampler2D u_Tex1;\n\ + void main()\n\ + {\n\ + gl_FragColor = texture2D(u_Tex0, v_TexCoord);\n\ + vec4 texcolor = texture2D(u_Tex0, v_TexCoord2);\n\ + if(texcolor.r > 0.9)\n\ + gl_FragColor *= texcolor.g > 0.9 ? u_Color[0] : u_Color[1];\n\ + else if(texcolor.g > 0.9)\n\ + gl_FragColor *= u_Color[2];\n\ + else if(texcolor.b > 0.9)\n\ + gl_FragColor *= u_Color[3];\n\ + if(gl_FragColor.a < 0.01) discard;\n\ + }\n"; + +#endif diff --git a/src/framework/graphics/textrender.cpp b/src/framework/graphics/textrender.cpp new file mode 100644 index 0000000..0e65d51 --- /dev/null +++ b/src/framework/graphics/textrender.cpp @@ -0,0 +1,109 @@ +#include "painter.h" +#include "textrender.h" +#include + +TextRender g_text; + +void TextRender::init() +{ + +} + +void TextRender::terminate() +{ + for (auto& cache : m_cache) { + cache.clear(); + } +} + +void TextRender::poll() +{ + static int iteration = 0; + int index = (iteration++) % INDEXES; + std::lock_guard lock(m_mutex[index]); + auto& cache = m_cache[index]; + if (cache.size() < 100) + return; + + ticks_t dropPoint = g_clock.millis(); + if (cache.size() > 500) + dropPoint -= 10; + else if (cache.size() > 250) + dropPoint -= 100; + else + dropPoint -= 1000; + + for (auto it = cache.begin(); it != cache.end(); ) { + if (it->second->lastUse < dropPoint) { + it = cache.erase(it); + continue; + } + ++it; + } +} + +uint64_t TextRender::addText(BitmapFontPtr font, const std::string& text, const Size& size, Fw::AlignmentFlag align) +{ + uint64_t hash = 1125899906842597ULL; + for (size_t i = 0; i < text.length(); ++i) { + hash = hash * 31 + text[i]; + } + hash = hash * 31 + size.width(); + hash = hash * 31 + size.height(); + hash = hash * 31 + (uint64_t)align; + hash = hash * 31 + (uint64_t)font->getId(); + + int index = hash % INDEXES; + m_mutex[index].lock(); + auto it = m_cache[index].find(hash); + if (it == m_cache[index].end()) { + m_cache[index][hash] = std::shared_ptr(new TextRenderCache{ font, text, size, align, font->getTexture(), CoordsBuffer(), g_clock.millis() }); + } + m_mutex[index].unlock(); + return hash; +} + +void TextRender::drawText(const Point& pos, uint64_t hash, const Color& color) +{ + int index = hash % INDEXES; + m_mutex[index].lock(); + auto _it = m_cache[index].find(hash); + if (_it == m_cache[index].end()) { + m_mutex[index].unlock(); + return; + } + auto it = _it->second; + m_mutex[index].unlock(); + if (it->font) { // calculate text coords + it->font->calculateDrawTextCoords(it->coords, it->text, Rect(0, 0, it->size), it->align); + it->coords.cache(); + it->text.clear(); + it->font.reset(); + } + it->lastUse = g_clock.millis(); + g_painterNew->drawText(pos, it->coords, color); +} + +void TextRender::drawColoredText(const Point& pos, uint64_t hash, const std::vector>& colors) +{ + if (colors.empty()) + return drawText(pos, hash, Color::white); + int index = hash % INDEXES; + m_mutex[index].lock(); + auto _it = m_cache[index].find(hash); + if (_it == m_cache[index].end()) { + m_mutex[index].unlock(); + return; + } + auto it = _it->second; + m_mutex[index].unlock(); + if (it->font) { // calculate text coords + it->font->calculateDrawTextCoords(it->coords, it->text, Rect(0, 0, it->size), it->align); + it->coords.cache(); + it->text.clear(); + it->font.reset(); + } + it->lastUse = g_clock.millis(); + g_painterNew->drawText(pos, it->coords, colors); +} + diff --git a/src/framework/graphics/textrender.h b/src/framework/graphics/textrender.h new file mode 100644 index 0000000..6a2b0e1 --- /dev/null +++ b/src/framework/graphics/textrender.h @@ -0,0 +1,38 @@ +#ifndef TEXTRENDER_H +#define TEXTRENDER_H + +#include +#include +#include "bitmapfont.h" +#include "coordsbuffer.h" +#include + +struct TextRenderCache { + BitmapFontPtr font; + std::string text; + Size size; + Fw::AlignmentFlag align; + TexturePtr texture; + CoordsBuffer coords; + ticks_t lastUse; +}; + +class TextRender +{ + static const int INDEXES = 10; +public: + void init(); + void terminate(); + void poll(); + uint64_t addText(BitmapFontPtr font, const std::string& text, const Size& size, Fw::AlignmentFlag align = Fw::AlignTopLeft); + void drawText(const Point& pos, uint64_t hash, const Color& color); + void drawColoredText(const Point& pos, uint64_t hash, const std::vector>& colors); + +private: + std::map> m_cache[INDEXES]; + std::mutex m_mutex[INDEXES]; +}; + +extern TextRender g_text; + +#endif diff --git a/src/framework/graphics/texture.cpp b/src/framework/graphics/texture.cpp new file mode 100644 index 0000000..baccbb4 --- /dev/null +++ b/src/framework/graphics/texture.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "texture.h" +#include "graphics.h" +#include "framebuffer.h" +#include "image.h" + +#include +#include +#include +#include + +uint Texture::uniqueId = 1; + +Texture::Texture(const Size& size, bool depthTexture, bool smooth, bool upsideDown) +{ + m_uniqueId = uniqueId++; + m_smooth = smooth; + m_upsideDown = upsideDown; + setupSize(size); + + g_stats.addTexture(); +} + +Texture::Texture(const ImagePtr& image, bool buildMipmaps, bool compress, bool smooth) +{ + if (!image) { + g_logger.fatal("Texture can't be created with null image!"); + } + + m_uniqueId = uniqueId++; + m_smooth = smooth; + m_image = image; + setupSize(m_image->getSize()); + + g_stats.addTexture(); +} + +Texture::~Texture() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + if (m_id != 0) { // free texture from gl memory + GLuint textureId = m_id; + g_graphicsDispatcher.addEvent([textureId] { + glDeleteTextures(1, &textureId); + }); + } + + g_stats.removeTexture(); +} + +void Texture::replace(const ImagePtr& image) +{ + m_uniqueId = uniqueId++; + if (m_id != 0) { // free existing texture from gl memory + GLuint textureId = m_id; + g_graphicsDispatcher.addEvent([textureId] { + glDeleteTextures(1, &textureId); + }); + } + if (!image) { + g_logger.fatal("Texture can't be replaced with null image!"); + } + m_id = 0; + m_image = image; + setupSize(m_image->getSize()); +} + +void Texture::resize(const Size& size) +{ + if(m_id == 0) + update(); + setupSize(size); + glBindTexture(GL_TEXTURE_2D, m_id); + setupPixels(0, m_size, nullptr, 4); + //m_needsUpdate = true; + /* update(); */ +} + + +void Texture::update() +{ + if (m_id == 0) { + glGenTextures(1, &m_id); + VALIDATE(m_id != 0); + glBindTexture(GL_TEXTURE_2D, m_id); + if (m_image) { + setupSize(m_image->getSize()); + int level = 0; + do { + setupPixels(level++, m_image->getSize(), m_image->getPixelData(), m_image->getBpp()); + } while (m_buildHardwareMipmaps && m_image->nextMipmap()); + } else { + setupPixels(0, m_size, nullptr, 4); + } + m_image = nullptr; // free image + m_needsUpdate = true; + g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__); + } + + if (m_needsUpdate) { + glBindTexture(GL_TEXTURE_2D, m_id); + setupWrap(); + setupFilters(); + setupTranformMatrix(); + m_needsUpdate = false; + g_graphics.checkForError(__FUNCTION__, __FILE__, __LINE__); + } +} + +bool Texture::buildHardwareMipmaps() +{ + m_buildHardwareMipmaps = true; + return true; +} + +void Texture::setSmooth(bool smooth) +{ + if(smooth == m_smooth) + return; + + m_smooth = smooth; + m_needsUpdate = true; +} + +void Texture::setRepeat(bool repeat) +{ + if(m_repeat == repeat) + return; + + m_repeat = repeat; + m_needsUpdate = true; +} + +void Texture::setUpsideDown(bool upsideDown) +{ + if(m_upsideDown == upsideDown) + return; + m_upsideDown = upsideDown; + m_needsUpdate = true; +} + +void Texture::setupSize(const Size& size) +{ + if (size.width() > g_graphics.getMaxTextureSize() || size.height() > g_graphics.getMaxTextureSize()) { + g_logger.fatal(stdext::format("Tried to create texture with size %ix%i while maximum texture size is %ix%i", + size.width(), size.height(), g_graphics.getMaxTextureSize(), g_graphics.getMaxTextureSize())); + } + m_size = size; +} + +void Texture::setupWrap() +{ + int texParam = GL_REPEAT; + if(!m_repeat) + texParam = GL_CLAMP_TO_EDGE; + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texParam); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texParam); +} + +void Texture::setupFilters() +{ + int minFilter; + int magFilter; + if(m_smooth) { + minFilter = m_hasMipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; + magFilter = GL_LINEAR; + } else { + minFilter = m_hasMipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; + magFilter = GL_NEAREST; + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); +} + +void Texture::setupTranformMatrix() +{ + if(m_upsideDown) { + m_transformMatrix = { 1.0f/m_size.width(), 0.0f, 0.0f, + 0.0f, -1.0f/m_size.height(), 0.0f, + 0.0f, m_size.height()/(float)m_size.height(), 1.0f }; + } else { + m_transformMatrix = { 1.0f/m_size.width(), 0.0f, 0.0f, + 0.0f, 1.0f/m_size.height(), 0.0f, + 0.0f, 0.0f, 1.0f }; + } +} + +void Texture::setupPixels(int level, const Size& size, uchar* pixels, int channels, bool compress) +{ + GLenum format = 0; + switch(channels) { + case 4: + format = GL_RGBA; + break; + case 3: + format = GL_RGB; + break; + case 2: + format = GL_LUMINANCE_ALPHA; + break; + case 1: + format = GL_LUMINANCE; + break; + } + + GLenum internalFormat = GL_RGBA; + glTexImage2D(GL_TEXTURE_2D, level, internalFormat, size.width(), size.height(), 0, format, GL_UNSIGNED_BYTE, pixels); +} diff --git a/src/framework/graphics/texture.h b/src/framework/graphics/texture.h new file mode 100644 index 0000000..09f78e9 --- /dev/null +++ b/src/framework/graphics/texture.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TEXTURE_H +#define TEXTURE_H + +#include "declarations.h" + +class Texture : public stdext::shared_object +{ + static uint uniqueId; +public: + Texture(const Size& size, bool depthTexture = false, bool smooth = false, bool upsideDown = false); + Texture(const ImagePtr& image, bool buildMipmaps = false, bool compress = false, bool smooth = false); + virtual ~Texture(); + virtual void replace(const ImagePtr& image); + void resize(const Size& size); + + // update must be called always before drawing, only this function can use opengl functions + virtual void update(); + + virtual void setUpsideDown(bool upsideDown); + virtual void setSmooth(bool smooth); + virtual void setRepeat(bool repeat); + virtual bool buildHardwareMipmaps(); + void setTime(ticks_t time) { m_time = time; } + + uint getId() { return m_id; } + uint getUniqueId() { return m_uniqueId; } + ticks_t getTime() { return m_time; } + int getWidth() { return m_size.width(); } + int getHeight() { return m_size.height(); } + const Size& getSize() { return m_size; } + const Matrix3& getTransformMatrix() { return m_transformMatrix; } + bool isEmpty() { return false; } + bool hasRepeat() { return m_repeat; } + bool hasMipmaps() { return m_hasMipmaps; } + virtual bool isAnimatedTexture() { return false; } + +protected: + + void uploadPixels(const ImagePtr& image, bool buildMipmaps = false, bool compress = false); + + void setupSize(const Size& size); + void setupWrap(); + void setupFilters(); + void setupTranformMatrix(); + void setupPixels(int level, const Size& size, uchar *pixels, int channels = 4, bool compress = false); + + uint m_id = 0; + uint m_uniqueId = 0; + ticks_t m_time = 0; + Size m_size; + Matrix3 m_transformMatrix; + bool m_hasMipmaps = false; + bool m_smooth = false; + bool m_upsideDown = false; + bool m_repeat = false; + bool m_buildHardwareMipmaps = false; + bool m_needsUpdate = false; + ImagePtr m_image; +}; + +#endif diff --git a/src/framework/http/http.cpp b/src/framework/http/http.cpp new file mode 100644 index 0000000..b680c6c --- /dev/null +++ b/src/framework/http/http.cpp @@ -0,0 +1,205 @@ +#include +#include +#include +#include + +#include "http.h" +#include "session.h" +#include "websocket.h" + +Http g_http; + +void Http::init() { + m_working = true; + m_thread = std::thread([&] { + m_ios.run(); + }); +} + +void Http::terminate() { + if (!m_working) + return; + m_working = false; + for (auto& ws : m_websockets) { + ws.second->close(); + } + for (auto& op : m_operations) { + op.second->canceled = true; + } + m_guard.reset(); + if (!m_thread.joinable()) { + stdext::millisleep(100); + m_ios.stop(); + } + m_thread.join(); +} + +int Http::get(const std::string& url, int timeout) { + if (!timeout) // lua is not working with default values + timeout = 5; + int operationId = m_operationId++; + + boost::asio::post(m_ios, [&, url, timeout, operationId] { + auto result = std::make_shared(); + result->url = url; + result->operationId = operationId; + m_operations[operationId] = result; + auto session = std::make_shared(m_ios, url, timeout, result, [&](HttpResult_ptr result) { + bool finished = result->finished; + g_dispatcher.addEventEx("Http::onGet", [result, finished]() { + if (!finished) { + g_lua.callGlobalField("g_http", "onGetProgress", result->operationId, result->url, result->progress); + return; + } + g_lua.callGlobalField("g_http", "onGet", result->operationId, result->url, result->error, std::string(result->response.begin(), result->response.end())); + }); + if (finished) { + m_operations.erase(operationId); + } + }); + session->start(); + }); + + return operationId; +} + +int Http::post(const std::string& url, const std::string& data, int timeout) { + if (!timeout) // lua is not working with default values + timeout = 5; + if (data.empty()) { + g_logger.error(stdext::format("Invalid post request for %s, empty data, use get instead", url)); + return -1; + } + + int operationId = m_operationId++; + boost::asio::post(m_ios, [&, url, data, timeout, operationId] { + auto result = std::make_shared(); + result->url = url; + result->operationId = operationId; + result->postData = data; + m_operations[operationId] = result; + auto session = std::make_shared(m_ios, url, timeout, result, [&](HttpResult_ptr result) { + bool finished = result->finished; + g_dispatcher.addEventEx("Http::onPost", [result, finished]() { + if (!finished) { + g_lua.callGlobalField("g_http", "onPostProgress", result->operationId, result->url, result->progress); + return; + } + g_lua.callGlobalField("g_http", "onPost", result->operationId, result->url, result->error, std::string(result->response.begin(), result->response.end())); + }); + if (finished) { + m_operations.erase(operationId); + } + }); + session->start(); + }); + return operationId; +} + +int Http::download(const std::string& url, std::string path, int timeout) { + if (!timeout) // lua is not working with default values + timeout = 5; + + int operationId = m_operationId++; + boost::asio::post(m_ios, [&, url, path, timeout, operationId] { + auto result = std::make_shared(); + result->url = url; + result->operationId = operationId; + m_operations[operationId] = result; + auto session = std::make_shared(m_ios, url, timeout, result, [&, path](HttpResult_ptr result) { + m_speed = ((result->size) * 10) / (1 + stdext::micros() - m_lastSpeedUpdate); + m_lastSpeedUpdate = stdext::micros(); + + if (!result->finished) { + int speed = m_speed; + g_dispatcher.addEventEx("Http::onDownloadProgress", [result, speed]() { + g_lua.callGlobalField("g_http", "onDownloadProgress", result->operationId, result->url, result->progress, speed); + }); + return; + } + std::string checksum = g_crypt.crc32(std::string(result->response.begin(), result->response.end()), false); + g_dispatcher.addEventEx("Http::onDownload", [&, result, path, checksum]() { + if (result->error.empty()) { + if (!path.empty() && path[0] == '/') + m_downloads[path.substr(1)] = result; + else + m_downloads[path] = result; + } + g_lua.callGlobalField("g_http", "onDownload", result->operationId, result->url, result->error, path, checksum); + }); + m_operations.erase(operationId); + }); + session->start(); + }); + return operationId; +} + +int Http::ws(const std::string& url, int timeout) +{ + if (!timeout) // lua is not working with default values + timeout = 5; + int operationId = m_operationId++; + + boost::asio::post(m_ios, [&, url, timeout, operationId] { + auto result = std::make_shared(); + result->url = url; + result->operationId = operationId; + m_operations[operationId] = result; + auto session = std::make_shared(m_ios, url, timeout, result, [&, result](WebsocketCallbackType type, std::string message) { + g_dispatcher.addEventEx("Http::ws", [result, type, message]() { + if (type == WEBSOCKET_OPEN) { + g_lua.callGlobalField("g_http", "onWsOpen", result->operationId, message); + } else if (type == WEBSOCKET_MESSAGE) { + g_lua.callGlobalField("g_http", "onWsMessage", result->operationId, message); + } else if (type == WEBSOCKET_CLOSE) { + g_lua.callGlobalField("g_http", "onWsClose", result->operationId, message); + } else if (type == WEBSOCKET_ERROR) { + g_lua.callGlobalField("g_http", "onWsError", result->operationId, message); + } + }); + if (type == WEBSOCKET_CLOSE) { + m_websockets.erase(result->operationId); + } + }); + m_websockets[result->operationId] = session; + session->start(); + }); + + return operationId; +} + +bool Http::wsSend(int operationId, std::string message) +{ + boost::asio::post(m_ios, [&, operationId, message] { + auto wit = m_websockets.find(operationId); + if (wit == m_websockets.end()) { + return; + } + wit->second->send(message); + }); + return true; +} + +bool Http::wsClose(int operationId) +{ + cancel(operationId); + return true; +} + + +bool Http::cancel(int id) { + boost::asio::post(m_ios, [&, id] { + auto wit = m_websockets.find(id); + if (wit != m_websockets.end()) { + wit->second->close(); + } + auto it = m_operations.find(id); + if (it == m_operations.end()) + return; + if (it->second->canceled) + return; + it->second->canceled = true; + }); + return true; +} + diff --git a/src/framework/http/http.h b/src/framework/http/http.h new file mode 100644 index 0000000..1b34fc2 --- /dev/null +++ b/src/framework/http/http.h @@ -0,0 +1,55 @@ +#ifndef HTTP_H +#define HTTP_H + +#include +#include "result.h" + +class WebsocketSession; + +class Http { +public: + Http() : m_ios(), m_guard(boost::asio::make_work_guard(m_ios)) {} + + void init(); + void terminate(); + + int get(const std::string& url, int timeout = 5); + int post(const std::string& url, const std::string& data, int timeout = 5); + int download(const std::string& url, std::string path, int timeout = 5); + int ws(const std::string& url, int timeout = 5); + bool wsSend(int operationId, std::string message); + bool wsClose(int operationId); + + bool cancel(int id); + + const std::map& downloads() { + return m_downloads; + } + void clearDownloads() { + m_downloads.clear(); + } + HttpResult_ptr getFile(std::string path) { + if (!path.empty() && path[0] == '/') + path = path.substr(1); + auto it = m_downloads.find(path); + if (it == m_downloads.end()) + return nullptr; + return it->second; + } + +private: + bool m_working = false; + int m_operationId = 1; + int m_speed = 0; + size_t m_lastSpeedUpdate = 0; + std::thread m_thread; + boost::asio::io_context m_ios; + boost::asio::executor_work_guard m_guard; + std::map m_operations; + std::map> m_websockets; + std::map m_downloads; +}; + +extern Http g_http; + +#endif // ! HTTP_H diff --git a/src/framework/http/result.h b/src/framework/http/result.h new file mode 100644 index 0000000..c37e876 --- /dev/null +++ b/src/framework/http/result.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +struct HttpResult { + std::string url; + int operationId = 0; + int status = 0; + int size = 0; + int progress = 0; // from 0 to 100 + int redirects = 0; // redirect + bool connected = false; + bool finished = false; + bool canceled = false; + std::string postData; + std::vector response; + std::string error; +}; + + +using HttpResult_ptr = std::shared_ptr; +using HttpResult_cb = std::function; diff --git a/src/framework/input/mouse.cpp b/src/framework/input/mouse.cpp new file mode 100644 index 0000000..615cd74 --- /dev/null +++ b/src/framework/input/mouse.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "mouse.h" +#include +#include +#include +#include + +Mouse g_mouse; + +void Mouse::init() +{ +} + +void Mouse::terminate() +{ + m_cursors.clear(); +} + +void Mouse::loadCursors(std::string filename) +{ + filename = g_resources.guessFilePath(filename, "otml"); + try { + OTMLDocumentPtr doc = OTMLDocument::parse(filename); + OTMLNodePtr cursorsNode = doc->at("Cursors"); + + for(const OTMLNodePtr& cursorNode : cursorsNode->children()) + addCursor(cursorNode->tag(), + stdext::resolve_path(cursorNode->valueAt("image"), cursorNode->source()), + cursorNode->valueAt("hot-spot")); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("unable to load cursors file: %s", e.what())); + } +} + +void Mouse::addCursor(const std::string& name, const std::string& file, const Point& hotSpot) +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&Mouse::addCursor, this, name, file, hotSpot)); + return; + } + + int cursorId = g_window.loadMouseCursor(file, hotSpot); + if(cursorId >= 0) { + m_cursors[name] = cursorId; + } else + g_logger.error(stdext::format("unable to load cursor %s", name)); +} + +void Mouse::pushCursor(const std::string& name) +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&Mouse::pushCursor, this, name)); + return; + } + + auto it = m_cursors.find(name); + if(it == m_cursors.end()) + return; + + int cursorId = it->second; + g_window.setMouseCursor(cursorId); + std::lock_guard lock(m_mutex); + m_cursorStack.push_back(cursorId); + return; +} + +void Mouse::popCursor(const std::string& name) +{ + if (g_mainThreadId != std::this_thread::get_id()) { + g_graphicsDispatcher.addEvent(std::bind(&Mouse::popCursor, this, name)); + return; + } + + std::lock_guard lock(m_mutex); + if(m_cursorStack.size() == 0) + return; + + if(name.empty() || m_cursors.find(name) == m_cursors.end()) + m_cursorStack.pop_back(); + else { + int cursorId = m_cursors[name]; + int index = -1; + for(uint i=0;i= 0) + m_cursorStack.erase(m_cursorStack.begin() + index); + else + return; + } + + if(m_cursorStack.size() > 0) + g_window.setMouseCursor(m_cursorStack.back()); + else + g_window.restoreMouseCursor(); +} + +bool Mouse::isCursorChanged() +{ + std::lock_guard lock(m_mutex); + return m_cursorStack.size() > 0; +} + +bool Mouse::isPressed(Fw::MouseButton mouseButton) +{ + return g_window.isMouseButtonPressed(mouseButton); +} diff --git a/src/framework/input/mouse.h b/src/framework/input/mouse.h new file mode 100644 index 0000000..a1b86fc --- /dev/null +++ b/src/framework/input/mouse.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +class Mouse +{ +public: + void init(); + void terminate(); + + void loadCursors(std::string filename); + void addCursor(const std::string& name, const std::string& file, const Point& hotSpot); + void pushCursor(const std::string& name); + void popCursor(const std::string& name); + bool isCursorChanged(); + bool isPressed(Fw::MouseButton mouseButton); + +private: + std::map m_cursors; + std::deque m_cursorStack; + std::mutex m_mutex; +}; + +extern Mouse g_mouse; diff --git a/src/framework/luaengine/declarations.h b/src/framework/luaengine/declarations.h new file mode 100644 index 0000000..38403d7 --- /dev/null +++ b/src/framework/luaengine/declarations.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_LUA_DECLARATIONS_H +#define FRAMEWORK_LUA_DECLARATIONS_H + +#include + +#include + +class LuaInterface; +class LuaObject; + +typedef std::function LuaCppFunction; +typedef std::unique_ptr LuaCppFunctionPtr; +typedef stdext::shared_object_ptr LuaObjectPtr; + +#endif diff --git a/src/framework/luaengine/lbitlib.cpp b/src/framework/luaengine/lbitlib.cpp new file mode 100644 index 0000000..941cb8b --- /dev/null +++ b/src/framework/luaengine/lbitlib.cpp @@ -0,0 +1,379 @@ +/* + * This is the bit32 library from lua 5.2.0, backported to + * lua 5.1.4. + * + * version 5.2.0-backport4 + * + * This backport was assembled by Sean Bolton (sean at smbolton + * dot com) almost entirely from the above mentioned Lua distributions, + * which are: + * + * Copyright (C) 1994-2011 Lua.org, PUC-Rio. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#define LUA_LIB + +extern "C" { +#if defined(_MSC_VER) || defined(ANDROID) +#include +#include +#include +#else +#include +#include +#include +#endif +} + +/* ----- adapted from lua-5.2.0 luaconf.h: ----- */ + +/* +@@ LUA_UNSIGNED is the integral type used by lua_pushunsigned/lua_tounsigned. +** It must have at least 32 bits. +*/ +#ifndef LUAI_INT32 +#define LUAI_INT32 int +#endif +#define LUA_UNSIGNED unsigned LUAI_INT32 + +#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) /* { */ + +/* On a Microsoft compiler on a Pentium, use assembler to avoid clashes + with a DirectX idiosyncrasy */ +#if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86) /* { */ + +#define MS_ASMTRICK + +#else /* }{ */ +/* the next definition uses a trick that should work on any machine + using IEEE754 with a 32-bit integer type */ + +#define LUA_IEEE754TRICK + +/* +@@ LUA_IEEEENDIAN is the endianness of doubles in your machine +** (0 for little endian, 1 for big endian); if not defined, Lua will +** check it dynamically. +*/ +/* check for known architectures */ +#if defined(__i386__) || defined(__i386) || defined(__X86__) || \ + defined (__x86_64) +#define LUA_IEEEENDIAN 0 +#elif defined(__POWERPC__) || defined(__ppc__) +#define LUA_IEEEENDIAN 1 +#endif + +#endif /* } */ + +#endif /* } */ + +/* ----- from lua-5.2.0 lua.h: ----- */ + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* ----- adapted from lua-5.2.0 llimits.h: ----- */ + +/* lua_number2unsigned is a macro to convert a lua_Number to a lua_Unsigned. +** lua_unsigned2number is a macro to convert a lua_Unsigned to a lua_Number. +*/ + +#if defined(MS_ASMTRICK) /* { */ +/* trick with Microsoft assembler for X86 */ + +#define lua_number2unsigned(i,n) \ + {__int64 l; __asm {__asm fld n __asm fistp l} i = (unsigned int)l;} + +#elif defined(LUA_IEEE754TRICK) /* }{ */ +/* the next trick should work on any machine using IEEE754 with + a 32-bit integer type */ + +union luai_Cast2 { double l_d; LUAI_INT32 l_p[2]; }; + +#if !defined(LUA_IEEEENDIAN) /* { */ +#define LUAI_EXTRAIEEE \ + static const union luai_Cast2 ieeeendian = {-(33.0 + 6755399441055744.0)}; +#define LUA_IEEEENDIAN (ieeeendian.l_p[1] == 33) +#else +#define LUAI_EXTRAIEEE /* empty */ +#endif /* } */ + +#define lua_number2int32(i,n,t) \ + { LUAI_EXTRAIEEE \ + volatile union luai_Cast2 u; u.l_d = (n) + 6755399441055744.0; \ + (i) = (t)u.l_p[LUA_IEEEENDIAN]; } + +#define lua_number2unsigned(i,n) lua_number2int32(i, n, lua_Unsigned) + +#endif /* } */ + +#if !defined(lua_number2unsigned) /* { */ +/* the following definition assures proper modulo behavior */ +#if defined(LUA_NUMBER_DOUBLE) +#include +#define SUPUNSIGNED ((lua_Number)(~(lua_Unsigned)0) + 1) +#define lua_number2unsigned(i,n) \ + ((i)=(lua_Unsigned)((n) - floor((n)/SUPUNSIGNED)*SUPUNSIGNED)) +#else +#define lua_number2unsigned(i,n) ((i)=(lua_Unsigned)(n)) +#endif +#endif /* } */ + +/* on several machines, coercion from unsigned to double is slow, + so it may be worth to avoid */ +#define lua_unsigned2number(u) \ + (((u) <= (lua_Unsigned)INT_MAX) ? (lua_Number)(int)(u) : (lua_Number)(u)) + +/* ----- adapted from lua-5.2.0 lapi.c: ----- */ + +static void lua_pushunsigned (lua_State *L, lua_Unsigned u) { + lua_Number n; + n = lua_unsigned2number(u); + lua_pushnumber(L, n); +} + +/* ----- adapted from lua-5.2.0-work3 lbitlib.c getuintarg(): ----- */ + +static lua_Unsigned luaL_checkunsigned (lua_State *L, int arg) { + lua_Unsigned r; + lua_Number x = lua_tonumber(L, arg); + if (x == 0) luaL_checktype(L, arg, LUA_TNUMBER); + lua_number2unsigned(r, x); + return r; +} + +/* ----- Lua 5.2 luaL_newlib() compatibility: ----- */ + +#define LUAMOD_API LUALIB_API +#define LUA_BIT32LIBNAME "bit32" +#define luaL_newlib(x, y) luaL_register(x, LUA_BIT32LIBNAME, y) + +/* ----- avoid a 'symbol redefined' warning below ----- */ + +#undef LUA_LIB + +/* ----- here follows the unmodified lbitlib.c from Lua 5.2.0 ----- */ + +/* +** $Id: lbitlib.c,v 1.16 2011/06/20 16:35:23 roberto Exp $ +** Standard library for bitwise operations +** See Copyright Notice in lua.h +*/ + +#define lbitlib_c +#define LUA_LIB + + +/* number of bits to consider in a number */ +#if !defined(LUA_NBITS) +#define LUA_NBITS 32 +#endif + + +#define ALLONES (~(((~(lua_Unsigned)0) << (LUA_NBITS - 1)) << 1)) + +/* macro to trim extra bits */ +#define trim(x) ((x) & ALLONES) + + +/* builds a number with 'n' ones (1 <= n <= LUA_NBITS) */ +#define mask(n) (~((ALLONES << 1) << ((n) - 1))) + + +typedef lua_Unsigned b_uint; + + + +static b_uint andaux (lua_State *L) { + int i, n = lua_gettop(L); + b_uint r = ~(b_uint)0; + for (i = 1; i <= n; i++) + r &= luaL_checkunsigned(L, i); + return trim(r); +} + + +static int b_and (lua_State *L) { + b_uint r = andaux(L); + lua_pushunsigned(L, r); + return 1; +} + + +static int b_test (lua_State *L) { + b_uint r = andaux(L); + lua_pushboolean(L, r != 0); + return 1; +} + + +static int b_or (lua_State *L) { + int i, n = lua_gettop(L); + b_uint r = 0; + for (i = 1; i <= n; i++) + r |= luaL_checkunsigned(L, i); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_xor (lua_State *L) { + int i, n = lua_gettop(L); + b_uint r = 0; + for (i = 1; i <= n; i++) + r ^= luaL_checkunsigned(L, i); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_not (lua_State *L) { + b_uint r = ~luaL_checkunsigned(L, 1); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_shift (lua_State *L, b_uint r, int i) { + if (i < 0) { /* shift right? */ + i = -i; + r = trim(r); + if (i >= LUA_NBITS) r = 0; + else r >>= i; + } + else { /* shift left */ + if (i >= LUA_NBITS) r = 0; + else r <<= i; + r = trim(r); + } + lua_pushunsigned(L, r); + return 1; +} + + +static int b_lshift (lua_State *L) { + return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkint(L, 2)); +} + + +static int b_rshift (lua_State *L) { + return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkint(L, 2)); +} + + +static int b_arshift (lua_State *L) { + b_uint r = luaL_checkunsigned(L, 1); + int i = luaL_checkint(L, 2); + if (i < 0 || !(r & ((b_uint)1 << (LUA_NBITS - 1)))) + return b_shift(L, r, -i); + else { /* arithmetic shift for 'negative' number */ + if (i >= LUA_NBITS) r = ALLONES; + else + r = trim((r >> i) | ~(~(b_uint)0 >> i)); /* add signal bit */ + lua_pushunsigned(L, r); + return 1; + } +} + + +static int b_rot (lua_State *L, int i) { + b_uint r = luaL_checkunsigned(L, 1); + i &= (LUA_NBITS - 1); /* i = i % NBITS */ + r = trim(r); + r = (r << i) | (r >> (LUA_NBITS - i)); + lua_pushunsigned(L, trim(r)); + return 1; +} + + +static int b_lrot (lua_State *L) { + return b_rot(L, luaL_checkint(L, 2)); +} + + +static int b_rrot (lua_State *L) { + return b_rot(L, -luaL_checkint(L, 2)); +} + + +/* +** get field and width arguments for field-manipulation functions, +** checking whether they are valid +*/ +static int fieldargs (lua_State *L, int farg, int *width) { + int f = luaL_checkint(L, farg); + int w = luaL_optint(L, farg + 1, 1); + luaL_argcheck(L, 0 <= f, farg, "field cannot be negative"); + luaL_argcheck(L, 0 < w, farg + 1, "width must be positive"); + if (f + w > LUA_NBITS) + luaL_error(L, "trying to access non-existent bits"); + *width = w; + return f; +} + + +static int b_extract (lua_State *L) { + int w; + b_uint r = luaL_checkunsigned(L, 1); + int f = fieldargs(L, 2, &w); + r = (r >> f) & mask(w); + lua_pushunsigned(L, r); + return 1; +} + + +static int b_replace (lua_State *L) { + int w; + b_uint r = luaL_checkunsigned(L, 1); + b_uint v = luaL_checkunsigned(L, 2); + int f = fieldargs(L, 3, &w); + int m = mask(w); + v &= m; /* erase bits outside given width */ + r = (r & ~(m << f)) | (v << f); + lua_pushunsigned(L, r); + return 1; +} + + +static const luaL_Reg bitlib[] = { + {"arshift", b_arshift}, + {"band", b_and}, + {"bnot", b_not}, + {"bor", b_or}, + {"bxor", b_xor}, + {"btest", b_test}, + {"extract", b_extract}, + {"lrotate", b_lrot}, + {"lshift", b_lshift}, + {"replace", b_replace}, + {"rrotate", b_rrot}, + {"rshift", b_rshift}, + {NULL, NULL} +}; + +int luaopen_bit32 (lua_State *L) { + luaL_newlib(L, bitlib); + return 1; +} + +#undef trim + diff --git a/src/framework/luaengine/lbitlib.h b/src/framework/luaengine/lbitlib.h new file mode 100644 index 0000000..bb8f1ba --- /dev/null +++ b/src/framework/luaengine/lbitlib.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LBITLIB_520_BACKPORT4_H +#define LBITLIB_520_BACKPORT4_H + +struct lua_State; + +int luaopen_bit32 (lua_State *L); + +#endif diff --git a/src/framework/luaengine/luabinder.h b/src/framework/luaengine/luabinder.h new file mode 100644 index 0000000..f2b3dd2 --- /dev/null +++ b/src/framework/luaengine/luabinder.h @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LUABINDER_H +#define LUABINDER_H + +// this file is and must be included only from luainterface.h +#include "luainterface.h" +#include "luaexception.h" + +#include +#include + +/// This namespace contains some dirty metaprogamming that uses a lot of C++0x features +/// The purpose here is to create templates that can bind any function from C++ +/// and expose in lua environment. This is done combining variadic templates, +/// lambdas, tuples and some type traits features from the new C++0x standard to create +/// templates that can detect functions's arguments and then generate lambdas. These lambdas +/// pops arguments from lua stack, call the bound C++ function and then +/// pushes the result to lua. +namespace luabinder +{ + /// Pack arguments from lua stack into a tuple recursively + template + struct pack_values_into_tuple { + template + static void call(Tuple& tuple, LuaInterface* lua) { + typedef typename std::tuple_element::type ValueType; + std::get(tuple) = lua->polymorphicPop(); + pack_values_into_tuple::call(tuple, lua); + } + }; + template<> + struct pack_values_into_tuple<0> { + template + static void call(Tuple& tuple, LuaInterface* lua) { } + }; + + /// C++ function caller that can push results to lua + template + typename std::enable_if::value, int>::type + call_fun_and_push_result(const F& f, LuaInterface* lua, const Args&... args) { + Ret ret = f(args...); + int numRets = lua->polymorphicPush(ret); + return numRets; + } + + /// C++ void function caller + template + typename std::enable_if::value, int>::type + call_fun_and_push_result(const F& f, LuaInterface* lua, const Args&... args) { + f(args...); + return 0; + } + + /// Expand arguments from tuple for later calling the C++ function + template + struct expand_fun_arguments { + template + static int call(const Tuple& tuple, const F& f, LuaInterface* lua, const Args&... args) { + return expand_fun_arguments::call(tuple, f, lua, std::get(tuple), args...); + } + }; + template + struct expand_fun_arguments<0,Ret> { + template + static int call(const Tuple& tuple, const F& f, LuaInterface* lua, const Args&... args) { + return call_fun_and_push_result(f, lua, args...); + } + }; + + /// Bind different types of functions generating a lambda + template + LuaCppFunction bind_fun_specializer(const F& f) { + enum { N = std::tuple_size::value }; + return [=](LuaInterface* lua) -> int { + while(lua->stackSize() != N) { + if(lua->stackSize() < N) + g_lua.pushNil(); + else + g_lua.pop(); + } + Tuple tuple; + pack_values_into_tuple::call(tuple, lua); + return expand_fun_arguments::call(tuple, f, lua); + }; + } + + /// Bind a customized function + inline + LuaCppFunction bind_fun(const std::function& f) { + return f; + } + + /// Bind a std::function + template + LuaCppFunction bind_fun(const std::function& f) { + typedef typename std::tuple::type...> Tuple; + return bind_fun_specializer::type, + decltype(f), + Tuple>(f); + } + + /// Specialization for lambdas + template + struct bind_lambda_fun; + + template + struct bind_lambda_fun { + static LuaCppFunction call(const Lambda& f) { + typedef typename std::tuple::type...> Tuple; + return bind_fun_specializer::type, + decltype(f), + Tuple>(f); + + } + }; + + template + typename std::enable_if::value, LuaCppFunction>::type bind_fun(const Lambda& f) { + typedef decltype(&Lambda::operator()) F; + return bind_lambda_fun::call(f); + } + + /// Convert to C++ functions pointers to std::function then bind + template + LuaCppFunction bind_fun(Ret (*f)(Args...)) { + return bind_fun(std::function(f)); + } + + /// Create member function lambdas + template + std::function&, const Args&...)> make_mem_func(Ret (C::* f)(Args...)) { + auto mf = std::mem_fn(f); + return [=](const stdext::shared_object_ptr& obj, const Args&... args) mutable -> Ret { + if(!obj) + throw LuaException("failed to call a member function because the passed object is nil"); + return mf(obj.get(), args...); + }; + } + template + std::function&, const Args&...)> make_mem_func(void (C::* f)(Args...)) { + auto mf = std::mem_fn(f); + return [=](const stdext::shared_object_ptr& obj, const Args&... args) mutable -> void { + if(!obj) + throw LuaException("failed to call a member function because the passed object is nil"); + mf(obj.get(), args...); + }; + } + + /// Create member function lambdas for singleton classes + template + std::function make_mem_func_singleton(Ret (C::* f)(Args...), C* instance) { + auto mf = std::mem_fn(f); + return [=](Args... args) mutable -> Ret { return mf(instance, args...); }; + } + template + std::function make_mem_func_singleton(void (C::* f)(Args...), C* instance) { + auto mf = std::mem_fn(f); + return [=](Args... args) mutable -> void { mf(instance, args...); }; + } + + + /// Bind member functions + template + LuaCppFunction bind_mem_fun(Ret (FC::* f)(Args...)) { + typedef typename std::tuple, typename stdext::remove_const_ref::type...> Tuple; + auto lambda = make_mem_func(f); + return bind_fun_specializer::type, + decltype(lambda), + Tuple>(lambda); + } + + /// Bind singleton member functions + template + LuaCppFunction bind_singleton_mem_fun(Ret (FC::*f)(Args...), C *instance) { + typedef typename std::tuple::type...> Tuple; + VALIDATE(instance); + auto lambda = make_mem_func_singleton(f, static_cast(instance)); + return bind_fun_specializer::type, + decltype(lambda), + Tuple>(lambda); + } + + /// Bind customized member functions + template + LuaCppFunction bind_mem_fun(int (C::*f)(LuaInterface*)) { + auto mf = std::mem_fn(f); + return [=](LuaInterface* lua) mutable -> int { + auto obj = lua->castValue>(1); + lua->remove(1); + return mf(obj, lua); + }; + } +} + +#endif diff --git a/src/framework/luaengine/luaexception.cpp b/src/framework/luaengine/luaexception.cpp new file mode 100644 index 0000000..10fbb27 --- /dev/null +++ b/src/framework/luaengine/luaexception.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "luaexception.h" +#include "luainterface.h" + +LuaException::LuaException(const std::string& error, int traceLevel) +{ + //g_lua.clearStack(); // on every exception, clear lua stack + generateLuaErrorMessage(error, traceLevel); +} + +void LuaException::generateLuaErrorMessage(const std::string& error, int traceLevel) +{ + // append trace level to error message + if(traceLevel >= 0) + m_what = stdext::format("LUA ERROR: %s", g_lua.traceback(error, traceLevel)); + else + m_what = stdext::format("LUA ERROR:\n%s", error); +} + +LuaBadNumberOfArgumentsException::LuaBadNumberOfArgumentsException(int expected, int got) +{ + std::string error = "attempt to call a function with wrong number of arguments"; + if(expected >= 0 && got >= 0) + error = stdext::format("%s (expected %d, but got %d)", error, expected, got); + generateLuaErrorMessage(error, 1); +} + +LuaBadValueCastException::LuaBadValueCastException(const std::string& luaTypeName, const std::string& cppTypeName) +{ + std::string error = stdext::format("attempt to cast a '%s' lua value to '%s'", luaTypeName, cppTypeName); + generateLuaErrorMessage(error, 0); +} diff --git a/src/framework/luaengine/luaexception.h b/src/framework/luaengine/luaexception.h new file mode 100644 index 0000000..2eedbdb --- /dev/null +++ b/src/framework/luaengine/luaexception.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LUAEXCEPTION_H +#define LUAEXCEPTION_H + +#include "declarations.h" + +class LuaException : public stdext::exception +{ +public: + LuaException(const std::string& error, int traceLevel = -1); + virtual ~LuaException() throw() { } + + void generateLuaErrorMessage(const std::string& error, int traceLevel); + + virtual const char* what() const throw() { return m_what.c_str(); } + +protected: + LuaException() { } + + std::string m_what; +}; + +class LuaBadNumberOfArgumentsException : public LuaException +{ +public: + LuaBadNumberOfArgumentsException(int expected = -1, int got = -1); +}; + +class LuaBadValueCastException : public LuaException +{ +public: + LuaBadValueCastException(const std::string& luaTypeName, const std::string& cppTypeName); +}; + +#endif diff --git a/src/framework/luaengine/luainterface.cpp b/src/framework/luaengine/luainterface.cpp new file mode 100644 index 0000000..916866e --- /dev/null +++ b/src/framework/luaengine/luainterface.cpp @@ -0,0 +1,1394 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "luainterface.h" +#include "luaobject.h" + +#include +#include +#if defined(_MSC_VER) || defined(ANDROID) +#include +#else +#include +#endif + +#include "lbitlib.h" + +LuaInterface g_lua; + +LuaInterface::LuaInterface() +{ + L = nullptr; + m_cppCallbackDepth = 0; + m_weakTableRef = 0; + m_totalObjRefs = 0; + m_totalFuncRefs = 0; +} + +LuaInterface::~LuaInterface() +{ +} + +void LuaInterface::init() +{ + createLuaState(); + + // store global environment reference + pushThread(); + getEnv(); + m_globalEnv = ref(); + pop(); + + // check if demangle_class is working as expected + VALIDATE(stdext::demangle_class() == "LuaObject"); + + // register LuaObject, the base of all other objects + registerClass(); + bindClassMemberFunction("getUseCount", &LuaObject::getUseCount); + bindClassMemberFunction("getClassName", &LuaObject::getClassName); + + registerClassMemberFunction("getFieldsTable", (LuaCppFunction) ([](LuaInterface* lua) -> int { + LuaObjectPtr obj = g_lua.popObject(); + obj->luaGetFieldsTable(); + return 1; + })); +} + +void LuaInterface::terminate() +{ + // close lua state, it will release all objects + closeLuaState(); + VALIDATE(m_totalFuncRefs == 0); + VALIDATE(m_totalObjRefs == 0); +} + +void LuaInterface::registerSingletonClass(const std::string& className) +{ + newTable(); + pushValue(); + setGlobal(className); + pop(); +} + +void LuaInterface::registerClass(const std::string& className, const std::string& baseClass) +{ + // creates the class table (that it's also the class methods table) + newTable(); + pushValue(); + setGlobal(className); + const int klass = getTop(); + + // creates the class fieldmethods table + newTable(); + pushValue(); + setGlobal(className + "_fieldmethods"); + int klass_fieldmethods = getTop(); + + // creates the class metatable + newTable(); + pushValue(); + setGlobal(className + "_mt"); + int klass_mt = getTop(); + + // set metatable metamethods + pushCppFunction(&LuaInterface::luaObjectGetEvent); + setField("__index", klass_mt); + pushCppFunction(&LuaInterface::luaObjectSetEvent); + setField("__newindex", klass_mt); + pushCppFunction(&LuaInterface::luaObjectEqualEvent); + setField("__eq", klass_mt); + pushCppFunction(&LuaInterface::luaObjectCollectEvent); + setField("__gc", klass_mt); + + // set some fields that will be used later in metatable + pushValue(klass); + setField("methods", klass_mt); + pushValue(klass_fieldmethods); + setField("fieldmethods", klass_mt); + + // redirect methods and fieldmethods to the base class ones + if(!className.empty() && className != "LuaObject") { + // the following code is what create classes hierarchy for lua, by reproducing: + // DerivedClass = { __index = BaseClass } + // DerivedClass_fieldmethods = { __index = BaseClass_methods } + + // redirect the class methods to the base methods + pushValue(klass); + newTable(); + getGlobal(baseClass); + setField("__index"); + setMetatable(); + pop(); + + // redirect the class fieldmethods to the base fieldmethods + pushValue(klass_fieldmethods); + newTable(); + getGlobal(baseClass + "_fieldmethods"); + setField("__index"); + setMetatable(); + pop(); + } + + // pops klass, klass_mt, klass_fieldmethods + pop(3); +} + +void LuaInterface::registerClassStaticFunction(const std::string& className, + const std::string& functionName, + const LuaCppFunction& function) +{ + registerClassMemberFunction(className, functionName, function); +} + +void LuaInterface::registerClassMemberFunction(const std::string& className, + const std::string& functionName, + const LuaCppFunction& function) +{ + getGlobal(className); + pushCppFunction(function); + setField(functionName); + pop(); +} + +void LuaInterface::registerClassMemberField(const std::string& className, + const std::string& field, + const LuaCppFunction& getFunction, + const LuaCppFunction& setFunction) +{ + getGlobal(className + "_fieldmethods"); + + if(getFunction) { + pushCppFunction(getFunction); + setField(stdext::format("get_%s", field)); + } + + if(setFunction) { + pushCppFunction(setFunction); + setField(stdext::format("set_%s", field)); + } + + pop(); +} + +void LuaInterface::registerGlobalFunction(const std::string& functionName, const LuaCppFunction& function) +{ + pushCppFunction(function); + setGlobal(functionName); +} + +int LuaInterface::luaObjectGetEvent(LuaInterface* lua) +{ + // stack: obj, key + LuaObjectPtr obj = lua->toObject(-2); + std::string key = lua->toString(-1); + VALIDATE(obj); + + lua->remove(-1); // removes key + + // if a get method for this key exists, calls it + lua->getMetatable(); // pushes obj metatable + lua->getField("fieldmethods"); // push obj fieldmethods + lua->remove(-2); // removes obj metatable + lua->getField(std::string("get_") + key); // pushes get method + lua->remove(-2); // remove obj fieldmethods + if(!lua->isNil()) { // is the get method not nil? + lua->insert(-2); // moves obj to the top + lua->signalCall(1, 1); // calls get method, arguments: obj + return 1; + } + lua->pop(); // pops the nil get method + + // if the field for this key exists, returns it + obj->luaGetField(key); + if(!lua->isNil()) { + lua->remove(-2); // removes the obj + // field value is on the stack + return 1; + } + lua->pop(); // pops the nil field + + // pushes the method assigned by this key + lua->getMetatable(); // pushes obj metatable + lua->getField("methods"); // push obj methods + lua->remove(-2); // removes obj metatable + lua->getField(key); // pushes obj method + lua->remove(-2); // remove obj methods + lua->remove(-2); // removes obj + + // the result value is on the stack + return 1; +} + +int LuaInterface::luaObjectSetEvent(LuaInterface* lua) +{ + // stack: obj, key, value + LuaObjectPtr obj = lua->toObject(-3); + std::string key = lua->toString(-2); + VALIDATE(obj); + + lua->remove(-2); // removes key + lua->insert(-2); // moves obj to the top + + // check if a set method for this field exists and call it + lua->getMetatable(); // pushes obj metatable + lua->getField("fieldmethods"); // push obj fieldmethods + lua->remove(-2); // removes obj metatable + lua->getField(std::string("set_") + key); // pushes set method + lua->remove(-2); // remove obj fieldmethods + if(!lua->isNil()) { // is the set method not nil? + lua->insert(-3); // moves func to -3 + lua->insert(-2); // moves obj to -2, and value to -1 + lua->signalCall(2, 0); // calls set method, arguments: obj, value + return 0; + } + lua->pop(); // pops the nil set method + + // no set method exists, then treats as an field and set it + lua->pop(); // pops the object + obj->luaSetField(key); // sets the obj field + return 0; +} + +int LuaInterface::luaObjectEqualEvent(LuaInterface* lua) +{ + // stack: obj1, obj2 + bool ret = false; + + // check if obj1 == obj2 + if(lua->isUserdata(-1) && lua->isUserdata(-2)) { + LuaObjectPtr* objPtr2 = static_cast(lua->popUserdata()); + LuaObjectPtr* objPtr1 = static_cast(lua->popUserdata()); + VALIDATE(objPtr1 && objPtr2); + if(*objPtr1 == *objPtr2) + ret = true; + } else + lua->pop(2); + + lua->pushBoolean(ret); + return 1; +} + +int LuaInterface::luaObjectCollectEvent(LuaInterface* lua) +{ + // gets object pointer + auto objPtr = static_cast(lua->popUserdata()); + VALIDATE(objPtr); + + // resets pointer to decrease object use count + objPtr->reset(); + g_lua.m_totalObjRefs--; + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +bool LuaInterface::safeRunScript(const std::string& fileName) +{ + try { + runScript(fileName); + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Failed to load script '%s': %s", fileName, e.what())); + return false; + } +} + +void LuaInterface::runScript(const std::string& fileName) +{ + loadScript(fileName); + safeCall(0, 0); +} + +void LuaInterface::runBuffer(const std::string& buffer, const std::string& source) +{ + loadBuffer(buffer, source); + safeCall(0, 0); +} + +void LuaInterface::loadScript(const std::string& fileName) +{ + // resolve file full path + std::string filePath = fileName; + if(!stdext::starts_with(fileName, "/")) + filePath = getCurrentSourcePath() + "/" + filePath; + + filePath = g_resources.guessFilePath(filePath, "lua"); + + std::string buffer = g_resources.readFileContents(filePath); + std::string source = std::string("@") + filePath; + loadBuffer(buffer, source); +} + +void LuaInterface::loadFunction(const std::string& buffer, const std::string& source) +{ + if(buffer.empty()) { + pushNil(); + return; + } + + std::string buf; + if(stdext::starts_with(buffer, "function")) + buf = stdext::format("__func = %s", buffer); + else + buf = stdext::format("__func = function(self)\n%s\nend", buffer); + + loadBuffer(buf, source); + safeCall(); + + // get the function + getGlobal("__func"); + + // reset the global __func + pushNil(); + setGlobal("__func"); +} + +void LuaInterface::evaluateExpression(const std::string& expression, const std::string& source) +{ + // evaluates the expression + if(!expression.empty()) { + std::string buffer = stdext::format("__exp = (%s)", expression); + loadBuffer(buffer, source); + safeCall(); + + // gets the expression result + getGlobal("__exp"); + + // resets global __exp + pushNil(); + setGlobal("__exp"); + } else + pushNil(); +} + +std::string LuaInterface::traceback(const std::string& errorMessage, int level) +{ + // gets debug.traceback + getGlobal("debug"); + getField("traceback"); + remove(-2); // remove debug + + // calls debug.traceback(errorMessage, level) + pushString(errorMessage); + pushInteger(level); + call(2,1); + + // returns the traceback message + return popString(); +} + +void LuaInterface::throwError(const std::string& message) +{ + if(isInCppCallback()) { + pushString(message); + error(); + } else + throw stdext::exception(message); +} + +std::string LuaInterface::getCurrentSourcePath(int level) +{ + std::string path; + if(!L) + return path; + + // check all stack functions for script source path + while(true) { + getStackFunction(level); // pushes stack function + + // only lua functions is wanted, because only them have a working directory + if(isLuaFunction()) { + path = functionSourcePath(); + break; + } else if(isNil()) { + pop(); + break; + } else + pop(); + + // next level + level++; + } + + return path; +} + +std::string LuaInterface::getCurrentFunction(int level) +{ + std::string path = "unknown"; + if(!L) + return path; + + // check all stack functions for script source path + while(true) { + getStackFunction(level); // pushes stack function + + // only lua functions is wanted, because only them have a working directory + if(isLuaFunction()) { + path = functionSource(); + break; + } else if(isNil()) { + pop(); + break; + } else + pop(); + + // next level + level++; + } + + return path; +} + +int LuaInterface::safeCall(int numArgs, int numRets, const std::shared_ptr& error) +{ + VALIDATE(hasIndex(-numArgs-1)); + + // saves the current stack size for calculating the number of results later + int previousStackSize = stackSize(); + + // pushes error function + int errorFuncIndex = previousStackSize - numArgs; + pushCFunction(&LuaInterface::luaErrorHandler); + insert(errorFuncIndex); + + // calls the function in protected mode (means errors will be caught) + int ret = pcall(numArgs, LUA_MULTRET, errorFuncIndex); + + remove(errorFuncIndex); // remove error func + + // if there was an error throw an exception + if (ret != 0) { + if (error) { + error->assign(popString()); + clearStack(); + return 0; + } else { + std::string errorMsg = popString(); + g_logger.error(stdext::format("Lua error: %s", errorMsg)); + throw LuaException(errorMsg); + } + } + int rets = (stackSize() + numArgs + 1) - previousStackSize; + while(numRets != -1 && rets != numRets) { + if(rets < numRets) { + pushNil(); + rets++; + } else { + pop(); + rets--; + } + } + + // returns the number of results + return rets; +} + +int LuaInterface::signalCall(int numArgs, int numRets) +{ + int rets = 0; + int funcIndex = -numArgs-1; + + try { + // must be a function + if(isFunction(funcIndex)) { + static std::shared_ptr error = std::make_shared(); + rets = safeCall(numArgs, -1, error); + if (!error->empty()) { + g_logger.error(stdext::format("protected lua call failed: %s", *error)); + error->clear(); + return rets; + } + + if(numRets != -1) { + if(rets != numRets) + throw LuaException("function call didn't return the expected number of results", 0); + } + } + // can also calls table of functions + else if(isTable(funcIndex)) { + // loop through table values + pushNil(); + bool done = false; + while(next(funcIndex-1)) { + if(isFunction()) { + // repush arguments + for(int i=0;i error = std::make_shared(); + int rets = safeCall(numArgs, -1, error); + if (!error->empty()) { + g_logger.error(stdext::format("protected lua call failed: %s", *error)); + error->clear(); + return rets; + } + if(rets == 1) { + done = popBoolean(); + if(done) { + pop(); + break; + } + } else if(rets != 0) + throw LuaException("function call didn't return the expected number of results", 0); + } else { + throw LuaException("attempt to call a non function", 0); + } + } + pop(numArgs + 1); // pops the table of function and arguments + + if(numRets == 1 || numRets == -1) { + rets = 1; + pushBoolean(done); + } + } + // nil values are ignored + else if(isNil(funcIndex)) { + pop(numArgs + 1); // pops the function and arguments + } + // if not nil, warn + else { + throw LuaException("attempt to call a non function value", 0); + } + } catch(stdext::exception& e) { + g_logger.error(stdext::format("protected lua call failed: %s", e.what())); + } + + // pushes nil values if needed + while(numRets != -1 && rets < numRets) { + pushNil(); + rets++; + } + + // returns the number of results on the stack + return rets; +} + +int LuaInterface::newSandboxEnv() +{ + newTable(); // pushes the new environment table + newTable(); // pushes the new environment metatable + getRef(getGlobalEnvironment()); // pushes the global environment + setField("__index"); // sets metatable __index to the global environment + setMetatable(); // assigns environment metatable + return ref(); // return a reference to the environment table +} + +/////////////////////////////////////////////////////////////////////////////// +// lua C functions + +int LuaInterface::luaScriptLoader(lua_State* L) +{ + // loads the script as a function + std::string fileName = g_lua.popString(); + + try { + g_lua.loadScript(fileName); + return 1; + } catch(stdext::exception& e) { + g_lua.pushString(std::string("\n\t") + e.what()); + return 1; + } +} + +int LuaInterface::lua_dofile(lua_State* L) +{ + std::string file = g_lua.popString(); + + try { + g_lua.loadScript(file); + g_lua.call(0, LUA_MULTRET); + return g_lua.stackSize(); + } catch(stdext::exception& e) { + g_lua.pushString(e.what()); + g_lua.error(); + return 0; + } +} + +int LuaInterface::lua_dofiles(lua_State* L) +{ + std::string contains = ""; + if(g_lua.getTop() > 2) { + contains = g_lua.popString(); + } + + bool recursive = false; + if(g_lua.getTop() > 1) { + recursive = g_lua.popBoolean(); + } + + std::string directory = g_lua.popString(); + g_lua.loadFiles(directory, recursive, contains); + + return 0; +} + +int LuaInterface::lua_loadfile(lua_State* L) +{ + std::string fileName = g_lua.popString(); + + try { + g_lua.loadScript(fileName); + return 1; + } catch(stdext::exception& e) { + g_lua.pushNil(); + g_lua.pushString(e.what()); + g_lua.error(); + return 2; + } +} + +int LuaInterface::luaErrorHandler(lua_State* L) +{ + // pops the error message + auto error = g_lua.popString(); + + // prevents repeated tracebacks + if(error.find("stack traceback:") == std::string::npos) + error = g_lua.traceback(error, 1); + + // pushes the new error message with traceback information + g_lua.pushString(error); + return 1; +} + +int LuaInterface::luaCppFunctionCallback(lua_State* L) +{ + // retrieves function pointer from userdata + auto funcPtr = static_cast(g_lua.popUpvalueUserdata()); + VALIDATE(funcPtr); + + int numRets = 0; + + // enable only for tests, high cpu usage + //AutoStat s(STATS_LUACALLBACK, g_lua.getCurrentFunction()); + + // do the call + try { + g_lua.m_cppCallbackDepth++; + numRets = (*(funcPtr->get()))(&g_lua); + g_lua.m_cppCallbackDepth--; + VALIDATE(numRets == g_lua.stackSize()); + } catch(stdext::exception& e) { + // cleanup stack + while(g_lua.stackSize() > 0) + g_lua.pop(); + numRets = 0; + g_lua.pushString(stdext::format("C++ call failed: %s", g_lua.traceback(e.what()))); + g_lua.error(); + } + catch (...) { + g_logger.fatal(stdext::format("Critical lua error!\nC++ call failed:\n%s|%s", g_lua.getCurrentFunction(), g_lua.traceback("fatal error"))); + } + + return numRets; +} + +int LuaInterface::luaCollectCppFunction(lua_State* L) +{ + auto funcPtr = static_cast(g_lua.popUserdata()); + VALIDATE(funcPtr); + funcPtr->reset(); + g_lua.m_totalFuncRefs--; + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// from here all next functions are interfaces for the Lua API + +void LuaInterface::createLuaState() +{ + // creates lua state + L = luaL_newstate(); + if(!L) + g_logger.fatal("Unable to create lua state"); + + // load lua standard libraries + luaL_openlibs(L); + + // load bit32 lib for bitwise operations + luaopen_bit32(L); + + // creates weak table + newTable(); + newTable(); + pushString("v"); + setField("__mode"); + setMetatable(); + m_weakTableRef = ref(); + + // installs script loader + getGlobal("package"); + getField("loaders"); + pushCFunction(&LuaInterface::luaScriptLoader); + rawSeti(5); + pop(2); + + // replace dofile + pushCFunction(&LuaInterface::lua_dofile); + setGlobal("dofile"); + + // dofiles + pushCFunction(&LuaInterface::lua_dofiles); + setGlobal("dofiles"); + + // replace loadfile + pushCFunction(&LuaInterface::lua_loadfile); + setGlobal("loadfile"); +} + +void LuaInterface::closeLuaState() +{ + if(L) { + // close lua, it also collects + lua_close(L); + L = NULL; + } +} + +void LuaInterface::collectGarbage() +{ + // prevents recursive collects + static bool collecting = false; + if(!collecting) { + collecting = true; + + // we must collect two times because __gc metamethod + // is called on uservalues only the second time + for(int i=0;i<2;++i) + lua_gc(L, LUA_GCCOLLECT, 0); + + collecting = false; + } +} + +void LuaInterface::loadBuffer(const std::string& buffer, const std::string& source) +{ + // loads lua buffer + int ret = luaL_loadbuffer(L, buffer.c_str(), buffer.length(), source.c_str()); + if(ret != 0) + throw LuaException(popString(), 0); +} + +int dumpWriter(lua_State *L, const void* p, size_t sz, void* ud) { + if (!ud) + return -1; + + std::string* ret = (std::string*)ud; + ret->append((const char*)p, sz); + return 0; +} + +std::string LuaInterface::generateByteCode(const std::string& buffer, std::string source) +{ + stdext::replace_all(source, "\\", "/"); + source = std::string("@/") + source; + + std::string ret; + int status = luaL_loadbuffer(L, buffer.c_str(), buffer.length(), source.c_str()); + if (status != 0) + return ret; + + lua_dump(L, dumpWriter, &ret); + clearStack(); + return ret; +} + +int LuaInterface::pcall(int numArgs, int numRets, int errorFuncIndex) +{ + VALIDATE(hasIndex(-numArgs - 1)); + return lua_pcall(L, numArgs, numRets, errorFuncIndex); +} + +void LuaInterface::call(int numArgs, int numRets) +{ + VALIDATE(hasIndex(-numArgs - 1)); + lua_call(L, numArgs, numRets); +} + +void LuaInterface::error() +{ + VALIDATE(hasIndex(-1)); + lua_error(L); +} + +int LuaInterface::ref() +{ + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + VALIDATE(ref != LUA_NOREF); + VALIDATE(ref < 2147483647); + return ref; +} + +int LuaInterface::weakRef() +{ + static int id = 0; + + // generates a new id + ++id; + if(id == 2147483647) + id = 0; + + // gets weak table + getRef(m_weakTableRef); + insert(-2); + + // sets weak_table[id] = v + rawSeti(id); + + // pops weak table + pop(); + + return id; +} + +void LuaInterface::unref(int ref) +{ + if(ref >= 0 && L != NULL) + luaL_unref(L, LUA_REGISTRYINDEX, ref); +} + +const char* LuaInterface::typeName(int index) +{ + VALIDATE(hasIndex(index)); + int type = lua_type(L, index); + return lua_typename(L, type); +} + +std::string LuaInterface::functionSourcePath() +{ + std::string path; + + // gets function source path + lua_Debug ar; + memset(&ar, 0, sizeof(ar)); + lua_getinfo(L, ">Sn", &ar); + if(ar.source) { + // scripts coming from files has source beginning with '@' + if(ar.source[0] == '@') { + path = ar.source; + path = path.substr(1, path.find_last_of("/") - 1); + path = path.substr(0, path.find_last_of(":")); + } + } + + return path; +} + +std::string LuaInterface::functionSource() +{ + std::string path; + + // gets function source path + lua_Debug ar; + memset(&ar, 0, sizeof(ar)); + lua_getinfo(L, ">Sn", &ar); + if(ar.source) { + // scripts coming from files has source beginning with '@' + //if(ar.source[0] == '@') { + path = ar.source; + //path = path.substr(1, path.find_last_of("/") - 1); + //path = path.substr(0, path.find_last_of(":")); + //} + } + + return path; +} + +void LuaInterface::insert(int index) +{ + VALIDATE(hasIndex(index)); + lua_insert(L, index); +} + +void LuaInterface::remove(int index) +{ + VALIDATE(hasIndex(index)); + lua_remove(L, index); +} + +bool LuaInterface::next(int index) +{ + VALIDATE(hasIndex(index)); + return lua_next(L, index); +} + +void LuaInterface::getStackFunction(int level) +{ + lua_Debug ar; + if(lua_getstack(L, level, &ar) == 1) + lua_getinfo(L, "f", &ar); + else + pushNil(); +} + +void LuaInterface::getRef(int ref) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); +} + +void LuaInterface::getWeakRef(int weakRef) +{ + // pushes weak_table[weakRef] + getRef(m_weakTableRef); + rawGeti(weakRef); + remove(-2); +} + +void LuaInterface::setGlobalEnvironment(int env) +{ + pushThread(); + getRef(env); + VALIDATE(isTable()); + setEnv(); + pop(); +} + +void LuaInterface::setMetatable(int index) +{ + VALIDATE(hasIndex(index)); + lua_setmetatable(L, index); +} + +void LuaInterface::getMetatable(int index) +{ + VALIDATE(hasIndex(index)); + lua_getmetatable(L, index); +} + +void LuaInterface::getField(const char* key, int index) +{ + VALIDATE(hasIndex(index)); + VALIDATE(isUserdata(index) || isTable(index)); + lua_getfield(L, index, key); +} + +void LuaInterface::setField(const char* key, int index) +{ + VALIDATE(hasIndex(index)); + VALIDATE(isUserdata(index) || isTable(index)); + lua_setfield(L, index, key); +} + +void LuaInterface::getEnv(int index) +{ + VALIDATE(hasIndex(index)); + lua_getfenv(L, index); +} + +void LuaInterface::setEnv(int index) +{ + VALIDATE(hasIndex(index)); + lua_setfenv(L, index); +} + +void LuaInterface::getTable(int index) +{ + VALIDATE(hasIndex(index)); + lua_gettable(L, index); +} + +void LuaInterface::setTable(int index) +{ + VALIDATE(hasIndex(index)); + lua_settable(L, index); +} + +void LuaInterface::clearTable(int index) +{ + VALIDATE(hasIndex(index) && isTable(index)); + pushNil(); // table, nil + while(next(index-1)) { // table, key, value + pop(); // table, key + pushValue(); // table, key, key + if(next(index-2)) { // table, key, nextkey, value + pop(); // table, key, nextkey + insert(-2); // table, nextkey, key + pushNil(); // table, nextkey, key, nil + rawSet(index-3); // table, nextkey + } else { // table, key + pushNil(); // table, key, nil + rawSet(index-2); // table + break; + } + } +} + +void LuaInterface::getGlobal(const std::string& key) +{ + lua_getglobal(L, key.c_str()); +} + +void LuaInterface::getGlobalField(const std::string& globalKey, const std::string& fieldKey) +{ + getGlobal(globalKey); + if(!isNil()) { + VALIDATE(isTable() || isUserdata()); + getField(fieldKey); + remove(-2); + } +} + +void LuaInterface::setGlobal(const std::string& key) +{ + VALIDATE(hasIndex(-1)); + lua_setglobal(L, key.c_str()); +} + +void LuaInterface::rawGet(int index) +{ + VALIDATE(hasIndex(index)); + lua_rawget(L, index); +} + +void LuaInterface::rawGeti(int n, int index) +{ + VALIDATE(hasIndex(index)); + lua_rawgeti(L, index, n); +} + +void LuaInterface::rawSet(int index) +{ + VALIDATE(hasIndex(index)); + lua_rawset(L, index); +} + +void LuaInterface::rawSeti(int n, int index) +{ + VALIDATE(hasIndex(index)); + lua_rawseti(L, index, n); +} + +void LuaInterface::newTable() +{ + lua_newtable(L); +} + +void LuaInterface::createTable(int narr, int nrec) +{ + lua_createtable(L, narr, nrec); +} + +void* LuaInterface::newUserdata(int size) +{ + return lua_newuserdata(L, size); +} + +void LuaInterface::pop(int n) +{ + if(n > 0) { + VALIDATE(hasIndex(-n)); + lua_pop(L, n); + } +} + +long LuaInterface::popInteger() +{ + VALIDATE(hasIndex(-1)); + long v = toInteger(-1); + pop(); + return v; +} + +double LuaInterface::popNumber() +{ + VALIDATE(hasIndex(-1)); + double v = toNumber(-1); + pop(); + return v; +} + +bool LuaInterface::popBoolean() +{ + VALIDATE(hasIndex(-1)); + bool v = toBoolean(-1); + pop(); + return v; +} + +std::string LuaInterface::popString() +{ + VALIDATE(hasIndex(-1)); + std::string v = toString(-1); + pop(); + return v; +} + +void* LuaInterface::popUserdata() +{ + VALIDATE(hasIndex(-1)); + void* v = toUserdata(-1); + pop(); + return v; +} + +LuaObjectPtr LuaInterface::popObject() +{ + VALIDATE(hasIndex(-1)); + LuaObjectPtr v = toObject(-1); + pop(); + return v; +} + +void* LuaInterface::popUpvalueUserdata() +{ + return lua_touserdata(L, lua_upvalueindex(1)); +} + +void LuaInterface::pushNil() +{ + lua_pushnil(L); + checkStack(); +} + +void LuaInterface::pushInteger(long v) +{ + lua_pushinteger(L, v); + checkStack(); +} + +void LuaInterface::pushNumber(double v) +{ + lua_pushnumber(L, v); + checkStack(); +} + +void LuaInterface::pushBoolean(bool v) +{ + lua_pushboolean(L, v); + checkStack(); +} + +void LuaInterface::pushCString(const char* v) +{ + if (!v) { + lua_pushnil(L); + } else { + lua_pushstring(L, v); + } + checkStack(); +} + +void LuaInterface::pushString(const std::string& v) +{ + lua_pushlstring(L, v.c_str(), v.length()); + checkStack(); +} + +void LuaInterface::pushLightUserdata(void* p) +{ + lua_pushlightuserdata(L, p); + checkStack(); +} + +void LuaInterface::pushThread() +{ + lua_pushthread(L); + checkStack(); +} + +void LuaInterface::pushObject(const LuaObjectPtr& obj) +{ + // fills a new userdata with a new LuaObjectPtr pointer + new(newUserdata(sizeof(LuaObjectPtr))) LuaObjectPtr(obj); + m_totalObjRefs++; + + obj->luaGetMetatable(); + if(isNil()) + g_logger.fatal(stdext::format("metatable for class '%s' not found, did you bind the C++ class?", obj->getClassName())); + setMetatable(); +} + +void LuaInterface::pushCFunction(LuaCFunction func, int n) +{ + lua_pushcclosure(L, func, n); + checkStack(); +} + +void LuaInterface::pushCppFunction(const LuaCppFunction& func) +{ + // create a pointer to func (this pointer will hold the function existence) + new(newUserdata(sizeof(LuaCppFunctionPtr))) LuaCppFunctionPtr(new LuaCppFunction(func)); + m_totalFuncRefs++; + + // sets the userdata __gc metamethod, needed to free the function pointer when it gets collected + newTable(); + pushCFunction(&LuaInterface::luaCollectCppFunction); + setField("__gc"); + setMetatable(); + + // actually pushes a C function callback that will call the cpp function + pushCFunction(&LuaInterface::luaCppFunctionCallback, 1); +} + +void LuaInterface::pushValue(int index) +{ + VALIDATE(hasIndex(index)); + lua_pushvalue(L, index); + checkStack(); +} + +bool LuaInterface::isNil(int index) +{ + VALIDATE(hasIndex(index)); + return lua_isnil(L, index); +} + +bool LuaInterface::isBoolean(int index) +{ + VALIDATE(hasIndex(index)); + return lua_isboolean(L, index); +} + +bool LuaInterface::isNumber(int index) +{ + VALIDATE(hasIndex(index)); + return lua_isnumber(L, index); +} + +bool LuaInterface::isString(int index) +{ + VALIDATE(hasIndex(index)); + return lua_isstring(L, index); +} + +bool LuaInterface::isTable(int index) +{ + VALIDATE(hasIndex(index)); + return lua_istable(L, index); +} + +bool LuaInterface::isFunction(int index) +{ + VALIDATE(hasIndex(index)); + return lua_isfunction(L, index); +} + +bool LuaInterface::isCFunction(int index) +{ + VALIDATE(hasIndex(index)); + return lua_iscfunction(L, index); +} + +bool LuaInterface::isUserdata(int index) +{ + VALIDATE(hasIndex(index)); + return lua_isuserdata(L, index); +} + +bool LuaInterface::toBoolean(int index) +{ + VALIDATE(hasIndex(index)); + return (bool)lua_toboolean(L, index); +} + +int LuaInterface::toInteger(int index) +{ + VALIDATE(hasIndex(index)); + return lua_tointeger(L, index); +} + +double LuaInterface::toNumber(int index) +{ + VALIDATE(hasIndex(index)); + return lua_tonumber(L, index); +} + +const char* LuaInterface::toCString(int index) +{ + VALIDATE(hasIndex(index)); + return lua_tostring(L, index); +} + +std::string LuaInterface::toString(int index) +{ + VALIDATE(hasIndex(index)); + std::string str; + size_t len; + const char *c_str = lua_tolstring(L, index, &len); + if(c_str && len > 0) + str.assign(c_str, len); + return str; +} + +void* LuaInterface::toUserdata(int index) +{ + VALIDATE(hasIndex(index)); + return lua_touserdata(L, index); +} + +LuaObjectPtr LuaInterface::toObject(int index) +{ + VALIDATE(hasIndex(index)); + if(isUserdata(index)) { + LuaObjectPtr* objRef = static_cast(toUserdata(index)); + if(objRef && *objRef) + return *objRef; + } + return nullptr; +} + +int LuaInterface::getTop() +{ + return lua_gettop(L); +} + +std::string LuaInterface::getSource(int level) +{ + lua_Debug ar; + ar.short_src[0] = 0; + ar.currentline = 0; + if (lua_getstack(L, level, &ar) == 1) { + lua_getinfo(L, "Sl", &ar); + } + return std::string(ar.short_src) + ":" + std::to_string(ar.currentline); +} + +void LuaInterface::loadFiles(std::string directory, bool recursive, std::string contains) +{ + for(const std::string& fileName : g_resources.listDirectoryFiles(directory)) { + std::string fullPath = directory + "/" + fileName; + + if(recursive && g_resources.directoryExists(fullPath)) { + loadFiles(fullPath, true, contains); + continue; + } + + if(!g_resources.isFileType(fileName, "lua")) + continue; + + if(!contains.empty() && fileName.find(contains) == std::string::npos) + continue; + + try { + g_lua.loadScript(fullPath); + g_lua.call(0, 0); + } catch(stdext::exception& e) { + g_lua.pushString(e.what()); + g_lua.error(); + } + } +} diff --git a/src/framework/luaengine/luainterface.h b/src/framework/luaengine/luainterface.h new file mode 100644 index 0000000..c884a20 --- /dev/null +++ b/src/framework/luaengine/luainterface.h @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LUAINTERFACE_H +#define LUAINTERFACE_H + +#include "declarations.h" + +#include + + +struct lua_State; +typedef int (*LuaCFunction) (lua_State *L); + +/// Class that manages LUA stuff +class LuaInterface +{ +public: + LuaInterface(); + ~LuaInterface(); + + void init(); + void terminate(); + + /// Register core script functions + void registerFunctions(); + + // functions that will register all script stuff in lua global environment + void registerSingletonClass(const std::string& className); + void registerClass(const std::string& className, const std::string& baseClass = "LuaObject"); + + void registerClassStaticFunction(const std::string& className, + const std::string& functionName, + const LuaCppFunction& function); + + void registerClassMemberFunction(const std::string& className, + const std::string& functionName, + const LuaCppFunction& function); + + void registerClassMemberField(const std::string& className, + const std::string& field, + const LuaCppFunction& getFunction, + const LuaCppFunction& setFunction); + + void registerGlobalFunction(const std::string& functionName, + const LuaCppFunction& function); + + // register shortcuts using templates + template + void registerClass() { + registerClass(stdext::demangle_class(), stdext::demangle_class()); + } + + template + void registerClassStaticFunction(const std::string& functionName, const LuaCppFunction& function) { + registerClassStaticFunction(stdext::demangle_class(), functionName, function); + } + + template + void registerClassMemberFunction(const std::string& functionName, const LuaCppFunction& function) { + registerClassMemberFunction(stdext::demangle_class(), functionName, function); + } + + template + void registerClassMemberField(const std::string& field, + const LuaCppFunction& getFunction, + const LuaCppFunction& setFunction) { + registerClassMemberField(stdext::demangle_class(), field, getFunction, setFunction); + } + + // methods for binding functions + template + void bindSingletonFunction(const std::string& functionName, F C::*function, C *instance); + template + void bindSingletonFunction(const std::string& className, const std::string& functionName, F C::* function, C* instance); + template + void bindSingletonFunction(const std::string& className, const std::string& functionName, const F& function); + + template + void bindClassStaticFunction(const std::string& functionName, const F& function); + template + void bindClassStaticFunction(const std::string& className, const std::string& functionName, const F& function); + + template + void bindClassMemberFunction(const std::string& functionName, F FC::*function); + template + void bindClassMemberFunction(const std::string& className, const std::string& functionName, F FC::*function); + + template + void bindClassMemberField(const std::string& fieldName, F1 FC::*getFunction, F2 FC::*setFunction); + template + void bindClassMemberField(const std::string& className, const std::string& fieldName, F1 FC::*getFunction, F2 FC::*setFunction); + + template + void bindClassMemberGetField(const std::string& fieldName, F FC::*getFunction); + template + void bindClassMemberGetField(const std::string& className, const std::string& fieldName, F FC::*getFunction); + + template + void bindClassMemberSetField(const std::string& fieldName, F FC::*setFunction); + template + void bindClassMemberSetField(const std::string& className, const std::string& fieldName, F FC::*setFunction); + + template + void bindGlobalFunction(const std::string& functionName, const F& function); + +private: + /// Metamethod that will retrieve fields values (that include functions) from the object when using '.' or ':' + static int luaObjectGetEvent(LuaInterface* lua); + /// Metamethod that is called when setting a field of the object by using the keyword '=' + static int luaObjectSetEvent(LuaInterface* lua); + /// Metamethod that will check equality of objects by using the keyword '==' + static int luaObjectEqualEvent(LuaInterface* lua); + /// Metamethod that is called every two lua garbage collections + /// for any LuaObject that have no references left in lua environment + /// anymore, thus this creates the possibility of holding an object + /// existence by lua until it got no references left + static int luaObjectCollectEvent(LuaInterface* lua); + +public: + /// Loads and runs a script, any errors are printed to stdout and returns false + bool safeRunScript(const std::string& fileName); + + /// Loads and runs a script + /// @exception LuaException is thrown on any lua error + void runScript(const std::string& fileName); + + /// Loads and runs the script from buffer + /// @exception LuaException is thrown on any lua error + void runBuffer(const std::string& buffer, const std::string& source); + + /// Loads a script file and pushes it's main function onto stack, + /// @exception LuaException is thrown on any lua error + void loadScript(const std::string& fileName); + + /// Loads a function from buffer and pushes it onto stack, + /// @exception LuaException is thrown on any lua error + void loadFunction(const std::string& buffer, const std::string& source = "lua function buffer"); + + /// Evaluates a lua expression and pushes the result value onto the stack + /// @exception LuaException is thrown on any lua error + void evaluateExpression(const std::string& expression, const std::string& source = "lua expression"); + + /// Generates a traceback message for the current call stack + /// @param errorMessage is an additional error message + /// @param level is the level of the traceback, 0 means trace from calling function + /// @return the generated traceback message + std::string traceback(const std::string& errorMessage = "", int level = 0); + + /// Throw a lua error if inside a lua call or generates an C++ stdext::exception + /// @param message is the error message wich will be displayed before the error traceback + /// @exception stdext::exception is thrown with the error message if the error is not captured by lua + void throwError(const std::string& message); + + /// Searches for the source of the current running function + std::string getCurrentSourcePath(int level = 0); + + /// gets current function name + std::string getCurrentFunction(int level = 0); + + /// @brief Calls a function + /// The function and arguments must be on top of the stack in order, + /// results are pushed onto the stack. + /// @exception LuaException is thrown on any lua error + /// @return number of results + int safeCall(int numArgs = 0, int numRets = -1, const std::shared_ptr& error = nullptr); + + /// Same as safeCall but catches exceptions and can also calls a table of functions, + /// if any error occurs it will be reported to stdout and returns 0 results + /// @param requestedResults is the number of results requested to pushes onto the stack, + /// if supplied, the call will always pushes that number of results, even if it fails + int signalCall(int numArgs = 0, int numRets = -1); + + /// @brief Creates a new environment table + /// The new environment table is redirected to the global environment (aka _G), + /// this allows to access global variables from _G in the new environment and + /// prevents new variables in this new environment to be set on the global environment + int newSandboxEnv(); + + template + int luaCallGlobalField(const std::string& global, const std::string& field, const T&... args); + + template + void callGlobalField(const std::string& global, const std::string& field, const T&... args); + + template + R callGlobalField(const std::string& global, const std::string& field, const T&... args); + + bool isInCppCallback() { return m_cppCallbackDepth != 0; } + +private: + /// Load scripts requested by lua 'require' + static int luaScriptLoader(lua_State* L); + /// Run scripts requested by lua 'dofile' + static int lua_dofile(lua_State* L); + /// Run scripts requested by lua 'dofiles' + static int lua_dofiles(lua_State* L); + /// Run scripts requested by lua 'dofiles' + static int lua_loadfile(lua_State* L); + /// Handle lua errors from safeCall + static int luaErrorHandler(lua_State* L); + /// Handle bound cpp functions callbacks + static int luaCppFunctionCallback(lua_State* L); + /// Collect bound cpp function pointers + static int luaCollectCppFunction(lua_State* L); + +public: + void createLuaState(); + void closeLuaState(); + + void collectGarbage(); + + void loadBuffer(const std::string& buffer, const std::string& source); + + std::string generateByteCode(const std::string & buffer, std::string source); + + int pcall(int numArgs = 0, int numRets = 0, int errorFuncIndex = 0); + void call(int numArgs = 0, int numRets = 0); + void error(); + + int ref(); + int weakRef(); + void unref(int ref); + void useValue() { pushValue(); ref(); } + + const char* typeName(int index = -1); + std::string functionSourcePath(); + std::string functionSource(); + + void insert(int index); + void remove(int index); + bool next(int index = -2); + + void checkStack() { VALIDATE(getTop() <= 20); } + void getStackFunction(int level = 0); + + void getRef(int ref); + void getWeakRef(int weakRef); + + int getGlobalEnvironment() { return m_globalEnv; } + void setGlobalEnvironment(int env); + void resetGlobalEnvironment() { setGlobalEnvironment(m_globalEnv); } + + void setMetatable(int index = -2); + void getMetatable(int index = -1); + + void getField(const char* key, int index = -1); + void getField(const std::string& key, int index = -1) { return getField(key.c_str(), index); } + void setField(const char* key, int index = -2); + void setField(const std::string& key, int index = -2) { return setField(key.c_str(), index); } + + void getTable(int index = -2); + void setTable(int index = -3); + void clearTable(int index = -1); + + void getEnv(int index = -1); + void setEnv(int index = -2); + + void getGlobal(const std::string& key); + void getGlobalField(const std::string& globalKey, const std::string& fieldKey); + void setGlobal(const std::string& key); + + void rawGet(int index = -1); + void rawGeti(int n, int index = -1); + void rawSet(int index = -3); + void rawSeti(int n, int index = -2); + + void newTable(); + void createTable(int narr, int nrec); + void* newUserdata(int size); + + void pop(int n = 1); + long popInteger(); + double popNumber(); + bool popBoolean(); + std::string popString(); + void* popUserdata(); + void* popUpvalueUserdata(); + LuaObjectPtr popObject(); + + void pushNil(); + void pushInteger(long v); + void pushNumber(double v); + void pushBoolean(bool v); + void pushCString(const char* v); + void pushString(const std::string& v); + void pushLightUserdata(void* p); + void pushThread(); + void pushValue(int index = -1); + void pushObject(const LuaObjectPtr& obj); + void pushCFunction(LuaCFunction func, int n = 0); + void pushCppFunction(const LuaCppFunction& func); + + bool isNil(int index = -1); + bool isBoolean(int index = -1); + bool isNumber(int index = -1); + bool isString(int index = -1); + bool isTable(int index = -1); + bool isFunction(int index = -1); + bool isCFunction(int index = -1); + bool isLuaFunction(int index = -1) { return (isFunction() && !isCFunction()); } + bool isUserdata(int index = -1); + + bool toBoolean(int index = -1); + int toInteger(int index = -1); + double toNumber(int index = -1); + const char* toCString(int index = -1); + std::string toString(int index = -1); + void* toUserdata(int index = -1); + LuaObjectPtr toObject(int index = -1); + + int getTop(); + int stackSize() { return getTop(); } + void clearStack() { pop(stackSize()); } + bool hasIndex(int index) { return (stackSize() >= (index < 0 ? -index : index) && index != 0); } + + std::string getSource(int level = 2); + + void loadFiles(std::string directory, bool recursive = false, std::string contains = ""); + + /// Pushes any type onto the stack + template + int polymorphicPush(const T& v, const Args&... args); + int polymorphicPush() { return 0; } + + /// Casts a value from stack to any type + /// @exception LuaBadValueCastException thrown if the cast fails + template + T castValue(int index = -1); + + /// Same as castValue but also pops + template + T polymorphicPop() { T v = castValue(); pop(1); return v; } + +private: + lua_State* L; + int m_weakTableRef; + int m_cppCallbackDepth; + int m_totalObjRefs; + int m_totalFuncRefs; + int m_globalEnv; +}; + +extern LuaInterface g_lua; + +// must be included after, because they need LuaInterface fully declared +#include "luaexception.h" +#include "luabinder.h" +#include "luavaluecasts.h" + +template +int LuaInterface::polymorphicPush(const T& v, const Args&... args) { + int r = push_luavalue(v); + return r + polymorphicPush(args...); +} + +// next templates must be defined after above includes + +template +void LuaInterface::bindSingletonFunction(const std::string& functionName, F C::*function, C *instance) { + registerClassStaticFunction(functionName, luabinder::bind_singleton_mem_fun(function, instance)); +} + +template +void LuaInterface::bindSingletonFunction(const std::string& className, const std::string& functionName, F C::*function, C *instance) { + registerClassStaticFunction(className, functionName, luabinder::bind_singleton_mem_fun(function, instance)); +} + +template +void LuaInterface::bindSingletonFunction(const std::string& className, const std::string& functionName, const F& function) +{ + registerClassStaticFunction(className, functionName, luabinder::bind_fun(function)); +} + +template +void LuaInterface::bindClassStaticFunction(const std::string& functionName, const F& function) { + registerClassStaticFunction(functionName, luabinder::bind_fun(function)); +} +template +void LuaInterface::bindClassStaticFunction(const std::string& className, const std::string& functionName, const F& function) { + registerClassStaticFunction(className, functionName, luabinder::bind_fun(function)); +} + +template +void LuaInterface::bindClassMemberFunction(const std::string& functionName, F FC::*function) { + registerClassMemberFunction(functionName, luabinder::bind_mem_fun(function)); +} +template +void LuaInterface::bindClassMemberFunction(const std::string& className, const std::string& functionName, F FC::*function) { + registerClassMemberFunction(className, functionName, luabinder::bind_mem_fun(function)); +} + +template +void LuaInterface::bindClassMemberField(const std::string& fieldName, F1 FC::*getFunction, F2 FC::*setFunction) { + registerClassMemberField(fieldName, luabinder::bind_mem_fun(getFunction), luabinder::bind_mem_fun(setFunction)); +} +template +void LuaInterface::bindClassMemberField(const std::string& className, const std::string& fieldName, F1 FC::*getFunction, F2 FC::*setFunction) { + registerClassMemberField(className, fieldName, luabinder::bind_mem_fun(getFunction), luabinder::bind_mem_fun(setFunction)); +} + +template +void LuaInterface::bindClassMemberGetField(const std::string& fieldName, F FC::*getFunction) { + registerClassMemberField(fieldName, luabinder::bind_mem_fun(getFunction), LuaCppFunction()); +} +template +void LuaInterface::bindClassMemberGetField(const std::string& className, const std::string& fieldName, F FC::*getFunction) { + registerClassMemberField(className, fieldName, luabinder::bind_mem_fun(getFunction), LuaCppFunction()); +} + +template +void LuaInterface::bindClassMemberSetField(const std::string& fieldName, F FC::*setFunction) { + registerClassMemberField(fieldName, LuaCppFunction(), luabinder::bind_mem_fun(setFunction)); +} +template +void LuaInterface::bindClassMemberSetField(const std::string& className, const std::string& fieldName, F FC::*setFunction) { + registerClassMemberField(className, fieldName, LuaCppFunction(), luabinder::bind_mem_fun(setFunction)); +} + +template +void LuaInterface::bindGlobalFunction(const std::string& functionName, const F& function) { + registerGlobalFunction(functionName, luabinder::bind_fun(function)); +} + +template +T LuaInterface::castValue(int index) { + T o; + if(!luavalue_cast(index, o)) + throw LuaBadValueCastException(typeName(index), stdext::demangle_type()); + return o; +} + +template +int LuaInterface::luaCallGlobalField(const std::string& global, const std::string& field, const T&... args) { + AutoStat s(STATS_LUA, std::string(global) + ":" + field); + + g_lua.getGlobalField(global, field); + int ret = 0; + + if(!g_lua.isNil()) { + int numArgs = g_lua.polymorphicPush(args...); + ret = g_lua.signalCall(numArgs); + } else + g_lua.pop(1); + return ret; +} + +template +void LuaInterface::callGlobalField(const std::string& global, const std::string& field, const T&... args) { + int rets = luaCallGlobalField(global, field, args...); + if(rets > 0) + pop(rets); +} + +template +R LuaInterface::callGlobalField(const std::string& global, const std::string& field, const T&... args) { + R result; + int rets = luaCallGlobalField(global, field, args...); + if(rets > 0) { + VALIDATE(rets == 1); + result = g_lua.polymorphicPop(); + } else + result = R(); + return result; +} + +#endif diff --git a/src/framework/luaengine/luaobject.cpp b/src/framework/luaengine/luaobject.cpp new file mode 100644 index 0000000..30245ce --- /dev/null +++ b/src/framework/luaengine/luaobject.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "luaobject.h" +#include "luainterface.h" + +#include +#include + +LuaObject::LuaObject() : + m_fieldsTableRef(-1) +{ +} + +LuaObject::~LuaObject() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + releaseLuaFieldsTable(); +} + +bool LuaObject::hasLuaField(const std::string& field) +{ + bool ret = false; + if(m_fieldsTableRef != -1) { + g_lua.getRef(m_fieldsTableRef); + g_lua.getField(field); // push the field value + ret = !g_lua.isNil(); + g_lua.pop(2); + } + return ret; +} + +void LuaObject::releaseLuaFieldsTable() +{ + if(m_fieldsTableRef != -1) { + g_lua.unref(m_fieldsTableRef); + m_fieldsTableRef = -1; + } +} + +void LuaObject::luaSetField(const std::string& key) +{ + // create fields table on the fly + if(m_fieldsTableRef == -1) { + g_lua.newTable(); // create fields table + m_fieldsTableRef = g_lua.ref(); // save a reference for it + } + + g_lua.getRef(m_fieldsTableRef); // push the table + g_lua.insert(-2); // move the value to the top + g_lua.setField(key); // set the field + g_lua.pop(); // pop the fields table +} + +void LuaObject::luaGetField(const std::string& key) +{ + if(m_fieldsTableRef != -1) { + g_lua.getRef(m_fieldsTableRef); // push the obj's fields table + g_lua.getField(key); // push the field value + g_lua.remove(-2); // remove the table + } else { + g_lua.pushNil(); + } +} + +void LuaObject::luaGetMetatable() +{ + static std::unordered_map metatableMap; + const std::type_info& tinfo = typeid(*this); + auto it = metatableMap.find(&tinfo); + + int metatableRef; + if(it == metatableMap.end()) { + g_lua.getGlobal(getClassName() + "_mt"); + metatableRef = g_lua.ref(); + metatableMap[&tinfo] = metatableRef; + } else + metatableRef = it->second; + + g_lua.getRef(metatableRef); +} + +void LuaObject::luaGetFieldsTable() +{ + if(m_fieldsTableRef != -1) + g_lua.getRef(m_fieldsTableRef); + else + g_lua.pushNil(); +} + +int LuaObject::getUseCount() +{ + return ref_count(); +} + +std::string LuaObject::getClassName() +{ + // TODO: this could be cached for more performance +#ifdef _MSC_VER + return stdext::demangle_name(typeid(*this).name()) + 6; +#else + return stdext::demangle_name(typeid(*this).name()); +#endif +} diff --git a/src/framework/luaengine/luaobject.h b/src/framework/luaengine/luaobject.h new file mode 100644 index 0000000..51c1829 --- /dev/null +++ b/src/framework/luaengine/luaobject.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LUAOBJECT_H +#define LUAOBJECT_H + +#include +#include "declarations.h" + +/// LuaObject, all script-able classes have it as base +// @bindclass +class LuaObject : public stdext::shared_object +{ +public: + LuaObject(); + virtual ~LuaObject(); + + template + void connectLuaField(const std::string& field, const std::function& f, bool pushFront = false); + + /// Calls a function or table of functions stored in a lua field, results are pushed onto the stack, + /// if any lua error occurs, it will be reported to stdout and return 0 results + /// @return the number of results + template + int luaCallLuaField(const std::string& field, const T&... args); + + template + R callLuaField(const std::string& field, const T&... args); + template + void callLuaField(const std::string& field, const T&... args); + + /// Returns true if the lua field exists + bool hasLuaField(const std::string& field); + + /// Sets a field in this lua object + template + void setLuaField(const std::string& key, const T& value); + + /// Gets a field from this lua object + template + T getLuaField(const std::string& key); + + /// Release fields table reference + void releaseLuaFieldsTable(); + + /// Sets a field from this lua object, the value must be on the stack + void luaSetField(const std::string& key); + + /// Gets a field from this lua object, the result is pushed onto the stack + void luaGetField(const std::string& key); + + /// Get object's metatable + void luaGetMetatable(); + + /// Gets the table containing all stored fields of this lua object, the result is pushed onto the stack + void luaGetFieldsTable(); + + /// Returns the number of references of this object + /// @note each userdata of this object on lua counts as a reference + int getUseCount(); + + /// Returns the derived class name, its the same name used in Lua + std::string getClassName(); + + LuaObjectPtr asLuaObject() { return static_self_cast(); } + + void operator=(const LuaObject& other) { } + +private: + int m_fieldsTableRef; +}; + +template +void connect(const LuaObjectPtr& obj, const std::string& field, const std::function& f, bool pushFront = false); + +template +typename std::enable_if::value, void>::type +connect(const LuaObjectPtr& obj, const std::string& field, const Lambda& f, bool pushFront = false); + +#include "luainterface.h" + +template +void LuaObject::connectLuaField(const std::string& field, const std::function& f, bool pushFront) +{ + luaGetField(field); + if(g_lua.isTable()) { + if(pushFront) + g_lua.pushInteger(1); + push_luavalue(f); + g_lua.callGlobalField("table","insert"); + } else { + if(g_lua.isNil()) { + push_luavalue(f); + luaSetField(field); + g_lua.pop(); + } else if(g_lua.isFunction()) { + g_lua.newTable(); + g_lua.insert(-2); + g_lua.rawSeti(1); + push_luavalue(f); + g_lua.rawSeti(2); + luaSetField(field); + } + } +} + +// connect for std::function +template +void connect(const LuaObjectPtr& obj, const std::string& field, const std::function& f, bool pushFront) { + obj->connectLuaField(field, f, pushFront); +} + +namespace luabinder { + template + struct connect_lambda; + + template + struct connect_lambda { + static void call(const LuaObjectPtr& obj, const std::string& field, const Lambda& f, bool pushFront) { + connect(obj, field, std::function(f), pushFront); + } + }; +}; + +// connect for lambdas +template +typename std::enable_if::value, void>::type +connect(const LuaObjectPtr& obj, const std::string& field, const Lambda& f, bool pushFront) { + typedef decltype(&Lambda::operator()) F; + luabinder::connect_lambda::call(obj, field, f, pushFront); +} + +template +int LuaObject::luaCallLuaField(const std::string& field, const T&... args) { + AutoStat s(STATS_LUA, getClassName() + ":" + field); + + // note that the field must be retrieved from this object lua value + // to force using the __index metamethod of it's metatable + // so cannot use LuaObject::getField here + // push field + //AutoStat s(STATS_LUA, field); + g_lua.pushObject(asLuaObject()); + g_lua.getField(field); + + int ret = 0; + if(!g_lua.isNil()) { + // the first argument is always this object (self) + g_lua.insert(-2); + int numArgs = g_lua.polymorphicPush(args...); + ret = g_lua.signalCall(1 + numArgs); + } else { + g_lua.pop(2); + } + + return ret; +} + +template +R LuaObject::callLuaField(const std::string& field, const T&... args) { + R result; + int rets = luaCallLuaField(field, args...); + if(rets > 0) { + VALIDATE(rets == 1); + result = g_lua.polymorphicPop(); + } else + result = R(); + return result; +} + +template +void LuaObject::callLuaField(const std::string& field, const T&... args) { + int rets = luaCallLuaField(field, args...); + if(rets > 0) + g_lua.pop(rets); +} + +template +void LuaObject::setLuaField(const std::string& key, const T& value) { + g_lua.polymorphicPush(value); + luaSetField(key); +} + +template +T LuaObject::getLuaField(const std::string& key) { + luaGetField(key); + return g_lua.polymorphicPop(); +} + +#endif diff --git a/src/framework/luaengine/luavaluecasts.cpp b/src/framework/luaengine/luavaluecasts.cpp new file mode 100644 index 0000000..7627d90 --- /dev/null +++ b/src/framework/luaengine/luavaluecasts.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "luavaluecasts.h" +#include "luainterface.h" +#include + +// bool +int push_luavalue(bool b) +{ + g_lua.pushBoolean(b); + return 1; +} + +bool luavalue_cast(int index, bool& b) +{ + b = g_lua.toBoolean(index); + return true; +} + +// int +int push_luavalue(int i) +{ + g_lua.pushInteger(i); + return 1; +} + +bool luavalue_cast(int index, int& i) +{ + i = g_lua.toInteger(index); + if(i == 0 && !g_lua.isNumber(index) && !g_lua.isNil()) + return false; + return true; +} + +// double +int push_luavalue(double d) +{ + g_lua.pushNumber(d); + return 1; +} + +bool luavalue_cast(int index, double& d) +{ + d = g_lua.toNumber(index); + if(d == 0 && !g_lua.isNumber(index) && !g_lua.isNil()) + return false; + return true; +} + +// string +int push_luavalue(const char* cstr) +{ + g_lua.pushCString(cstr); + return 1; +} + +int push_luavalue(const std::string& str) +{ + g_lua.pushString(str); + return 1; +} + +bool luavalue_cast(int index, std::string& str) +{ + str = g_lua.toString(index); + return true; +} + +// lua cpp function +int push_luavalue(const LuaCppFunction& func) +{ + g_lua.pushCppFunction(func); + return 1; +} + +// color +int push_luavalue(const Color& color) +{ + g_lua.createTable(0, 4); + g_lua.pushInteger(color.r()); + g_lua.setField("r"); + g_lua.pushInteger(color.g()); + g_lua.setField("g"); + g_lua.pushInteger(color.b()); + g_lua.setField("b"); + g_lua.pushInteger(color.a()); + g_lua.setField("a"); + return 1; +} + +bool luavalue_cast(int index, Color& color) +{ + if(g_lua.isTable(index)) { + g_lua.getField("r", index); + color.setRed((int)g_lua.popInteger()); + g_lua.getField("g", index); + color.setGreen((int)g_lua.popInteger()); + g_lua.getField("b", index); + color.setBlue((int)g_lua.popInteger()); + g_lua.getField("a", index); + color.setAlpha((int)g_lua.popInteger()); + return true; + } else if(g_lua.isString()) { + return stdext::cast(g_lua.toString(index), color); + } else if(g_lua.isNil()) { + color = Color::white; + return true; + } + return false; +} + +// rect +int push_luavalue(const Rect& rect) +{ + g_lua.createTable(0, 4); + g_lua.pushInteger(rect.x()); + g_lua.setField("x"); + g_lua.pushInteger(rect.y()); + g_lua.setField("y"); + g_lua.pushInteger(rect.width()); + g_lua.setField("width"); + g_lua.pushInteger(rect.height()); + g_lua.setField("height"); + return 1; +} + +bool luavalue_cast(int index, Rect& rect) +{ + if(g_lua.isTable(index)) { + g_lua.getField("x", index); + rect.setX(g_lua.popInteger()); + g_lua.getField("y", index); + rect.setY(g_lua.popInteger()); + g_lua.getField("width", index); + rect.setWidth(g_lua.popInteger()); + g_lua.getField("height", index); + rect.setHeight(g_lua.popInteger()); + return true; + } else if(g_lua.isString()) { + return stdext::cast(g_lua.toString(index), rect); + } else if(g_lua.isNil()) { + rect = Rect(); + return true; + } + return false; +} + +// point +int push_luavalue(const Point& point) +{ + g_lua.createTable(0, 2); + g_lua.pushInteger(point.x); + g_lua.setField("x"); + g_lua.pushInteger(point.y); + g_lua.setField("y"); + return 1; +} + +bool luavalue_cast(int index, Point& point) +{ + if(g_lua.isTable(index)) { + g_lua.getField("x", index); + point.x = g_lua.popInteger(); + g_lua.getField("y", index); + point.y = g_lua.popInteger(); + return true; + } else if(g_lua.isString()) { + return stdext::cast(g_lua.toString(index), point); + } else if(g_lua.isNil()) { + point = Point(); + return true; + } + return false; +} + +// size +int push_luavalue(const Size& size) +{ + g_lua.createTable(0, 2); + g_lua.pushInteger(size.width()); + g_lua.setField("width"); + g_lua.pushInteger(size.height()); + g_lua.setField("height"); + return 1; +} + +bool luavalue_cast(int index, Size& size) +{ + if(g_lua.isTable(index)) { + g_lua.getField("width", index); + size.setWidth(g_lua.popInteger()); + g_lua.getField("height", index); + size.setHeight(g_lua.popInteger()); + return true; + } else if(g_lua.isString()) { + return stdext::cast(g_lua.toString(index), size); + } else if(g_lua.isNil()) { + size = Size(); + return true; + } + return false; +} + +// otml nodes +void push_otml_subnode_luavalue(const OTMLNodePtr& node) +{ + if(node->hasValue()) { + union { + bool b; + double d; + long l; + }; + std::string value = node->rawValue(); + if(stdext::cast(value, b)) + g_lua.pushBoolean(b); + else if(stdext::cast(value, l)) + g_lua.pushInteger(l); + else if(stdext::cast(value, d)) + g_lua.pushNumber(d); + else + g_lua.pushString(value); + } else if(node->hasChildren()) { + g_lua.newTable(); + bool pushedChild = false; + int currentIndex = 1; + for(const OTMLNodePtr& cnode : node->children()) { + push_otml_subnode_luavalue(cnode); + if(!g_lua.isNil()) { + if(cnode->isUnique()) { + g_lua.pushString(cnode->tag()); + g_lua.insert(-2); + g_lua.rawSet(); + } else + g_lua.rawSeti(currentIndex++); + pushedChild = true; + } else + g_lua.pop(); + } + if(!pushedChild) { + g_lua.pop(); + g_lua.pushNil(); + } + } else + g_lua.pushNil(); +} + +int push_luavalue(const OTMLNodePtr& node) +{ + if(node) { + g_lua.newTable(); + int currentIndex = 1; + for(const OTMLNodePtr& cnode : node->children()) { + push_otml_subnode_luavalue(cnode); + if(cnode->isUnique() && !cnode->tag().empty()) { + g_lua.setField(cnode->tag()); + } else + g_lua.rawSeti(currentIndex++); + } + } else + g_lua.pushNil(); + return 1; +} + +bool luavalue_cast(int index, OTMLNodePtr& node) +{ + node = OTMLNode::create(); + node->setUnique(true); + if(g_lua.isTable(index)) { + g_lua.pushNil(); + while(g_lua.next(index < 0 ? index-1 : index)) { + std::string cnodeName; + if(g_lua.isString(-2)) { + g_lua.pushValue(-2); + cnodeName = g_lua.toString(); + g_lua.pop(); + } else + VALIDATE(g_lua.isNumber()); + if(g_lua.isTable()) { + OTMLNodePtr cnode; + if(luavalue_cast(-1, cnode)) { + if(cnodeName.empty()) + cnode->setUnique(false); + else + cnode->setTag(cnodeName); + node->addChild(cnode); + } + } else { + std::string value; + if(g_lua.isBoolean()) + value = stdext::unsafe_cast(g_lua.toBoolean()); + else + value = g_lua.toString(); + if(cnodeName.empty()) + node->writeIn(value); + else + node->writeAt(cnodeName, value); + } + g_lua.pop(); + } + return true; + } + return false; +} + +// object ptr +bool luavalue_cast(int index, LuaObjectPtr& obj) { + if(g_lua.isUserdata(index)) { + obj = g_lua.toObject(index); + return true; + } else if(g_lua.isNil(index)) { + obj = nullptr; + return true; + } + return false; +} diff --git a/src/framework/luaengine/luavaluecasts.h b/src/framework/luaengine/luavaluecasts.h new file mode 100644 index 0000000..ebb68d6 --- /dev/null +++ b/src/framework/luaengine/luavaluecasts.h @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LUAVALUECASTS_H +#define LUAVALUECASTS_H + +// this file is and must be included only from luainterface.h + +#include "declarations.h" +#include + +template +int push_internal_luavalue(T v); + +// bool +int push_luavalue(bool b); +bool luavalue_cast(int index, bool& b); + +// int +int push_luavalue(int i); +bool luavalue_cast(int index, int& i); + +// double +int push_luavalue(double d); +bool luavalue_cast(int index, double& d); + +// float +inline int push_luavalue(float f) { push_luavalue((double)f); return 1; } +inline bool luavalue_cast(int index, float& f) { double d; bool r = luavalue_cast(index, d); f = d; return r; } + +// int8 +inline int push_luavalue(int8 v) { push_luavalue((int)v); return 1; } +inline bool luavalue_cast(int index, int8& v) { int i; bool r = luavalue_cast(index, i); v = i; return r; } +// uint8 +inline int push_luavalue(uint8 v) { push_luavalue((int)v); return 1; } +inline bool luavalue_cast(int index, uint8& v){ int i; bool r = luavalue_cast(index, i); v = i; return r; } +// int16 +inline int push_luavalue(int16 v) { push_luavalue((int)v); return 1; } +inline bool luavalue_cast(int index, int16& v){ int i; bool r = luavalue_cast(index, i); v = i; return r; } +// uint16 +inline int push_luavalue(uint16 v) { push_luavalue((int)v); return 1; } +inline bool luavalue_cast(int index, uint16& v){ int i; bool r = luavalue_cast(index, i); v = i; return r; } +// uint32 +inline int push_luavalue(uint32 v) { push_luavalue((double)v); return 1; } +inline bool luavalue_cast(int index, uint32& v) { double d; bool r = luavalue_cast(index, d); v = d; return r; } +// int64 +inline int push_luavalue(int64 v) { push_luavalue((double)v); return 1; } +inline bool luavalue_cast(int index, int64& v) { double d; bool r = luavalue_cast(index, d); v = d; return r; } +// uint64 +inline int push_luavalue(uint64 v) { push_luavalue((double)v); return 1; } +inline bool luavalue_cast(int index, uint64& v) { double d; bool r = luavalue_cast(index, d); v = d; return r; } + +// string +int push_luavalue(const char* cstr); +int push_luavalue(const std::string& str); +bool luavalue_cast(int index, std::string& str); + +// lua cpp function +int push_luavalue(const LuaCppFunction& func); + +// color +int push_luavalue(const Color& color); +bool luavalue_cast(int index, Color& color); + +// rect +int push_luavalue(const Rect& rect); +bool luavalue_cast(int index, Rect& rect); + +// point +int push_luavalue(const Point& point); +bool luavalue_cast(int index, Point& point); + +// size +int push_luavalue(const Size& size); +bool luavalue_cast(int index, Size& size); + +// otml nodes +int push_luavalue(const OTMLNodePtr& node); +bool luavalue_cast(int index, OTMLNodePtr& node); + +// enum +template +typename std::enable_if::value, int>::type +push_luavalue(T e) { return push_luavalue((int)e); } + +template +typename std::enable_if::value, bool>::type +luavalue_cast(int index, T& myenum); + +// LuaObject pointers +template +typename std::enable_if::value, int>::type +push_luavalue(const T& obj); + +bool luavalue_cast(int index, LuaObjectPtr& obj); + +template +typename std::enable_if::value, bool>::type +luavalue_cast(int index, stdext::shared_object_ptr& ptr); + +// std::function +template +int push_luavalue(const std::function& func); + +template +bool luavalue_cast(int index, std::function& func); + +template +typename std::enable_if::value, bool>::type +luavalue_cast(int index, std::function& func); + +// list +template +int push_luavalue(const std::list& list); + +template +bool luavalue_cast(int index, std::list& list); + +// vector +template +int push_luavalue(const std::vector& vec); + +template +bool luavalue_cast(int index, std::vector& vec); + +// set +template +int push_luavalue(const std::set& vec); + +template +bool luavalue_cast(int index, std::set& vec); + +// deque +template +int push_luavalue(const std::deque& vec); + +template +bool luavalue_cast(int index, std::deque& vec); + +// map +template +int push_luavalue(const std::map& map); + +template +bool luavalue_cast(int index, std::map& map); + +// pair +template +bool luavalue_cast(int index, std::pair& pair); + +// tuple +template +int push_luavalue(const std::tuple& tuple); + +template +int push_internal_luavalue(const std::tuple& tuple); + +// start definitions + +#include "luaexception.h" +#include "luainterface.h" +#include "luaobject.h" + +template +int push_internal_luavalue(T v) { + return push_luavalue(v); +} + +template +typename std::enable_if::value, bool>::type +luavalue_cast(int index, T& myenum) { + int i; + if(luavalue_cast(index, i)) { + myenum = (T)i; + return true; + } + return false; +} + +template +typename std::enable_if::value, int>::type +push_luavalue(const T& obj) { + if(obj) + g_lua.pushObject(obj); + else + g_lua.pushNil(); + return 1; +} + +template +typename std::enable_if::value, bool>::type +luavalue_cast(int index, stdext::shared_object_ptr& ptr) { + LuaObjectPtr obj; + if(!luavalue_cast(index, obj)) + return false; + if(obj) + ptr = obj->dynamic_self_cast(); + else + ptr = nullptr; + return true; +} + +template +int push_luavalue(const std::function& func) { + if(func) { + LuaCppFunction f = luabinder::bind_fun(func); + g_lua.pushCppFunction(f); + } else + g_lua.pushNil(); + return 1; +} + +template +bool luavalue_cast(int index, std::function& func) { + if(g_lua.isFunction(index)) { + g_lua.pushValue(index); + // weak references are used here, this means that the script must hold another reference + // to this function, otherwise it will expire + int funcWeakRef = g_lua.weakRef(); + func = [=](Args... args) { + // note that we must catch exceptions, because this lambda can be called from anywhere + // and most of them won't catch exceptions (e.g. dispatcher) + g_lua.getWeakRef(funcWeakRef); + auto error = std::make_shared(); + if(g_lua.isFunction()) { + int numArgs = g_lua.polymorphicPush(args...); + int rets = g_lua.safeCall(numArgs, -1, error); + if (!error->empty()) { + g_logger.error(stdext::format("lua function callback failed: %s", *error)); + } else { + g_lua.pop(rets); + } + } else { + g_logger.error("attempt to call an expired lua function from C++, did you forget to hold a reference for that function?"); + } + }; + return true; + } else if(g_lua.isNil(index)) { + func = std::function(); + return true; + } + return false; +} + +template +typename std::enable_if::value, bool>::type +luavalue_cast(int index, std::function& func) { + if(g_lua.isFunction(index)) { + g_lua.pushValue(index); + // weak references are used here, this means that the script must hold another reference + // to this function, otherwise it will expire + int funcWeakRef = g_lua.weakRef(); + func = [=](Args... args) -> Ret { + // note that we must catch exceptions, because this lambda can be called from anywhere + // and most of them won't catch exceptions (e.g. dispatcher) + try { + g_lua.getWeakRef(funcWeakRef); + if(g_lua.isFunction()) { + int numArgs = g_lua.polymorphicPush(args...); + if(g_lua.safeCall(numArgs) != 1) + throw LuaException("a function from lua didn't retrieve the expected number of results", 0); + return g_lua.polymorphicPop(); + } else { + throw LuaException("attempt to call an expired lua function from C++," + "did you forget to hold a reference for that function?", 0); + } + } catch(LuaException& e) { + g_logger.error(stdext::format("lua function callback failed: %s", e.what())); + } + return Ret(); + }; + return true; + } else if(g_lua.isNil(index)) { + func = std::function(); + return true; + } + return false; +} + +template +int push_luavalue(const std::list& list) { + g_lua.createTable(list.size(), 0); + int i = 1; + for(const T& v : list) { + push_internal_luavalue(v); + g_lua.rawSeti(i); + i++; + } + return 1; +} + +template +bool luavalue_cast(int index, std::list& list) +{ + if(g_lua.isTable(index)) { + g_lua.pushNil(); + while(g_lua.next(index < 0 ? index-1 : index)) { + T value; + if(luavalue_cast(-1, value)) + list.push_back(value); + g_lua.pop(); + } + return true; + } + return false; +} + +template +int push_luavalue(const std::vector& vec) +{ + g_lua.createTable(vec.size(), 0); + int i = 1; + for (const T& v : vec) { + push_internal_luavalue(v); + g_lua.rawSeti(i); + i++; + } + return 1; +} + +template +bool luavalue_cast(int index, std::vector& vec) +{ + if (g_lua.isTable(index)) { + g_lua.pushNil(); + while (g_lua.next(index < 0 ? index - 1 : index)) { + T value; + if (luavalue_cast(-1, value)) + vec.push_back(value); + g_lua.pop(); + } + return true; + } + return false; +} + +template +int push_luavalue(const std::set& set) +{ + g_lua.createTable(set.size(), 0); + int i = 1; + for (const T& v : set) { + push_internal_luavalue(v); + g_lua.rawSeti(i); + i++; + } + return 1; +} + +template +bool luavalue_cast(int index, std::set& set) +{ + if (g_lua.isTable(index)) { + g_lua.pushNil(); + while (g_lua.next(index < 0 ? index - 1 : index)) { + T value; + if (luavalue_cast(-1, value)) + set.insert(value); + g_lua.pop(); + } + return true; + } + return false; +} + +template +int push_luavalue(const std::deque& vec) { + g_lua.createTable(vec.size(), 0); + int i = 1; + for(const T& v : vec) { + push_internal_luavalue(v); + g_lua.rawSeti(i); + i++; + } + return 1; +} + +template +bool luavalue_cast(int index, std::deque& vec) +{ + if(g_lua.isTable(index)) { + g_lua.pushNil(); + while(g_lua.next(index < 0 ? index-1 : index)) { + T value; + if(luavalue_cast(-1, value)) + vec.push_back(value); + g_lua.pop(); + } + return true; + } + return false; +} + +template +int push_luavalue(const std::map& map) +{ + g_lua.newTable(); + for(auto& it : map) { + push_internal_luavalue(it.first); + push_internal_luavalue(it.second); + g_lua.rawSet(); + } + return 1; +} + +template +bool luavalue_cast(int index, std::map& map) +{ + if(g_lua.isTable(index)) { + g_lua.pushNil(); + while(g_lua.next(index < 0 ? index-1 : index)) { + K key; + V value; + if(luavalue_cast(-1, value) && luavalue_cast(-2, key)) + map[key] = value; + g_lua.pop(); + } + return true; + } + return false; +} + +template +bool luavalue_cast(int index, std::pair& pair) +{ + if (g_lua.isTable(index)) { + g_lua.pushNil(); + if (g_lua.next(index < 0 ? index - 1 : index)) { + K value; + if (!luavalue_cast(-1, value)) + pair.first = value; + g_lua.pop(); + } else { + return false; + } + if (g_lua.next(index < 0 ? index - 1 : index)) { + V value; + if (!luavalue_cast(-1, value)) + pair.second = value; + g_lua.pop(); + } else { + return false; + } + + return true; + } + return false; +} + + + +template +struct push_tuple_internal_luavalue { + template + static void call(const Tuple& tuple) { + push_internal_luavalue(std::get(tuple)); + g_lua.rawSeti(N); + push_tuple_internal_luavalue::call(tuple); + } +}; + +template<> +struct push_tuple_internal_luavalue<0> { + template + static void call(const Tuple& tuple) { } +}; + +template +int push_internal_luavalue(const std::tuple& tuple) { + g_lua.newTable(); + push_tuple_internal_luavalue::call(tuple); + return 1; +} + +template +struct push_tuple_luavalue { + template + static void call(const Tuple& tuple) { + push_internal_luavalue(std::get::value - N>(tuple)); + push_tuple_luavalue::call(tuple); + } +}; + +template<> +struct push_tuple_luavalue<0> { + template + static void call(const Tuple& tuple) { } +}; + +template +int push_luavalue(const std::tuple& tuple) { + push_tuple_luavalue::call(tuple); + return sizeof...(Args); +} + +#endif diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp new file mode 100644 index 0000000..2aa8b01 --- /dev/null +++ b/src/framework/luafunctions.cpp @@ -0,0 +1,992 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FW_SOUND +#include +#include +#include +#include +#include +#endif + +#ifdef FW_GRAPHICS +#include +#include +#include +#include +#include +#include +#endif + +#ifdef FW_NET +#include +#include +#ifdef FW_PROXY +#include +#endif +#endif + +#ifdef FW_SQL +#include +#endif + +#include + +#include + + +void Application::registerLuaFunctions() +{ + // conversion globals + g_lua.bindGlobalFunction("torect", [](const std::string& v) { return stdext::from_string(v); }); + g_lua.bindGlobalFunction("topoint", [](const std::string& v) { return stdext::from_string(v); }); + g_lua.bindGlobalFunction("tocolor", [](const std::string& v) { return stdext::from_string(v); }); + g_lua.bindGlobalFunction("tosize", [](const std::string& v) { return stdext::from_string(v); }); + g_lua.bindGlobalFunction("recttostring", [](const Rect& v) { return stdext::to_string(v); }); + g_lua.bindGlobalFunction("pointtostring", [](const Point& v) { return stdext::to_string(v); }); + g_lua.bindGlobalFunction("colortostring", [](const Color& v) { return stdext::to_string(v); }); + g_lua.bindGlobalFunction("sizetostring", [](const Size& v) { return stdext::to_string(v); }); + g_lua.bindGlobalFunction("iptostring", [](uint32 v) { return stdext::ip_to_string(v); }); + g_lua.bindGlobalFunction("stringtoip", [](const std::string& v) { return stdext::string_to_ip(v); }); + g_lua.bindGlobalFunction("listSubnetAddresses", [](uint32 a, uint8 b) { return stdext::listSubnetAddresses(a, b); }); + g_lua.bindGlobalFunction("ucwords", [](std::string s) { return stdext::ucwords(s); }); + g_lua.bindGlobalFunction("regexMatch", [](std::string s, const std::string& exp) { + int limit = 10000; + std::vector> ret; + if (s.empty() || exp.empty()) + return ret; + try { + std::smatch m; + std::regex e(exp, std::regex::ECMAScript); + while (std::regex_search (s,m,e)) { + ret.push_back(std::vector()); + for (auto x:m) + ret[ret.size() - 1].push_back(x); + s = m.suffix().str(); + if (--limit == 0) + return ret; + } + } catch (...) { + } + return ret; + }); + + // Platform + g_lua.registerSingletonClass("g_platform"); + g_lua.bindSingletonFunction("g_platform", "spawnProcess", &Platform::spawnProcess, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getProcessId", &Platform::getProcessId, &g_platform); + g_lua.bindSingletonFunction("g_platform", "isProcessRunning", &Platform::isProcessRunning, &g_platform); + g_lua.bindSingletonFunction("g_platform", "copyFile", &Platform::copyFile, &g_platform); + g_lua.bindSingletonFunction("g_platform", "fileExists", &Platform::fileExists, &g_platform); + g_lua.bindSingletonFunction("g_platform", "removeFile", &Platform::removeFile, &g_platform); + g_lua.bindSingletonFunction("g_platform", "killProcess", &Platform::killProcess, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getTempPath", &Platform::getTempPath, &g_platform); + g_lua.bindSingletonFunction("g_platform", "openUrl", &Platform::openUrl, &g_platform); + g_lua.bindSingletonFunction("g_platform", "openDir", &Platform::openDir, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getCPUName", &Platform::getCPUName, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getTotalSystemMemory", &Platform::getTotalSystemMemory, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getMemoryUsage", &Platform::getMemoryUsage, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getOSName", &Platform::getOSName, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getFileModificationTime", &Platform::getFileModificationTime, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getMacAddresses", &Platform::getMacAddresses, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getUserName", &Platform::getUserName, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getDlls", &Platform::getDlls, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getProcesses", &Platform::getProcesses, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getWindows", &Platform::getWindows, &g_platform); + + // Application + g_lua.registerSingletonClass("g_app"); + g_lua.bindSingletonFunction("g_app", "setName", &Application::setName, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "setCompactName", &Application::setCompactName, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "setVersion", &Application::setVersion, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "isRunning", &Application::isRunning, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "isStopping", &Application::isStopping, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getName", &Application::getName, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getCompactName", &Application::getCompactName, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getVersion", &Application::getVersion, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getBuildCompiler", &Application::getBuildCompiler, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getBuildDate", &Application::getBuildDate, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getBuildRevision", &Application::getBuildRevision, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getBuildCommit", &Application::getBuildCommit, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getBuildType", &Application::getBuildType, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getBuildArch", &Application::getBuildArch, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getAuthor", &Application::getAuthor, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getOs", &Application::getOs, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getStartupOptions", &Application::getStartupOptions, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "exit", &Application::exit, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "quick_exit", &Application::quick_exit, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "isMobile", &Application::isMobile, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "restart", &Application::restart, static_cast(&g_app)); + + // Crypt + g_lua.registerSingletonClass("g_crypt"); + g_lua.bindSingletonFunction("g_crypt", "genUUID", &Crypt::genUUID, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "setMachineUUID", &Crypt::setMachineUUID, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "getMachineUUID", &Crypt::getMachineUUID, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "encrypt", &Crypt::encrypt, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "decrypt", &Crypt::decrypt, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "sha1Encode", &Crypt::sha1Encode, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "md5Encode", &Crypt::md5Encode, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "crc32", &Crypt::crc32, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "rsaGenerateKey", &Crypt::rsaGenerateKey, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "rsaSetPublicKey", &Crypt::rsaSetPublicKey, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "rsaSetPrivateKey", &Crypt::rsaSetPrivateKey, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "rsaCheckKey", &Crypt::rsaCheckKey, &g_crypt); + g_lua.bindSingletonFunction("g_crypt", "rsaGetSize", &Crypt::rsaGetSize, &g_crypt); + + // Clock + g_lua.registerSingletonClass("g_clock"); + g_lua.bindSingletonFunction("g_clock", "micros", &Clock::micros, &g_clock); + g_lua.bindSingletonFunction("g_clock", "millis", &Clock::millis, &g_clock); + g_lua.bindSingletonFunction("g_clock", "seconds", &Clock::seconds, &g_clock); + g_lua.bindSingletonFunction("g_clock", "realMillis", &Clock::realMillis, &g_clock); + g_lua.bindSingletonFunction("g_clock", "realMicros", &Clock::realMicros, &g_clock); + + // ConfigManager + g_lua.registerSingletonClass("g_configs"); + g_lua.bindSingletonFunction("g_configs", "getSettings", &ConfigManager::getSettings, &g_configs); + g_lua.bindSingletonFunction("g_configs", "get", &ConfigManager::get, &g_configs); + g_lua.bindSingletonFunction("g_configs", "loadSettings", &ConfigManager::loadSettings, &g_configs); + g_lua.bindSingletonFunction("g_configs", "load", &ConfigManager::load, &g_configs); + g_lua.bindSingletonFunction("g_configs", "unload", &ConfigManager::unload, &g_configs); + g_lua.bindSingletonFunction("g_configs", "create", &ConfigManager::create, &g_configs); + + // Logger + g_lua.registerSingletonClass("g_logger"); + g_lua.bindSingletonFunction("g_logger", "log", &Logger::log, &g_logger); + g_lua.bindSingletonFunction("g_logger", "fireOldMessages", &Logger::fireOldMessages, &g_logger); + g_lua.bindSingletonFunction("g_logger", "setLogFile", &Logger::setLogFile, &g_logger); + g_lua.bindSingletonFunction("g_logger", "setOnLog", &Logger::setOnLog, &g_logger); + g_lua.bindSingletonFunction("g_logger", "debug", &Logger::debug, &g_logger); + g_lua.bindSingletonFunction("g_logger", "info", &Logger::info, &g_logger); + g_lua.bindSingletonFunction("g_logger", "warning", &Logger::warning, &g_logger); + g_lua.bindSingletonFunction("g_logger", "error", &Logger::error, &g_logger); + g_lua.bindSingletonFunction("g_logger", "fatal", &Logger::fatal, &g_logger); + g_lua.bindSingletonFunction("g_logger", "getLastLog", &Logger::getLastLog, &g_logger); + + // Lua stats + g_lua.registerSingletonClass("g_stats"); + g_lua.bindSingletonFunction("g_stats", "types", &Stats::types, &g_stats); + g_lua.bindSingletonFunction("g_stats", "get", &Stats::get, &g_stats); + g_lua.bindSingletonFunction("g_stats", "clear", &Stats::clear, &g_stats); + g_lua.bindSingletonFunction("g_stats", "clearAll", &Stats::clearAll, &g_stats); + g_lua.bindSingletonFunction("g_stats", "getSlow", &Stats::getSlow, &g_stats); + g_lua.bindSingletonFunction("g_stats", "clearSlow", &Stats::clearSlow, &g_stats); + g_lua.bindSingletonFunction("g_stats", "getSleepTime", &Stats::getSleepTime, &g_stats); + g_lua.bindSingletonFunction("g_stats", "resetSleepTime", &Stats::resetSleepTime, &g_stats); + g_lua.bindSingletonFunction("g_stats", "getWidgetsInfo", &Stats::getWidgetsInfo, &g_stats); + + g_lua.registerSingletonClass("g_extras"); + g_lua.bindSingletonFunction("g_extras", "set", &Extras::set, &g_extras); + g_lua.bindSingletonFunction("g_extras", "get", &Extras::get, &g_extras); + g_lua.bindSingletonFunction("g_extras", "getDescription", &Extras::getDescription, &g_extras); + g_lua.bindSingletonFunction("g_extras", "getAll", &Extras::getAll, &g_extras); + + g_lua.registerSingletonClass("g_http"); + g_lua.bindSingletonFunction("g_http", "get", &Http::get, &g_http); + g_lua.bindSingletonFunction("g_http", "post", &Http::post, &g_http); + g_lua.bindSingletonFunction("g_http", "download", &Http::download, &g_http); + g_lua.bindSingletonFunction("g_http", "ws", &Http::ws, &g_http); + g_lua.bindSingletonFunction("g_http", "wsSend", &Http::wsSend, &g_http); + g_lua.bindSingletonFunction("g_http", "wsClose", &Http::wsClose, &g_http); + g_lua.bindSingletonFunction("g_http", "cancel", &Http::cancel, &g_http); + + g_lua.registerSingletonClass("g_atlas"); + g_lua.bindSingletonFunction("g_atlas", "getStats", &Atlas::getStats, &g_atlas); + + // ModuleManager + g_lua.registerSingletonClass("g_modules"); + g_lua.bindSingletonFunction("g_modules", "discoverModules", &ModuleManager::discoverModules, &g_modules); + g_lua.bindSingletonFunction("g_modules", "autoLoadModules", &ModuleManager::autoLoadModules, &g_modules); + g_lua.bindSingletonFunction("g_modules", "discoverModule", &ModuleManager::discoverModule, &g_modules); + g_lua.bindSingletonFunction("g_modules", "ensureModuleLoaded", &ModuleManager::ensureModuleLoaded, &g_modules); + g_lua.bindSingletonFunction("g_modules", "unloadModules", &ModuleManager::unloadModules, &g_modules); + g_lua.bindSingletonFunction("g_modules", "reloadModules", &ModuleManager::reloadModules, &g_modules); + g_lua.bindSingletonFunction("g_modules", "getModule", &ModuleManager::getModule, &g_modules); + g_lua.bindSingletonFunction("g_modules", "getModules", &ModuleManager::getModules, &g_modules); + + // EventDispatcher + g_lua.registerSingletonClass("g_dispatcher"); + g_lua.bindSingletonFunction("g_dispatcher", "addEvent", &EventDispatcher::addEventEx, &g_dispatcher); + g_lua.bindSingletonFunction("g_dispatcher", "scheduleEvent", &EventDispatcher::scheduleEventEx, &g_dispatcher); + g_lua.bindSingletonFunction("g_dispatcher", "cycleEvent", &EventDispatcher::cycleEventEx, &g_dispatcher); + + // ResourceManager + g_lua.registerSingletonClass("g_resources"); + g_lua.bindSingletonFunction("g_resources", "fileExists", &ResourceManager::fileExists, &g_resources); + g_lua.bindSingletonFunction("g_resources", "directoryExists", &ResourceManager::directoryExists, &g_resources); + g_lua.bindSingletonFunction("g_resources", "getWorkDir", &ResourceManager::getWorkDir, &g_resources); + g_lua.bindSingletonFunction("g_resources", "getWriteDir", &ResourceManager::getWriteDir, &g_resources); + g_lua.bindSingletonFunction("g_resources", "getBinaryName", &ResourceManager::getBinaryName, &g_resources); + g_lua.bindSingletonFunction("g_resources", "listDirectoryFiles", &ResourceManager::listDirectoryFiles, &g_resources); + g_lua.bindSingletonFunction("g_resources", "isFileType", &ResourceManager::isFileType, &g_resources); + g_lua.bindSingletonFunction("g_resources", "readFileContents", &ResourceManager::readFileContentsSafe, &g_resources); + g_lua.bindSingletonFunction("g_resources", "writeFileContents", &ResourceManager::writeFileContents, &g_resources); + g_lua.bindSingletonFunction("g_resources", "guessFilePath", &ResourceManager::guessFilePath, &g_resources); + g_lua.bindSingletonFunction("g_resources", "makeDir", &ResourceManager::makeDir, &g_resources); + g_lua.bindSingletonFunction("g_resources", "deleteFile", &ResourceManager::deleteFile, &g_resources); + g_lua.bindSingletonFunction("g_resources", "readCrashLog", [] { return std::string(); }); + g_lua.bindSingletonFunction("g_resources", "deleteCrashLog", [] { return std::string(); }); + + g_lua.bindSingletonFunction("g_resources", "resolvePath", &ResourceManager::resolvePath, &g_resources); + g_lua.bindSingletonFunction("g_resources", "isLoadedFromMemory", &ResourceManager::isLoadedFromMemory, &g_resources); + g_lua.bindSingletonFunction("g_resources", "isLoadedFromArchive", &ResourceManager::isLoadedFromArchive, &g_resources); + g_lua.bindSingletonFunction("g_resources", "listUpdateableFiles", [] { return std::list(); } ); + g_lua.bindSingletonFunction("g_resources", "fileChecksum", &ResourceManager::fileChecksum, &g_resources); + g_lua.bindSingletonFunction("g_resources", "filesChecksums", &ResourceManager::filesChecksums, &g_resources); + g_lua.bindSingletonFunction("g_resources", "selfChecksum", &ResourceManager::selfChecksum, &g_resources); + g_lua.bindSingletonFunction("g_resources", "updateData", &ResourceManager::updateData, &g_resources); + g_lua.bindSingletonFunction("g_resources", "updateExecutable", &ResourceManager::updateExecutable, &g_resources); + g_lua.bindSingletonFunction("g_resources", "setLayout", &ResourceManager::setLayout, &g_resources); + g_lua.bindSingletonFunction("g_resources", "getLayout", &ResourceManager::getLayout, &g_resources); + + // Config + g_lua.registerClass(); + g_lua.bindClassMemberFunction("save", &Config::save); + g_lua.bindClassMemberFunction("setValue", &Config::setValue); + g_lua.bindClassMemberFunction("setList", &Config::setList); + g_lua.bindClassMemberFunction("getValue", &Config::getValue); + g_lua.bindClassMemberFunction("getList", &Config::getList); + g_lua.bindClassMemberFunction("exists", &Config::exists); + g_lua.bindClassMemberFunction("remove", &Config::remove); + g_lua.bindClassMemberFunction("setNode", &Config::setNode); + g_lua.bindClassMemberFunction("getNode", &Config::getNode); + g_lua.bindClassMemberFunction("mergeNode", &Config::mergeNode); + g_lua.bindClassMemberFunction("getFileName", &Config::getFileName); + + // Module + g_lua.registerClass(); + g_lua.bindClassMemberFunction("load", &Module::load); + g_lua.bindClassMemberFunction("unload", &Module::unload); + g_lua.bindClassMemberFunction("reload", &Module::reload); + g_lua.bindClassMemberFunction("canReload", &Module::canReload); + g_lua.bindClassMemberFunction("canUnload", &Module::canUnload); + g_lua.bindClassMemberFunction("isLoaded", &Module::isLoaded); + g_lua.bindClassMemberFunction("isReloadble", &Module::isReloadable); + g_lua.bindClassMemberFunction("isSandboxed", &Module::isSandboxed); + g_lua.bindClassMemberFunction("getDescription", &Module::getDescription); + g_lua.bindClassMemberFunction("getName", &Module::getName); + g_lua.bindClassMemberFunction("getAuthor", &Module::getAuthor); + g_lua.bindClassMemberFunction("getWebsite", &Module::getWebsite); + g_lua.bindClassMemberFunction("getVersion", &Module::getVersion); + g_lua.bindClassMemberFunction("getSandbox", &Module::getSandbox); + g_lua.bindClassMemberFunction("isAutoLoad", &Module::isAutoLoad); + g_lua.bindClassMemberFunction("getAutoLoadPriority", &Module::getAutoLoadPriority); + + // Event + g_lua.registerClass(); + g_lua.bindClassMemberFunction("cancel", &Event::cancel); + g_lua.bindClassMemberFunction("execute", &Event::execute); + g_lua.bindClassMemberFunction("isCanceled", &Event::isCanceled); + g_lua.bindClassMemberFunction("isExecuted", &Event::isExecuted); + + // ScheduledEvent + g_lua.registerClass(); + g_lua.bindClassMemberFunction("nextCycle", &ScheduledEvent::nextCycle); + g_lua.bindClassMemberFunction("ticks", &ScheduledEvent::ticks); + g_lua.bindClassMemberFunction("remainingTicks", &ScheduledEvent::remainingTicks); + g_lua.bindClassMemberFunction("delay", &ScheduledEvent::delay); + g_lua.bindClassMemberFunction("cyclesExecuted", &ScheduledEvent::cyclesExecuted); + g_lua.bindClassMemberFunction("maxCycles", &ScheduledEvent::maxCycles); + +#ifdef FW_GRAPHICS + // GraphicalApplication + g_lua.bindSingletonFunction("g_app", "setMaxFps", &GraphicalApplication::setMaxFps, &g_app); + g_lua.bindSingletonFunction("g_app", "getMaxFps", &GraphicalApplication::getMaxFps, &g_app); + g_lua.bindSingletonFunction("g_app", "getFps", &GraphicalApplication::getFps, &g_app); + g_lua.bindSingletonFunction("g_app", "getGraphicsFps", &GraphicalApplication::getGraphicsFps, &g_app); + g_lua.bindSingletonFunction("g_app", "getProcessingFps", &GraphicalApplication::getProcessingFps, &g_app); + g_lua.bindSingletonFunction("g_app", "isOnInputEvent", &GraphicalApplication::isOnInputEvent, &g_app); + g_lua.bindSingletonFunction("g_app", "doScreenshot", &GraphicalApplication::doScreenshot, &g_app); + g_lua.bindSingletonFunction("g_app", "scaleDown", &GraphicalApplication::scaleDown, &g_app); + g_lua.bindSingletonFunction("g_app", "scaleUp", &GraphicalApplication::scaleUp, &g_app); + g_lua.bindSingletonFunction("g_app", "scale", &GraphicalApplication::scale, &g_app); + + // AdaptiveRenderer + g_lua.registerSingletonClass("g_adaptiveRenderer"); + g_lua.bindSingletonFunction("g_adaptiveRenderer", "getLevel", &AdaptiveRenderer::getLevel, &g_adaptiveRenderer); + g_lua.bindSingletonFunction("g_adaptiveRenderer", "setLevel", &AdaptiveRenderer::setForcedLevel, &g_adaptiveRenderer); + g_lua.bindSingletonFunction("g_adaptiveRenderer", "getDebugInfo", &AdaptiveRenderer::getDebugInfo, &g_adaptiveRenderer); + + // PlatformWindow + g_lua.registerSingletonClass("g_window"); + g_lua.bindSingletonFunction("g_window", "move", &PlatformWindow::move, &g_window); + g_lua.bindSingletonFunction("g_window", "resize", &PlatformWindow::resize, &g_window); + g_lua.bindSingletonFunction("g_window", "show", &PlatformWindow::show, &g_window); + g_lua.bindSingletonFunction("g_window", "hide", &PlatformWindow::hide, &g_window); + g_lua.bindSingletonFunction("g_window", "poll", [] {}); // for backward compability + g_lua.bindSingletonFunction("g_window", "maximize", &PlatformWindow::maximize, &g_window); + g_lua.bindSingletonFunction("g_window", "restoreMouseCursor", &PlatformWindow::restoreMouseCursor, &g_window); + g_lua.bindSingletonFunction("g_window", "showMouse", &PlatformWindow::showMouse, &g_window); + g_lua.bindSingletonFunction("g_window", "hideMouse", &PlatformWindow::hideMouse, &g_window); + g_lua.bindSingletonFunction("g_window", "setTitle", &PlatformWindow::setTitle, &g_window); + g_lua.bindSingletonFunction("g_window", "setMouseCursor", &PlatformWindow::setMouseCursor, &g_window); + g_lua.bindSingletonFunction("g_window", "setMinimumSize", &PlatformWindow::setMinimumSize, &g_window); + g_lua.bindSingletonFunction("g_window", "setFullscreen", &PlatformWindow::setFullscreen, &g_window); + g_lua.bindSingletonFunction("g_window", "setVerticalSync", &PlatformWindow::setVerticalSync, &g_window); + g_lua.bindSingletonFunction("g_window", "setIcon", &PlatformWindow::setIcon, &g_window); + g_lua.bindSingletonFunction("g_window", "setClipboardText", &PlatformWindow::setClipboardText, &g_window); + g_lua.bindSingletonFunction("g_window", "getDisplaySize", &PlatformWindow::getDisplaySize, &g_window); + g_lua.bindSingletonFunction("g_window", "getClipboardText", &PlatformWindow::getClipboardText, &g_window); + g_lua.bindSingletonFunction("g_window", "getPlatformType", &PlatformWindow::getPlatformType, &g_window); + g_lua.bindSingletonFunction("g_window", "getDisplayWidth", &PlatformWindow::getDisplayWidth, &g_window); + g_lua.bindSingletonFunction("g_window", "getDisplayHeight", &PlatformWindow::getDisplayHeight, &g_window); + g_lua.bindSingletonFunction("g_window", "getUnmaximizedSize", &PlatformWindow::getUnmaximizedSize, &g_window); + g_lua.bindSingletonFunction("g_window", "getSize", &PlatformWindow::getSize, &g_window); + g_lua.bindSingletonFunction("g_window", "getWidth", &PlatformWindow::getWidth, &g_window); + g_lua.bindSingletonFunction("g_window", "getHeight", &PlatformWindow::getHeight, &g_window); + g_lua.bindSingletonFunction("g_window", "getUnmaximizedPos", &PlatformWindow::getUnmaximizedPos, &g_window); + g_lua.bindSingletonFunction("g_window", "getPosition", &PlatformWindow::getPosition, &g_window); + g_lua.bindSingletonFunction("g_window", "getX", &PlatformWindow::getX, &g_window); + g_lua.bindSingletonFunction("g_window", "getY", &PlatformWindow::getY, &g_window); + g_lua.bindSingletonFunction("g_window", "getMousePosition", &PlatformWindow::getMousePosition, &g_window); + g_lua.bindSingletonFunction("g_window", "getKeyboardModifiers", &PlatformWindow::getKeyboardModifiers, &g_window); + g_lua.bindSingletonFunction("g_window", "isKeyPressed", &PlatformWindow::isKeyPressed, &g_window); + g_lua.bindSingletonFunction("g_window", "isMouseButtonPressed", &PlatformWindow::isMouseButtonPressed, &g_window); + g_lua.bindSingletonFunction("g_window", "isVisible", &PlatformWindow::isVisible, &g_window); + g_lua.bindSingletonFunction("g_window", "isFullscreen", &PlatformWindow::isFullscreen, &g_window); + g_lua.bindSingletonFunction("g_window", "isMaximized", &PlatformWindow::isMaximized, &g_window); + g_lua.bindSingletonFunction("g_window", "hasFocus", &PlatformWindow::hasFocus, &g_window); + g_lua.bindSingletonFunction("g_window", "showTextEditor", &PlatformWindow::showTextEditor, &g_window); + + // Input + g_lua.registerSingletonClass("g_mouse"); + g_lua.bindSingletonFunction("g_mouse", "loadCursors", &Mouse::loadCursors, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "addCursor", &Mouse::addCursor, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "pushCursor", &Mouse::pushCursor, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "popCursor", &Mouse::popCursor, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "isCursorChanged", &Mouse::isCursorChanged, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "isPressed", &Mouse::isPressed, &g_mouse); + + // Graphics + g_lua.registerSingletonClass("g_graphics"); + g_lua.bindSingletonFunction("g_graphics", "getVendor", &Graphics::getVendor, &g_graphics); + g_lua.bindSingletonFunction("g_graphics", "getRenderer", &Graphics::getRenderer, &g_graphics); + g_lua.bindSingletonFunction("g_graphics", "getVersion", &Graphics::getVersion, &g_graphics); + g_lua.bindSingletonFunction("g_graphics", "getExtensions", &Graphics::getExtensions, &g_graphics); + + // Textures + g_lua.registerSingletonClass("g_textures"); + g_lua.bindSingletonFunction("g_textures", "preload", &TextureManager::preload, &g_textures); + g_lua.bindSingletonFunction("g_textures", "clearCache", &TextureManager::clearCache, &g_textures); + g_lua.bindSingletonFunction("g_textures", "reload", &TextureManager::reload, &g_textures); + + // UI + g_lua.registerSingletonClass("g_ui"); + g_lua.bindSingletonFunction("g_ui", "clearStyles", &UIManager::clearStyles, &g_ui); + g_lua.bindSingletonFunction("g_ui", "importStyle", &UIManager::importStyle, &g_ui); + g_lua.bindSingletonFunction("g_ui", "importStyleFromString", &UIManager::importStyleFromString, &g_ui); + g_lua.bindSingletonFunction("g_ui", "getStyle", &UIManager::getStyle, &g_ui); + g_lua.bindSingletonFunction("g_ui", "getStyleClass", &UIManager::getStyleClass, &g_ui); + g_lua.bindSingletonFunction("g_ui", "loadUI", &UIManager::loadUI, &g_ui); + g_lua.bindSingletonFunction("g_ui", "loadUIFromString", &UIManager::loadUIFromString, &g_ui); + g_lua.bindSingletonFunction("g_ui", "displayUI", &UIManager::displayUI, &g_ui); + g_lua.bindSingletonFunction("g_ui", "createWidget", &UIManager::createWidget, &g_ui); + g_lua.bindSingletonFunction("g_ui", "createWidgetFromOTML", &UIManager::createWidgetFromOTML, &g_ui); + g_lua.bindSingletonFunction("g_ui", "getRootWidget", &UIManager::getRootWidget, &g_ui); + g_lua.bindSingletonFunction("g_ui", "getDraggingWidget", &UIManager::getDraggingWidget, &g_ui); + g_lua.bindSingletonFunction("g_ui", "getPressedWidget", &UIManager::getPressedWidget, &g_ui); + g_lua.bindSingletonFunction("g_ui", "setDebugBoxesDrawing", &UIManager::setDebugBoxesDrawing, &g_ui); + g_lua.bindSingletonFunction("g_ui", "isDrawingDebugBoxes", &UIManager::isDrawingDebugBoxes, &g_ui); + g_lua.bindSingletonFunction("g_ui", "isMouseGrabbed", &UIManager::isMouseGrabbed, &g_ui); + g_lua.bindSingletonFunction("g_ui", "isKeyboardGrabbed", &UIManager::isKeyboardGrabbed, &g_ui); + + // FontManager + g_lua.registerSingletonClass("g_fonts"); + g_lua.bindSingletonFunction("g_fonts", "clearFonts", &FontManager::clearFonts, &g_fonts); + g_lua.bindSingletonFunction("g_fonts", "importFont", &FontManager::importFont, &g_fonts); + g_lua.bindSingletonFunction("g_fonts", "fontExists", &FontManager::fontExists, &g_fonts); + g_lua.bindSingletonFunction("g_fonts", "setDefaultFont", &FontManager::setDefaultFont, &g_fonts); + + // Particles, for backward compability + g_lua.registerSingletonClass("g_particles"); + g_lua.bindSingletonFunction("g_particles", "importParticle", [](const std::string& v) {}); + + // UIWidget + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UIWidgetPtr(new UIWidget); }); + g_lua.bindClassMemberFunction("addChild", &UIWidget::addChild); + g_lua.bindClassMemberFunction("insertChild", &UIWidget::insertChild); + g_lua.bindClassMemberFunction("removeChild", &UIWidget::removeChild); + g_lua.bindClassMemberFunction("focusChild", &UIWidget::focusChild); + g_lua.bindClassMemberFunction("focusNextChild", &UIWidget::focusNextChild); + g_lua.bindClassMemberFunction("focusPreviousChild", &UIWidget::focusPreviousChild); + g_lua.bindClassMemberFunction("lowerChild", &UIWidget::lowerChild); + g_lua.bindClassMemberFunction("raiseChild", &UIWidget::raiseChild); + g_lua.bindClassMemberFunction("moveChildToIndex", &UIWidget::moveChildToIndex); + g_lua.bindClassMemberFunction("reorderChildren", &UIWidget::reorderChildren); + g_lua.bindClassMemberFunction("lockChild", &UIWidget::lockChild); + g_lua.bindClassMemberFunction("unlockChild", &UIWidget::unlockChild); + g_lua.bindClassMemberFunction("mergeStyle", &UIWidget::mergeStyle); + g_lua.bindClassMemberFunction("applyStyle", &UIWidget::applyStyle); + g_lua.bindClassMemberFunction("addAnchor", &UIWidget::addAnchor); + g_lua.bindClassMemberFunction("removeAnchor", &UIWidget::removeAnchor); + g_lua.bindClassMemberFunction("fill", &UIWidget::fill); + g_lua.bindClassMemberFunction("centerIn", &UIWidget::centerIn); + g_lua.bindClassMemberFunction("breakAnchors", &UIWidget::breakAnchors); + g_lua.bindClassMemberFunction("updateParentLayout", &UIWidget::updateParentLayout); + g_lua.bindClassMemberFunction("updateLayout", &UIWidget::updateLayout); + g_lua.bindClassMemberFunction("lock", &UIWidget::lock); + g_lua.bindClassMemberFunction("unlock", &UIWidget::unlock); + g_lua.bindClassMemberFunction("focus", &UIWidget::focus); + g_lua.bindClassMemberFunction("lower", &UIWidget::lower); + g_lua.bindClassMemberFunction("raise", &UIWidget::raise); + g_lua.bindClassMemberFunction("grabMouse", &UIWidget::grabMouse); + g_lua.bindClassMemberFunction("ungrabMouse", &UIWidget::ungrabMouse); + g_lua.bindClassMemberFunction("grabKeyboard", &UIWidget::grabKeyboard); + g_lua.bindClassMemberFunction("ungrabKeyboard", &UIWidget::ungrabKeyboard); + g_lua.bindClassMemberFunction("bindRectToParent", &UIWidget::bindRectToParent); + g_lua.bindClassMemberFunction("destroy", &UIWidget::destroy); + g_lua.bindClassMemberFunction("destroyChildren", &UIWidget::destroyChildren); + g_lua.bindClassMemberFunction("setId", &UIWidget::setId); + g_lua.bindClassMemberFunction("setParent", &UIWidget::setParent); + g_lua.bindClassMemberFunction("setLayout", &UIWidget::setLayout); + g_lua.bindClassMemberFunction("setRect", &UIWidget::setRect); + g_lua.bindClassMemberFunction("setStyle", &UIWidget::setStyle); + g_lua.bindClassMemberFunction("setStyleFromNode", &UIWidget::setStyleFromNode); + g_lua.bindClassMemberFunction("setEnabled", &UIWidget::setEnabled); + g_lua.bindClassMemberFunction("setVisible", &UIWidget::setVisible); + g_lua.bindClassMemberFunction("setOn", &UIWidget::setOn); + g_lua.bindClassMemberFunction("setChecked", &UIWidget::setChecked); + g_lua.bindClassMemberFunction("setFocusable", &UIWidget::setFocusable); + g_lua.bindClassMemberFunction("setPhantom", &UIWidget::setPhantom); + g_lua.bindClassMemberFunction("setDraggable", &UIWidget::setDraggable); + g_lua.bindClassMemberFunction("setFixedSize", &UIWidget::setFixedSize); + g_lua.bindClassMemberFunction("setClipping", &UIWidget::setClipping); + g_lua.bindClassMemberFunction("setLastFocusReason", &UIWidget::setLastFocusReason); + g_lua.bindClassMemberFunction("setAutoFocusPolicy", &UIWidget::setAutoFocusPolicy); + g_lua.bindClassMemberFunction("setAutoRepeatDelay", &UIWidget::setAutoRepeatDelay); + g_lua.bindClassMemberFunction("setVirtualOffset", &UIWidget::setVirtualOffset); + g_lua.bindClassMemberFunction("isVisible", &UIWidget::isVisible); + g_lua.bindClassMemberFunction("isChildLocked", &UIWidget::isChildLocked); + g_lua.bindClassMemberFunction("hasChild", &UIWidget::hasChild); + g_lua.bindClassMemberFunction("getChildIndex", &UIWidget::getChildIndex); + g_lua.bindClassMemberFunction("getMarginRect", &UIWidget::getMarginRect); + g_lua.bindClassMemberFunction("getPaddingRect", &UIWidget::getPaddingRect); + g_lua.bindClassMemberFunction("getChildrenRect", &UIWidget::getChildrenRect); + g_lua.bindClassMemberFunction("getAnchoredLayout", &UIWidget::getAnchoredLayout); + g_lua.bindClassMemberFunction("getRootParent", &UIWidget::getRootParent); + g_lua.bindClassMemberFunction("getChildAfter", &UIWidget::getChildAfter); + g_lua.bindClassMemberFunction("getChildBefore", &UIWidget::getChildBefore); + g_lua.bindClassMemberFunction("getChildById", &UIWidget::getChildById); + g_lua.bindClassMemberFunction("getChildByPos", &UIWidget::getChildByPos); + g_lua.bindClassMemberFunction("getChildByIndex", &UIWidget::getChildByIndex); + g_lua.bindClassMemberFunction("recursiveGetChildById", &UIWidget::recursiveGetChildById); + g_lua.bindClassMemberFunction("recursiveGetChildByPos", &UIWidget::recursiveGetChildByPos); + g_lua.bindClassMemberFunction("recursiveGetChildren", &UIWidget::recursiveGetChildren); + g_lua.bindClassMemberFunction("recursiveGetChildrenByPos", &UIWidget::recursiveGetChildrenByPos); + g_lua.bindClassMemberFunction("recursiveGetChildrenByMarginPos", &UIWidget::recursiveGetChildrenByMarginPos); + g_lua.bindClassMemberFunction("backwardsGetWidgetById", &UIWidget::backwardsGetWidgetById); + g_lua.bindClassMemberFunction("resize", &UIWidget::resize); + g_lua.bindClassMemberFunction("move", &UIWidget::move); + g_lua.bindClassMemberFunction("hide", &UIWidget::hide); + g_lua.bindClassMemberFunction("show", &UIWidget::show); + g_lua.bindClassMemberFunction("disable", &UIWidget::disable); + g_lua.bindClassMemberFunction("enable", &UIWidget::enable); + g_lua.bindClassMemberFunction("isActive", &UIWidget::isActive); + g_lua.bindClassMemberFunction("isEnabled", &UIWidget::isEnabled); + g_lua.bindClassMemberFunction("isDisabled", &UIWidget::isDisabled); + g_lua.bindClassMemberFunction("isFocused", &UIWidget::isFocused); + g_lua.bindClassMemberFunction("isHovered", &UIWidget::isHovered); + g_lua.bindClassMemberFunction("isPressed", &UIWidget::isPressed); + g_lua.bindClassMemberFunction("isFirst", &UIWidget::isFirst); + g_lua.bindClassMemberFunction("isMiddle", &UIWidget::isMiddle); + g_lua.bindClassMemberFunction("isLast", &UIWidget::isLast); + g_lua.bindClassMemberFunction("isAlternate", &UIWidget::isAlternate); + g_lua.bindClassMemberFunction("isChecked", &UIWidget::isChecked); + g_lua.bindClassMemberFunction("isOn", &UIWidget::isOn); + g_lua.bindClassMemberFunction("isDragging", &UIWidget::isDragging); + g_lua.bindClassMemberFunction("isHidden", &UIWidget::isHidden); + g_lua.bindClassMemberFunction("isExplicitlyEnabled", &UIWidget::isExplicitlyEnabled); + g_lua.bindClassMemberFunction("isExplicitlyVisible", &UIWidget::isExplicitlyVisible); + g_lua.bindClassMemberFunction("isFocusable", &UIWidget::isFocusable); + g_lua.bindClassMemberFunction("isPhantom", &UIWidget::isPhantom); + g_lua.bindClassMemberFunction("isDraggable", &UIWidget::isDraggable); + g_lua.bindClassMemberFunction("isFixedSize", &UIWidget::isFixedSize); + g_lua.bindClassMemberFunction("isClipping", &UIWidget::isClipping); + g_lua.bindClassMemberFunction("isDestroyed", &UIWidget::isDestroyed); + g_lua.bindClassMemberFunction("hasChildren", &UIWidget::hasChildren); + g_lua.bindClassMemberFunction("containsMarginPoint", &UIWidget::containsMarginPoint); + g_lua.bindClassMemberFunction("containsPaddingPoint", &UIWidget::containsPaddingPoint); + g_lua.bindClassMemberFunction("containsPoint", &UIWidget::containsPoint); + g_lua.bindClassMemberFunction("getId", &UIWidget::getId); + g_lua.bindClassMemberFunction("getSource", &UIWidget::getSource); + g_lua.bindClassMemberFunction("getParent", &UIWidget::getParent); + g_lua.bindClassMemberFunction("getFocusedChild", &UIWidget::getFocusedChild); + g_lua.bindClassMemberFunction("getChildren", &UIWidget::getChildren); + g_lua.bindClassMemberFunction("getFirstChild", &UIWidget::getFirstChild); + g_lua.bindClassMemberFunction("getLastChild", &UIWidget::getLastChild); + g_lua.bindClassMemberFunction("getLayout", &UIWidget::getLayout); + g_lua.bindClassMemberFunction("getStyle", &UIWidget::getStyle); + g_lua.bindClassMemberFunction("getChildCount", &UIWidget::getChildCount); + g_lua.bindClassMemberFunction("getLastFocusReason", &UIWidget::getLastFocusReason); + g_lua.bindClassMemberFunction("getAutoFocusPolicy", &UIWidget::getAutoFocusPolicy); + g_lua.bindClassMemberFunction("getAutoRepeatDelay", &UIWidget::getAutoRepeatDelay); + g_lua.bindClassMemberFunction("getVirtualOffset", &UIWidget::getVirtualOffset); + g_lua.bindClassMemberFunction("getStyleName", &UIWidget::getStyleName); + g_lua.bindClassMemberFunction("getLastClickPosition", &UIWidget::getLastClickPosition); + g_lua.bindClassMemberFunction("setX", &UIWidget::setX); + g_lua.bindClassMemberFunction("setY", &UIWidget::setY); + g_lua.bindClassMemberFunction("setWidth", &UIWidget::setWidth); + g_lua.bindClassMemberFunction("setHeight", &UIWidget::setHeight); + g_lua.bindClassMemberFunction("setSize", &UIWidget::setSize); + g_lua.bindClassMemberFunction("setPosition", &UIWidget::setPosition); + g_lua.bindClassMemberFunction("setColor", &UIWidget::setColor); + g_lua.bindClassMemberFunction("setBackgroundColor", &UIWidget::setBackgroundColor); + g_lua.bindClassMemberFunction("setBackgroundOffsetX", &UIWidget::setBackgroundOffsetX); + g_lua.bindClassMemberFunction("setBackgroundOffsetY", &UIWidget::setBackgroundOffsetY); + g_lua.bindClassMemberFunction("setBackgroundOffset", &UIWidget::setBackgroundOffset); + g_lua.bindClassMemberFunction("setBackgroundWidth", &UIWidget::setBackgroundWidth); + g_lua.bindClassMemberFunction("setBackgroundHeight", &UIWidget::setBackgroundHeight); + g_lua.bindClassMemberFunction("setBackgroundSize", &UIWidget::setBackgroundSize); + g_lua.bindClassMemberFunction("setBackgroundRect", &UIWidget::setBackgroundRect); + g_lua.bindClassMemberFunction("setIcon", &UIWidget::setIcon); + g_lua.bindClassMemberFunction("setIconColor", &UIWidget::setIconColor); + g_lua.bindClassMemberFunction("setIconOffsetX", &UIWidget::setIconOffsetX); + g_lua.bindClassMemberFunction("setIconOffsetY", &UIWidget::setIconOffsetY); + g_lua.bindClassMemberFunction("setIconOffset", &UIWidget::setIconOffset); + g_lua.bindClassMemberFunction("setIconWidth", &UIWidget::setIconWidth); + g_lua.bindClassMemberFunction("setIconHeight", &UIWidget::setIconHeight); + g_lua.bindClassMemberFunction("setIconSize", &UIWidget::setIconSize); + g_lua.bindClassMemberFunction("setIconRect", &UIWidget::setIconRect); + g_lua.bindClassMemberFunction("setIconClip", &UIWidget::setIconClip); + g_lua.bindClassMemberFunction("setIconAlign", &UIWidget::setIconAlign); + g_lua.bindClassMemberFunction("setBorderWidth", &UIWidget::setBorderWidth); + g_lua.bindClassMemberFunction("setBorderWidthTop", &UIWidget::setBorderWidthTop); + g_lua.bindClassMemberFunction("setBorderWidthRight", &UIWidget::setBorderWidthRight); + g_lua.bindClassMemberFunction("setBorderWidthBottom", &UIWidget::setBorderWidthBottom); + g_lua.bindClassMemberFunction("setBorderWidthLeft", &UIWidget::setBorderWidthLeft); + g_lua.bindClassMemberFunction("setBorderColor", &UIWidget::setBorderColor); + g_lua.bindClassMemberFunction("setBorderColorTop", &UIWidget::setBorderColorTop); + g_lua.bindClassMemberFunction("setBorderColorRight", &UIWidget::setBorderColorRight); + g_lua.bindClassMemberFunction("setBorderColorBottom", &UIWidget::setBorderColorBottom); + g_lua.bindClassMemberFunction("setBorderColorLeft", &UIWidget::setBorderColorLeft); + g_lua.bindClassMemberFunction("setMargin", &UIWidget::setMargin); + g_lua.bindClassMemberFunction("setMarginHorizontal", &UIWidget::setMarginHorizontal); + g_lua.bindClassMemberFunction("setMarginVertical", &UIWidget::setMarginVertical); + g_lua.bindClassMemberFunction("setMarginTop", &UIWidget::setMarginTop); + g_lua.bindClassMemberFunction("setMarginRight", &UIWidget::setMarginRight); + g_lua.bindClassMemberFunction("setMarginBottom", &UIWidget::setMarginBottom); + g_lua.bindClassMemberFunction("setMarginLeft", &UIWidget::setMarginLeft); + g_lua.bindClassMemberFunction("setPadding", &UIWidget::setPadding); + g_lua.bindClassMemberFunction("setPaddingHorizontal", &UIWidget::setPaddingHorizontal); + g_lua.bindClassMemberFunction("setPaddingVertical", &UIWidget::setPaddingVertical); + g_lua.bindClassMemberFunction("setPaddingTop", &UIWidget::setPaddingTop); + g_lua.bindClassMemberFunction("setPaddingRight", &UIWidget::setPaddingRight); + g_lua.bindClassMemberFunction("setPaddingBottom", &UIWidget::setPaddingBottom); + g_lua.bindClassMemberFunction("setPaddingLeft", &UIWidget::setPaddingLeft); + g_lua.bindClassMemberFunction("setOpacity", &UIWidget::setOpacity); + g_lua.bindClassMemberFunction("setRotation", &UIWidget::setRotation); + g_lua.bindClassMemberFunction("getX", &UIWidget::getX); + g_lua.bindClassMemberFunction("getY", &UIWidget::getY); + g_lua.bindClassMemberFunction("getPosition", &UIWidget::getPosition); + g_lua.bindClassMemberFunction("getWidth", &UIWidget::getWidth); + g_lua.bindClassMemberFunction("getHeight", &UIWidget::getHeight); + g_lua.bindClassMemberFunction("getSize", &UIWidget::getSize); + g_lua.bindClassMemberFunction("getRect", &UIWidget::getRect); + g_lua.bindClassMemberFunction("getColor", &UIWidget::getColor); + g_lua.bindClassMemberFunction("getBackgroundColor", &UIWidget::getBackgroundColor); + g_lua.bindClassMemberFunction("getBackgroundOffsetX", &UIWidget::getBackgroundOffsetX); + g_lua.bindClassMemberFunction("getBackgroundOffsetY", &UIWidget::getBackgroundOffsetY); + g_lua.bindClassMemberFunction("getBackgroundOffset", &UIWidget::getBackgroundOffset); + g_lua.bindClassMemberFunction("getBackgroundWidth", &UIWidget::getBackgroundWidth); + g_lua.bindClassMemberFunction("getBackgroundHeight", &UIWidget::getBackgroundHeight); + g_lua.bindClassMemberFunction("getBackgroundSize", &UIWidget::getBackgroundSize); + g_lua.bindClassMemberFunction("getBackgroundRect", &UIWidget::getBackgroundRect); + g_lua.bindClassMemberFunction("getIconColor", &UIWidget::getIconColor); + g_lua.bindClassMemberFunction("getIconOffsetX", &UIWidget::getIconOffsetX); + g_lua.bindClassMemberFunction("getIconOffsetY", &UIWidget::getIconOffsetY); + g_lua.bindClassMemberFunction("getIconOffset", &UIWidget::getIconOffset); + g_lua.bindClassMemberFunction("getIconWidth", &UIWidget::getIconWidth); + g_lua.bindClassMemberFunction("getIconHeight", &UIWidget::getIconHeight); + g_lua.bindClassMemberFunction("getIconSize", &UIWidget::getIconSize); + g_lua.bindClassMemberFunction("getIconRect", &UIWidget::getIconRect); + g_lua.bindClassMemberFunction("getIconClip", &UIWidget::getIconClip); + g_lua.bindClassMemberFunction("getIconAlign", &UIWidget::getIconAlign); + g_lua.bindClassMemberFunction("getBorderTopColor", &UIWidget::getBorderTopColor); + g_lua.bindClassMemberFunction("getBorderRightColor", &UIWidget::getBorderRightColor); + g_lua.bindClassMemberFunction("getBorderBottomColor", &UIWidget::getBorderBottomColor); + g_lua.bindClassMemberFunction("getBorderLeftColor", &UIWidget::getBorderLeftColor); + g_lua.bindClassMemberFunction("getBorderTopWidth", &UIWidget::getBorderTopWidth); + g_lua.bindClassMemberFunction("getBorderRightWidth", &UIWidget::getBorderRightWidth); + g_lua.bindClassMemberFunction("getBorderBottomWidth", &UIWidget::getBorderBottomWidth); + g_lua.bindClassMemberFunction("getBorderLeftWidth", &UIWidget::getBorderLeftWidth); + g_lua.bindClassMemberFunction("getMarginTop", &UIWidget::getMarginTop); + g_lua.bindClassMemberFunction("getMarginRight", &UIWidget::getMarginRight); + g_lua.bindClassMemberFunction("getMarginBottom", &UIWidget::getMarginBottom); + g_lua.bindClassMemberFunction("getMarginLeft", &UIWidget::getMarginLeft); + g_lua.bindClassMemberFunction("getPaddingTop", &UIWidget::getPaddingTop); + g_lua.bindClassMemberFunction("getPaddingRight", &UIWidget::getPaddingRight); + g_lua.bindClassMemberFunction("getPaddingBottom", &UIWidget::getPaddingBottom); + g_lua.bindClassMemberFunction("getPaddingLeft", &UIWidget::getPaddingLeft); + g_lua.bindClassMemberFunction("getOpacity", &UIWidget::getOpacity); + g_lua.bindClassMemberFunction("getRotation", &UIWidget::getRotation); + g_lua.bindClassMemberFunction("setQRCode", &UIWidget::setQRCode); + g_lua.bindClassMemberFunction("setImageSource", &UIWidget::setImageSource); + g_lua.bindClassMemberFunction("setImageSourceBase64", &UIWidget::setImageSourceBase64); + g_lua.bindClassMemberFunction("setImageClip", &UIWidget::setImageClip); + g_lua.bindClassMemberFunction("setImageOffsetX", &UIWidget::setImageOffsetX); + g_lua.bindClassMemberFunction("setImageOffsetY", &UIWidget::setImageOffsetY); + g_lua.bindClassMemberFunction("setImageOffset", &UIWidget::setImageOffset); + g_lua.bindClassMemberFunction("setImageWidth", &UIWidget::setImageWidth); + g_lua.bindClassMemberFunction("setImageHeight", &UIWidget::setImageHeight); + g_lua.bindClassMemberFunction("setImageSize", &UIWidget::setImageSize); + g_lua.bindClassMemberFunction("setImageRect", &UIWidget::setImageRect); + g_lua.bindClassMemberFunction("setImageColor", &UIWidget::setImageColor); + g_lua.bindClassMemberFunction("setImageFixedRatio", &UIWidget::setImageFixedRatio); + g_lua.bindClassMemberFunction("setImageRepeated", &UIWidget::setImageRepeated); + g_lua.bindClassMemberFunction("setImageSmooth", &UIWidget::setImageSmooth); + g_lua.bindClassMemberFunction("setImageAutoResize", &UIWidget::setImageAutoResize); + g_lua.bindClassMemberFunction("setImageBorderTop", &UIWidget::setImageBorderTop); + g_lua.bindClassMemberFunction("setImageBorderRight", &UIWidget::setImageBorderRight); + g_lua.bindClassMemberFunction("setImageBorderBottom", &UIWidget::setImageBorderBottom); + g_lua.bindClassMemberFunction("setImageBorderLeft", &UIWidget::setImageBorderLeft); + g_lua.bindClassMemberFunction("setImageBorder", &UIWidget::setImageBorder); + g_lua.bindClassMemberFunction("getImageClip", &UIWidget::getImageClip); + g_lua.bindClassMemberFunction("getImageOffsetX", &UIWidget::getImageOffsetX); + g_lua.bindClassMemberFunction("getImageOffsetY", &UIWidget::getImageOffsetY); + g_lua.bindClassMemberFunction("getImageOffset", &UIWidget::getImageOffset); + g_lua.bindClassMemberFunction("getImageWidth", &UIWidget::getImageWidth); + g_lua.bindClassMemberFunction("getImageHeight", &UIWidget::getImageHeight); + g_lua.bindClassMemberFunction("getImageSize", &UIWidget::getImageSize); + g_lua.bindClassMemberFunction("getImageRect", &UIWidget::getImageRect); + g_lua.bindClassMemberFunction("getImageColor", &UIWidget::getImageColor); + g_lua.bindClassMemberFunction("isImageFixedRatio", &UIWidget::isImageFixedRatio); + g_lua.bindClassMemberFunction("isImageSmooth", &UIWidget::isImageSmooth); + g_lua.bindClassMemberFunction("isImageAutoResize", &UIWidget::isImageAutoResize); + g_lua.bindClassMemberFunction("getImageBorderTop", &UIWidget::getImageBorderTop); + g_lua.bindClassMemberFunction("getImageBorderRight", &UIWidget::getImageBorderRight); + g_lua.bindClassMemberFunction("getImageBorderBottom", &UIWidget::getImageBorderBottom); + g_lua.bindClassMemberFunction("getImageBorderLeft", &UIWidget::getImageBorderLeft); + g_lua.bindClassMemberFunction("getImageTextureWidth", &UIWidget::getImageTextureWidth); + g_lua.bindClassMemberFunction("getImageTextureHeight", &UIWidget::getImageTextureHeight); + g_lua.bindClassMemberFunction("resizeToText", &UIWidget::resizeToText); + g_lua.bindClassMemberFunction("clearText", &UIWidget::clearText); + g_lua.bindClassMemberFunction("setText", &UIWidget::setText); + g_lua.bindClassMemberFunction("setColoredText", &UIWidget::setColoredText); + g_lua.bindClassMemberFunction("setTextAlign", &UIWidget::setTextAlign); + g_lua.bindClassMemberFunction("setTextOffset", &UIWidget::setTextOffset); + g_lua.bindClassMemberFunction("setTextWrap", &UIWidget::setTextWrap); + g_lua.bindClassMemberFunction("setTextAutoResize", &UIWidget::setTextAutoResize); + g_lua.bindClassMemberFunction("setTextVerticalAutoResize", &UIWidget::setTextVerticalAutoResize); + g_lua.bindClassMemberFunction("setTextHorizontalAutoResize", &UIWidget::setTextHorizontalAutoResize); + g_lua.bindClassMemberFunction("setFont", &UIWidget::setFont); + g_lua.bindClassMemberFunction("getText", &UIWidget::getText); + g_lua.bindClassMemberFunction("getDrawText", &UIWidget::getDrawText); + g_lua.bindClassMemberFunction("getTextAlign", &UIWidget::getTextAlign); + g_lua.bindClassMemberFunction("getTextOffset", &UIWidget::getTextOffset); + g_lua.bindClassMemberFunction("getTextWrap", &UIWidget::getTextWrap); + g_lua.bindClassMemberFunction("getFont", &UIWidget::getFont); + g_lua.bindClassMemberFunction("getTextSize", &UIWidget::getTextSize); + g_lua.bindClassMemberFunction("getUseCount", &UIWidget::getUseCount); + + // UILayout + g_lua.registerClass(); + g_lua.bindClassMemberFunction("update", &UILayout::update); + g_lua.bindClassMemberFunction("updateLater", &UILayout::updateLater); + g_lua.bindClassMemberFunction("applyStyle", &UILayout::applyStyle); + g_lua.bindClassMemberFunction("addWidget", &UILayout::addWidget); + g_lua.bindClassMemberFunction("removeWidget", &UILayout::removeWidget); + g_lua.bindClassMemberFunction("disableUpdates", &UILayout::disableUpdates); + g_lua.bindClassMemberFunction("enableUpdates", &UILayout::enableUpdates); + g_lua.bindClassMemberFunction("setParent", &UILayout::setParent); + g_lua.bindClassMemberFunction("getParentWidget", &UILayout::getParentWidget); + g_lua.bindClassMemberFunction("isUpdateDisabled", &UILayout::isUpdateDisabled); + g_lua.bindClassMemberFunction("isUpdating", &UILayout::isUpdating); + g_lua.bindClassMemberFunction("isUIAnchorLayout", &UILayout::isUIAnchorLayout); + g_lua.bindClassMemberFunction("isUIBoxLayout", &UILayout::isUIBoxLayout); + g_lua.bindClassMemberFunction("isUIHorizontalLayout", &UILayout::isUIHorizontalLayout); + g_lua.bindClassMemberFunction("isUIVerticalLayout", &UILayout::isUIVerticalLayout); + g_lua.bindClassMemberFunction("isUIGridLayout", &UILayout::isUIGridLayout); + + // UIBoxLayout + g_lua.registerClass(); + g_lua.bindClassMemberFunction("setSpacing", &UIBoxLayout::setSpacing); + g_lua.bindClassMemberFunction("setFitChildren", &UIBoxLayout::setFitChildren); + + // UIVerticalLayout + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", [](UIWidgetPtr parent){ return UIVerticalLayoutPtr(new UIVerticalLayout(parent)); } ); + g_lua.bindClassMemberFunction("setAlignBottom", &UIVerticalLayout::setAlignBottom); + g_lua.bindClassMemberFunction("isAlignBottom", &UIVerticalLayout::isAlignBottom); + + // UIHorizontalLayout + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", [](UIWidgetPtr parent){ return UIHorizontalLayoutPtr(new UIHorizontalLayout(parent)); } ); + g_lua.bindClassMemberFunction("setAlignRight", &UIHorizontalLayout::setAlignRight); + + // UIGridLayout + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", [](UIWidgetPtr parent){ return UIGridLayoutPtr(new UIGridLayout(parent)); }); + g_lua.bindClassMemberFunction("setCellSize", &UIGridLayout::setCellSize); + g_lua.bindClassMemberFunction("setCellWidth", &UIGridLayout::setCellWidth); + g_lua.bindClassMemberFunction("setCellHeight", &UIGridLayout::setCellHeight); + g_lua.bindClassMemberFunction("setCellSpacing", &UIGridLayout::setCellSpacing); + g_lua.bindClassMemberFunction("setFlow", &UIGridLayout::setFlow); + g_lua.bindClassMemberFunction("setNumColumns", &UIGridLayout::setNumColumns); + g_lua.bindClassMemberFunction("setNumLines", &UIGridLayout::setNumLines); + g_lua.bindClassMemberFunction("getNumColumns", &UIGridLayout::getNumColumns); + g_lua.bindClassMemberFunction("getNumLines", &UIGridLayout::getNumLines); + g_lua.bindClassMemberFunction("getCellSize", &UIGridLayout::getCellSize); + g_lua.bindClassMemberFunction("getCellSpacing", &UIGridLayout::getCellSpacing); + g_lua.bindClassMemberFunction("isUIGridLayout", &UIGridLayout::isUIGridLayout); + + // UIAnchorLayout + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", [](UIWidgetPtr parent){ return UIAnchorLayoutPtr(new UIAnchorLayout(parent)); } ); + g_lua.bindClassMemberFunction("removeAnchors", &UIAnchorLayout::removeAnchors); + g_lua.bindClassMemberFunction("centerIn", &UIAnchorLayout::centerIn); + g_lua.bindClassMemberFunction("fill", &UIAnchorLayout::fill); + + // UITextEdit + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UITextEditPtr(new UITextEdit); } ); + g_lua.bindClassMemberFunction("setCursorPos", &UITextEdit::setCursorPos); + g_lua.bindClassMemberFunction("setSelection", &UITextEdit::setSelection); + g_lua.bindClassMemberFunction("setCursorVisible", &UITextEdit::setCursorVisible); + g_lua.bindClassMemberFunction("setChangeCursorImage", &UITextEdit::setChangeCursorImage); + g_lua.bindClassMemberFunction("setTextHidden", &UITextEdit::setTextHidden); + g_lua.bindClassMemberFunction("setValidCharacters", &UITextEdit::setValidCharacters); + g_lua.bindClassMemberFunction("setShiftNavigation", &UITextEdit::setShiftNavigation); + g_lua.bindClassMemberFunction("setMultiline", &UITextEdit::setMultiline); + g_lua.bindClassMemberFunction("setEditable", &UITextEdit::setEditable); + g_lua.bindClassMemberFunction("setSelectable", &UITextEdit::setSelectable); + g_lua.bindClassMemberFunction("setSelectionColor", &UITextEdit::setSelectionColor); + g_lua.bindClassMemberFunction("setSelectionBackgroundColor", &UITextEdit::setSelectionBackgroundColor); + g_lua.bindClassMemberFunction("setMaxLength", &UITextEdit::setMaxLength); + g_lua.bindClassMemberFunction("setTextVirtualOffset", &UITextEdit::setTextVirtualOffset); + g_lua.bindClassMemberFunction("getTextVirtualOffset", &UITextEdit::getTextVirtualOffset); + g_lua.bindClassMemberFunction("getTextVirtualSize", &UITextEdit::getTextVirtualSize); + g_lua.bindClassMemberFunction("getTextTotalSize", &UITextEdit::getTextTotalSize); + g_lua.bindClassMemberFunction("moveCursorHorizontally", &UITextEdit::moveCursorHorizontally); + g_lua.bindClassMemberFunction("moveCursorVertically", &UITextEdit::moveCursorVertically); + g_lua.bindClassMemberFunction("appendText", &UITextEdit::appendText); + g_lua.bindClassMemberFunction("wrapText", &UITextEdit::wrapText); + g_lua.bindClassMemberFunction("removeCharacter", &UITextEdit::removeCharacter); + g_lua.bindClassMemberFunction("blinkCursor", &UITextEdit::blinkCursor); + g_lua.bindClassMemberFunction("del", &UITextEdit::del); + g_lua.bindClassMemberFunction("paste", &UITextEdit::paste); + g_lua.bindClassMemberFunction("copy", &UITextEdit::copy); + g_lua.bindClassMemberFunction("cut", &UITextEdit::cut); + g_lua.bindClassMemberFunction("selectAll", &UITextEdit::selectAll); + g_lua.bindClassMemberFunction("clearSelection", &UITextEdit::clearSelection); + g_lua.bindClassMemberFunction("getDisplayedText", &UITextEdit::getDisplayedText); + g_lua.bindClassMemberFunction("getSelection", &UITextEdit::getSelection); + g_lua.bindClassMemberFunction("getTextPos", &UITextEdit::getTextPos); + g_lua.bindClassMemberFunction("getCursorPos", &UITextEdit::getCursorPos); + g_lua.bindClassMemberFunction("getMaxLength", &UITextEdit::getMaxLength); + g_lua.bindClassMemberFunction("getSelectionStart", &UITextEdit::getSelectionStart); + g_lua.bindClassMemberFunction("getSelectionEnd", &UITextEdit::getSelectionEnd); + g_lua.bindClassMemberFunction("getSelectionColor", &UITextEdit::getSelectionColor); + g_lua.bindClassMemberFunction("getSelectionBackgroundColor", &UITextEdit::getSelectionBackgroundColor); + g_lua.bindClassMemberFunction("hasSelection", &UITextEdit::hasSelection); + g_lua.bindClassMemberFunction("isEditable", &UITextEdit::isEditable); + g_lua.bindClassMemberFunction("isSelectable", &UITextEdit::isSelectable); + g_lua.bindClassMemberFunction("isCursorVisible", &UITextEdit::isCursorVisible); + g_lua.bindClassMemberFunction("isChangingCursorImage", &UITextEdit::isChangingCursorImage); + g_lua.bindClassMemberFunction("isTextHidden", &UITextEdit::isTextHidden); + g_lua.bindClassMemberFunction("isShiftNavigation", &UITextEdit::isShiftNavigation); + g_lua.bindClassMemberFunction("isMultiline", &UITextEdit::isMultiline); + + g_lua.registerClass(); + g_lua.registerClass(); + g_lua.bindClassMemberFunction("addMultiTexture", &PainterShaderProgram::addMultiTexture); +#endif + +#ifdef FW_NET + // Server + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", &Server::create); + g_lua.bindClassMemberFunction("close", &Server::close); + g_lua.bindClassMemberFunction("isOpen", &Server::isOpen); + g_lua.bindClassMemberFunction("acceptNext", &Server::acceptNext); + + // Connection + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getIp", &Connection::getIp); + + // Protocol + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return ProtocolPtr(new Protocol); }); + g_lua.bindClassMemberFunction("connect", &Protocol::connect); + g_lua.bindClassMemberFunction("disconnect", &Protocol::disconnect); + g_lua.bindClassMemberFunction("isConnected", &Protocol::isConnected); + g_lua.bindClassMemberFunction("isConnecting", &Protocol::isConnecting); + g_lua.bindClassMemberFunction("getConnection", &Protocol::getConnection); + g_lua.bindClassMemberFunction("setConnection", &Protocol::setConnection); + g_lua.bindClassMemberFunction("send", &Protocol::send); + g_lua.bindClassMemberFunction("recv", &Protocol::recv); + g_lua.bindClassMemberFunction("setXteaKey", &Protocol::setXteaKey); + g_lua.bindClassMemberFunction("getXteaKey", &Protocol::getXteaKey); + g_lua.bindClassMemberFunction("generateXteaKey", &Protocol::generateXteaKey); + g_lua.bindClassMemberFunction("enableXteaEncryption", &Protocol::enableXteaEncryption); + g_lua.bindClassMemberFunction("enableChecksum", &Protocol::enableChecksum); + g_lua.bindClassMemberFunction("enableBigPackets", &Protocol::enableBigPackets); + + // InputMessage + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return InputMessagePtr(new InputMessage); }); + g_lua.bindClassMemberFunction("setBuffer", &InputMessage::setBuffer); + g_lua.bindClassMemberFunction("getBuffer", &InputMessage::getBuffer); + g_lua.bindClassMemberFunction("skipBytes", &InputMessage::skipBytes); + g_lua.bindClassMemberFunction("getU8", &InputMessage::getU8); + g_lua.bindClassMemberFunction("getU16", &InputMessage::getU16); + g_lua.bindClassMemberFunction("getU32", &InputMessage::getU32); + g_lua.bindClassMemberFunction("getU64", &InputMessage::getU64); + g_lua.bindClassMemberFunction("getString", &InputMessage::getString); + g_lua.bindClassMemberFunction("peekU8", &InputMessage::peekU8); + g_lua.bindClassMemberFunction("peekU16", &InputMessage::peekU16); + g_lua.bindClassMemberFunction("peekU32", &InputMessage::peekU32); + g_lua.bindClassMemberFunction("peekU64", &InputMessage::peekU64); + g_lua.bindClassMemberFunction("decryptRsa", &InputMessage::decryptRsa); + g_lua.bindClassMemberFunction("getReadSize", &InputMessage::getReadSize); + g_lua.bindClassMemberFunction("getUnreadSize", &InputMessage::getUnreadSize); + g_lua.bindClassMemberFunction("getMessageSize", &InputMessage::getMessageSize); + g_lua.bindClassMemberFunction("eof", &InputMessage::eof); + + // OutputMessage + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return OutputMessagePtr(new OutputMessage); }); + g_lua.bindClassMemberFunction("setBuffer", &OutputMessage::setBuffer); + g_lua.bindClassMemberFunction("getBuffer", &OutputMessage::getBuffer); + g_lua.bindClassMemberFunction("reset", &OutputMessage::reset); + g_lua.bindClassMemberFunction("addU8", &OutputMessage::addU8); + g_lua.bindClassMemberFunction("addU16", &OutputMessage::addU16); + g_lua.bindClassMemberFunction("addU32", &OutputMessage::addU32); + g_lua.bindClassMemberFunction("addU64", &OutputMessage::addU64); + g_lua.bindClassMemberFunction("addString", &OutputMessage::addString); + g_lua.bindClassMemberFunction("addPaddingBytes", &OutputMessage::addPaddingBytes); + g_lua.bindClassMemberFunction("encryptRsa", &OutputMessage::encryptRsa); + g_lua.bindClassMemberFunction("getMessageSize", &OutputMessage::getMessageSize); + g_lua.bindClassMemberFunction("setMessageSize", &OutputMessage::setMessageSize); + g_lua.bindClassMemberFunction("getWritePos", &OutputMessage::getWritePos); + g_lua.bindClassMemberFunction("setWritePos", &OutputMessage::setWritePos); + +#ifdef FW_PROXY + g_lua.registerSingletonClass("g_proxy"); + g_lua.bindSingletonFunction("g_proxy", "addProxy", &ProxyManager::addProxy, &g_proxy); + g_lua.bindSingletonFunction("g_proxy", "removeProxy", &ProxyManager::removeProxy, &g_proxy); + g_lua.bindSingletonFunction("g_proxy", "clear", &ProxyManager::clear, &g_proxy); + g_lua.bindSingletonFunction("g_proxy", "setMaxActiveProxies", &ProxyManager::setMaxActiveProxies, &g_proxy); + g_lua.bindSingletonFunction("g_proxy", "getProxies", &ProxyManager::getProxies, &g_proxy); + g_lua.bindSingletonFunction("g_proxy", "getProxiesDebugInfo", &ProxyManager::getProxiesDebugInfo, &g_proxy); + g_lua.bindSingletonFunction("g_proxy", "getPing", &ProxyManager::getPing, &g_proxy); +#endif + +#endif + +#ifdef FW_SOUND + // SoundManager + g_lua.registerSingletonClass("g_sounds"); + g_lua.bindSingletonFunction("g_sounds", "preload", &SoundManager::preload, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "play", &SoundManager::play, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "getChannel", &SoundManager::getChannel, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "stopAll", &SoundManager::stopAll, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "enableAudio", &SoundManager::enableAudio, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "disableAudio", &SoundManager::disableAudio, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "setAudioEnabled", &SoundManager::setAudioEnabled, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "isAudioEnabled", &SoundManager::isAudioEnabled, &g_sounds); + + g_lua.registerClass(); + g_lua.registerClass(); + g_lua.registerClass(); + + g_lua.registerClass(); + g_lua.bindClassMemberFunction("play", &SoundChannel::play); + g_lua.bindClassMemberFunction("stop", &SoundChannel::stop); + g_lua.bindClassMemberFunction("enqueue", &SoundChannel::enqueue); + g_lua.bindClassMemberFunction("enable", &SoundChannel::enable); + g_lua.bindClassMemberFunction("disable", &SoundChannel::disable); + g_lua.bindClassMemberFunction("setGain", &SoundChannel::setGain); + g_lua.bindClassMemberFunction("getGain", &SoundChannel::getGain); + g_lua.bindClassMemberFunction("setEnabled", &SoundChannel::setEnabled); + g_lua.bindClassMemberFunction("isEnabled", &SoundChannel::isEnabled); + g_lua.bindClassMemberFunction("getId", &SoundChannel::getId); +#endif + +#ifdef FW_SQL + // Database + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getDatabaseEngine", &Database::getDatabaseEngine); + g_lua.bindClassMemberFunction("isConnected", &Database::isConnected); + g_lua.bindClassMemberFunction("getStringComparer", &Database::getStringComparer); + g_lua.bindClassMemberFunction("getUpdateLimiter", &Database::getUpdateLimiter); + g_lua.bindClassMemberFunction("getLastInsertedRowID", &Database::getLastInsertedRowID); + g_lua.bindClassMemberFunction("escapeString", &Database::escapeString); + //g_lua.bindClassMemberFunction("escapeBlob", &Database::escapeBlob); // need to write a cast for this type to work (if needed) + + // DBQuery + /* (not sure if this class will work as a luafunction) + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return DBQuery(); }); + g_lua.bindClassMemberFunction("append", &DBQuery::append); + g_lua.bindClassMemberFunction("set", &DBQuery::set); + */ + + // DBResult + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getDataInt", &DBResult::getDataInt); + g_lua.bindClassMemberFunction("getDataLong", &DBResult::getDataLong); + g_lua.bindClassMemberFunction("getDataString", &DBResult::getDataString); + //g_lua.bindClassMemberFunction("getDataStream", &DBResult::getDataStream); // need to write a cast for this type to work (if needed) + g_lua.bindClassMemberFunction("getRowCount", &DBResult::getRowCount); + g_lua.bindClassMemberFunction("free", &DBResult::free); + g_lua.bindClassMemberFunction("next", &DBResult::next); + + // MySQL + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return DatabaseMySQLPtr(new DatabaseMySQL); }); + g_lua.bindClassMemberFunction("connect", &DatabaseMySQL::connect); + g_lua.bindClassMemberFunction("beginTransaction", &DatabaseMySQL::beginTransaction); + g_lua.bindClassMemberFunction("rollback", &DatabaseMySQL::rollback); + g_lua.bindClassMemberFunction("commit", &DatabaseMySQL::commit); + g_lua.bindClassMemberFunction("executeQuery", &DatabaseMySQL::executeQuery); + g_lua.bindClassMemberFunction("storeQuery", &DatabaseMySQL::storeQuery); + + // MySQLResult + g_lua.registerClass(); + g_lua.bindClassMemberFunction("getDataInt", &MySQLResult::getDataInt); + g_lua.bindClassMemberFunction("getDataLong", &MySQLResult::getDataLong); + g_lua.bindClassMemberFunction("getDataString", &MySQLResult::getDataString); + //g_lua.bindClassMemberFunction("getDataStream", &MySQLResult::getDataStream); // need to write a cast for this type to work (if needed) + g_lua.bindClassMemberFunction("getRowCount", &MySQLResult::getRowCount); + g_lua.bindClassMemberFunction("free", &MySQLResult::free); + g_lua.bindClassMemberFunction("next", &MySQLResult::next); +#endif +} diff --git a/src/framework/net/connection.cpp b/src/framework/net/connection.cpp new file mode 100644 index 0000000..9657a51 --- /dev/null +++ b/src/framework/net/connection.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "connection.h" + +#include +#include +#include +#include +#include +#include + +asio::io_service g_ioService; +std::list> Connection::m_outputStreams; + +Connection::Connection() : + m_readTimer(g_ioService), + m_writeTimer(g_ioService), + m_delayedWriteTimer(g_ioService), + m_resolver(g_ioService), + m_socket(g_ioService) +{ + m_connected = false; + m_connecting = false; +} + +Connection::~Connection() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + close(); +} + +void Connection::poll() +{ + AutoStat s(STATS_MAIN, "PollConnection"); + // reset must always be called prior to poll + g_ioService.reset(); + g_ioService.poll(); +} + +void Connection::terminate() +{ + g_ioService.stop(); + m_outputStreams.clear(); +} + +void Connection::close() +{ + if(!m_connected && !m_connecting) + return; + + // flush send data before disconnecting on clean connections + if(m_connected && !m_error && m_outputStream) + internal_write(); + + m_connecting = false; + m_connected = false; + m_connectCallback = nullptr; + m_errorCallback = nullptr; + m_recvCallback = nullptr; + + m_resolver.cancel(); + m_readTimer.cancel(); + m_writeTimer.cancel(); + m_delayedWriteTimer.cancel(); + + if(m_socket.is_open()) { + boost::system::error_code ec; + m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + m_socket.close(); + } +} + +void Connection::connect(const std::string& host, uint16 port, const std::function& connectCallback) +{ + m_connected = false; + m_connecting = true; + m_error.clear(); + m_connectCallback = connectCallback; + + asio::ip::tcp::resolver::query query(host, stdext::unsafe_cast(port)); + m_resolver.async_resolve(query, std::bind(&Connection::onResolve, asConnection(), std::placeholders::_1, std::placeholders::_2)); + + m_readTimer.cancel(); + m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT)); + m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + +void Connection::internal_connect(asio::ip::basic_resolver::iterator endpointIterator) +{ + m_socket.async_connect(*endpointIterator, std::bind(&Connection::onConnect, asConnection(), std::placeholders::_1)); + + m_readTimer.cancel(); + m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT)); + m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + +void Connection::write(uint8* buffer, size_t size) +{ + if(!m_connected) + return; + + // we can't send the data right away, otherwise we could create tcp congestion + if(!m_outputStream) { + if(!m_outputStreams.empty()) { + m_outputStream = m_outputStreams.front(); + m_outputStreams.pop_front(); + } else + m_outputStream = std::shared_ptr(new asio::streambuf); + + m_delayedWriteTimer.cancel(); + m_delayedWriteTimer.expires_from_now(std::chrono::milliseconds(0)); + m_delayedWriteTimer.async_wait(std::bind(&Connection::onCanWrite, asConnection(), std::placeholders::_1)); + } + + std::ostream os(m_outputStream.get()); + os.write((const char*)buffer, size); + os.flush(); +} + +void Connection::internal_write() +{ + if(!m_connected) + return; + + std::shared_ptr outputStream = m_outputStream; + m_outputStream = nullptr; + + asio::async_write(m_socket, + *outputStream, + std::bind(&Connection::onWrite, asConnection(), std::placeholders::_1, std::placeholders::_2, outputStream)); + + m_writeTimer.cancel(); + m_writeTimer.expires_from_now(std::chrono::seconds(WRITE_TIMEOUT)); + m_writeTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + +void Connection::read(uint32 bytes, const RecvCallback& callback) +{ + if(!m_connected) + return; + + m_recvCallback = callback; + + asio::async_read(m_socket, + asio::buffer(m_inputStream.prepare(bytes)), + std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); + + m_readTimer.cancel(); + m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT)); + m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + +void Connection::read_until(const std::string& what, const RecvCallback& callback) +{ + if(!m_connected) + return; + + m_recvCallback = callback; + + asio::async_read_until(m_socket, + m_inputStream, + what.c_str(), + std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); + + m_readTimer.cancel(); + m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT)); + m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + +void Connection::read_some(const RecvCallback& callback) +{ + if(!m_connected) + return; + + m_recvCallback = callback; + + m_socket.async_read_some(asio::buffer(m_inputStream.prepare(RECV_BUFFER_SIZE)), + std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); + + m_readTimer.cancel(); + m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT)); + m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + +void Connection::onResolve(const boost::system::error_code& error, asio::ip::basic_resolver::iterator endpointIterator) +{ + m_readTimer.cancel(); + + if(error == asio::error::operation_aborted) + return; + + if(!error) + internal_connect(endpointIterator); + else + handleError(error); +} + +void Connection::onConnect(const boost::system::error_code& error) +{ + m_readTimer.cancel(); + m_activityTimer.restart(); + + if(error == asio::error::operation_aborted) + return; + + if(!error) { + m_connected = true; + + // disable nagle's algorithm, this make the game play smoother + boost::asio::ip::tcp::no_delay option(true); + m_socket.set_option(option); + boost::system::error_code ecc; + m_socket.set_option(boost::asio::socket_base::send_buffer_size(524288), ecc); + m_socket.set_option(boost::asio::socket_base::receive_buffer_size(524288), ecc); + + if(m_connectCallback) + m_connectCallback(); + } else + handleError(error); + + m_connecting = false; +} + +void Connection::onCanWrite(const boost::system::error_code& error) +{ + m_delayedWriteTimer.cancel(); + + if(error == asio::error::operation_aborted) + return; + + if(m_connected) + internal_write(); +} + +void Connection::onWrite(const boost::system::error_code& error, size_t writeSize, std::shared_ptr outputStream) +{ + m_writeTimer.cancel(); + + if(error == asio::error::operation_aborted) + return; + + // free output stream and store for using it again later + outputStream->consume(outputStream->size()); + m_outputStreams.push_back(outputStream); + + if(m_connected && error) + handleError(error); +} + +void Connection::onRecv(const boost::system::error_code& error, size_t recvSize) +{ + m_readTimer.cancel(); + m_activityTimer.restart(); + + if(error == asio::error::operation_aborted) + return; + + if(m_connected) { + if(!error) { + if(m_recvCallback) { + const char* header = boost::asio::buffer_cast(m_inputStream.data()); + m_recvCallback((uint8*)header, recvSize); + } + } else + handleError(error); + } + + if(!error) + m_inputStream.consume(recvSize); +} + +void Connection::onTimeout(const boost::system::error_code& error) +{ + if(error == asio::error::operation_aborted) + return; + + handleError(asio::error::timed_out); +} + +void Connection::handleError(const boost::system::error_code& error) +{ + if(error == asio::error::operation_aborted) + return; + + m_error = error; + if(m_errorCallback) + m_errorCallback(error); + if(m_connected || m_connecting) + close(); +} + +int Connection::getIp() +{ + boost::system::error_code error; + const boost::asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error); + if(!error) + return boost::asio::detail::socket_ops::host_to_network_long(ip.address().to_v4().to_ulong()); + + g_logger.error("Getting remote ip"); + return 0; +} diff --git a/src/framework/net/connection.h b/src/framework/net/connection.h new file mode 100644 index 0000000..37f6268 --- /dev/null +++ b/src/framework/net/connection.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "declarations.h" +#include +#include +#include + +class Connection : public LuaObject +{ + typedef std::function ErrorCallback; + typedef std::function RecvCallback; + + static constexpr int32_t READ_TIMEOUT = 30; + static constexpr int32_t WRITE_TIMEOUT = 30; + + enum { + SEND_BUFFER_SIZE = 327680, + RECV_BUFFER_SIZE = 327680 + }; + +public: + Connection(); + ~Connection(); + + static void poll(); + static void terminate(); + + void connect(const std::string& host, uint16 port, const std::function& connectCallback); + void close(); + + void write(uint8* buffer, size_t size); + void read(uint32 bytes, const RecvCallback& callback); + void read_until(const std::string& what, const RecvCallback& callback); + void read_some(const RecvCallback& callback); + + void setErrorCallback(const ErrorCallback& errorCallback) { m_errorCallback = errorCallback; } + + int getIp(); + boost::system::error_code getError() { return m_error; } + bool isConnecting() { return m_connecting; } + bool isConnected() { return m_connected; } + ticks_t getElapsedTicksSinceLastRead() { return m_connected ? m_activityTimer.elapsed_millis() : -1; } + + ConnectionPtr asConnection() { return static_self_cast(); } + +protected: + void internal_connect(asio::ip::basic_resolver::iterator endpointIterator); + void internal_write(); + void onResolve(const boost::system::error_code& error, asio::ip::tcp::resolver::iterator endpointIterator); + void onConnect(const boost::system::error_code& error); + void onCanWrite(const boost::system::error_code& error); + void onWrite(const boost::system::error_code& error, size_t writeSize, std::shared_ptr outputStream); + void onRecv(const boost::system::error_code& error, size_t recvSize); + void onTimeout(const boost::system::error_code& error); + void handleError(const boost::system::error_code& error); + + std::function m_connectCallback; + ErrorCallback m_errorCallback; + RecvCallback m_recvCallback; + + asio::steady_timer m_readTimer; + asio::steady_timer m_writeTimer; + asio::steady_timer m_delayedWriteTimer; + asio::ip::tcp::resolver m_resolver; + asio::ip::tcp::socket m_socket; + + static std::list> m_outputStreams; + std::shared_ptr m_outputStream; + asio::streambuf m_inputStream; + bool m_connected; + bool m_connecting; + boost::system::error_code m_error; + stdext::timer m_activityTimer; + + friend class Server; +}; + +#endif diff --git a/src/framework/net/declarations.h b/src/framework/net/declarations.h new file mode 100644 index 0000000..4d9217f --- /dev/null +++ b/src/framework/net/declarations.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_NET_DECLARATIONS_H +#define FRAMEWORK_NET_DECLARATIONS_H + +#include + +namespace asio = boost::asio; + +class InputMessage; +class OutputMessage; +class Connection; +class Protocol; +class Server; + +typedef stdext::shared_object_ptr InputMessagePtr; +typedef stdext::shared_object_ptr OutputMessagePtr; +typedef stdext::shared_object_ptr ConnectionPtr; +typedef stdext::shared_object_ptr ProtocolPtr; +typedef stdext::shared_object_ptr ServerPtr; + +#endif diff --git a/src/framework/net/inputmessage.cpp b/src/framework/net/inputmessage.cpp new file mode 100644 index 0000000..0033f96 --- /dev/null +++ b/src/framework/net/inputmessage.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "inputmessage.h" +#include +#include + +InputMessage::InputMessage() +{ + reset(); +} + +void InputMessage::reset() +{ + m_messageSize = 0; + m_readPos = MAX_HEADER_SIZE; + m_headerPos = MAX_HEADER_SIZE; +} + +void InputMessage::setBuffer(const std::string& buffer) +{ + int len = buffer.size(); + checkWrite(MAX_HEADER_SIZE + len); + memcpy(m_buffer + MAX_HEADER_SIZE, buffer.c_str(), len); + m_readPos = MAX_HEADER_SIZE; + m_headerPos = MAX_HEADER_SIZE; + m_messageSize = len; +} + +uint8 InputMessage::getU8() +{ + checkRead(1); + uint8 v = m_buffer[m_readPos]; + m_readPos += 1; + return v; +} + +uint16 InputMessage::getU16() +{ + checkRead(2); + uint16 v = stdext::readULE16(m_buffer + m_readPos); + m_readPos += 2; + return v; +} + +uint32 InputMessage::getU32() +{ + checkRead(4); + uint32 v = stdext::readULE32(m_buffer + m_readPos); + m_readPos += 4; + return v; +} + +uint64 InputMessage::getU64() +{ + checkRead(8); + uint64 v = stdext::readULE64(m_buffer + m_readPos); + m_readPos += 8; + return v; +} + +std::string InputMessage::getString() +{ + uint16 stringLength = getU16(); + checkRead(stringLength); + char* v = (char*)(m_buffer + m_readPos); + m_readPos += stringLength; + return std::string(v, stringLength); +} + +double InputMessage::getDouble() +{ + uint8 precision = getU8(); + int32 v = getU32() - INT_MAX; + return (v / std::pow((float)10, precision)); +} + +bool InputMessage::decryptRsa(int size) +{ + checkRead(size); + g_crypt.rsaDecrypt((unsigned char*)m_buffer + m_readPos, size); + return (getU8() == 0x00); +} + +void InputMessage::fillBuffer(uint8 *buffer, uint32 size) +{ + checkWrite(m_readPos + size); + memcpy(m_buffer + m_readPos, buffer, size); + m_messageSize += size; +} + +void InputMessage::setHeaderSize(uint32 size) +{ + VALIDATE(MAX_HEADER_SIZE >= size); + m_headerPos = MAX_HEADER_SIZE - size; + m_readPos = m_headerPos; +} + +bool InputMessage::readChecksum() +{ + uint32 receivedCheck = getU32(); + uint32 checksum = stdext::adler32(m_buffer + m_readPos, getUnreadSize()); + return receivedCheck == checksum; +} + +bool InputMessage::canRead(int bytes) +{ + if((m_readPos - m_headerPos + bytes > m_messageSize) || (m_readPos + bytes > BUFFER_MAXSIZE)) + return false; + return true; +} +void InputMessage::checkRead(int bytes) +{ + if(!canRead(bytes)) + throw stdext::exception("InputMessage eof reached"); +} + +void InputMessage::checkWrite(int bytes) +{ + if(bytes > BUFFER_MAXSIZE) + throw stdext::exception("InputMessage max buffer size reached"); +} + +void InputMessage::addZlibFooter() +{ + if (m_messageSize + 4 > BUFFER_MAXSIZE) + return; + m_buffer[m_messageSize + m_headerPos] = 0x00; + m_buffer[m_messageSize + m_headerPos + 1] = 0x00; + m_buffer[m_messageSize + m_headerPos + 2] = 0xFF; + m_buffer[m_messageSize + m_headerPos + 3] = 0xFF; + m_messageSize += 4; +} diff --git a/src/framework/net/inputmessage.h b/src/framework/net/inputmessage.h new file mode 100644 index 0000000..556e224 --- /dev/null +++ b/src/framework/net/inputmessage.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef INPUTMESSAGE_H +#define INPUTMESSAGE_H + +#include "declarations.h" +#include + +// @bindclass +class InputMessage : public LuaObject +{ +public: + enum { + BUFFER_MAXSIZE = 327680, + MAX_HEADER_SIZE = 12 + }; + + InputMessage(); + + void setBuffer(const std::string& buffer); + std::string getBuffer() { return std::string((char*)m_buffer + m_headerPos, m_messageSize); } + + void skipBytes(uint32 bytes) { m_readPos += bytes; } + void setReadPos(uint32 readPos) { m_readPos = readPos; } + uint8 getU8(); + uint16 getU16(); + uint32 getU32(); + uint64 getU64(); + std::string getString(); + double getDouble(); + + uint8 peekU8() { uint8 v = getU8(); m_readPos-=1; return v; } + uint16 peekU16() { uint16 v = getU16(); m_readPos-=2; return v; } + uint32 peekU32() { uint32 v = getU32(); m_readPos-=4; return v; } + uint64 peekU64() { uint64 v = getU64(); m_readPos-=8; return v; } + + bool decryptRsa(int size); + + int getReadSize() { return m_readPos - m_headerPos; } + int getReadPos() { return m_readPos; } + int getUnreadSize() { return m_messageSize - (m_readPos - m_headerPos); } + uint32 getMessageSize() { return m_messageSize; } + + bool eof() { return (m_readPos - m_headerPos) >= m_messageSize; } + +protected: + void reset(); + void fillBuffer(uint8 *buffer, uint32 size); + + void setHeaderSize(uint32 size); + void setMessageSize(uint32 size) { m_messageSize = size; } + + uint8* getReadBuffer() { return m_buffer + m_readPos; } + uint8* getHeaderBuffer() { return m_buffer + m_headerPos; } + uint8* getDataBuffer() { return m_buffer + MAX_HEADER_SIZE; } + uint32 getHeaderSize() { return (MAX_HEADER_SIZE - m_headerPos); } + + uint32 readSize(bool bigSize) { return bigSize ? getU32() : getU16(); } + bool readChecksum(); + + void addZlibFooter(); + + friend class Protocol; + +private: + bool canRead(int bytes); + void checkRead(int bytes); + void checkWrite(int bytes); + + uint32 m_headerPos; + uint32 m_readPos; + uint32 m_messageSize; + uint8 m_buffer[BUFFER_MAXSIZE]; +}; + +#endif diff --git a/src/framework/net/outputmessage.cpp b/src/framework/net/outputmessage.cpp new file mode 100644 index 0000000..48f5814 --- /dev/null +++ b/src/framework/net/outputmessage.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +OutputMessage::OutputMessage() +{ + reset(); +} + +void OutputMessage::reset() +{ + m_writePos = MAX_HEADER_SIZE; + m_headerPos = MAX_HEADER_SIZE; + m_messageSize = 0; +} + +void OutputMessage::setBuffer(const std::string& buffer) +{ + int len = buffer.size(); + reset(); + checkWrite(len); + memcpy((char*)(m_buffer + m_writePos), buffer.c_str(), len); + m_writePos += len; + m_messageSize += len; +} + +void OutputMessage::addU8(uint8 value) +{ + checkWrite(1); + m_buffer[m_writePos] = value; + m_writePos += 1; + m_messageSize += 1; +} + +void OutputMessage::addU16(uint16 value) +{ + checkWrite(2); + stdext::writeULE16(m_buffer + m_writePos, value); + m_writePos += 2; + m_messageSize += 2; +} + +void OutputMessage::addU32(uint32 value) +{ + checkWrite(4); + stdext::writeULE32(m_buffer + m_writePos, value); + m_writePos += 4; + m_messageSize += 4; +} + +void OutputMessage::addU64(uint64 value) +{ + checkWrite(8); + stdext::writeULE64(m_buffer + m_writePos, value); + m_writePos += 8; + m_messageSize += 8; +} + +void OutputMessage::addString(const std::string& buffer) +{ + int len = buffer.length(); + if(len > MAX_STRING_LENGTH) + throw stdext::exception(stdext::format("string length > %d", MAX_STRING_LENGTH)); + checkWrite(len + 2); + addU16(len); + memcpy((char*)(m_buffer + m_writePos), buffer.c_str(), len); + m_writePos += len; + m_messageSize += len; +} + +void OutputMessage::addPaddingBytes(int bytes, uint8 byte) +{ + if(bytes <= 0) + return; + checkWrite(bytes); + memset((void*)&m_buffer[m_writePos], byte, bytes); + m_writePos += bytes; + m_messageSize += bytes; +} + +void OutputMessage::encryptRsa() +{ + uint32_t size = g_crypt.rsaGetSize(); + if(m_messageSize < size) + throw stdext::exception("insufficient bytes in buffer to encrypt"); + + if(!g_crypt.rsaEncrypt((unsigned char*)m_buffer + m_writePos - size, size)) + throw stdext::exception("rsa encryption failed"); +} + +void OutputMessage::writeChecksum() +{ + uint32 checksum = stdext::adler32(m_buffer + m_headerPos, m_messageSize); + VALIDATE(m_headerPos >= 4); + m_headerPos -= 4; + stdext::writeULE32(m_buffer + m_headerPos, checksum); + m_messageSize += 4; +} + +void OutputMessage::writeMessageSize(bool bigSize) +{ + VALIDATE(m_headerPos >= (bigSize ? 4 : 2)); + m_headerPos -= (bigSize ? 4 : 2); + if (bigSize) { + stdext::writeULE32(m_buffer + m_headerPos, m_messageSize); + } else { + stdext::writeULE16(m_buffer + m_headerPos, m_messageSize); + } + m_messageSize += (bigSize ? 4 : 2); +} + +bool OutputMessage::canWrite(int bytes) +{ + if(m_writePos + bytes > BUFFER_MAXSIZE) + return false; + return true; +} + +void OutputMessage::checkWrite(int bytes) +{ + if(!canWrite(bytes)) + throw stdext::exception("OutputMessage max buffer size reached"); +} diff --git a/src/framework/net/outputmessage.h b/src/framework/net/outputmessage.h new file mode 100644 index 0000000..6dfa295 --- /dev/null +++ b/src/framework/net/outputmessage.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OUTPUTMESSAGE_H +#define OUTPUTMESSAGE_H + +#include "declarations.h" +#include + +// @bindclass +class OutputMessage : public LuaObject +{ +public: + enum { + BUFFER_MAXSIZE = 327680, + MAX_STRING_LENGTH = 65536, + MAX_HEADER_SIZE = 12 + }; + + OutputMessage(); + + void reset(); + + void setBuffer(const std::string& buffer); + std::string getBuffer() { return std::string((char*)m_buffer + m_headerPos, m_messageSize); } + + void addU8(uint8 value); + void addU16(uint16 value); + void addU32(uint32 value); + void addU64(uint64 value); + void addString(const std::string& buffer); + void addPaddingBytes(int bytes, uint8 byte = 0); + + void encryptRsa(); + + uint32 getWritePos() { return m_writePos; } + uint32 getMessageSize() { return m_messageSize; } + + void setWritePos(uint32 writePos) { m_writePos = writePos; } + void setMessageSize(uint32 messageSize) { m_messageSize = messageSize; } + +protected: + uint8* getWriteBuffer() { return m_buffer + m_writePos; } + uint8* getHeaderBuffer() { return m_buffer + m_headerPos; } + uint8* getDataBuffer() { return m_buffer + MAX_HEADER_SIZE; } + + void writeChecksum(); + void writeMessageSize(bool bigSize); + + friend class Protocol; + +private: + bool canWrite(int bytes); + void checkWrite(int bytes); + + uint32 m_headerPos; + uint32 m_writePos; + uint32 m_messageSize; + uint8 m_buffer[BUFFER_MAXSIZE]; +}; + +#endif diff --git a/src/framework/net/protocol.cpp b/src/framework/net/protocol.cpp new file mode 100644 index 0000000..baadbc6 --- /dev/null +++ b/src/framework/net/protocol.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "protocol.h" +#include "connection.h" +#include +#include + +extern asio::io_service g_ioService; + +Protocol::Protocol() +{ + m_xteaEncryptionEnabled = false; + m_checksumEnabled = false; + m_bigPackets = false; + m_compression = false; + m_inputMessage = InputMessagePtr(new InputMessage); + + // compression + m_zstreamBuffer.resize(InputMessage::BUFFER_MAXSIZE); + m_zstream.next_in = m_inputMessage->getDataBuffer(); + m_zstream.next_out = m_zstreamBuffer.data(); + m_zstream.avail_in = 0; + m_zstream.avail_out = 0; + m_zstream.total_in = 0; + m_zstream.total_out = 0; + m_zstream.zalloc = nullptr; + m_zstream.zfree = nullptr; + m_zstream.opaque = nullptr; + m_zstream.data_type = Z_BINARY; + inflateInit2(&m_zstream, -15); +} + +Protocol::~Protocol() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); +#endif + disconnect(); + inflateEnd(&m_zstream); +} + +void Protocol::connect(const std::string& host, uint16 port) +{ +#ifdef FW_PROXY + if (host == "proxy" || host == "0.0.0.0" || (host == "127.0.0.1" && g_proxy.isActive())) { + m_disconnected = false; + m_proxy = g_proxy.addSession(port, + std::bind(&Protocol::onProxyPacket, asProtocol(), std::placeholders::_1), + std::bind(&Protocol::onProxyDisconnected, asProtocol(), std::placeholders::_1)); + return onConnect(); + } +#endif + m_connection = ConnectionPtr(new Connection); + m_connection->setErrorCallback(std::bind(&Protocol::onError, asProtocol(), std::placeholders::_1)); + m_connection->connect(host, port, std::bind(&Protocol::onConnect, asProtocol())); +} + +void Protocol::disconnect() +{ +#ifdef FW_PROXY + m_disconnected = true; + if (m_proxy) { + g_proxy.removeSession(m_proxy); + return; + } +#endif + if(m_connection) { + m_connection->close(); + m_connection.reset(); + } +} + +bool Protocol::isConnected() +{ +#ifdef FW_PROXY + if (m_proxy) + return !m_disconnected; +#endif + if(m_connection && m_connection->isConnected()) + return true; + return false; +} + +bool Protocol::isConnecting() +{ +#ifdef FW_PROXY + if (m_proxy) + return false; +#endif + if(m_connection && m_connection->isConnecting()) + return true; + return false; +} + +void Protocol::send(const OutputMessagePtr& outputMessage) +{ + // encrypt + if(m_xteaEncryptionEnabled) + xteaEncrypt(outputMessage); + + // write checksum + if(m_checksumEnabled) + outputMessage->writeChecksum(); + + // write message size + outputMessage->writeMessageSize(m_bigPackets); + +#ifdef FW_PROXY + if (m_proxy) { + auto packet = std::make_shared(outputMessage->getHeaderBuffer(), outputMessage->getWriteBuffer()); + g_proxy.send(m_proxy, packet); + outputMessage->reset(); + return; + } +#endif + + // send + if(m_connection) + m_connection->write(outputMessage->getHeaderBuffer(), outputMessage->getMessageSize()); + + // reset message to allow reuse + outputMessage->reset(); +} + +void Protocol::recv() +{ +#ifdef FW_PROXY + if (m_proxy) { + return; + } +#endif + m_inputMessage->reset(); + + // first update message header size + int headerSize = m_bigPackets ? 4 : 2; // 2 or 4 bytes for message size + if(m_checksumEnabled) + headerSize += 4; // 4 bytes for checksum + if(m_xteaEncryptionEnabled) + headerSize += m_bigPackets ? 4 : 2; // 2 or 4 bytes for XTEA encrypted message size + m_inputMessage->setHeaderSize(headerSize); + + // read the first 2 bytes which contain the message size + if(m_connection) + m_connection->read(m_bigPackets ? 4 : 2, std::bind(&Protocol::internalRecvHeader, asProtocol(), std::placeholders::_1, std::placeholders::_2)); +} + +void Protocol::internalRecvHeader(uint8* buffer, uint32 size) +{ + // read message size + m_inputMessage->fillBuffer(buffer, size); + uint32 remainingSize = m_inputMessage->readSize(m_bigPackets); + + // read remaining message data + if(m_connection) + m_connection->read(remainingSize, std::bind(&Protocol::internalRecvData, asProtocol(), std::placeholders::_1, std::placeholders::_2)); +} + +void Protocol::internalRecvData(uint8* buffer, uint32 size) +{ + // process data only if really connected + if(!isConnected()) { + g_logger.traceError("received data while disconnected"); + return; + } + + m_inputMessage->fillBuffer(buffer, size); + + bool decompress = false; + if(m_checksumEnabled) { + if (m_inputMessage->peekU32() == 0) { // compressed data + m_inputMessage->getU32(); + decompress = true; + } else if (!m_inputMessage->readChecksum()) { + g_logger.traceError(stdext::format("got a network message with invalid checksum, size: %i", (int)m_inputMessage->getMessageSize())); + return; + } + } + + if(m_xteaEncryptionEnabled) { + if(!xteaDecrypt(m_inputMessage)) { + g_logger.traceError("failed to decrypt message"); + return; + } + } + + if (decompress || m_compression) { + m_inputMessage->addZlibFooter(); + m_zstream.next_in = m_inputMessage->getDataBuffer(); + m_zstream.next_out = m_zstreamBuffer.data(); + m_zstream.avail_in = m_inputMessage->getUnreadSize(); + m_zstream.avail_out = m_zstreamBuffer.size(); + if (inflate(&m_zstream, Z_SYNC_FLUSH) != Z_OK) { + g_logger.traceError("failed to decompress message"); + return; + } + int decryptedSize = m_zstreamBuffer.size() - m_zstream.avail_out; + if (decryptedSize == 0) { + g_logger.traceError(stdext::format("invalid size of decompressed message - %i", (int)decryptedSize)); + return; + } + m_inputMessage->fillBuffer(m_zstreamBuffer.data(), decryptedSize); + m_inputMessage->setMessageSize(m_inputMessage->getHeaderSize() + decryptedSize); + } + onRecv(m_inputMessage); +} + +void Protocol::generateXteaKey() +{ + std::mt19937 eng(std::time(NULL)); + std::uniform_int_distribution unif(0, 0xFFFFFFFF); + m_xteaKey[0] = unif(eng); + m_xteaKey[1] = unif(eng); + m_xteaKey[2] = unif(eng); + m_xteaKey[3] = unif(eng); +} + +void Protocol::setXteaKey(uint32 a, uint32 b, uint32 c, uint32 d) +{ + m_xteaKey[0] = a; + m_xteaKey[1] = b; + m_xteaKey[2] = c; + m_xteaKey[3] = d; +} + +std::vector Protocol::getXteaKey() +{ + std::vector xteaKey; + xteaKey.resize(4); + for(int i = 0; i < 4; ++i) + xteaKey[i] = m_xteaKey[i]; + return xteaKey; +} + +bool Protocol::xteaDecrypt(const InputMessagePtr& inputMessage) +{ + uint32 encryptedSize = inputMessage->getUnreadSize(); + if(encryptedSize % 8 != 0) { + g_logger.traceError(stdext::format("invalid encrypted network message %i", (int)encryptedSize)); + return false; + } + + uint32 *buffer = (uint32*)(inputMessage->getReadBuffer()); + uint32_t readPos = 0; + + while(readPos < encryptedSize/4) { + uint32 v0 = buffer[readPos], v1 = buffer[readPos + 1]; + uint32 delta = 0x61C88647; + uint32 sum = 0xC6EF3720; + + for(int32 i = 0; i < 32; i++) { + v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + m_xteaKey[sum>>11 & 3]); + sum += delta; + v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + m_xteaKey[sum & 3]); + } + buffer[readPos] = v0; buffer[readPos + 1] = v1; + readPos = readPos + 2; + } + + uint32 decryptedSize = m_bigPackets ? (inputMessage->getU32() + 4) : (inputMessage->getU16() + 2); + int sizeDelta = decryptedSize - encryptedSize; + if(sizeDelta > 0 || -sizeDelta > (int)encryptedSize) { + g_logger.traceError("invalid decrypted network message"); + return false; + } + + inputMessage->setMessageSize(inputMessage->getMessageSize() + sizeDelta); + return true; +} + +void Protocol::xteaEncrypt(const OutputMessagePtr& outputMessage) +{ + outputMessage->writeMessageSize(m_bigPackets); + uint32 encryptedSize = outputMessage->getMessageSize(); + + //add bytes until reach 8 multiple + if((encryptedSize % 8) != 0) { + uint32 n = 8 - (encryptedSize % 8); + outputMessage->addPaddingBytes(n); + encryptedSize += n; + } + + uint32_t readPos = 0; + uint32 *buffer = (uint32*)(outputMessage->getDataBuffer() - (m_bigPackets ? 4 : 2)); + while(readPos < encryptedSize / 4) { + uint32 v0 = buffer[readPos], v1 = buffer[readPos + 1]; + uint32 delta = 0x61C88647; + uint32 sum = 0; + + for(int32 i = 0; i < 32; i++) { + v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + m_xteaKey[sum & 3]); + sum -= delta; + v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + m_xteaKey[sum>>11 & 3]); + } + buffer[readPos] = v0; buffer[readPos + 1] = v1; + readPos = readPos + 2; + } +} + +void Protocol::onConnect() +{ + callLuaField("onConnect"); +} + +void Protocol::onRecv(const InputMessagePtr& inputMessage) +{ + callLuaField("onRecv", inputMessage); +} + +void Protocol::onError(const boost::system::error_code& err) +{ + callLuaField("onError", err.message(), err.value()); + disconnect(); +} + +#ifdef FW_PROXY +void Protocol::onProxyPacket(ProxyPacketPtr packet) +{ + if (m_disconnected) + return; + auto self(asProtocol()); + boost::asio::post(g_ioService, [&, self, packet] + { + if (m_disconnected) + return; + m_inputMessage->reset(); + + // first update message header size + int headerSize = m_bigPackets ? 4 : 2; // 2 bytes for message size + if (m_checksumEnabled) + headerSize += 4; // 4 bytes for checksum + if (m_xteaEncryptionEnabled) + headerSize += m_bigPackets ? 4 : 2; // 2 bytes for XTEA encrypted message size + m_inputMessage->setHeaderSize(headerSize); + m_inputMessage->fillBuffer(packet->data(), m_bigPackets ? 4 : 2); + m_inputMessage->readSize(m_bigPackets); + internalRecvData(packet->data() + (m_bigPackets ? 4 : 2), packet->size() - (m_bigPackets ? 4 : 2)); + }); +} + +void Protocol::onProxyDisconnected(boost::system::error_code ec) +{ + if (m_disconnected) + return; + auto self(asProtocol()); + boost::asio::post(g_ioService, [&, self, ec] { + if (m_disconnected) + return; + m_disconnected = true; + onError(ec); + }); +} +#endif \ No newline at end of file diff --git a/src/framework/net/protocol.h b/src/framework/net/protocol.h new file mode 100644 index 0000000..6bfb6b1 --- /dev/null +++ b/src/framework/net/protocol.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include "declarations.h" +#include "inputmessage.h" +#include "outputmessage.h" +#include "connection.h" + +#include + +#ifdef FW_PROXY +#include +#endif + +#include + +// @bindclass +class Protocol : public LuaObject +{ +public: + Protocol(); + virtual ~Protocol(); + + void connect(const std::string& host, uint16 port); + void disconnect(); + + bool isConnected(); + bool isConnecting(); + ticks_t getElapsedTicksSinceLastRead() { return m_connection ? m_connection->getElapsedTicksSinceLastRead() : -1; } + + ConnectionPtr getConnection() { return m_connection; } + void setConnection(const ConnectionPtr& connection) { m_connection = connection; } + + void generateXteaKey(); + void setXteaKey(uint32 a, uint32 b, uint32 c, uint32 d); + std::vector getXteaKey(); + void enableXteaEncryption() { m_xteaEncryptionEnabled = true; } + + void enableChecksum() { m_checksumEnabled = true; } + void enableBigPackets() { m_bigPackets = true; } + void enableCompression() { m_compression = true; } + + virtual void send(const OutputMessagePtr& outputMessage); + virtual void recv(); + + ProtocolPtr asProtocol() { return static_self_cast(); } + +protected: + virtual void onConnect(); + virtual void onRecv(const InputMessagePtr& inputMessage); + virtual void onError(const boost::system::error_code& err); + +#ifdef FW_PROXY + void onProxyPacket(ProxyPacketPtr packet); + void onProxyDisconnected(boost::system::error_code ec); + uint32_t m_proxy = 0; + bool m_disconnected = false; +#endif + + uint32 m_xteaKey[4]; + +private: + void internalRecvHeader(uint8* buffer, uint32 size); + void internalRecvData(uint8* buffer, uint32 size); + + bool xteaDecrypt(const InputMessagePtr& inputMessage); + void xteaEncrypt(const OutputMessagePtr& outputMessage); + + bool m_checksumEnabled; + bool m_xteaEncryptionEnabled; + bool m_bigPackets; + bool m_compression; + ConnectionPtr m_connection; + InputMessagePtr m_inputMessage; + z_stream m_zstream; + std::vector m_zstreamBuffer; +}; + +#endif diff --git a/src/framework/net/server.cpp b/src/framework/net/server.cpp new file mode 100644 index 0000000..19c81e7 --- /dev/null +++ b/src/framework/net/server.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "server.h" +#include "connection.h" + +extern asio::io_service g_ioService; + +Server::Server(int port) + : m_acceptor(g_ioService, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) +{ +} + +ServerPtr Server::create(int port) +{ + try { + Server *server = new Server(port); + return ServerPtr(server); + } + catch(const std::exception& e) { + g_logger.error(stdext::format("Failed to initialize server: %s", e.what())); + return ServerPtr(); + } +} + +void Server::close() +{ + m_isOpen = false; + m_acceptor.cancel(); + m_acceptor.close(); +} + +void Server::acceptNext() +{ + ConnectionPtr connection = ConnectionPtr(new Connection); + connection->m_connecting = true; + auto self = static_self_cast(); + m_acceptor.async_accept(connection->m_socket, [=](const boost::system::error_code& error) { + if(!error) { + connection->m_connected = true; + connection->m_connecting = false; + } + self->callLuaField("onAccept", connection, error.message(), error.value()); + }); +} diff --git a/src/framework/net/server.h b/src/framework/net/server.h new file mode 100644 index 0000000..7509406 --- /dev/null +++ b/src/framework/net/server.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "declarations.h" +#include + +class Server : public LuaObject +{ +public: + Server(int port); + static ServerPtr create(int port); + bool isOpen() { return m_isOpen; } + void close(); + + void acceptNext(); + +private: + stdext::boolean m_isOpen; + asio::ip::tcp::acceptor m_acceptor; +}; + +#endif diff --git a/src/framework/otml/declarations.h b/src/framework/otml/declarations.h new file mode 100644 index 0000000..64c01af --- /dev/null +++ b/src/framework/otml/declarations.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_OTML_DECLARATIONS_H +#define FRAMEWORK_OTML_DECLARATIONS_H + +#include + +class OTMLNode; +class OTMLDocument; +class OTMLParser; +class OTMLEmitter; + +typedef stdext::shared_object_ptr OTMLNodePtr; +typedef stdext::shared_object_ptr OTMLDocumentPtr; +typedef std::vector OTMLNodeList; + +#endif diff --git a/src/framework/otml/otml.h b/src/framework/otml/otml.h new file mode 100644 index 0000000..0f40752 --- /dev/null +++ b/src/framework/otml/otml.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OTML_H +#define OTML_H + +#include "otmldocument.h" +#include "otmlnode.h" + +#endif \ No newline at end of file diff --git a/src/framework/otml/otmldocument.cpp b/src/framework/otml/otmldocument.cpp new file mode 100644 index 0000000..070d3d2 --- /dev/null +++ b/src/framework/otml/otmldocument.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "otmldocument.h" +#include "otmlparser.h" +#include "otmlemitter.h" + +#include + +OTMLDocumentPtr OTMLDocument::create() +{ + OTMLDocumentPtr doc(new OTMLDocument); + doc->setTag("doc"); + return doc; +} + +OTMLDocumentPtr OTMLDocument::parse(const std::string& fileName) +{ + std::stringstream fin; + std::string source = g_resources.resolvePath(fileName); + g_resources.readFileStream(source, fin); + return parse(fin, source); +} + +OTMLDocumentPtr OTMLDocument::parseString(const std::string& data, const std::string& source) +{ + std::istringstream is(data); + return parse(is, source); +} + +OTMLDocumentPtr OTMLDocument::parse(std::istream& in, const std::string& source) +{ + OTMLDocumentPtr doc(new OTMLDocument); + doc->setSource(source); + OTMLParser parser(doc, in); + parser.parse(); + return doc; +} + +std::string OTMLDocument::emit() +{ + return OTMLEmitter::emitNode(asOTMLNode()) + "\n"; +} + +bool OTMLDocument::save(const std::string& fileName) +{ + m_source = fileName; + return g_resources.writeFileContents(fileName, emit()); +} + diff --git a/src/framework/otml/otmldocument.h b/src/framework/otml/otmldocument.h new file mode 100644 index 0000000..c7d437a --- /dev/null +++ b/src/framework/otml/otmldocument.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OTMLDOCUMENT_H +#define OTMLDOCUMENT_H + +#include "otmlnode.h" + +class OTMLDocument : public OTMLNode +{ +public: + virtual ~OTMLDocument() { } + + /// Create a new OTML document for filling it with nodes + static OTMLDocumentPtr create(); + + /// Parse OTML from a file + static OTMLDocumentPtr parse(const std::string& fileName); + + /// Parse OTML from a string + static OTMLDocumentPtr parseString(const std::string& data, const std::string& source); + + /// Parse OTML from input stream + /// @param source is the file name that will be used to show errors messages + static OTMLDocumentPtr parse(std::istream& in, const std::string& source); + + /// Emits this document and all it's children to a std::string + std::string emit(); + + /// Save this document to a file + bool save(const std::string& fileName); + +private: + OTMLDocument() { } +}; + +#endif diff --git a/src/framework/otml/otmlemitter.cpp b/src/framework/otml/otmlemitter.cpp new file mode 100644 index 0000000..0adf7a1 --- /dev/null +++ b/src/framework/otml/otmlemitter.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "otmlemitter.h" +#include "otmldocument.h" + +std::string OTMLEmitter::emitNode(const OTMLNodePtr& node, int currentDepth) +{ + std::stringstream ss; + + // emit nodes + if(currentDepth >= 0) { + // fill spaces for current depth + for(int i=0;ihasTag()) { + ss << node->tag(); + + // add ':' to if the node is unique or has value + if(node->hasValue() || node->isUnique() || node->isNull()) + ss << ":"; + } else + ss << "-"; + + // emit node value + if(node->isNull()) + ss << " ~"; + else if(node->hasValue()) { + ss << " "; + + std::string value = node->value(); + + // emit multiline values + if(value.find("\n") != std::string::npos) { + if(value[value.length()-1] == '\n' && value[value.length()-2] == '\n') + ss << "|+"; + else if(value[value.length()-1] == '\n') + ss << "|"; + else + ss << "|-"; + + // multilines + for(std::size_t pos = 0; pos < value.length(); ++pos) { + ss << "\n"; + + // fill spaces for multiline depth + for(int i=0;isize();++i) { + if(currentDepth >= 0 || i != 0) + ss << "\n"; + ss << emitNode(node->atIndex(i), currentDepth+1); + } + + return ss.str(); +} diff --git a/src/framework/otml/otmlemitter.h b/src/framework/otml/otmlemitter.h new file mode 100644 index 0000000..c429834 --- /dev/null +++ b/src/framework/otml/otmlemitter.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OTMLEMITTER_H +#define OTMLEMITTER_H + +#include "declarations.h" + +class OTMLEmitter +{ +public: + /// Emits a node and it's children to a std::string + static std::string emitNode(const OTMLNodePtr& node, int currentDepth = -1); +}; + +#endif diff --git a/src/framework/otml/otmlexception.cpp b/src/framework/otml/otmlexception.cpp new file mode 100644 index 0000000..e87e3ce --- /dev/null +++ b/src/framework/otml/otmlexception.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "otmlexception.h" +#include "otmldocument.h" + +OTMLException::OTMLException(const OTMLNodePtr& node, const std::string& error) +{ + std::stringstream ss; + ss << "OTML error"; + if(!node->source().empty()) + ss << " in '" << node->source() << "'"; + ss << ": " << error; + m_what = ss.str(); +} + +OTMLException::OTMLException(const OTMLDocumentPtr& doc, const std::string& error, int line) +{ + std::stringstream ss; + ss << "OTML error"; + if(doc && !doc->source().empty()) { + ss << " in '" << doc->source() << "'"; + if(line >= 0) + ss << " at line " << line; + } + ss << ": " << error; + m_what = ss.str(); +} diff --git a/src/framework/otml/otmlexception.h b/src/framework/otml/otmlexception.h new file mode 100644 index 0000000..0b23e48 --- /dev/null +++ b/src/framework/otml/otmlexception.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OTMLEXCEPTION_H +#define OTMLEXCEPTION_H + +#include "declarations.h" + +/// All OTML errors throw this exception +class OTMLException : public stdext::exception +{ +public: + OTMLException(const OTMLNodePtr& node, const std::string& error); + OTMLException(const OTMLDocumentPtr& doc, const std::string& error, int line = -1); + virtual ~OTMLException() throw() { }; + + virtual const char* what() const throw() { return m_what.c_str(); } + +protected: + std::string m_what; +}; + +#endif diff --git a/src/framework/otml/otmlnode.cpp b/src/framework/otml/otmlnode.cpp new file mode 100644 index 0000000..dde07d2 --- /dev/null +++ b/src/framework/otml/otmlnode.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "otmlnode.h" +#include "otmlemitter.h" +#include "otmldocument.h" + +#include + +OTMLNodePtr OTMLNode::create(std::string tag, bool unique) +{ + OTMLNodePtr node(new OTMLNode); + node->setTag(tag); + node->setUnique(unique); + return node; +} + +OTMLNodePtr OTMLNode::create(std::string tag, std::string value) +{ + OTMLNodePtr node(new OTMLNode); + node->setTag(tag); + node->setValue(value); + node->setUnique(true); + return node; +} + +bool OTMLNode::hasChildren() +{ + for(const OTMLNodePtr& child : m_children) { + if (!child->isNull()) + return true; + } + return false; +} + +OTMLNodePtr OTMLNode::get(const std::string& childTag) +{ + //if (g_extras.OTMLChildIdCache) { + if (childTag.size() > 0 && childTag[0] == '!') + g_logger.fatal(stdext::format("Invalid childTag %s", childTag)); + auto it = m_childrenTagCache.find(childTag); + if (it != m_childrenTagCache.end() && !it->second->isNull()) + return it->second; + //} + + for(const OTMLNodePtr& child : m_children) { + if (child->tag() == childTag && !child->isNull()) { + std::string tag = child->tag(); + if (tag.size() > 0 && tag[0] == '!') + tag = tag.substr(1); + m_childrenTagCache[tag] = child; + child->lockTag(); + return child; + } + } + return nullptr; +} + +OTMLNodePtr OTMLNode::getIndex(int childIndex) +{ + if(childIndex < size() && childIndex >= 0) + return m_children[childIndex]; + return nullptr; +} + +OTMLNodePtr OTMLNode::at(const std::string& childTag) +{ + OTMLNodePtr res; + for(const OTMLNodePtr& child : m_children) { + if(child->tag() == childTag && !child->isNull()) { + res = child; + break; + } + } + if(!res) + throw OTMLException(asOTMLNode(), stdext::format("child node with tag '%s' not found", childTag)); + return res; +} + +OTMLNodePtr OTMLNode::atIndex(int childIndex) +{ + if(childIndex >= size() || childIndex < 0) + throw OTMLException(asOTMLNode(), stdext::format("child node with index '%d' not found", childIndex)); + return m_children[childIndex]; +} + +void OTMLNode::addChild(const OTMLNodePtr& newChild) +{ + // replace is needed when the tag is marked as unique + if(newChild->hasTag()) { + for(const OTMLNodePtr& node : m_children) { + if(node->tag() == newChild->tag() && (node->isUnique() || newChild->isUnique())) { + newChild->setUnique(true); + + if(node->hasChildren() && newChild->hasChildren()) { + OTMLNodePtr tmpNode = node->clone(); + tmpNode->merge(newChild); + newChild->copy(tmpNode); + } + + replaceChild(node, newChild); + + // remove any other child with the same tag + auto it = m_children.begin(); + while(it != m_children.end()) { + OTMLNodePtr node = (*it); + if(node != newChild && node->tag() == newChild->tag()) { + std::string tag = newChild->tag(); + if (tag.size() > 0 && tag[0] == '!') + tag = tag.substr(1); + auto cacheIt = m_childrenTagCache.find(tag); + if (cacheIt != m_childrenTagCache.end()) { + if (cacheIt->second != newChild) { + m_childrenTagCache.erase(cacheIt); + m_childrenTagCache[tag] = newChild; + newChild->lockTag(); + } + } + it = m_children.erase(it); + } else + ++it; + } + return; + } + } + } + + m_children.push_back(newChild); + std::string tag = newChild->tag(); + if (tag.size() > 0 && tag[0] == '!') + tag = tag.substr(1); + m_childrenTagCache[tag] = newChild; + newChild->lockTag(); +} + +bool OTMLNode::removeChild(const OTMLNodePtr& oldChild) +{ + auto it = std::find(m_children.begin(), m_children.end(), oldChild); + if(it != m_children.end()) { + m_children.erase(it); + m_childrenTagCache.erase((*it)->tag()); + return true; + } + return false; +} + +bool OTMLNode::replaceChild(const OTMLNodePtr& oldChild, const OTMLNodePtr& newChild) +{ + auto it = std::find(m_children.begin(), m_children.end(), oldChild); + if(it != m_children.end()) { + std::string tag = (*it)->tag(); + if (tag.size() > 0 && tag[0] == '!') + tag = tag.substr(1); + auto cacheIt = m_childrenTagCache.find(tag); + if (cacheIt != m_childrenTagCache.end()) { + if (cacheIt->second == (*it)) + m_childrenTagCache.erase(cacheIt); + } + it = m_children.erase(it); + + m_children.insert(it, newChild); + tag = newChild->tag(); + if (tag.size() > 0 && tag[0] == '!') + tag = tag.substr(1); + m_childrenTagCache[tag] = newChild; + newChild->lockTag(); + return true; + } + return false; +} + +void OTMLNode::copy(const OTMLNodePtr& node) +{ + setTag(node->tag()); + setValue(node->rawValue()); + setUnique(node->isUnique()); + setNull(node->isNull()); + setSource(node->source()); + clear(); + for(const OTMLNodePtr& child : node->m_children) + addChild(child->clone()); +} + +void OTMLNode::merge(const OTMLNodePtr& node) +{ + for(const OTMLNodePtr& child : node->m_children) + addChild(child->clone()); + setTag(node->tag()); + setSource(node->source()); +} + +void OTMLNode::clear() +{ + m_children.clear(); + m_childrenTagCache.clear(); +} + +OTMLNodeList OTMLNode::children() +{ + OTMLNodeList children; + for(const OTMLNodePtr& child : m_children) + if(!child->isNull()) + children.push_back(child); + return children; +} + +OTMLNodePtr OTMLNode::clone() +{ + OTMLNodePtr myClone(new OTMLNode); + myClone->setTag(m_tag); + myClone->setValue(m_value); + myClone->setUnique(m_unique); + myClone->setNull(m_null); + myClone->setSource(m_source); + for(const OTMLNodePtr& child : m_children) + myClone->addChild(child->clone()); + return myClone; +} + +std::string OTMLNode::emit() +{ + return OTMLEmitter::emitNode(asOTMLNode(), 0); +} + +void OTMLNode::setTag(const std::string& tag) { + if (m_tagLocked && tag != m_tag) { + std::string correct_tag = m_tag; + if (correct_tag.size() > 0 && m_tag[0] == '!') + correct_tag = correct_tag.substr(1); + if(correct_tag != tag) + g_logger.fatal(stdext::format("Trying to setTag for locked QTMLNode %s to %s", m_tag, tag)); + } + m_tag = tag; +} diff --git a/src/framework/otml/otmlnode.h b/src/framework/otml/otmlnode.h new file mode 100644 index 0000000..6208714 --- /dev/null +++ b/src/framework/otml/otmlnode.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OTMLNODE_H +#define OTMLNODE_H + +#include "declarations.h" + +class OTMLNode : public stdext::shared_object +{ +public: + virtual ~OTMLNode() { } + + static OTMLNodePtr create(std::string tag = "", bool unique = false); + static OTMLNodePtr create(std::string tag, std::string value); + + std::string tag() { return m_tag; } + int size() { return m_children.size(); } + std::string source() { return m_source; } + std::string rawValue() { return m_value; } + + bool isUnique() { return m_unique; } + bool isNull() { return m_null; } + + bool hasTag() { return !m_tag.empty(); } + bool hasValue() { return !m_value.empty(); } + bool hasChildren(); + bool hasChildAt(const std::string& childTag) { return !!get(childTag); } + bool hasChildAtIndex(int childIndex) { return !!getIndex(childIndex); } + + void setTag(const std::string& tag); + void setValue(const std::string& value) { m_value = value; } + void setNull(bool null) { m_null = null; } + void setUnique(bool unique) { m_unique = unique; } + void setSource(const std::string& source) { m_source = source; } + + void lockTag() { + m_tagLocked = true; + } + + OTMLNodePtr get(const std::string& childTag); + OTMLNodePtr getIndex(int childIndex); + + OTMLNodePtr at(const std::string& childTag); + OTMLNodePtr atIndex(int childIndex); + + void addChild(const OTMLNodePtr& newChild); + bool removeChild(const OTMLNodePtr& oldChild); + bool replaceChild(const OTMLNodePtr& oldChild, const OTMLNodePtr& newChild); + void copy(const OTMLNodePtr& node); + void merge(const OTMLNodePtr& node); + void clear(); + + OTMLNodeList children(); + OTMLNodePtr clone(); + + template + T value(); + template + T valueAt(const std::string& childTag); + template + T valueAtIndex(int childIndex); + template + T valueAt(const std::string& childTag, const T& def); + template + T valueAtIndex(int childIndex, const T& def); + + template + void write(const T& v); + template + void writeAt(const std::string& childTag, const T& v); + template + void writeIn(const T& v); + + virtual std::string emit(); + + OTMLNodePtr asOTMLNode() { return static_self_cast(); } + +protected: + OTMLNode() : m_unique(false), m_null(false) { } + + OTMLNodeList m_children; + std::unordered_map m_childrenTagCache; + std::string m_tag; + std::string m_value; + std::string m_source; + bool m_unique; + bool m_null; + bool m_tagLocked = false; +}; + +#include "otmlexception.h" + +template<> +inline std::string OTMLNode::value() { + std::string value = m_value; + if(stdext::starts_with(value, "\"") && stdext::ends_with(value, "\"")) { + value = value.substr(1, value.length()-2); + stdext::replace_all(value, "\\\\", "\\"); + stdext::replace_all(value, "\\\"", "\""); + stdext::replace_all(value, "\\t", "\t"); + stdext::replace_all(value, "\\n", "\n"); + stdext::replace_all(value, "\\'", "\'"); + } + return value; +} + +template +T OTMLNode::value() { + T ret; + if(!stdext::cast(m_value, ret)) + throw OTMLException(asOTMLNode(), stdext::format("failed to cast node value '%s' to type '%s'", m_value, stdext::demangle_type())); + return ret; +} + +template +T OTMLNode::valueAt(const std::string& childTag) { + OTMLNodePtr node = at(childTag); + return node->value(); +} + +template +T OTMLNode::valueAtIndex(int childIndex) { + OTMLNodePtr node = atIndex(childIndex); + return node->value(); +} + +template +T OTMLNode::valueAt(const std::string& childTag, const T& def) { + if(OTMLNodePtr node = get(childTag)) + if(!node->isNull()) + return node->value(); + return def; +} + +template +T OTMLNode::valueAtIndex(int childIndex, const T& def) { + if(OTMLNodePtr node = getIndex(childIndex)) + return node->value(); + return def; +} + +template +void OTMLNode::write(const T& v) { + m_value = stdext::safe_cast(v); +} + +template +void OTMLNode::writeAt(const std::string& childTag, const T& v) { + OTMLNodePtr child = OTMLNode::create(childTag); + child->setUnique(true); + child->write(v); + addChild(child); +} + +template +void OTMLNode::writeIn(const T& v) { + OTMLNodePtr child = OTMLNode::create(); + child->write(v); + addChild(child); +} + +#endif + diff --git a/src/framework/otml/otmlparser.cpp b/src/framework/otml/otmlparser.cpp new file mode 100644 index 0000000..256fb7d --- /dev/null +++ b/src/framework/otml/otmlparser.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "otmlparser.h" +#include "otmldocument.h" +#include "otmlexception.h" +#include + +OTMLParser::OTMLParser(OTMLDocumentPtr doc, std::istream& in) : + currentDepth(0), currentLine(0), + doc(doc), currentParent(doc), previousNode(0), + in(in) +{ +} + +void OTMLParser::parse() +{ + if(!in.good()) + throw OTMLException(doc, "cannot read from input stream"); + + while(!in.eof()) + parseLine(getNextLine()); +} + +std::string OTMLParser::getNextLine() +{ + currentLine++; + std::string line; + std::getline(in, line); + return line; +} + +int OTMLParser::getLineDepth(const std::string& line, bool multilining) +{ + // count number of spaces at the line beginning + std::size_t spaces = 0; + while(line[spaces] == ' ') + spaces++; + + // pre calculate depth + int depth = spaces / 2; + + if(!multilining || depth <= currentDepth) { + // check the next character is a tab + if(line[spaces] == '\t') + throw OTMLException(doc, "indentation with tabs are not allowed", currentLine); + + // must indent every 2 spaces + if(spaces % 2 != 0) + throw OTMLException(doc, "must indent every 2 spaces", currentLine); + } + + return depth; +} + +void OTMLParser::parseLine(std::string line) +{ + int depth = getLineDepth(line); + + if(depth == -1) + return; + + // remove line sides spaces + stdext::trim(line); + + // skip empty lines + if(line.empty()) + return; + + // skip comments + if(stdext::starts_with(line, "//")) + return; + + // a depth above, change current parent to the previous added node + if(depth == currentDepth+1) { + currentParent = previousNode; + // a depth below, change parent to previous parent + } else if(depth < currentDepth) { + for(int i=0;i dotsPos+1) + value = data.substr(dotsPos+1); + // node that has only a tag + } else { + tag = data; + } + + stdext::trim(tag); + stdext::trim(value); + + // process multitine values + if(value == "|" || value == "|-" || value == "|+") { + // reads next lines until we can a value below the same depth + std::string multiLineData; + do { + size_t lastPos = in.tellg(); + std::string line = getNextLine(); + int depth = getLineDepth(line, true); + + // depth above current depth, add the text to the multiline + if(depth > currentDepth) { + multiLineData += line.substr((currentDepth+1)*2); + // it has contents below the current depth + } else { + // if not empty, its a node + stdext::trim(line); + if(!line.empty()) { + // rewind and break + in.seekg(lastPos, std::ios::beg); + currentLine--; + break; + } + } + multiLineData += "\n"; + } while(!in.eof()); + + /* determine how to treat new lines at the end + * | strip all new lines at the end and add just a new one + * |- strip all new lines at the end + * |+ keep all the new lines at the end (the new lines until next node) + */ + if(value == "|" || value == "|-") { + // remove all new lines at the end + int lastPos = multiLineData.length(); + while(multiLineData[--lastPos] == '\n') + multiLineData.erase(lastPos, 1); + + if(value == "|") + multiLineData.append("\n"); + } // else it's |+ + + value = multiLineData; + } + + // create the node + OTMLNodePtr node = OTMLNode::create(tag); + + node->setUnique(dotsPos != std::string::npos); + node->setTag(tag); + node->setSource(doc->source() + ":" + stdext::unsafe_cast(nodeLine)); + + // ~ is considered the null value + if(value == "~") + node->setNull(true); + else { + if(stdext::starts_with(value, "[") && stdext::ends_with(value, "]")) { + std::string tmp = value.substr(1, value.length()-2); + boost::tokenizer> tokens(tmp); + for(std::string v : tokens) { + stdext::trim(v); + node->writeIn(v); + } + } else + node->setValue(value); + } + + currentParent->addChild(node); + parentMap[node] = currentParent; + previousNode = node; +} diff --git a/src/framework/otml/otmlparser.h b/src/framework/otml/otmlparser.h new file mode 100644 index 0000000..364c347 --- /dev/null +++ b/src/framework/otml/otmlparser.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OTMLPARSER_H +#define OTMLPARSER_H + +#include "declarations.h" + +class OTMLParser +{ +public: + OTMLParser(OTMLDocumentPtr doc, std::istream& in); + + /// Parse the entire document + void parse(); + +private: + /// Retrieve next line from the input stream + std::string getNextLine(); + /// Counts depth of a line (every 2 spaces increments one depth) + int getLineDepth(const std::string& line, bool multilining = false); + + /// Parse each line of the input stream + void parseLine(std::string line); + /// Parse nodes tag and value + void parseNode(const std::string& data); + + int currentDepth; + int currentLine; + OTMLDocumentPtr doc; + OTMLNodePtr currentParent; + std::unordered_map parentMap; + OTMLNodePtr previousNode; + std::istream& in; +}; + +#endif diff --git a/src/framework/pch.h b/src/framework/pch.h new file mode 100644 index 0000000..02af543 --- /dev/null +++ b/src/framework/pch.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PCH_H +#define PCH_H + +// common C headers +#include +#include +#include +#include +#include + +// common STL headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// new +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef ANDROID +#include +#endif + + // boost +#ifdef ANDROID +#define BOOST_UUID_RANDOM_PROVIDER_FORCE_POSIX +#endif +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/src/framework/platform/crashhandler.h b/src/framework/platform/crashhandler.h new file mode 100644 index 0000000..571e5a4 --- /dev/null +++ b/src/framework/platform/crashhandler.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef CRASHHANDLER_H +#define CRASHHANDLER_H + +#ifdef CRASH_HANDLER +void installCrashHandler(); +void uninstallCrashHandler(); +#endif + +#endif diff --git a/src/framework/platform/platform.cpp b/src/framework/platform/platform.cpp new file mode 100644 index 0000000..a258813 --- /dev/null +++ b/src/framework/platform/platform.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "platform.h" + +Platform g_platform; diff --git a/src/framework/platform/platform.h b/src/framework/platform/platform.h new file mode 100644 index 0000000..1be5dc0 --- /dev/null +++ b/src/framework/platform/platform.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +#include +#include +#include + +class Platform +{ +public: + void processArgs(std::vector& args); + bool spawnProcess(std::string process, const std::vector& args); + int getProcessId(); + bool isProcessRunning(const std::string& name); + bool killProcess(const std::string& name); + std::string getTempPath(); + std::string getCurrentDir(); + bool copyFile(std::string from, std::string to); + bool fileExists(std::string file); + bool removeFile(std::string file); + ticks_t getFileModificationTime(std::string file); + bool openUrl(std::string url, bool now = false); + bool openDir(std::string path, bool now = false); + std::string getCPUName(); + double getTotalSystemMemory(); + double getMemoryUsage(); + std::string getOSName(); + std::string traceback(const std::string& where, int level = 1, int maxDepth = 32); + std::vector getMacAddresses(); + std::string getUserName(); + std::vector getDlls(); + std::vector getProcesses(); + std::vector getWindows(); +}; + +extern Platform g_platform; + +#endif diff --git a/src/framework/platform/platformwindow.cpp b/src/framework/platform/platformwindow.cpp new file mode 100644 index 0000000..acad7de --- /dev/null +++ b/src/framework/platform/platformwindow.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "platformwindow.h" + +#if defined(WIN32) +#include "win32window.h" +WIN32Window window; +#elif defined(ANDROID) +#include "androidwindow.h" +#else +#include "x11window.h" +#include +X11Window window; +#endif + +#include +#include +#include + +#ifdef ANDROID +PlatformWindow& g_window = g_androidWindow; +#else +PlatformWindow& g_window = window; +#endif + +int PlatformWindow::loadMouseCursor(const std::string& file, const Point& hotSpot) +{ + ImagePtr image = Image::load(file); + + if(!image) { + g_logger.traceError(stdext::format("unable to load cursor image file %s", file)); + return -1; + } + + if(image->getBpp() != 4) { + g_logger.error("the cursor image must have 4 channels"); + return -1; + } + + if(image->getWidth() != 32 || image->getHeight() != 32) { + g_logger.error("the cursor image must have 32x32 dimension"); + return -1; + } + + return internalLoadMouseCursor(image, hotSpot); +} + +void PlatformWindow::updateUnmaximizedCoords() +{ + if(!isMaximized() && !isFullscreen()) { + m_unmaximizedPos = m_position; + m_unmaximizedSize = m_size; + } +} + +void PlatformWindow::processKeyDown(Fw::Key keyCode) +{ + if (std::this_thread::get_id() != g_dispatcherThreadId) { + g_dispatcher.addEvent(std::bind(&PlatformWindow::processKeyDown, this, keyCode)); + return; + } + + if(keyCode == Fw::KeyUnknown) + return; + + if(keyCode == Fw::KeyCtrl) { + m_inputEvent.keyboardModifiers |= Fw::KeyboardCtrlModifier; + return; + } else if(keyCode == Fw::KeyAlt) { + m_inputEvent.keyboardModifiers |= Fw::KeyboardAltModifier; + return; + } else if(keyCode == Fw::KeyShift) { + m_inputEvent.keyboardModifiers |= Fw::KeyboardShiftModifier; + return; + } + + if(m_keysState[keyCode]) + return; + + m_keysState[keyCode] = true; + m_lastKeysPress[keyCode] = -1; + + m_inputEvent.reset(Fw::KeyDownInputEvent); + m_inputEvent.type = Fw::KeyDownInputEvent; + m_inputEvent.keyCode = keyCode; + + if(m_onInputEvent) { + m_onInputEvent(m_inputEvent); + + m_inputEvent.reset(Fw::KeyPressInputEvent); + m_inputEvent.keyCode = keyCode; + m_lastKeysPress[keyCode] = g_clock.millis(); + m_firstKeysPress[keyCode] = g_clock.millis(); + m_onInputEvent(m_inputEvent); + } +} + +void PlatformWindow::processKeyUp(Fw::Key keyCode) +{ + if (std::this_thread::get_id() != g_dispatcherThreadId) { + g_dispatcher.addEvent(std::bind(&PlatformWindow::processKeyUp, this, keyCode)); + return; + } + + if(keyCode == Fw::KeyUnknown) + return; + + if(keyCode == Fw::KeyCtrl) { + m_inputEvent.keyboardModifiers &= ~Fw::KeyboardCtrlModifier; + return; + } else if(keyCode == Fw::KeyAlt) { + m_inputEvent.keyboardModifiers &= ~Fw::KeyboardAltModifier; + return; + } else if(keyCode == Fw::KeyShift) { + m_inputEvent.keyboardModifiers &= ~Fw::KeyboardShiftModifier; + return; + } else if(keyCode == Fw::KeyNumLock) { + for(uchar k = Fw::KeyNumpad0; k <= Fw::KeyNumpad9; ++k) { + if(m_keysState[(Fw::Key)k]) + processKeyUp((Fw::Key)k); + } + } + + if(!m_keysState[keyCode]) + return; + + m_keysState[keyCode] = false; + + if(m_onInputEvent) { + m_inputEvent.reset(Fw::KeyUpInputEvent); + m_inputEvent.keyCode = keyCode; + m_onInputEvent(m_inputEvent); + } +} + +void PlatformWindow::releaseAllKeys() +{ + if (std::this_thread::get_id() != g_dispatcherThreadId) { + g_dispatcher.addEvent(std::bind(&PlatformWindow::releaseAllKeys, this)); + return; + } + + for(auto it : m_keysState) { + Fw::Key keyCode = it.first; + bool pressed = it.second; + + if(!pressed) + continue; + + processKeyUp(keyCode); + } + + m_inputEvent.keyboardModifiers = 0; + + for(int i=0;i<4;++i) + m_mouseButtonStates[i] = false; +} + +void PlatformWindow::fireKeysPress() +{ + if (std::this_thread::get_id() != g_dispatcherThreadId) { + g_dispatcher.addEvent(std::bind(&PlatformWindow::fireKeysPress, this)); + return; + } + + // avoid massive checks + if(m_keyPressTimer.ticksElapsed() < 10) + return; + m_keyPressTimer.restart(); + + for(auto it : m_keysState) { + Fw::Key keyCode = it.first; + bool pressed = it.second; + + if(!pressed) + continue; + + ticks_t lastPressTicks = m_lastKeysPress[keyCode]; + ticks_t firstKeyPress = m_firstKeysPress[keyCode]; + if(g_clock.millis() - lastPressTicks >= KEY_PRESS_REPEAT_INTERVAL) { + if(m_onInputEvent) { + m_inputEvent.reset(Fw::KeyPressInputEvent); + m_inputEvent.keyCode = keyCode; + m_inputEvent.autoRepeatTicks = g_clock.millis() - firstKeyPress; + m_onInputEvent(m_inputEvent); + } + m_lastKeysPress[keyCode] = g_clock.millis(); + } + } +} + diff --git a/src/framework/platform/platformwindow.h b/src/framework/platform/platformwindow.h new file mode 100644 index 0000000..9a3eb48 --- /dev/null +++ b/src/framework/platform/platformwindow.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PLATFORMWINDOW_H +#define PLATFORMWINDOW_H + +#include +#include +#include +#include + +//@bindsingleton g_window +class PlatformWindow +{ + enum { + KEY_PRESS_REPEAT_INTERVAL = 30, + }; + + typedef std::function OnResizeCallback; + typedef std::function OnInputEventCallback; + +public: + virtual void init() = 0; + virtual void terminate() = 0; + + virtual void move(const Point& pos) = 0; + virtual void resize(const Size& size) = 0; + virtual void show() = 0; + virtual void hide() = 0; + virtual void maximize() = 0; + virtual void poll() = 0; + virtual void swapBuffers() = 0; + virtual void showMouse() = 0; + virtual void hideMouse() = 0; + virtual void displayFatalError(const std::string& message) { } + + int loadMouseCursor(const std::string& file, const Point& hotSpot); + virtual void setMouseCursor(int cursorId) = 0; + virtual void restoreMouseCursor() = 0; + + virtual void setTitle(const std::string& title) = 0; + virtual void setMinimumSize(const Size& minimumSize) = 0; + virtual void setFullscreen(bool fullscreen) = 0; + virtual void setVerticalSync(bool enable) = 0; + virtual void setIcon(const std::string& iconFile) = 0; + virtual void setClipboardText(const std::string& text) = 0; + + bool hasVerticalSync() { return m_verticalSync; } + + virtual Size getDisplaySize() = 0; + virtual std::string getClipboardText() = 0; + virtual std::string getPlatformType() = 0; + + int getDisplayWidth() { return getDisplaySize().width(); } + int getDisplayHeight() { return getDisplaySize().height(); } + + Size getUnmaximizedSize() { return m_unmaximizedSize; } + Size getSize() { return m_size; } + Size getMinimumSize() { return m_minimumSize; } + int getWidth() { return m_size.width(); } + int getHeight() { return m_size.height(); } + Point getUnmaximizedPos() { return m_unmaximizedPos; } + Point getPosition() { return m_position; } + int getX() { return m_position.x; } + int getY() { return m_position.y; } + Point getMousePosition() { return m_inputEvent.mousePos; } + int getKeyboardModifiers() { return m_inputEvent.keyboardModifiers; } + + bool isKeyPressed(Fw::Key keyCode) { return m_keysState[keyCode]; } + bool isMouseButtonPressed(Fw::MouseButton mouseButton) { return m_mouseButtonStates[mouseButton]; } + bool isVisible() { return m_visible; } + bool isMaximized() { return m_maximized; } + bool isFullscreen() { return m_fullscreen; } + bool hasFocus() { return m_focused; } + + void setOnClose(const std::function& onClose) { m_onClose = onClose; } + void setOnResize(const OnResizeCallback& onResize) { m_onResize = onResize; } + void setOnInputEvent(const OnInputEventCallback& onInputEvent) { m_onInputEvent = onInputEvent; } + + virtual void showTextEditor(const std::string& title, const std::string& description, const std::string& text, int flags) {} + virtual void handleTextInput(std::string text) {} // for android + + void setScaling(float scaling) { m_scaling = scaling; } + +protected: + virtual int internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot) = 0; + + void updateUnmaximizedCoords(); + + void processKeyDown(Fw::Key keyCode); + void processKeyUp(Fw::Key keyCode); + void releaseAllKeys(); + void fireKeysPress(); + + std::map m_keyMap; + std::map> m_keysState; + std::map m_firstKeysPress; + std::map m_lastKeysPress; + Timer m_keyPressTimer; + + Size m_size; + Size m_minimumSize; + Point m_position; + Size m_unmaximizedSize; + Point m_unmaximizedPos; + InputEvent m_inputEvent; + stdext::boolean m_mouseButtonStates[4]; + + stdext::boolean m_created; + stdext::boolean m_visible; + stdext::boolean m_focused; + stdext::boolean m_fullscreen; + stdext::boolean m_maximized; + bool m_verticalSync = false; + float m_scaling = 1.0; + + std::function m_onClose; + OnResizeCallback m_onResize; + OnInputEventCallback m_onInputEvent; +}; + +extern PlatformWindow& g_window; + +#endif diff --git a/src/framework/sound/combinedsoundsource.cpp b/src/framework/sound/combinedsoundsource.cpp new file mode 100644 index 0000000..7b0e130 --- /dev/null +++ b/src/framework/sound/combinedsoundsource.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "combinedsoundsource.h" + +CombinedSoundSource::CombinedSoundSource() : SoundSource(0) +{ +} + +void CombinedSoundSource::addSource(const SoundSourcePtr& source) +{ + m_sources.push_back(source); +} + +void CombinedSoundSource::play() +{ + for(const SoundSourcePtr& source : m_sources) + source->play(); +} + +void CombinedSoundSource::stop() +{ + for(const SoundSourcePtr& source : m_sources) + source->stop(); +} + +bool CombinedSoundSource::isBuffering() +{ + for(const SoundSourcePtr& source : m_sources) { + if(source->isBuffering()) + return true; + } + return false; +} + +bool CombinedSoundSource::isPlaying() +{ + for(const SoundSourcePtr& source : m_sources) { + if(source->isPlaying()) + return true; + } + return false; +} + +void CombinedSoundSource::setLooping(bool looping) +{ + for(const SoundSourcePtr& source : m_sources) + source->setLooping(looping); +} + +void CombinedSoundSource::setRelative(bool relative) +{ + for(const SoundSourcePtr& source : m_sources) + source->setRelative(relative); +} + +void CombinedSoundSource::setReferenceDistance(float distance) +{ + for(const SoundSourcePtr& source : m_sources) + source->setReferenceDistance(distance); +} + +void CombinedSoundSource::setGain(float gain) +{ + for(const SoundSourcePtr& source : m_sources) + source->setGain(gain); +} + +void CombinedSoundSource::setPitch(float pitch) +{ + for(const SoundSourcePtr& source : m_sources) + source->setPitch(pitch); +} + +void CombinedSoundSource::setPosition(const Point& pos) +{ + for(const SoundSourcePtr& source : m_sources) + source->setPosition(pos); +} + +void CombinedSoundSource::setVelocity(const Point& velocity) +{ + for(const SoundSourcePtr& source : m_sources) + source->setVelocity(velocity); +} + +void CombinedSoundSource::setFading(SoundSource::FadeState state, float fadetime) +{ + for(const SoundSourcePtr& source : m_sources) + source->setFading(state, fadetime); +} + +void CombinedSoundSource::update() +{ + for(const SoundSourcePtr& source : m_sources) + source->update(); +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/combinedsoundsource.h b/src/framework/sound/combinedsoundsource.h new file mode 100644 index 0000000..e4da04e --- /dev/null +++ b/src/framework/sound/combinedsoundsource.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef COMBINEDSOUNDSOURCE_H +#define COMBINEDSOUNDSOURCE_H + +#include "soundsource.h" + +class CombinedSoundSource : public SoundSource +{ +public: + CombinedSoundSource(); + + void addSource(const SoundSourcePtr& source); + std::vector getSources() { return m_sources; } + + void play(); + void stop(); + + bool isBuffering(); + bool isPlaying(); + + void setLooping(bool looping); + void setRelative(bool relative); + void setReferenceDistance(float distance); + void setGain(float gain); + void setPitch(float pitch); + void setPosition(const Point& pos); + void setVelocity(const Point& velocity); + void setFading(FadeState state, float fadetime); + +protected: + virtual void update(); + +private: + std::vector m_sources; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/declarations.h b/src/framework/sound/declarations.h new file mode 100644 index 0000000..dc92d3d --- /dev/null +++ b/src/framework/sound/declarations.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef FRAMEWORK_SOUND_DECLARATIONS_H +#define FRAMEWORK_SOUND_DECLARATIONS_H + +#include + +#define AL_LIBTYPE_STATIC + +#include +#include + +class SoundManager; +class SoundSource; +class SoundBuffer; +class SoundFile; +class SoundChannel; +class StreamSoundSource; +class CombinedSoundSource; +class OggSoundFile; + +typedef stdext::shared_object_ptr SoundSourcePtr; +typedef stdext::shared_object_ptr SoundFilePtr; +typedef stdext::shared_object_ptr SoundBufferPtr; +typedef stdext::shared_object_ptr SoundChannelPtr; +typedef stdext::shared_object_ptr StreamSoundSourcePtr; +typedef stdext::shared_object_ptr CombinedSoundSourcePtr; +typedef stdext::shared_object_ptr OggSoundFilePtr; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/oggsoundfile.cpp b/src/framework/sound/oggsoundfile.cpp new file mode 100644 index 0000000..556fe96 --- /dev/null +++ b/src/framework/sound/oggsoundfile.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "oggsoundfile.h" + +OggSoundFile::OggSoundFile(const FileStreamPtr& fileStream) : SoundFile(fileStream) +{ + memset(&m_vorbisFile, 0, sizeof(m_vorbisFile)); +} + +OggSoundFile::~OggSoundFile() +{ + ov_clear(&m_vorbisFile); +} + +bool OggSoundFile::prepareOgg() +{ + ov_callbacks callbacks = { cb_read, cb_seek, cb_close, cb_tell }; + ov_open_callbacks(m_file.get(), &m_vorbisFile, 0, 0, callbacks); + + vorbis_info* vi = ov_info(&m_vorbisFile, -1); + if(!vi) { + g_logger.error(stdext::format("ogg file not supported: %s", m_file->name())); + return false; + } + + m_channels = vi->channels; + m_rate = vi->rate; + m_bps = 16; + m_size = ov_pcm_total(&m_vorbisFile, -1) * (m_bps/8) * m_channels; + + return true; +} + +int OggSoundFile::read(void *buffer, int bufferSize) +{ + char* bytesBuffer = reinterpret_cast(buffer); + int section = 0; + size_t totalBytesRead = 0; + + while(bufferSize > 0) { + size_t bytesToRead = bufferSize; + long bytesRead = ov_read(&m_vorbisFile, bytesBuffer, bytesToRead, 0, 2, 1, §ion); + if(bytesRead == 0) + break; + + bufferSize -= bytesRead; + bytesBuffer += bytesRead; + totalBytesRead += bytesRead; + } + + return totalBytesRead; +} + +void OggSoundFile::reset() +{ + ov_pcm_seek(&m_vorbisFile, 0); +} + +size_t OggSoundFile::cb_read(void* ptr, size_t size, size_t nmemb, void* source) +{ + FileStream *file = static_cast(source); + return file->read(ptr, size, nmemb); +} + +int OggSoundFile::cb_seek(void* source, ogg_int64_t offset, int whence) +{ + FileStream *file = static_cast(source); + switch(whence) { + case SEEK_SET: + file->seek(offset); + return 0; + case SEEK_CUR: + file->seek(file->tell() + offset); + return 0; + case SEEK_END: + file->seek(file->size() + offset); + return 0; + } + return -1; +} + +int OggSoundFile::cb_close(void* source) +{ + FileStream *file = static_cast(source); + file->close(); + return 0; +} + +long OggSoundFile::cb_tell(void* source) +{ + FileStream *file = static_cast(source); + return file->tell(); +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/oggsoundfile.h b/src/framework/sound/oggsoundfile.h new file mode 100644 index 0000000..cc230fe --- /dev/null +++ b/src/framework/sound/oggsoundfile.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef OGGSOUNDFILE_H +#define OGGSOUNDFILE_H + +#include "soundfile.h" + +#include + +class OggSoundFile : public SoundFile +{ +public: + OggSoundFile(const FileStreamPtr& fileStream); + virtual ~OggSoundFile(); + + bool prepareOgg(); + + int read(void *buffer, int bufferSize); + void reset(); + +private: + static size_t cb_read(void* ptr, size_t size, size_t nmemb, void* source); + static int cb_seek(void* source, ogg_int64_t offset, int whence); + static int cb_close(void* source); + static long cb_tell(void* source); + + OggVorbis_File m_vorbisFile; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundbuffer.cpp b/src/framework/sound/soundbuffer.cpp new file mode 100644 index 0000000..b021987 --- /dev/null +++ b/src/framework/sound/soundbuffer.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "soundbuffer.h" +#include "soundfile.h" + +SoundBuffer::SoundBuffer() +{ + m_bufferId = 0; + alGenBuffers(1, &m_bufferId); + VALIDATE(alGetError() == AL_NO_ERROR); +} + +SoundBuffer::~SoundBuffer() +{ + alDeleteBuffers(1, &m_bufferId); + VALIDATE(alGetError() == AL_NO_ERROR); +} + +bool SoundBuffer::fillBuffer(const SoundFilePtr& soundFile) +{ + ALenum format = soundFile->getSampleFormat(); + if(format == AL_UNDETERMINED) { + g_logger.error(stdext::format("unable to determine sample format for '%s'", soundFile->getName())); + return false; + } + + DataBuffer samples(soundFile->getSize()); + int read = soundFile->read(&samples[0], soundFile->getSize()); + if(read == 0) { + g_logger.error(stdext::format("unable to fill audio buffer data for '%s'", soundFile->getName())); + return false; + } + + return fillBuffer(format, samples, samples.size(), soundFile->getRate()); +} + +bool SoundBuffer::fillBuffer(ALenum sampleFormat, const DataBuffer& data, int size, int rate) +{ + alBufferData(m_bufferId, sampleFormat, &data[0], size, rate); + ALenum err = alGetError(); + if(err != AL_NO_ERROR) { + g_logger.error(stdext::format("unable to fill audio buffer data: %s", alGetString(err))); + return false; + } + return true; +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundbuffer.h b/src/framework/sound/soundbuffer.h new file mode 100644 index 0000000..5f7738d --- /dev/null +++ b/src/framework/sound/soundbuffer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef SOUNDBUFFER_H +#define SOUNDBUFFER_H + +#include "declarations.h" + +#include + +class SoundBuffer : public stdext::shared_object +{ +public: + SoundBuffer(); + ~SoundBuffer(); + + bool fillBuffer(const SoundFilePtr& soundFile); + bool fillBuffer(ALenum sampleFormat, const DataBuffer& data, int size, int rate); + + uint getBufferId() { return m_bufferId; } + +private: + uint m_bufferId; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundchannel.cpp b/src/framework/sound/soundchannel.cpp new file mode 100644 index 0000000..445e53b --- /dev/null +++ b/src/framework/sound/soundchannel.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "soundchannel.h" +#include "streamsoundsource.h" +#include "soundmanager.h" + +SoundSourcePtr SoundChannel::play(const std::string& filename, float fadetime, float gain) +{ + if(!g_sounds.isAudioEnabled() || !m_enabled) + return nullptr; + + if(m_currentSource) + m_currentSource->stop(); + + m_currentSource = g_sounds.play(filename, fadetime, m_gain*gain); + return m_currentSource; +} + +void SoundChannel::stop(float fadetime) +{ + m_queue.clear(); + + if(m_currentSource) { + if(fadetime > 0) + m_currentSource->setFading(StreamSoundSource::FadingOff, fadetime); + else { + m_currentSource->stop(); + m_currentSource = nullptr; + } + } +} + +void SoundChannel::enqueue(const std::string& filename, float fadetime, float gain) +{ + static std::random_device rd; + static std::mt19937 g(rd()); + + if(gain == 0) + gain = 1.0f; + m_queue.push_back(QueueEntry{g_sounds.resolveSoundFile(filename), fadetime, gain}); + std::shuffle(m_queue.begin(), m_queue.end(), g); + //update(); +} + +void SoundChannel::update() +{ + if(m_currentSource && !m_currentSource->isPlaying()) + m_currentSource = nullptr; + + if(!m_currentSource && !m_queue.empty() && g_sounds.isAudioEnabled() && m_enabled) { + QueueEntry entry = m_queue.front(); + m_queue.pop_front(); + m_queue.push_back(entry); + play(entry.filename, entry.fadetime, entry.gain); + } +} + +void SoundChannel::setEnabled(bool enable) +{ + if(m_enabled == enable) + return; + + if(enable) { + m_enabled = true; + update(); + } else { + m_enabled = false; + if(m_currentSource) { + m_currentSource->stop(); + m_currentSource = nullptr; + } + } +} + +void SoundChannel::setGain(float gain) +{ + if(m_currentSource) + m_currentSource->setGain(gain); + m_gain = gain; +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundchannel.h b/src/framework/sound/soundchannel.h new file mode 100644 index 0000000..972ac38 --- /dev/null +++ b/src/framework/sound/soundchannel.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef SHOUNDCHANNEL_H +#define SHOUNDCHANNEL_H + +#include "soundsource.h" + +// @bindclass +class SoundChannel : public LuaObject +{ +public: + SoundChannel(int id) : m_id(id), m_gain(1) { } + + SoundSourcePtr play(const std::string& filename, float fadetime = 0, float gain = 1.0f); + void stop(float fadetime = 0); + void enqueue(const std::string& filename, float fadetime = 0, float gain = 1.0f); + void enable() { setEnabled(true); } + void disable() { setEnabled(false); } + + void setGain(float gain); + float getGain() { return m_gain; } + + void setEnabled(bool enable); + bool isEnabled() { return m_enabled; } + + int getId() { return m_id; } + +protected: + void update(); + friend class SoundManager; + +private: + struct QueueEntry { + std::string filename; + float fadetime; + float gain; + }; + std::deque m_queue; + SoundSourcePtr m_currentSource; + stdext::boolean m_enabled; + int m_id; + float m_gain; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundfile.cpp b/src/framework/sound/soundfile.cpp new file mode 100644 index 0000000..62e9919 --- /dev/null +++ b/src/framework/sound/soundfile.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "soundfile.h" +#include "oggsoundfile.h" +#include + +SoundFile::SoundFile(const FileStreamPtr& fileStream) +{ + m_file = fileStream; +} + +SoundFilePtr SoundFile::loadSoundFile(const std::string& filename) +{ + stdext::timer t; + FileStreamPtr file = g_resources.openFile(filename); + if(!file) + stdext::throw_exception(stdext::format("unable to open %s", filename)); + + char magic[4]; + file->read(magic, 4); + file->seek(0); + + SoundFilePtr soundFile; + if(strncmp(magic, "OggS", 4) == 0) { + OggSoundFilePtr oggSoundFile = OggSoundFilePtr(new OggSoundFile(file)); + if(oggSoundFile->prepareOgg()) + soundFile = oggSoundFile; + } else + stdext::throw_exception(stdext::format("unknown sound file format %s", filename)); + + return soundFile; +} + +ALenum SoundFile::getSampleFormat() +{ + if(m_channels == 2) { + if(m_bps == 16) + return AL_FORMAT_STEREO16; + else if(m_bps == 8) + return AL_FORMAT_STEREO8; + } else if(m_channels == 1) { + if(m_bps == 16) + return AL_FORMAT_MONO16; + else if(m_bps == 8) + return AL_FORMAT_MONO8; + } + return AL_UNDETERMINED; +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundfile.h b/src/framework/sound/soundfile.h new file mode 100644 index 0000000..dc4b165 --- /dev/null +++ b/src/framework/sound/soundfile.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef SOUNDFILE_H +#define SOUNDFILE_H + +#include "declarations.h" +#include + +class SoundFile : public stdext::shared_object +{ +public: + SoundFile(const FileStreamPtr& fileStream); + virtual ~SoundFile() { } + static SoundFilePtr loadSoundFile(const std::string& filename); + + virtual int read(void *buffer, int bufferSize) { return -1; } + virtual void reset() { } + bool eof() { return m_file->eof(); } + + ALenum getSampleFormat(); + + int getChannels() { return m_channels; } + int getRate() { return m_rate; } + int getBps() { return m_bps; } + int getSize() { return m_size; } + std::string getName() { return m_file ? m_file->name() : std::string(); } + +protected: + FileStreamPtr m_file; + int m_channels; + int m_rate; + int m_bps; + int m_size; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundmanager.cpp b/src/framework/sound/soundmanager.cpp new file mode 100644 index 0000000..385b0f0 --- /dev/null +++ b/src/framework/sound/soundmanager.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "soundmanager.h" +#include "soundsource.h" +#include "soundbuffer.h" +#include "soundfile.h" +#include "streamsoundsource.h" +#include "combinedsoundsource.h" + +#include +#include +#include +#include +#include +#include + +SoundManager g_sounds; + +void SoundManager::init() +{ + m_device = alcOpenDevice(NULL); + if(!m_device) { + g_logger.error("unable to open audio device"); + return; + } + + m_context = alcCreateContext(m_device, NULL); + if(!m_context) { + g_logger.error(stdext::format("unable to create audio context: %s", alcGetString(m_device, alcGetError(m_device)))); + return; + } + + if(alcMakeContextCurrent(m_context) != ALC_TRUE) { + g_logger.error(stdext::format("unable to make context current: %s", alcGetString(m_device, alcGetError(m_device)))); + return; + } +} + +void SoundManager::terminate() +{ + ensureContext(); + + for(auto it = m_streamFiles.begin(); it != m_streamFiles.end();++it) { + auto& future = it->second; + future.wait(); + } + m_streamFiles.clear(); + + m_sources.clear(); + m_buffers.clear(); + m_channels.clear(); + + m_audioEnabled = false; + + alcMakeContextCurrent(nullptr); + + if(m_context) { + alcDestroyContext(m_context); + m_context = nullptr; + } + + if(m_device) { + alcCloseDevice(m_device); + m_device = nullptr; + } +} + +void SoundManager::poll() +{ + AutoStat s(STATS_MAIN, "PollSounds"); + + static ticks_t lastUpdate = 0; + ticks_t now = g_clock.millis(); + + if(now - lastUpdate < POLL_DELAY) + return; + + lastUpdate = now; + + ensureContext(); + + for(auto it = m_streamFiles.begin(); it != m_streamFiles.end();) { + StreamSoundSourcePtr source = it->first; + auto& future = it->second; + + if(future.valid()) { + SoundFilePtr sound = future.get(); + if(sound) + source->setSoundFile(sound); + else + source->stop(); + it = m_streamFiles.erase(it); + } else { + ++it; + } + } + + for(auto it = m_sources.begin(); it != m_sources.end();) { + SoundSourcePtr source = *it; + + source->update(); + + if(!source->isPlaying()) + it = m_sources.erase(it); + else + ++it; + } + + for(auto it : m_channels) { + it.second->update(); + } + + if(m_context) { + alcProcessContext(m_context); + } +} + +void SoundManager::setAudioEnabled(bool enable) +{ + if(m_audioEnabled == enable) + return; + + m_audioEnabled = enable; + if(!enable) { + ensureContext(); + for(const SoundSourcePtr& source : m_sources) { + source->stop(); + } + } +} + +void SoundManager::preload(std::string filename) +{ + filename = resolveSoundFile(filename); + + auto it = m_buffers.find(filename); + if(it != m_buffers.end()) + return; + + ensureContext(); + SoundFilePtr soundFile = SoundFile::loadSoundFile(filename); + + // only keep small files + if(!soundFile || soundFile->getSize() > MAX_CACHE_SIZE) + return; + + SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); + if(buffer->fillBuffer(soundFile)) + m_buffers[filename] = buffer; +} + +SoundSourcePtr SoundManager::play(std::string filename, float fadetime, float gain) +{ + if(!m_audioEnabled) + return nullptr; + + ensureContext(); + + filename = resolveSoundFile(filename); + SoundSourcePtr soundSource = createSoundSource(filename); + if(!soundSource) { + g_logger.error(stdext::format("unable to play '%s'", filename)); + return nullptr; + } + + soundSource->setName(filename); + soundSource->setRelative(true); + soundSource->setGain(gain); + + if(fadetime > 0) + soundSource->setFading(StreamSoundSource::FadingOn, fadetime); + + soundSource->play(); + + m_sources.push_back(soundSource); + + return soundSource; +} + +SoundChannelPtr SoundManager::getChannel(int channel) +{ + ensureContext(); + if(!m_channels[channel]) + m_channels[channel] = SoundChannelPtr(new SoundChannel(channel)); + return m_channels[channel]; +} + +void SoundManager::stopAll() +{ + ensureContext(); + for(const SoundSourcePtr& source : m_sources) { + source->stop(); + } + + for(auto it : m_channels) { + it.second->stop(); + } +} + +SoundSourcePtr SoundManager::createSoundSource(const std::string& filename) +{ + SoundSourcePtr source; + + try { + auto it = m_buffers.find(filename); + if(it != m_buffers.end()) { + source = SoundSourcePtr(new SoundSource); + source->setBuffer(it->second); + } else { +#if defined __linux && !defined OPENGL_ES + // due to OpenAL implementation bug, stereo buffers are always downmixed to mono on linux systems + // this is hack to work around the issue + // solution taken from http://opensource.creative.com/pipermail/openal/2007-April/010355.html + CombinedSoundSourcePtr combinedSource(new CombinedSoundSource); + StreamSoundSourcePtr streamSource; + + streamSource = StreamSoundSourcePtr(new StreamSoundSource); + streamSource->downMix(StreamSoundSource::DownMixLeft); + streamSource->setRelative(true); + streamSource->setPosition(Point(-128, 0)); + combinedSource->addSource(streamSource); + m_streamFiles[streamSource] = g_asyncDispatcher.schedule([=]() -> SoundFilePtr { + stdext::timer a; + try { + return SoundFile::loadSoundFile(filename); + } catch(std::exception& e) { + g_logger.error(e.what()); + return nullptr; + } + }); + + streamSource = StreamSoundSourcePtr(new StreamSoundSource); + streamSource->downMix(StreamSoundSource::DownMixRight); + streamSource->setRelative(true); + streamSource->setPosition(Point(128,0)); + combinedSource->addSource(streamSource); + m_streamFiles[streamSource] = g_asyncDispatcher.schedule([=]() -> SoundFilePtr { + try { + return SoundFile::loadSoundFile(filename); + } catch(std::exception& e) { + g_logger.error(e.what()); + return nullptr; + } + }); + + source = combinedSource; +#else + StreamSoundSourcePtr streamSource(new StreamSoundSource); + m_streamFiles[streamSource] = g_asyncDispatcher.schedule([=]() -> SoundFilePtr { + try { + return SoundFile::loadSoundFile(filename); + } catch(std::exception& e) { + g_logger.error(e.what()); + return nullptr; + } + }); + source = streamSource; +#endif + } + } catch(std::exception& e) { + g_logger.error(stdext::format("failed to load sound source: '%s'", e.what())); + return nullptr; + } + + return source; +} + +std::string SoundManager::resolveSoundFile(std::string file) +{ + file = g_resources.guessFilePath(file, "ogg"); + file = g_resources.resolvePath(file); + return file; +} + +void SoundManager::ensureContext() +{ + if(m_context) + alcMakeContextCurrent(m_context); +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundmanager.h b/src/framework/sound/soundmanager.h new file mode 100644 index 0000000..cbace85 --- /dev/null +++ b/src/framework/sound/soundmanager.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef SOUNDMANAGER_H +#define SOUNDMANAGER_H + +#include "declarations.h" +#include "soundchannel.h" + +//@bindsingleton g_sounds +class SoundManager +{ + enum { + MAX_CACHE_SIZE = 100000, + POLL_DELAY = 100 + }; +public: + void init(); + void terminate(); + void poll(); + + void setAudioEnabled(bool enable); + bool isAudioEnabled() { return m_device && m_context && m_audioEnabled ; } + void enableAudio() { setAudioEnabled(true); } + void disableAudio() { setAudioEnabled(true); } + void stopAll(); + + void preload(std::string filename); + SoundSourcePtr play(std::string filename, float fadetime = 0, float gain = 0); + SoundChannelPtr getChannel(int channel); + + std::string resolveSoundFile(std::string file); + void ensureContext(); + +private: + SoundSourcePtr createSoundSource(const std::string& filename); + + ALCdevice *m_device; + ALCcontext *m_context; + + std::map> m_streamFiles; + std::unordered_map m_buffers; + std::vector m_sources; + stdext::boolean m_audioEnabled; + std::unordered_map m_channels; +}; + +extern SoundManager g_sounds; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundsource.cpp b/src/framework/sound/soundsource.cpp new file mode 100644 index 0000000..b4cb249 --- /dev/null +++ b/src/framework/sound/soundsource.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "soundsource.h" +#include "soundbuffer.h" + +#include + +SoundSource::SoundSource() +{ + m_sourceId = 0; + m_channel = 0; + m_fadeState = NoFading; + m_fadeTime = 0; + m_fadeStartTime = 0; + m_fadeGain = 0; + m_gain = 1.0f; + + alGenSources(1, &m_sourceId); + VALIDATE(alGetError() == AL_NO_ERROR); + setReferenceDistance(128); +} + +SoundSource::~SoundSource() +{ + if(m_sourceId != 0) { + stop(); + alDeleteSources(1, &m_sourceId); + VALIDATE(alGetError() == AL_NO_ERROR); + } +} + +void SoundSource::play() +{ + alSourcePlay(m_sourceId); + VALIDATE(alGetError() == AL_NO_ERROR); +} + +void SoundSource::stop() +{ + alSourceStop(m_sourceId); + VALIDATE(alGetError() == AL_NO_ERROR); + if(m_buffer) { + alSourcei(m_sourceId, AL_BUFFER, AL_NONE); + VALIDATE(alGetError() == AL_NO_ERROR); + m_buffer = nullptr; + } +} + +bool SoundSource::isBuffering() +{ + int state = AL_PLAYING; + alGetSourcei(m_sourceId, AL_SOURCE_STATE, &state); + return state != AL_STOPPED; +} + +void SoundSource::setBuffer(const SoundBufferPtr& buffer) +{ + alSourcei(m_sourceId, AL_BUFFER, buffer->getBufferId()); + VALIDATE(alGetError() == AL_NO_ERROR); + m_buffer = buffer; +} + +void SoundSource::setLooping(bool looping) +{ + alSourcei(m_sourceId, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); +} + +void SoundSource::setRelative(bool relative) +{ + alSourcei(m_sourceId, AL_SOURCE_RELATIVE, relative ? AL_TRUE : AL_FALSE); +} + +void SoundSource::setReferenceDistance(float distance) +{ + alSourcef(m_sourceId, AL_REFERENCE_DISTANCE, distance); +} + +void SoundSource::setGain(float gain) +{ + alSourcef(m_sourceId, AL_GAIN, gain); + m_gain = gain; +} + +void SoundSource::setPitch(float pitch) +{ + alSourcef(m_sourceId, AL_PITCH, pitch); +} + +void SoundSource::setPosition(const Point& pos) +{ + alSource3f(m_sourceId, AL_POSITION, pos.x, pos.y, 0); +} + +void SoundSource::setVelocity(const Point& velocity) +{ + alSource3f(m_sourceId, AL_VELOCITY, velocity.x, velocity.y, 0); +} + +void SoundSource::setFading(FadeState state, float fadeTime) +{ + float now = stdext::millis() / 1000.0f; + if(m_fadeState != NoFading) { + float elapsed = now - m_fadeStartTime; + float add; + if(m_fadeState == FadingOn) + add = -(1-(elapsed / m_fadeTime))*fadeTime; + else + add = -(elapsed / m_fadeTime)*fadeTime; + m_fadeStartTime = now + add; + } else + m_fadeStartTime = now; + + m_fadeState = state; + m_fadeTime = fadeTime; + m_fadeGain = m_gain; + + if(m_fadeState == FadingOn) + setGain(0.0); +} + +void SoundSource::update() +{ + float now = stdext::millis() / 1000.0f; + if(m_fadeState == FadingOn) { + float elapsed = now - m_fadeStartTime; + if(elapsed >= m_fadeTime) { + m_fadeState = NoFading; + } else { + setGain((elapsed / m_fadeTime) * m_fadeGain); + } + } else if(m_fadeState == FadingOff) { + float elapsed = now - m_fadeStartTime; + if(elapsed >= m_fadeTime) { + setGain(m_fadeGain); + stop(); + m_fadeState = NoFading; + } else { + setGain(((m_fadeTime - elapsed) / m_fadeTime) * m_fadeGain); + } + } +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/soundsource.h b/src/framework/sound/soundsource.h new file mode 100644 index 0000000..4124239 --- /dev/null +++ b/src/framework/sound/soundsource.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef SOUNDSOURCE_H +#define SOUNDSOURCE_H + +#include "declarations.h" +#include "soundbuffer.h" +#include + +class SoundSource : public LuaObject +{ +protected: + SoundSource(uint sourceId) : m_sourceId(sourceId) { } + +public: + enum FadeState { NoFading, FadingOn, FadingOff }; + + SoundSource(); + virtual ~SoundSource(); + + virtual void play(); + virtual void stop(); + + virtual bool isBuffering(); + virtual bool isPlaying() { return isBuffering(); } + + void setName(const std::string& name) { m_name = name; } + virtual void setLooping(bool looping); + virtual void setRelative(bool relative); + virtual void setReferenceDistance(float distance); + virtual void setGain(float gain); + virtual void setPitch(float pitch); + virtual void setPosition(const Point& pos); + virtual void setVelocity(const Point& velocity); + virtual void setFading(FadeState state, float fadetime); + + std::string getName() { return m_name; } + uchar getChannel() { return m_channel; } + float getGain() { return m_gain; } + +protected: + void setBuffer(const SoundBufferPtr& buffer); + void setChannel(uchar channel) { m_channel = channel; } + + virtual void update(); + friend class SoundManager; + friend class CombinedSoundSource; + + uint m_sourceId; + uchar m_channel; + std::string m_name; + SoundBufferPtr m_buffer; + FadeState m_fadeState; + float m_fadeStartTime; + float m_fadeTime; + float m_fadeGain; + float m_gain; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/framework/sound/streamsoundsource.cpp b/src/framework/sound/streamsoundsource.cpp new file mode 100644 index 0000000..08061cc --- /dev/null +++ b/src/framework/sound/streamsoundsource.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#include "streamsoundsource.h" +#include "soundbuffer.h" +#include "soundfile.h" + +#include +#include + +StreamSoundSource::StreamSoundSource() +{ + for(auto& buffer : m_buffers) + buffer = SoundBufferPtr(new SoundBuffer); + m_downMix = NoDownMix; +} + +StreamSoundSource::~StreamSoundSource() +{ + stop(); +} + +void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile) +{ + m_soundFile = soundFile; + if(m_waitingFile) { + m_waitingFile = false; + play(); + } +} + +void StreamSoundSource::play() +{ + m_playing = true; + + if(!m_soundFile) { + m_waitingFile = true; + return; + } + + if(m_eof) { + m_soundFile->reset(); + m_eof = false; + } + + queueBuffers(); + + SoundSource::play(); +} + +void StreamSoundSource::stop() +{ + m_playing = false; + + if(m_waitingFile) + return; + + SoundSource::stop(); + unqueueBuffers(); +} + +void StreamSoundSource::queueBuffers() +{ + int queued; + alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued); + for(int i = 0; i < STREAM_FRAGMENTS - queued; ++i) { + if(!fillBufferAndQueue(m_buffers[i]->getBufferId())) + break; + } +} + +void StreamSoundSource::unqueueBuffers() +{ + int queued; + alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued); + for(int i = 0; i < queued; ++i) { + uint buffer; + alSourceUnqueueBuffers(m_sourceId, 1, &buffer); + } +} + +void StreamSoundSource::update() +{ + if(m_waitingFile) + return; + + SoundSource::update(); + + int processed = 0; + alGetSourcei(m_sourceId, AL_BUFFERS_PROCESSED, &processed); + for(int i = 0; i < processed; ++i) { + uint buffer; + alSourceUnqueueBuffers(m_sourceId, 1, &buffer); + //SoundManager::check_al_error("Couldn't unqueue audio buffer: "); + + if(!fillBufferAndQueue(buffer)) + break; + } + + if(!isBuffering() && m_playing) { + if(!m_looping && m_eof) { + stop(); + } else if(processed == 0) { + g_logger.traceError("audio buffer underrun"); + play(); + } else if(m_looping) { + play(); + } + } +} + +bool StreamSoundSource::fillBufferAndQueue(uint buffer) +{ + if(m_waitingFile) + return false; + + // fill buffer + static DataBuffer bufferData(2*STREAM_FRAGMENT_SIZE); + ALenum format = m_soundFile->getSampleFormat(); + + int maxRead = STREAM_FRAGMENT_SIZE; + if(m_downMix != NoDownMix) + maxRead *= 2; + + int bytesRead = 0; + do { + bytesRead += m_soundFile->read(&bufferData[bytesRead], maxRead - bytesRead); + + // end of sound file + if(bytesRead < maxRead) { + if(m_looping) + m_soundFile->reset(); + else { + m_eof = true; + break; + } + } + } while(bytesRead < maxRead); + + if(bytesRead > 0) { + if(m_downMix != NoDownMix) { + if(format == AL_FORMAT_STEREO16) { + VALIDATE(bytesRead % 2 == 0); + bytesRead /= 2; + uint16_t *data = (uint16_t*)bufferData.data(); + for(int i=0;igetRate()); + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + g_logger.error(stdext::format("unable to refill audio buffer for '%s': %s", m_soundFile->getName(), alGetString(err))); + + alSourceQueueBuffers(m_sourceId, 1, &buffer); + err = alGetError(); + if(err != AL_NO_ERROR) + g_logger.error(stdext::format("unable to queue audio buffer for '%s': %s", m_soundFile->getName(), alGetString(err))); + } + + // return false if there aren't more buffers to fill + return (bytesRead >= STREAM_FRAGMENT_SIZE && !m_eof); +} + +void StreamSoundSource::downMix(StreamSoundSource::DownMix downMix) +{ + m_downMix = downMix; +} + +#endif \ No newline at end of file diff --git a/src/framework/sound/streamsoundsource.h b/src/framework/sound/streamsoundsource.h new file mode 100644 index 0000000..7aa2c1f --- /dev/null +++ b/src/framework/sound/streamsoundsource.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FW_SOUND + +#ifndef STREAMSOUNDSOURCE_H +#define STREAMSOUNDSOURCE_H + +#include "soundsource.h" + +class StreamSoundSource : public SoundSource +{ + enum { + STREAM_BUFFER_SIZE = 1024 * 400, + STREAM_FRAGMENTS = 4, + STREAM_FRAGMENT_SIZE = STREAM_BUFFER_SIZE / STREAM_FRAGMENTS + }; + +public: + enum DownMix { NoDownMix, DownMixLeft, DownMixRight }; + + StreamSoundSource(); + virtual ~StreamSoundSource(); + + void play(); + void stop(); + + bool isPlaying() { return m_playing; } + + void setSoundFile(const SoundFilePtr& soundFile); + + void downMix(DownMix downMix); + + void update(); + +private: + void queueBuffers(); + void unqueueBuffers(); + bool fillBufferAndQueue(uint buffer); + + SoundFilePtr m_soundFile; + std::array m_buffers; + DownMix m_downMix; + stdext::boolean m_looping; + stdext::boolean m_playing; + stdext::boolean m_eof; + stdext::boolean m_waitingFile; +}; + +#endif + +#endif diff --git a/src/framework/stdext/any.h b/src/framework/stdext/any.h new file mode 100644 index 0000000..11a6a44 --- /dev/null +++ b/src/framework/stdext/any.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_ANY_H +#define STDEXT_ANY_H + +#include +#include + +namespace stdext { + +class any { +public: + struct placeholder { + virtual ~placeholder() { } + virtual const std::type_info& type() const = 0; + virtual placeholder* clone() const = 0; + }; + + template + struct holder : public placeholder { + holder(const T& value) : held(value) { } + const std::type_info& type() const { return typeid(T); } + placeholder* clone() const { return new holder(held); } + T held; + private: + holder& operator=(const holder &); + }; + + placeholder* content; + + any() : content(nullptr) { } + any(const any& other) : content(other.content ? other.content->clone() : nullptr) { } + template any(const T& value) : content(new holder(value)) { } + ~any() { if(content) delete content; } + + any& swap(any& rhs) { std::swap(content, rhs.content); return *this; } + + template any& operator=(const T& rhs) { any(rhs).swap(*this); return *this; } + any& operator=(any rhs) { rhs.swap(*this); return *this; } + + bool empty() const { return !content; } + template const T& cast() const; + const std::type_info & type() const { return content ? content->type() : typeid(void); } +}; + +template +const T& any_cast(const any& operand) { + VALIDATE(operand.type() == typeid(T)); + return static_cast*>(operand.content)->held; +} + +template const T& any::cast() const { return any_cast(*this); } + +} + +#endif diff --git a/src/framework/stdext/boolean.h b/src/framework/stdext/boolean.h new file mode 100644 index 0000000..5f0f981 --- /dev/null +++ b/src/framework/stdext/boolean.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BOOLEAN_H +#define BOOLEAN_H + +namespace stdext { + +template +struct boolean { + boolean() : v(def) { } + operator bool &() { return v; } + operator bool const &() const { return v; } + bool& operator=(const bool& o) { v = o; return v; } +private: + bool v; +}; + +} + +#endif diff --git a/src/framework/stdext/cast.h b/src/framework/stdext/cast.h new file mode 100644 index 0000000..918d033 --- /dev/null +++ b/src/framework/stdext/cast.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_CAST_H +#define STDEXT_CAST_H + +#include "exception.h" +#include "demangle.h" + +#include +#include +#include + +namespace stdext { + +// cast a type to another type +template +bool cast(const T& in, R& out) { + std::stringstream ss; + ss << in; + ss >> out; + return !!ss && ss.eof(); +} + +// cast a type to string +template +bool cast(const T& in, std::string& out) { + std::stringstream ss; + ss << in; + out = ss.str(); + return true; +} + +// cast string to string +template<> +inline bool cast(const std::string& in, std::string& out) { + out = in; + return true; +} + +// special cast from string to boolean +template<> +inline bool cast(const std::string& in, bool& b) { + if(in == "true") + b = true; + else if(in == "false") + b = false; + else + return false; + return true; +} + +// special cast from string to char +template<> +inline bool cast(const std::string& in, char& c) { + if(in.length() != 1) + return false; + c = in[0]; + return true; +} + +// special cast from string to long +template<> +inline bool cast(const std::string& in, long& l) { + if(in.find_first_not_of("-0123456789") != std::string::npos) + return false; + std::size_t t = in.find_last_of('-'); + if(t != std::string::npos && t != 0) + return false; + l = atol(in.c_str()); + return true; +} + +// special cast from string to int +template<> +inline bool cast(const std::string& in, int& i) { + long l; + if(cast(in, l)) { + i=l; + return true; + } + return false; +} + +// special cast from string to double +template<> +inline bool cast(const std::string& in, double& d) { + if(in.find_first_not_of("-0123456789.") != std::string::npos) + return false; + std::size_t t = in.find_last_of('-'); + if(t != std::string::npos && t != 0) + return false; + t = in.find_first_of('.'); + if(t != std::string::npos && (t == 0 || t == in.length()-1 || in.find_first_of('.', t+1) != std::string::npos)) + return false; + d = atof(in.c_str()); + return true; +} + +// special cast from string to float +template<> +inline bool cast(const std::string& in, float& f) { + double d; + if(cast(in, d)) { + f=(float)d; + return true; + } + return false; +} + +// special cast from boolean to string +template<> +inline bool cast(const bool& in, std::string& out) { + out = (in ? "true" : "false"); + return true; +} + +// used by safe_cast +class cast_exception : public exception { +public: + virtual ~cast_exception() throw() { } + template + void update_what() { + std::stringstream ss; + ss << "failed to cast value of type '" << demangle_type() << "' to type '" << demangle_type() << "'"; + m_what = ss.str(); + } + virtual const char* what() const throw() { return m_what.c_str(); } +private: + std::string m_what; +}; + +// cast a type to another type, any error throws a cast_exception +template +R safe_cast(const T& t) { + R r; + if(!cast(t, r)) { + cast_exception e; + e.update_what(); + throw e; + } + return r; +} + +// cast a type to another type, cast errors are ignored +template +R unsafe_cast(const T& t, R def = R()) { + try { + return safe_cast(t); + } catch(cast_exception& e) { + std::cerr << "CAST ERROR: " << e.what() << std::endl; + return def; + } +} + +} + +#endif diff --git a/src/framework/stdext/compiler.h b/src/framework/stdext/compiler.h new file mode 100644 index 0000000..3e27dc1 --- /dev/null +++ b/src/framework/stdext/compiler.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_COMPILER_H +#define STDEXT_COMPILER_H + +#ifdef WIN32 +#include +#endif + +#ifdef __clang__ + // clang is supported + #define BUILD_COMPILER "clang " __VERSION__ +#elif defined(__GNUC__) + #if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #error "Sorry, you need gcc 4.6 or greater to compile." + #endif + #define BUILD_COMPILER "gcc " __VERSION__ +#elif defined(_MSC_VER) + #if _MSC_VER < 1800 + #error "You need Visual Studio 2013 or greater to compile." + #endif + #pragma warning(disable:4244) // conversion from 'A' to 'B', possible loss of data + #pragma warning(disable:4267) // '?' : conversion from 'A' to 'B', possible loss of data + #pragma warning(disable:4305) // 'initializing' : truncation from 'A' to 'B' + #pragma warning(disable:4146) // unary minus operator applied to unsigned type, result still unsigned + #pragma warning(disable:4800) // 'A' : forcing value to bool 'true' or 'false' (performance warning) + + #if _MSC_VER == 1912 || _MSC_VER == 1911 || _MSC_VER == 1910 + #define BUILD_COMPILER "Visual Studio 2017" + #elif _MSC_VER == 1900 + #define BUILD_COMPILER "Visual Studio 2015" + #elif _MSC_VER == 1800 + #define BUILD_COMPILER "Visual Studio 2013" + #else + #define BUILD_COMPILER "Visual Studio" + #endif + + #define __PRETTY_FUNCTION__ __FUNCDNAME__ +#else + #error "Compiler not supported." +#endif + +/// Branch Prediction. See the GCC Manual for more information. +/// NB: These are used when speed is need most; do not use in normal +/// code, they may slow down stuff. +#if defined(__clang__) || defined(__GNUC__) +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#if !defined(_MSC_VER) && !defined(__GXX_EXPERIMENTAL_CXX0X__) +#error "C++0x is required to compile this application. Try updating your compiler." +#endif + +#endif diff --git a/src/framework/stdext/demangle.cpp b/src/framework/stdext/demangle.cpp new file mode 100644 index 0000000..59def57 --- /dev/null +++ b/src/framework/stdext/demangle.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "demangle.h" + +#ifdef _MSC_VER + +#include +#include + +#pragma warning (push) +#pragma warning (disable:4091) // warning C4091: 'typedef ': ignored on left of '' when no variable is declared +#include +#pragma warning (pop) + +#else + +#include +#include +#include + +#endif + +namespace stdext { + +const char* demangle_name(const char* name) +{ +#ifdef _MSC_VER + static char buffer[1024]; + UnDecorateSymbolName(name, buffer, sizeof(buffer), UNDNAME_COMPLETE); + return buffer; +#else + size_t len; + int status; + static char buffer[1024]; + char* demangled = abi::__cxa_demangle(name, 0, &len, &status); + if(demangled) { + strcpy(buffer, demangled); + free(demangled); + } + return buffer; +#endif +} + +} diff --git a/src/framework/stdext/demangle.h b/src/framework/stdext/demangle.h new file mode 100644 index 0000000..3c4ed60 --- /dev/null +++ b/src/framework/stdext/demangle.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_DEMANGLE_H +#define STDEXT_DEMANGLE_H + +#include +#include + +namespace stdext { + +/// Demangle names for GNU g++ compiler +const char* demangle_name(const char* name); + +/// Returns the name of a class +template std::string demangle_class() { +#ifdef _MSC_VER + return demangle_name(typeid(T).name()) + 6; +#else + return demangle_name(typeid(T).name()); +#endif +} + +/// Returns the name of a type +template std::string demangle_type() { return demangle_name(typeid(T).name()); } + +} + +#endif diff --git a/src/framework/stdext/dumper.h b/src/framework/stdext/dumper.h new file mode 100644 index 0000000..4dfc7a9 --- /dev/null +++ b/src/framework/stdext/dumper.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_DUMPER_H +#define STDEXT_DUMPER_H + +#include + +namespace stdext { + +static struct { + struct dumper_dummy { + ~dumper_dummy() { std::cout << std::endl; } + template dumper_dummy& operator<<(const T& v) { std::cout << v << " "; return *this; } + }; + template dumper_dummy operator<<(const T& v) const { dumper_dummy d; d << v; return d; } +} dump; + +} + +using stdext::dump; + +#endif diff --git a/src/framework/stdext/dynamic_storage.h b/src/framework/stdext/dynamic_storage.h new file mode 100644 index 0000000..8e8bff1 --- /dev/null +++ b/src/framework/stdext/dynamic_storage.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_DYNAMICSTORAGE_H +#define STDEXT_DYNAMICSTORAGE_H + +#include "types.h" +#include "any.h" +#include + +namespace stdext { + +template +class dynamic_storage { +public: + template void set(const Key& k, const T& value) { + if(m_data.size() <= k) + m_data.resize(k+1); + m_data[k] = value; + } + bool remove(const Key& k) { + if(m_data.size() < k) + return false; + if(m_data[k].empty()) + return false; + m_data[k] = any(); + return true; + } + template T get(const Key& k) const { return has(k) ? any_cast(m_data[k]) : T(); } + bool has(const Key& k) const { return k < m_data.size() && !m_data[k].empty(); } + + std::size_t size() const { + std::size_t count = 0; + for(std::size_t i=0;i m_data; +}; + +} + +#endif diff --git a/src/framework/stdext/exception.h b/src/framework/stdext/exception.h new file mode 100644 index 0000000..3cbd022 --- /dev/null +++ b/src/framework/stdext/exception.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_EXCEPTION_H +#define STDEXT_EXCEPTION_H + +#include +#include + +namespace stdext { + +class exception : public std::exception +{ +public: + exception() { } + exception(const std::string& what) : m_what(what) { } + virtual ~exception() throw() { }; + virtual const char* what() const throw() { return m_what.c_str(); } +protected: + std::string m_what; +}; + +/// Throws a generic exception +inline void throw_exception(const std::string& what) { throw exception(what); } + +} + +#endif diff --git a/src/framework/stdext/fastrand.h b/src/framework/stdext/fastrand.h new file mode 100644 index 0000000..200f119 --- /dev/null +++ b/src/framework/stdext/fastrand.h @@ -0,0 +1,12 @@ +#pragma once + +namespace stdext { + +static int fastrand() +{ + static int g_seed = (214013 + 2531011); + g_seed = (214013 * g_seed + 2531011); + return (g_seed >> 16) & 0x7FFF; +}; + +} \ No newline at end of file diff --git a/src/framework/stdext/format.h b/src/framework/stdext/format.h new file mode 100644 index 0000000..2625c81 --- /dev/null +++ b/src/framework/stdext/format.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_FORMAT_H +#define STDEXT_FORMAT_H + +#include "traits.h" + +#include +#include +#include +#include +#include +#include + +namespace stdext { + +template void print_ostream(std::ostringstream& stream, const T& last) { stream << last; } +template +void print_ostream(std::ostringstream& stream, const T& first, const Args&... rest) { stream << "\t" << first; print_ostream(stream, rest...); } +template + +// Utility for printing variables just like lua +void print(const T&... args) { std::ostringstream buf; print_ostream(buf, args...); std::cout << buf.str() << std::endl; } + +template +typename std::enable_if::value || + std::is_pointer::value || + std::is_floating_point::value || + std::is_enum::value, T>::type sprintf_cast(const T& t) { return t; } +inline const char *sprintf_cast(const char *s) { return s; } +inline const char *sprintf_cast(const std::string& s) { return s.c_str(); } + +template struct expand_snprintf { + template static int call(char *s, size_t maxlen, const char *format, const Tuple& tuple, const Args&... args) { + return expand_snprintf::call(s, maxlen, format, tuple, sprintf_cast(std::get(tuple)), args...); }}; +template<> struct expand_snprintf<0> { + template static int call(char *s, size_t maxlen, const char *format, const Tuple& tuple, const Args&... args) { +#ifdef _MSC_VER + return _snprintf(s, maxlen, format, args...); +#else + return snprintf(s, maxlen, format, args...); +#endif + } +}; + +// Improved snprintf that accepts std::string and other types +template +int snprintf(char *s, size_t maxlen, const char *format, const Args&... args) { + std::tuple::type...> tuple(args...); + return expand_snprintf::value>::call(s, maxlen, format, tuple); +} + +template +inline int snprintf(char *s, size_t maxlen, const char *format) { + std::strncpy(s, format, maxlen); + s[maxlen-1] = 0; + return strlen(s); +} + +template +inline std::string format() { return std::string(); } + +template +inline std::string format(const std::string& format) { return format; } + +// Format strings with the sprintf style, accepting std::string and string convertible types for %s +template +std::string format(const std::string& format, const Args&... args) { + int n = snprintf(NULL, 0, format.c_str(), args...); + VALIDATE(n != -1); + std::string buffer(n + 1, '\0'); + n = snprintf(&buffer[0], buffer.size(), format.c_str(), args...); + VALIDATE(n != -1); + buffer.resize(n); + return buffer; +} + +} + +#endif diff --git a/src/framework/stdext/math.cpp b/src/framework/stdext/math.cpp new file mode 100644 index 0000000..ffc877c --- /dev/null +++ b/src/framework/stdext/math.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "math.h" +#include + +#ifdef _MSC_VER + #pragma warning(disable:4267) // '?' : conversion from 'A' to 'B', possible loss of data +#endif + +namespace stdext { + +uint32_t adler32(const uint8_t *buffer, size_t size) { + size_t a = 1, b = 0, tlen; + while(size > 0) { + tlen = size > 5552 ? 5552 : size; + size -= tlen; + do { + a += *buffer++; + b += a; + } while (--tlen); + + a %= 65521; + b %= 65521; + } + return (b << 16) | a; +} + +long random_range(long min, long max) +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution dis(0, 2147483647); + return min + (dis(gen) % (max - min + 1)); +} + +float random_range(float min, float max) +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution dis(0.0, 1.0); + return min + (max - min)*dis(gen); +} + +double round(double r) +{ + return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5); +} + +} \ No newline at end of file diff --git a/src/framework/stdext/math.h b/src/framework/stdext/math.h new file mode 100644 index 0000000..7744eb8 --- /dev/null +++ b/src/framework/stdext/math.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_MATH_H +#define STDEXT_MATH_H + +#include +#include "types.h" + +namespace stdext { + +inline bool is_power_of_two(size_t v) { return ((v != 0) && !(v & (v - 1))); } +inline size_t to_power_of_two(size_t v) { if(v == 0) return 0; size_t r = 1; while(r < v && r != 0xffffffff) r <<= 1; return r; } + +inline uint16_t readULE16(const uchar *addr) { return (uint16_t)addr[1] << 8 | addr[0]; } +inline uint32_t readULE32(const uchar *addr) { return (uint32_t)readULE16(addr + 2) << 16 | readULE16(addr); } +inline uint64_t readULE64(const uchar *addr) { return (uint64_t)readULE32(addr + 4) << 32 | readULE32(addr); } + +inline void writeULE16(uchar *addr, uint16_t value) { addr[1] = value >> 8; addr[0] = (uint8_t)value; } +inline void writeULE32(uchar *addr, uint32_t value) { writeULE16(addr + 2, value >> 16); writeULE16(addr, (uint16_t)value); } +inline void writeULE64(uchar *addr, uint64_t value) { writeULE32(addr + 4, value >> 32); writeULE32(addr, (uint32_t)value); } + +inline int16_t readSLE16(const uchar *addr) { return (int16_t)addr[1] << 8 | addr[0]; } +inline int32_t readSLE32(const uchar *addr) { return (int32_t)readSLE16(addr + 2) << 16 | readSLE16(addr); } +inline int64_t readSLE64(const uchar *addr) { return (int64_t)readSLE32(addr + 4) << 32 | readSLE32(addr); } + +inline void writeSLE16(uchar *addr, int16_t value) { addr[1] = value >> 8; addr[0] = (int8_t)value; } +inline void writeSLE32(uchar *addr, int32_t value) { writeSLE16(addr + 2, value >> 16); writeSLE16(addr, (int16_t)value); } +inline void writeSLE64(uchar *addr, int64_t value) { writeSLE32(addr + 4, value >> 32); writeSLE32(addr, (int32_t)value); } + +uint32_t adler32(const uint8_t *buffer, size_t size); + +long random_range(long min, long max); +float random_range(float min, float max); + +double round(double r); + +template +T clamp(T x, T min, T max) { return std::max(min, std::min(x, max)); } + +} + +#endif diff --git a/src/framework/stdext/net.cpp b/src/framework/stdext/net.cpp new file mode 100644 index 0000000..7cb7c5b --- /dev/null +++ b/src/framework/stdext/net.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "net.h" +#include +#include +#include + +namespace stdext { + +std::string ip_to_string(uint32 ip) +{ + ip = boost::asio::detail::socket_ops::network_to_host_long(ip); + boost::asio::ip::address_v4 address_v4 = boost::asio::ip::address_v4(ip); + return address_v4.to_string(); +} + +uint32 string_to_ip(const std::string& string) +{ + boost::asio::ip::address_v4 address_v4 = boost::asio::ip::address_v4::from_string(string); + return boost::asio::detail::socket_ops::host_to_network_long(address_v4.to_ulong()); +} + +std::vector listSubnetAddresses(uint32 address, uint8 mask) +{ + std::vector list; + if(mask < 32) { + uint32 bitmask = (0xFFFFFFFF >> mask); + for(uint32 i = 0; i <= bitmask; i++) { + uint32 ip = boost::asio::detail::socket_ops::host_to_network_long((boost::asio::detail::socket_ops::network_to_host_long(address) & (~bitmask)) | i); + if((ip >> 24) != 0 && (ip >> 24) != 0xFF) + list.push_back(ip); + } + } + else + list.push_back(address); + + return list; +} + +} diff --git a/src/framework/stdext/net.h b/src/framework/stdext/net.h new file mode 100644 index 0000000..2993f2b --- /dev/null +++ b/src/framework/stdext/net.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_NET_H +#define STDEXT_NET_H + +#include "types.h" +#include +#include + +namespace stdext { + std::string ip_to_string(uint32 ip); + uint32 string_to_ip(const std::string& string); + std::vector listSubnetAddresses(uint32 address, uint8 mask); +} + +#endif diff --git a/src/framework/stdext/packed_any.h b/src/framework/stdext/packed_any.h new file mode 100644 index 0000000..28802a8 --- /dev/null +++ b/src/framework/stdext/packed_any.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_PACKEDANY_H +#define STDEXT_PACKEDANY_H + +#include +#include +#include +#include + +namespace stdext { + +// disable memory alignment +#pragma pack(push,1) + +template +struct can_pack_in_any : std::integral_constant::value)> {}; + +// improved to use less memory +class packed_any { +public: + struct placeholder { + virtual ~placeholder() { } + virtual const std::type_info& type() const = 0; + virtual placeholder* clone() const = 0; + }; + + template + struct holder : public placeholder { + holder(const T& value) : held(value) { } + const std::type_info& type() const { return typeid(T); } + placeholder* clone() const { return new holder(held); } + T held; + private: + holder& operator=(const holder &); + }; + + placeholder* content; + bool scalar; + + packed_any() : + content(nullptr), scalar(false) { } + packed_any(const packed_any& other) : + content(!other.scalar && other.content ? other.content->clone() : other.content), + scalar(other.scalar) { } + template + packed_any(const T& value, typename std::enable_if<(can_pack_in_any::value)>::type* = nullptr) : + content(reinterpret_cast(static_cast(value))), scalar(true) { } + template + packed_any(const T& value, typename std::enable_if::value)>::type* = nullptr) : + content(new holder(value)), scalar(false) { } + ~packed_any() + { if(!scalar && content) delete content; } + + packed_any& swap(packed_any& rhs) { std::swap(content, rhs.content); std::swap(scalar, rhs.scalar); return *this; } + + template packed_any& operator=(const T& rhs) { packed_any(rhs).swap(*this); return *this; } + packed_any& operator=(packed_any rhs) { rhs.swap(*this); return *this; } + + bool empty() const { return !scalar && !content; } + template T cast() const; + const std::type_info& type() const { + if(!scalar) + return content ? content->type() : typeid(void); + else + return typeid(std::size_t); + } +}; + +template +typename std::enable_if::value, T>::type +packed_any_cast(const packed_any& operand) { + VALIDATE(operand.scalar); + union { + T v; + packed_any::placeholder* content; + }; + content = operand.content; + return v; +} + +template +typename std::enable_if::value, T>::type +packed_any_cast(const packed_any& operand) { + VALIDATE(operand.type() == typeid(T)); + return static_cast*>(operand.content)->held; +} + +template T packed_any::cast() const { return packed_any_cast(*this); } + +// restore memory alignment +#pragma pack(pop) + +} + +#endif diff --git a/src/framework/stdext/packed_storage.h b/src/framework/stdext/packed_storage.h new file mode 100644 index 0000000..38b26d2 --- /dev/null +++ b/src/framework/stdext/packed_storage.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_PACKEDSTORAGE_H +#define STDEXT_PACKEDSTORAGE_H + +#include "types.h" +#include "packed_any.h" + +namespace stdext { + +// disable memory alignment +#pragma pack(push,1) + +// this class was designed to use less memory as possible +template +class packed_storage { + struct value_pair { + Key id; + packed_any value; + }; + +public: + packed_storage() : m_values(nullptr), m_size(0) { } + ~packed_storage() { if(m_values) delete[] m_values; } + + template + void set(Key id, const T& value) { + for(SizeType i=0;i 0) { + std::copy(m_values, m_values + m_size, tmp); + delete[] m_values; + } + m_values = tmp; + m_values[m_size++] = { id, packed_any(value) }; + } + + bool remove(Key id) { + auto begin = m_values; + auto end = m_values + m_size; + auto it = std::find_if(begin, end, [=](const value_pair& pair) -> bool { return pair.id == id; } ); + if(it == end) + return false; + int pos = it - begin; + auto tmp = new value_pair[m_size-1]; + std::copy(begin, begin + pos, tmp); + std::copy(begin + pos + 1, end, tmp + pos); + delete[] m_values; + m_values = tmp; + m_size--; + return true; + } + + template + T get(Key id) const { + for(SizeType i=0;i(m_values[i].value); + return T(); + } + + bool has(Key id) const { + for(SizeType i=0;i + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_SHARED_OBJECT_H +#define STDEXT_SHARED_OBJECT_H + +#include "types.h" +#include +#include +#include +#include + +namespace stdext { + +template +class shared_object_ptr; + +class shared_object +{ +public: + shared_object() : refs(0) { } + virtual ~shared_object() { } + void add_ref() { ++refs; } + void dec_ref() { if(--refs == 0) delete this; } + refcount_t ref_count() { return refs; } + + template stdext::shared_object_ptr static_self_cast() { return stdext::shared_object_ptr(static_cast(this)); } + template stdext::shared_object_ptr dynamic_self_cast() { return stdext::shared_object_ptr(dynamic_cast(this)); } + template stdext::shared_object_ptr const_self_cast() { return stdext::shared_object_ptr(const_cast(this)); } + +private: + std::atomic refs; +}; + +template +class shared_object_ptr +{ +public: + typedef T element_type; + + shared_object_ptr(): px(nullptr) { } + shared_object_ptr(T* p, bool add_ref = true) : px(p) { + static_assert(std::is_base_of::value, "classes using shared_object_ptr must be a derived of stdext::shared_object"); + if(px != nullptr && add_ref) + this->add_ref(); + } + shared_object_ptr(shared_object_ptr const& rhs): px(rhs.px) { if(px != nullptr) add_ref(); } + template + shared_object_ptr(shared_object_ptr const& rhs, typename std::enable_if::value, U*>::type = nullptr) : px(rhs.get()) { if(px != nullptr) add_ref(); } + ~shared_object_ptr() { if(px != nullptr) dec_ref(); } + + void reset() { shared_object_ptr().swap(*this); } + void reset(T* rhs) { shared_object_ptr(rhs).swap(*this); } + void swap(shared_object_ptr& rhs) { std::swap(px, rhs.px); } + T* get() const { return px; } + + refcount_t use_count() const { return ((shared_object*)px)->ref_count(); } + bool is_unique() const { return use_count() == 1; } + + template shared_object_ptr& operator=(shared_object_ptr const& rhs) { shared_object_ptr(rhs).swap(*this); return *this; } + + T& operator*() const { VALIDATE(px != nullptr); return *px; } + T* operator->() const { VALIDATE(px != nullptr); return px; } + + shared_object_ptr& operator=(shared_object_ptr const& rhs) { shared_object_ptr(rhs).swap(*this); return *this; } + shared_object_ptr& operator=(T* rhs) { shared_object_ptr(rhs).swap(*this); return *this; } + + // implicit conversion to bool + typedef T* shared_object_ptr::*unspecified_bool_type; + operator unspecified_bool_type() const { return px == nullptr ? nullptr : &shared_object_ptr::px; } + bool operator!() const { return px == nullptr; } + + // std::move support + shared_object_ptr(shared_object_ptr&& rhs): px(rhs.px) { rhs.px = nullptr; } + shared_object_ptr& operator=(shared_object_ptr&& rhs) { shared_object_ptr(static_cast(rhs)).swap(*this); return *this; } + +private: + void add_ref() { ((shared_object*)px)->add_ref(); } + void dec_ref() { ((shared_object*)px)->dec_ref(); } + + T* px; +}; + +template bool operator==(shared_object_ptr const& a, shared_object_ptr const& b) { return a.get() == b.get(); } +template bool operator!=(shared_object_ptr const& a, shared_object_ptr const& b) { return a.get() != b.get(); } +template bool operator==(shared_object_ptr const& a, U* b) { return a.get() == b; } +template bool operator!=(shared_object_ptr const& a, U* b) { return a.get() != b; } +template bool operator==(T * a, shared_object_ptr const& b) { return a == b.get(); } +template bool operator!=(T * a, shared_object_ptr const& b) { return a != b.get(); } +template bool operator<(shared_object_ptr const& a, shared_object_ptr const& b) { return std::less()(a.get(), b.get()); } + +template T* get_pointer(shared_object_ptr const& p) { return p.get(); } +template shared_object_ptr static_pointer_cast(shared_object_ptr const& p) { return static_cast(p.get()); } +template shared_object_ptr const_pointer_cast(shared_object_ptr const& p) { return const_cast(p.get()); } +template shared_object_ptr dynamic_pointer_cast(shared_object_ptr const& p) { return dynamic_cast(p.get()); } +template stdext::shared_object_ptr make_shared_object(Args... args) { return stdext::shared_object_ptr(new T(args...)); } + +// operator<< support +template std::basic_ostream& operator<<(std::basic_ostream& os, shared_object_ptr const& p) { os << p.get(); return os; } + +} + +namespace std { + +// hash, for unordered_map support +template struct hash> { size_t operator()(const stdext::shared_object_ptr& p) const { return std::hash()(p.get()); } }; + +// swap support +template void swap(stdext::shared_object_ptr& lhs, stdext::shared_object_ptr& rhs) { lhs.swap(rhs); } + +} + +#endif diff --git a/src/framework/stdext/stdext.h b/src/framework/stdext/stdext.h new file mode 100644 index 0000000..5e4d1e4 --- /dev/null +++ b/src/framework/stdext/stdext.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_H +#define STDEXT_H + +#include "compiler.h" +#include "dumper.h" +#include "types.h" +#include "exception.h" +#include "demangle.h" +#include "cast.h" +#include "math.h" +#include "string.h" +#include "time.h" +#include "boolean.h" +#include "shared_object.h" +#include "any.h" +#include "packed_any.h" +#include "dynamic_storage.h" +#include "packed_storage.h" +#include "format.h" + +#endif diff --git a/src/framework/stdext/string.cpp b/src/framework/stdext/string.cpp new file mode 100644 index 0000000..f50b69d --- /dev/null +++ b/src/framework/stdext/string.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "string.h" +#include "format.h" +#include +#include +#include + +#ifdef _MSC_VER + #pragma warning(disable:4267) // '?' : conversion from 'A' to 'B', possible loss of data +#endif + +namespace stdext { + +std::string resolve_path(const std::string& filePath, std::string sourcePath) +{ + if (filePath.empty()) + return ""; + if(stdext::starts_with(filePath, "/")) + return filePath; + if(!stdext::ends_with(sourcePath, "/")) { + std::size_t slashPos = sourcePath.find_last_of("/"); + if(slashPos == std::string::npos) + throw_exception(format("invalid source path '%s', for file '%s'", sourcePath, filePath)); + sourcePath = sourcePath.substr(0, slashPos + 1); + } + return sourcePath + filePath; +} + +std::string date_time_string() +{ + char date[32]; + std::time_t tnow; + std::time(&tnow); + std::tm* ts = std::localtime(&tnow); + std::strftime(date, 32, "%b %d %Y %H:%M:%S", ts); + return std::string(date); +} + +std::string timestamp_to_date(time_t tnow) +{ + char date[32]; + std::tm* ts = std::localtime(&tnow); + std::strftime(date, 32, "%b %d %Y %H:%M:%S", ts); + return std::string(date); +} + +std::string dec_to_hex(uint32_t num) +{ + std::string str; + std::ostringstream o; + o << std::setw(8) << std::setfill('0') << std::hex << num; + str = o.str(); + return str; +} + +std::string dec_to_hex(uint64_t num) +{ + std::string str; + std::ostringstream o; + o << std::setw(16) << std::setfill('0') << std::hex << num; + str = o.str(); + return str; +} + +uint64_t hex_to_dec(const std::string& str) +{ + uint64_t num; + std::istringstream i(str); + i >> std::hex >> num; + return num; +} + +bool is_valid_utf8(const std::string& src) +{ + const unsigned char *bytes = (const unsigned char *)src.c_str(); + while(*bytes) { + if( (// ASCII + // use bytes[0] <= 0x7F to allow ASCII control characters + bytes[0] == 0x09 || + bytes[0] == 0x0A || + bytes[0] == 0x0D || + (0x20 <= bytes[0] && bytes[0] <= 0x7E) + ) + ) { + bytes += 1; + continue; + } + if( (// non-overlong 2-byte + (0xC2 <= bytes[0] && bytes[0] <= 0xDF) && + (0x80 <= bytes[1] && bytes[1] <= 0xBF) + ) + ) { + bytes += 2; + continue; + } + if( (// excluding overlongs + bytes[0] == 0xE0 && + (0xA0 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) + ) || + (// straight 3-byte + ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) || + bytes[0] == 0xEE || + bytes[0] == 0xEF) && + (0x80 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) + ) || + (// excluding surrogates + bytes[0] == 0xED && + (0x80 <= bytes[1] && bytes[1] <= 0x9F) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) + ) + ) { + bytes += 3; + continue; + } + if( (// planes 1-3 + bytes[0] == 0xF0 && + (0x90 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) && + (0x80 <= bytes[3] && bytes[3] <= 0xBF) + ) || + (// planes 4-15 + (0xF1 <= bytes[0] && bytes[0] <= 0xF3) && + (0x80 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) && + (0x80 <= bytes[3] && bytes[3] <= 0xBF) + ) || + (// plane 16 + bytes[0] == 0xF4 && + (0x80 <= bytes[1] && bytes[1] <= 0x8F) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) && + (0x80 <= bytes[3] && bytes[3] <= 0xBF) + ) + ) { + bytes += 4; + continue; + } + return false; + } + return true; +} + +std::string utf8_to_latin1(const std::string& src) +{ + std::string out; + for(uint i=0;i= 32 && c < 128) || c == 0x0d || c == 0x0a || c == 0x09) + out += c; + else if(c == 0xc2 || c == 0xc3) { + uchar c2 = src[i++]; + if(c == 0xc2) { + if(c2 > 0xa1 && c2 < 0xbb) + out += c2; + } else if(c == 0xc3) + out += 64 + c2; + } else if(c >= 0xc4 && c <= 0xdf) + i += 1; + else if(c >= 0xe0 && c <= 0xed) + i += 2; + else if(c >= 0xf0 && c <= 0xf4) + i += 3; + } + return out; +} + +std::string latin1_to_utf8(const std::string& src) +{ + std::string out; + for(uchar c : src) { + if((c >= 32 && c < 128) || c == 0x0d || c == 0x0a || c == 0x09) + out += c; + else { + out += 0xc2 + (c > 0xbf); + out += 0x80 + (c & 0x3f); + } + } + return out; +} + +#ifdef WIN32 +#include +#include +std::wstring utf8_to_utf16(const std::string& src) +{ + std::wstring res; + wchar_t out[65536]; + if(MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, out, 65536)) + res = out; + return res; +} + +std::string utf16_to_utf8(const std::wstring& src) +{ + std::string res; + char out[65536]; + if(WideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, out, 65536, NULL, NULL)) + res = out; + return res; +} + +std::wstring latin1_to_utf16(const std::string& src) +{ + return utf8_to_utf16(latin1_to_utf8(src)); +} + +std::string utf16_to_latin1(const std::wstring& src) +{ + return utf8_to_latin1(utf16_to_utf8(src)); +} +#endif + +void tolower(std::string& str) +{ + std::transform(str.begin(), str.end(), str.begin(), lochar); +} + +void toupper(std::string& str) +{ + std::transform(str.begin(), str.end(), str.begin(), upchar); +} + +void trim(std::string& str) +{ + boost::trim(str); +} + +char upchar(char c) +{ + if((c >= 97 && c <= 122) || (uchar)c >= 224) + c -= 32; + return c; +} + +char lochar(char c) +{ + if((c >= 65 && c <= 90) || ((uchar)c >= 192 && (uchar)c <= 223)) + c += 32; + return c; +} + +void ucwords(std::string& str) +{ + uint32 strLen = str.length(); + if(strLen == 0) + return; + + str[0] = upchar(str[0]); + for(uint32 i = 1; i < strLen; ++i) { + if(str[i - 1] == ' ') + str[i] = upchar(str[i]); + } +} + +bool ends_with(const std::string& str, const std::string& test) +{ + return boost::ends_with(str, test); +} + +bool starts_with(const std::string& str, const std::string& test) +{ + return boost::starts_with(str, test); +} + +void replace_all(std::string& str, const std::string& search, const std::string& replacement) +{ + return boost::replace_all(str, search, replacement); +} + +std::vector split(const std::string& str, const std::string& separators) +{ + std::vector splitted; + boost::split(splitted, str, boost::is_any_of(std::string(separators))); + return splitted; +} + +} diff --git a/src/framework/stdext/string.h b/src/framework/stdext/string.h new file mode 100644 index 0000000..250a4a4 --- /dev/null +++ b/src/framework/stdext/string.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_STRING_H +#define STDEXT_STRING_H + +#include +#include +#include + +#include "types.h" +#include "cast.h" + +namespace stdext { + +template std::string to_string(const T& t) { return unsafe_cast(t); } +template T from_string(const std::string& str, T def = T()) { return unsafe_cast(str, def); } + +/// Resolve a file path by combining sourcePath with filePath +std::string resolve_path(const std::string& filePath, std::string sourcePath); +/// Get current date and time in a std::string +std::string date_time_string(); +std::string timestamp_to_date(time_t tnow); + +std::string dec_to_hex(uint32_t num); +std::string dec_to_hex(uint64_t num); +uint64_t hex_to_dec(const std::string& str); +void tolower(std::string& str); +void toupper(std::string& str); +void trim(std::string& str); +void ucwords(std::string& str); +char upchar(char c); +char lochar(char c); +bool ends_with(const std::string& str, const std::string& test); +bool starts_with(const std::string& str, const std::string& test); +void replace_all(std::string& str, const std::string& search, const std::string& replacement); + +bool is_valid_utf8(const std::string& src); +std::string utf8_to_latin1(const std::string& src); +std::string latin1_to_utf8(const std::string& src); + +#ifdef WIN32 +std::wstring utf8_to_utf16(const std::string& src); +std::string utf16_to_utf8(const std::wstring& src); +std::string utf16_to_latin1(const std::wstring& src); +std::wstring latin1_to_utf16(const std::string& src); +#endif + +std::vector split(const std::string& str, const std::string& separators = " "); +template std::vector split(const std::string& str, const std::string& separators = " ") { + std::vector splitted = split(str, separators); + std::vector results(splitted.size()); + for(uint i=0;i(splitted[i]); + return results; +} + +} + +#endif diff --git a/src/framework/stdext/thread.h b/src/framework/stdext/thread.h new file mode 100644 index 0000000..1d4b415 --- /dev/null +++ b/src/framework/stdext/thread.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef THREAD_H +#define THREAD_H + +#include +#include +#include + +#endif + diff --git a/src/framework/stdext/time.cpp b/src/framework/stdext/time.cpp new file mode 100644 index 0000000..8c0462b --- /dev/null +++ b/src/framework/stdext/time.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "time.h" +#include +#include +#include + +namespace stdext { + +const static auto startup_time = std::chrono::high_resolution_clock::now(); + +ticks_t time() { + return std::time(NULL); +} + +ticks_t millis() +{ + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startup_time).count(); +} + +ticks_t micros() { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startup_time).count(); +} + +void millisleep(size_t ms) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +}; + +void microsleep(size_t us) +{ + std::this_thread::sleep_for(std::chrono::microseconds(us)); +}; + +} diff --git a/src/framework/stdext/time.h b/src/framework/stdext/time.h new file mode 100644 index 0000000..3451d04 --- /dev/null +++ b/src/framework/stdext/time.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_TIME_H +#define STDEXT_TIME_H + +#include "types.h" + +namespace stdext { + +ticks_t time(); +ticks_t millis(); +ticks_t micros(); +void millisleep(size_t ms); +void microsleep(size_t us); + +struct timer { +public: + timer() { restart(); } + float elapsed_seconds() { return (float)((stdext::micros() - m_start)/1000000.0); } + ticks_t elapsed_millis() { return (stdext::micros() - m_start)/1000; } + ticks_t elapsed_micros() { return stdext::micros() - m_start; } + void restart(int shift = 0) { m_start = stdext::micros() - shift; } +private: + ticks_t m_start; +}; + +} + +#endif + diff --git a/src/framework/stdext/traits.h b/src/framework/stdext/traits.h new file mode 100644 index 0000000..5e896b3 --- /dev/null +++ b/src/framework/stdext/traits.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_TRAITS_H +#define STDEXT_TRAITS_H + +#include + +namespace stdext { + +template struct replace_extent { typedef T type; }; +template struct replace_extent { typedef const T* type; }; +template struct replace_extent { typedef const T* type;}; +template struct remove_const_ref { typedef typename std::remove_const::type>::type type; }; + +}; + +#endif diff --git a/src/framework/stdext/types.h b/src/framework/stdext/types.h new file mode 100644 index 0000000..e258170 --- /dev/null +++ b/src/framework/stdext/types.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STDEXT_TYPES_H +#define STDEXT_TYPES_H + +#include +#include + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef uint64_t uint64; +typedef uint32_t uint32; +typedef uint16_t uint16; +typedef uint8_t uint8; +typedef int64_t int64; +typedef int32_t int32; +typedef int16_t int16; +typedef int8_t int8; + +typedef int64 ticks_t; +typedef uint_fast32_t refcount_t; + +using std::size_t; +using std::ptrdiff_t; + +#endif diff --git a/src/framework/stdext/uri.cpp b/src/framework/stdext/uri.cpp new file mode 100644 index 0000000..d89dee3 --- /dev/null +++ b/src/framework/stdext/uri.cpp @@ -0,0 +1,24 @@ +#include +#include + +#include "uri.h" + +ParsedURI parseURI(const std::string& url) +{ + ParsedURI result; + auto value_or = [](const std::string& value, std::string&& deflt) -> std::string { + return (value.empty() ? deflt : value); + }; + // Note: only "http", "https", "ws", and "wss" protocols are supported + static const std::regex PARSE_URL{ R"((([httpsw]{2,5})://)?([^/ :]+)(:(\d+))?(/(.+)?))", + std::regex_constants::ECMAScript | std::regex_constants::icase }; + std::smatch match; + if (std::regex_match(url, match, PARSE_URL) && match.size() == 8) { + result.protocol = value_or(boost::algorithm::to_lower_copy(std::string(match[2])), "http"); + result.domain = match[3]; + const bool is_sequre_protocol = (result.protocol == "https" || result.protocol == "wss"); + result.port = value_or(match[5], (is_sequre_protocol) ? "443" : "80"); + result.query = value_or(match[6], "/"); + } + return result; +} \ No newline at end of file diff --git a/src/framework/stdext/uri.h b/src/framework/stdext/uri.h new file mode 100644 index 0000000..5363499 --- /dev/null +++ b/src/framework/stdext/uri.h @@ -0,0 +1,10 @@ +#include + +struct ParsedURI { + std::string protocol; + std::string domain; // only domain must be present + std::string port; + std::string query; // everything after '?', possibly nothing +}; + +ParsedURI parseURI(const std::string& url); \ No newline at end of file diff --git a/src/framework/ui/declarations.h b/src/framework/ui/declarations.h new file mode 100644 index 0000000..281999a --- /dev/null +++ b/src/framework/ui/declarations.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_UI_DECLARATIONS_H +#define FRAMEWORK_UI_DECLARATIONS_H + +#include + +class UIManager; +class UIWidget; +class UITextEdit; +class UILayout; +class UIBoxLayout; +class UIHorizontalLayout; +class UIVerticalLayout; +class UIGridLayout; +class UIAnchor; +class UIAnchorGroup; +class UIAnchorLayout; + +typedef stdext::shared_object_ptr UIWidgetPtr; +typedef stdext::shared_object_ptr UITextEditPtr; +typedef stdext::shared_object_ptr UILayoutPtr; +typedef stdext::shared_object_ptr UIBoxLayoutPtr; +typedef stdext::shared_object_ptr UIHorizontalLayoutPtr; +typedef stdext::shared_object_ptr UIVerticalLayoutPtr; +typedef stdext::shared_object_ptr UIGridLayoutPtr; +typedef stdext::shared_object_ptr UIAnchorPtr; +typedef stdext::shared_object_ptr UIAnchorGroupPtr; +typedef stdext::shared_object_ptr UIAnchorLayoutPtr; + +typedef std::deque UIWidgetList; +typedef std::vector UIAnchorList; + +#endif diff --git a/src/framework/ui/ui.h b/src/framework/ui/ui.h new file mode 100644 index 0000000..e6d4748 --- /dev/null +++ b/src/framework/ui/ui.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UI_H +#define UI_H + +#include "uimanager.h" +#include "uiwidget.h" +#include "uitextedit.h" +#include "uilayout.h" +#include "uihorizontallayout.h" +#include "uiverticallayout.h" +#include "uigridlayout.h" +#include "uianchorlayout.h" + +#endif diff --git a/src/framework/ui/uianchorlayout.cpp b/src/framework/ui/uianchorlayout.cpp new file mode 100644 index 0000000..d9dac12 --- /dev/null +++ b/src/framework/ui/uianchorlayout.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uianchorlayout.h" +#include "uiwidget.h" + +UIWidgetPtr UIAnchor::getHookedWidget(const UIWidgetPtr& widget, const UIWidgetPtr& parentWidget) +{ + // determine hooked widget + UIWidgetPtr hookedWidget; + if(parentWidget) { + if(m_hookedWidgetId == "parent") + hookedWidget = parentWidget; + else if(m_hookedWidgetId == "next") + hookedWidget = parentWidget->getChildAfter(widget); + else if(m_hookedWidgetId == "prev") + hookedWidget = parentWidget->getChildBefore(widget); + else + hookedWidget = parentWidget->getChildById(m_hookedWidgetId); + } + return hookedWidget; +} + +int UIAnchor::getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget) +{ + // determine hooked widget edge point + Rect hookedWidgetRect = hookedWidget->getRect(); + if(hookedWidget == parentWidget) + hookedWidgetRect = parentWidget->getPaddingRect(); + + int point = 0; + switch(m_hookedEdge) { + case Fw::AnchorLeft: + point = hookedWidgetRect.left(); + break; + case Fw::AnchorRight: + point = hookedWidgetRect.right(); + break; + case Fw::AnchorTop: + point = hookedWidgetRect.top(); + break; + case Fw::AnchorBottom: + point = hookedWidgetRect.bottom(); + break; + case Fw::AnchorHorizontalCenter: + point = hookedWidgetRect.horizontalCenter(); + break; + case Fw::AnchorVerticalCenter: + point = hookedWidgetRect.verticalCenter(); + break; + default: + // must never happens + VALIDATE(false); + break; + } + + if(hookedWidget == parentWidget) { + switch(m_hookedEdge) { + case Fw::AnchorLeft: + case Fw::AnchorRight: + case Fw::AnchorHorizontalCenter: + point -= parentWidget->getVirtualOffset().x; + break; + case Fw::AnchorBottom: + case Fw::AnchorTop: + case Fw::AnchorVerticalCenter: + point -= parentWidget->getVirtualOffset().y; + break; + default: + break; + } + } + + return point; +} + +void UIAnchorGroup::addAnchor(const UIAnchorPtr& anchor) +{ + // duplicated anchors must be replaced + for(UIAnchorPtr& other : m_anchors) { + if(other->getAnchoredEdge() == anchor->getAnchoredEdge()) { + other = anchor; + return; + } + } + m_anchors.push_back(anchor); +} + +void UIAnchorLayout::addAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, + const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge) +{ + if(!anchoredWidget) + return; + + VALIDATE(anchoredWidget != getParentWidget()); + + UIAnchorPtr anchor(new UIAnchor(anchoredEdge, hookedWidgetId, hookedEdge)); + UIAnchorGroupPtr& anchorGroup = m_anchorsGroups[anchoredWidget]; + if(!anchorGroup) + anchorGroup = UIAnchorGroupPtr(new UIAnchorGroup); + + anchorGroup->addAnchor(anchor); + + // layout must be updated because a new anchor got in + update(); +} + +void UIAnchorLayout::removeAnchors(const UIWidgetPtr& anchoredWidget) +{ + m_anchorsGroups.erase(anchoredWidget); + update(); +} + +bool UIAnchorLayout::hasAnchors(const UIWidgetPtr& anchoredWidget) +{ + return m_anchorsGroups.find(anchoredWidget) != m_anchorsGroups.end(); +} + +void UIAnchorLayout::centerIn(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId) +{ + addAnchor(anchoredWidget, Fw::AnchorHorizontalCenter, hookedWidgetId, Fw::AnchorHorizontalCenter); + addAnchor(anchoredWidget, Fw::AnchorVerticalCenter, hookedWidgetId, Fw::AnchorVerticalCenter); +} + +void UIAnchorLayout::fill(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId) +{ + addAnchor(anchoredWidget, Fw::AnchorLeft, hookedWidgetId, Fw::AnchorLeft); + addAnchor(anchoredWidget, Fw::AnchorRight, hookedWidgetId, Fw::AnchorRight); + addAnchor(anchoredWidget, Fw::AnchorTop, hookedWidgetId, Fw::AnchorTop); + addAnchor(anchoredWidget, Fw::AnchorBottom, hookedWidgetId, Fw::AnchorBottom); +} + +void UIAnchorLayout::addWidget(const UIWidgetPtr& widget) +{ + update(); +} + +void UIAnchorLayout::removeWidget(const UIWidgetPtr& widget) +{ + removeAnchors(widget); +} + +bool UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, const UIAnchorGroupPtr& anchorGroup, UIWidgetPtr first) +{ + UIWidgetPtr parentWidget = getParentWidget(); + if(!parentWidget) + return false; + + if(first == widget) { + g_logger.error(stdext::format("child '%s' of parent widget '%s' is recursively anchored to itself, please fix this", widget->getId(), parentWidget->getId())); + return false; + } + + if(!first) + first = widget; + + Rect newRect = widget->getRect(); + bool verticalMoved = false; + bool horizontalMoved = false; + + // calculates new rect based on anchors + for(const UIAnchorPtr& anchor : anchorGroup->getAnchors()) { + // skip invalid anchors + if(anchor->getHookedEdge() == Fw::AnchorNone) + continue; + + // determine hooked widget + UIWidgetPtr hookedWidget = anchor->getHookedWidget(widget, parentWidget); + + // skip invalid anchors + if(!hookedWidget) + continue; + + if(hookedWidget != getParentWidget()) { + // update this hooked widget anchors + auto it = m_anchorsGroups.find(hookedWidget); + if(it != m_anchorsGroups.end()) { + const UIAnchorGroupPtr& hookedAnchorGroup = it->second; + if(!hookedAnchorGroup->isUpdated()) + updateWidget(hookedWidget, hookedAnchorGroup, first); + } + } + + int point = anchor->getHookedPoint(hookedWidget, parentWidget); + + switch(anchor->getAnchoredEdge()) { + case Fw::AnchorHorizontalCenter: + newRect.moveHorizontalCenter(point + widget->getMarginLeft() - widget->getMarginRight()); + horizontalMoved = true; + break; + case Fw::AnchorLeft: + if(!horizontalMoved) { + newRect.moveLeft(point + widget->getMarginLeft()); + horizontalMoved = true; + } else + newRect.setLeft(point + widget->getMarginLeft()); + break; + case Fw::AnchorRight: + if(!horizontalMoved) { + newRect.moveRight(point - widget->getMarginRight()); + horizontalMoved = true; + } else + newRect.setRight(point - widget->getMarginRight()); + break; + case Fw::AnchorVerticalCenter: + newRect.moveVerticalCenter(point + widget->getMarginTop() - widget->getMarginBottom()); + verticalMoved = true; + break; + case Fw::AnchorTop: + if(!verticalMoved) { + newRect.moveTop(point + widget->getMarginTop()); + verticalMoved = true; + } else + newRect.setTop(point + widget->getMarginTop()); + break; + case Fw::AnchorBottom: + if(!verticalMoved) { + newRect.moveBottom(point - widget->getMarginBottom()); + verticalMoved = true; + } else + newRect.setBottom(point - widget->getMarginBottom()); + break; + default: + break; + } + } + + bool changed = false; + if(widget->setRect(newRect)) + changed = true; + anchorGroup->setUpdated(true); + return changed; +} + +bool UIAnchorLayout::internalUpdate() +{ + bool changed = false; + + // reset all anchors groups update state + for(auto& it : m_anchorsGroups) { + const UIAnchorGroupPtr& anchorGroup = it.second; + anchorGroup->setUpdated(false); + } + + // update all anchors + for(auto& it : m_anchorsGroups) { + const UIWidgetPtr& widget = it.first; + const UIAnchorGroupPtr& anchorGroup = it.second; + if(!anchorGroup->isUpdated()) { + if(updateWidget(widget, anchorGroup)) + changed = true; + } + } + + return changed; +} diff --git a/src/framework/ui/uianchorlayout.h b/src/framework/ui/uianchorlayout.h new file mode 100644 index 0000000..b129232 --- /dev/null +++ b/src/framework/ui/uianchorlayout.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIANCHORLAYOUT_H +#define UIANCHORLAYOUT_H + +#include "uilayout.h" + +class UIAnchor : public stdext::shared_object +{ +public: + UIAnchor(Fw::AnchorEdge anchoredEdge, const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge) : + m_anchoredEdge(anchoredEdge), m_hookedEdge(hookedEdge), m_hookedWidgetId(hookedWidgetId) { } + + Fw::AnchorEdge getAnchoredEdge() const { return m_anchoredEdge; } + Fw::AnchorEdge getHookedEdge() const { return m_hookedEdge; } + + virtual UIWidgetPtr getHookedWidget(const UIWidgetPtr& widget, const UIWidgetPtr& parentWidget); + virtual int getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget); + +protected: + Fw::AnchorEdge m_anchoredEdge; + Fw::AnchorEdge m_hookedEdge; + std::string m_hookedWidgetId; +}; + +class UIAnchorGroup : public stdext::shared_object +{ +public: + UIAnchorGroup() : m_updated(true) { } + + void addAnchor(const UIAnchorPtr& anchor); + const UIAnchorList& getAnchors() { return m_anchors; } + bool isUpdated() { return m_updated; } + void setUpdated(bool updated) { m_updated = updated; } + +private: + UIAnchorList m_anchors; + bool m_updated; +}; + +// @bindclass +class UIAnchorLayout : public UILayout +{ +public: + UIAnchorLayout(UIWidgetPtr parentWidget) : UILayout(parentWidget) { } + + void addAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, + const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge); + void removeAnchors(const UIWidgetPtr& anchoredWidget); + bool hasAnchors(const UIWidgetPtr& anchoredWidget); + void centerIn(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId); + void fill(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId); + + void addWidget(const UIWidgetPtr& widget); + void removeWidget(const UIWidgetPtr& widget); + + bool isUIAnchorLayout() { return true; } + +protected: + virtual bool internalUpdate(); + virtual bool updateWidget(const UIWidgetPtr& widget, const UIAnchorGroupPtr& anchorGroup, UIWidgetPtr first = nullptr); + std::unordered_map m_anchorsGroups; +}; + +#endif diff --git a/src/framework/ui/uiboxlayout.cpp b/src/framework/ui/uiboxlayout.cpp new file mode 100644 index 0000000..b64f7b8 --- /dev/null +++ b/src/framework/ui/uiboxlayout.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiboxlayout.h" +#include "uiwidget.h" + +UIBoxLayout::UIBoxLayout(UIWidgetPtr parentWidget) : UILayout(parentWidget) +{ + m_spacing = 0; +} + +void UIBoxLayout::applyStyle(const OTMLNodePtr& styleNode) +{ + UILayout::applyStyle(styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "spacing") + setSpacing(node->value()); + else if(node->tag() == "fit-children") + setFitChildren(node->value()); + } +} + diff --git a/src/framework/ui/uiboxlayout.h b/src/framework/ui/uiboxlayout.h new file mode 100644 index 0000000..1fb93c6 --- /dev/null +++ b/src/framework/ui/uiboxlayout.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIBOXLAYOUT_H +#define UIBOXLAYOUT_H + +#include "uilayout.h" + +// @bindclass +class UIBoxLayout : public UILayout +{ +public: + UIBoxLayout(UIWidgetPtr parentWidget); + + void applyStyle(const OTMLNodePtr& styleNode); + void addWidget(const UIWidgetPtr& widget) { update(); } + void removeWidget(const UIWidgetPtr& widget) { update(); } + + void setSpacing(int spacing) { m_spacing = spacing; update(); } + void setFitChildren(bool fitParent) { m_fitChildren = fitParent; update(); } + + bool isUIBoxLayout() { return true; } + +protected: + stdext::boolean m_fitChildren; + int m_spacing; +}; + +#endif diff --git a/src/framework/ui/uigridlayout.cpp b/src/framework/ui/uigridlayout.cpp new file mode 100644 index 0000000..eed81c3 --- /dev/null +++ b/src/framework/ui/uigridlayout.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uigridlayout.h" +#include "uiwidget.h" + +#include + +UIGridLayout::UIGridLayout(UIWidgetPtr parentWidget): UILayout(parentWidget) +{ + m_cellSize = Size(16,16); + m_cellSpacing = 0; + m_numColumns = 1; + m_numLines = 0; +} + +void UIGridLayout::applyStyle(const OTMLNodePtr& styleNode) +{ + UILayout::applyStyle(styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "cell-size") + setCellSize(node->value()); + else if(node->tag() == "cell-width") + setCellWidth(node->value()); + else if(node->tag() == "cell-height") + setCellHeight(node->value()); + else if(node->tag() == "cell-spacing") + setCellSpacing(node->value()); + else if(node->tag() == "num-columns") + setNumColumns(node->value()); + else if(node->tag() == "num-lines") + setNumLines(node->value()); + else if(node->tag() == "fit-children") + setFitChildren(node->value()); + else if(node->tag() == "auto-spacing") + setAutoSpacing(node->value()); + else if(node->tag() == "flow") + setFlow(node->value()); + } +} + +void UIGridLayout::removeWidget(const UIWidgetPtr& widget) +{ + update(); +} + +void UIGridLayout::addWidget(const UIWidgetPtr& widget) +{ + update(); +} + +bool UIGridLayout::internalUpdate() +{ + bool changed = false; + UIWidgetPtr parentWidget = getParentWidget(); + if(!parentWidget) + return false; + + UIWidgetList widgets = parentWidget->getChildren(); + + Rect clippingRect = parentWidget->getPaddingRect(); + Point topLeft = clippingRect.topLeft(); + + int numColumns = m_numColumns; + if(m_flow && m_cellSize.width() > 0) { + numColumns = (clippingRect.width() + m_cellSpacing) / (m_cellSize.width() + m_cellSpacing); + if(numColumns > 0) { + m_numColumns = numColumns; + m_numLines = std::ceil(widgets.size() / (float)numColumns); + } + } + + if(numColumns <= 0) + numColumns = 1; + + int cellSpacing = m_cellSpacing; + if(m_autoSpacing && numColumns > 1) + cellSpacing = (clippingRect.width() - numColumns * m_cellSize.width()) / (numColumns - 1); + + int index = 0; + int preferredHeight = 0; + for(const UIWidgetPtr& widget : widgets) { + if(!widget->isExplicitlyVisible()) + continue; + + int line = index / numColumns; + int column = index % numColumns; + Point virtualPos = Point(column * (m_cellSize.width() + cellSpacing), line * (m_cellSize.height() + cellSpacing)); + preferredHeight = virtualPos.y + m_cellSize.height(); + Point pos = topLeft + virtualPos - parentWidget->getVirtualOffset(); + Rect dest = Rect(pos, m_cellSize); + dest.expand(-widget->getMarginTop(), -widget->getMarginRight(), -widget->getMarginBottom(), -widget->getMarginLeft()); + + if(widget->setRect(dest)) + changed = true; + + index++; + + if(m_numLines > 0 && index >= m_numColumns * m_numLines) + break; + } + preferredHeight += parentWidget->getPaddingTop() + parentWidget->getPaddingBottom(); + + if(m_fitChildren && preferredHeight != parentWidget->getHeight()) { + // must set the preferred height later + g_dispatcher.addEvent([=] { + parentWidget->setHeight(preferredHeight); + }); + } + + return changed; +} diff --git a/src/framework/ui/uigridlayout.h b/src/framework/ui/uigridlayout.h new file mode 100644 index 0000000..535f9c8 --- /dev/null +++ b/src/framework/ui/uigridlayout.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIGRIDLAYOUT_H +#define UIGRIDLAYOUT_H + +#include "uilayout.h" + +// @bindclass +class UIGridLayout : public UILayout +{ +public: + UIGridLayout(UIWidgetPtr parentWidget); + + void applyStyle(const OTMLNodePtr& styleNode); + void removeWidget(const UIWidgetPtr& widget); + void addWidget(const UIWidgetPtr& widget); + + void setCellSize(const Size& size) { m_cellSize = size; update(); } + void setCellWidth(int width) { m_cellSize.setWidth(width); update(); } + void setCellHeight(int height) { m_cellSize.setHeight(height); update(); } + void setCellSpacing(int spacing) { m_cellSpacing = spacing; update(); } + void setNumColumns(int columns) { m_numColumns = columns; update(); } + void setNumLines(int lines) { m_numLines = lines; update(); } + void setAutoSpacing(bool enable) { m_autoSpacing = enable; update(); } + void setFitChildren(bool enable) { m_fitChildren = enable; update(); } + void setFlow(bool enable) { m_flow = enable; update(); } + + Size getCellSize() { return m_cellSize; } + int getCellSpacing() { return m_cellSpacing; } + int getNumColumns() { return m_numColumns; } + int getNumLines() { return m_numLines; } + + virtual bool isUIGridLayout() { return true; } + +protected: + bool internalUpdate(); + +private: + Size m_cellSize; + int m_cellSpacing; + int m_numColumns; + int m_numLines; + stdext::boolean m_autoSpacing; + stdext::boolean m_fitChildren; + stdext::boolean m_flow; +}; + +#endif diff --git a/src/framework/ui/uihorizontallayout.cpp b/src/framework/ui/uihorizontallayout.cpp new file mode 100644 index 0000000..078fc52 --- /dev/null +++ b/src/framework/ui/uihorizontallayout.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uihorizontallayout.h" +#include "uiwidget.h" +#include + + +void UIHorizontalLayout::applyStyle(const OTMLNodePtr& styleNode) +{ + UIBoxLayout::applyStyle(styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "align-right") + setAlignRight(node->value()); + } +} + +bool UIHorizontalLayout::internalUpdate() +{ + UIWidgetPtr parentWidget = getParentWidget(); + if(!parentWidget) + return false; + UIWidgetList widgets = parentWidget->getChildren(); + + if(m_alignRight) + std::reverse(widgets.begin(), widgets.end()); + + bool changed = false; + Rect paddingRect = parentWidget->getPaddingRect(); + Point pos = (m_alignRight) ? paddingRect.topRight() : paddingRect.topLeft(); + int preferredWidth = 0; + int gap; + + for(const UIWidgetPtr& widget : widgets) { + if(!widget->isExplicitlyVisible()) + continue; + + Size size = widget->getSize(); + + gap = (m_alignRight) ? -(widget->getMarginRight()+widget->getWidth()) : widget->getMarginLeft(); + pos.x += gap; + preferredWidth += gap; + + if(widget->isFixedSize()) { + if(widget->getTextAlign() & Fw::AlignTop) { + pos.y = paddingRect.top() + widget->getMarginTop(); + } else if(widget->getTextAlign() & Fw::AlignBottom) { + pos.y = paddingRect.bottom() - widget->getHeight() - widget->getMarginBottom(); + pos.y = std::max(pos.y, paddingRect.top()); + } else { // center it + pos.y = paddingRect.top() + (paddingRect.height() - (widget->getMarginTop() + widget->getHeight() + widget->getMarginBottom()))/2; + pos.y = std::max(pos.y, paddingRect.top()); + } + } else { + // expand height + size.setHeight(paddingRect.height() - (widget->getMarginTop() + widget->getMarginBottom())); + pos.y = paddingRect.top() + (paddingRect.height() - size.height())/2; + } + + if(widget->setRect(Rect(pos - parentWidget->getVirtualOffset(), size))) + changed = true; + + gap = (m_alignRight) ? -widget->getMarginLeft() : (widget->getWidth() + widget->getMarginRight()); + gap += m_spacing; + pos.x += gap; + preferredWidth += gap; + } + + preferredWidth -= m_spacing; + preferredWidth += parentWidget->getPaddingLeft() + parentWidget->getPaddingRight(); + + if(m_fitChildren && preferredWidth != parentWidget->getWidth()) { + // must set the preferred width later + g_dispatcher.addEvent([=] { + parentWidget->setWidth(preferredWidth); + }); + } + + return changed; +} diff --git a/src/framework/ui/uihorizontallayout.h b/src/framework/ui/uihorizontallayout.h new file mode 100644 index 0000000..312955d --- /dev/null +++ b/src/framework/ui/uihorizontallayout.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIHORIZONTALLAYOUT_H +#define UIHORIZONTALLAYOUT_H + +#include "uiboxlayout.h" + +class UIHorizontalLayout : public UIBoxLayout +{ +public: + UIHorizontalLayout(UIWidgetPtr parentWidget) : UIBoxLayout(parentWidget) { } + + void applyStyle(const OTMLNodePtr& styleNode); + + void setAlignRight(bool aliginRight) { m_alignRight = aliginRight; update(); } + + bool isUIHorizontalLayout() { return true; } + +protected: + bool internalUpdate(); + + Fw::AlignmentFlag m_alignChidren; + stdext::boolean m_alignRight; +}; + +#endif diff --git a/src/framework/ui/uilayout.cpp b/src/framework/ui/uilayout.cpp new file mode 100644 index 0000000..63d7c95 --- /dev/null +++ b/src/framework/ui/uilayout.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uilayout.h" +#include "uiwidget.h" + +#include + +void UILayout::update() +{ + //logTraceCounter(); + if(!m_parentWidget) + return; + + /* + UIWidgetPtr parent = parentWidget; + do { + UILayoutPtr ownerLayout = parent->getLayout(); + if(ownerLayout && ownerLayout->isUpdateDisabled()) + return; + parent = parent->getParent(); + } while(parent); + */ + + if(m_updateDisabled) + return; + + if(m_updating) { + updateLater(); + return; + } + + m_updating = true; + internalUpdate(); + m_parentWidget->onLayoutUpdate(); + m_updating = false; +} + +void UILayout::updateLater() +{ + if(m_updateDisabled || m_updateScheduled) + return; + + if(!getParentWidget()) + return; + + auto self = static_self_cast(); + g_dispatcher.addEvent([self] { + self->m_updateScheduled = false; + self->update(); + }); + m_updateScheduled = true; +} diff --git a/src/framework/ui/uilayout.h b/src/framework/ui/uilayout.h new file mode 100644 index 0000000..f1eea74 --- /dev/null +++ b/src/framework/ui/uilayout.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UILAYOUT_H +#define UILAYOUT_H + +#include "declarations.h" +#include +#include + +// @bindclass +class UILayout : public LuaObject +{ +public: + UILayout(UIWidgetPtr parentWidget) : m_parentWidget(parentWidget) { m_updateDisabled = 0; } + + void update(); + void updateLater(); + + virtual void applyStyle(const OTMLNodePtr& styleNode) { } + virtual void addWidget(const UIWidgetPtr& widget) { } + virtual void removeWidget(const UIWidgetPtr& widget) { } + void disableUpdates() { m_updateDisabled++; } + void enableUpdates() { m_updateDisabled = std::max(m_updateDisabled-1,0); } + + void setParent(UIWidgetPtr parentWidget) { m_parentWidget = parentWidget; } + UIWidgetPtr getParentWidget() { return m_parentWidget; } + + bool isUpdateDisabled() { return m_updateDisabled > 0; } + bool isUpdating() { return m_updating; } + + virtual bool isUIAnchorLayout() { return false; } + virtual bool isUIBoxLayout() { return false; } + virtual bool isUIHorizontalLayout() { return false; } + virtual bool isUIVerticalLayout() { return false; } + virtual bool isUIGridLayout() { return false; } + +protected: + virtual bool internalUpdate() { return false; } + + int m_updateDisabled; + stdext::boolean m_updating; + stdext::boolean m_updateScheduled; + UIWidgetPtr m_parentWidget; +}; + +#endif diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp new file mode 100644 index 0000000..5fc9e7b --- /dev/null +++ b/src/framework/ui/uimanager.cpp @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uimanager.h" +#include "ui.h" + +#include +#include +#include +#include +#include +#include +#include + +UIManager g_ui; + +void UIManager::init() +{ + // creates root widget + m_rootWidget = UIWidgetPtr(new UIWidget); + m_rootWidget->setId("root"); + m_mouseReceiver = m_rootWidget; + m_keyboardReceiver = m_rootWidget; +} + +void UIManager::terminate() +{ + // destroy root widget and its children + m_rootWidget->destroy(); + m_mouseReceiver = nullptr; + m_keyboardReceiver = nullptr; + m_rootWidget = nullptr; + m_draggingWidget = nullptr; + m_hoveredWidget = nullptr; + m_pressedWidget = nullptr; + m_styles.clear(); + m_destroyedWidgets.clear(); + m_checkEvent = nullptr; +} + +void UIManager::render(Fw::DrawPane drawPane) +{ + m_rootWidget->draw(m_rootWidget->getRect(), drawPane); +} + +void UIManager::resize(const Size& size) +{ + m_rootWidget->setSize(size); +} + +void UIManager::inputEvent(const InputEvent& event) +{ + UIWidgetList widgetList; + switch(event.type) { + case Fw::KeyTextInputEvent: + m_keyboardReceiver->propagateOnKeyText(event.keyText); + break; + case Fw::KeyDownInputEvent: + m_keyboardReceiver->propagateOnKeyDown(event.keyCode, event.keyboardModifiers); + break; + case Fw::KeyPressInputEvent: + m_keyboardReceiver->propagateOnKeyPress(event.keyCode, event.keyboardModifiers, event.autoRepeatTicks); + break; + case Fw::KeyUpInputEvent: + m_keyboardReceiver->propagateOnKeyUp(event.keyCode, event.keyboardModifiers); + break; + case Fw::MousePressInputEvent: + if(event.mouseButton == Fw::MouseLeftButton && m_mouseReceiver->isVisible()) { + UIWidgetPtr pressedWidget = m_mouseReceiver->recursiveGetChildByPos(event.mousePos, false); + if(pressedWidget && !pressedWidget->isEnabled()) + pressedWidget = nullptr; + updatePressedWidget(pressedWidget, event.mousePos); + } + + m_mouseReceiver->propagateOnMouseEvent(event.mousePos, widgetList); + for(const UIWidgetPtr& widget : widgetList) { + widget->recursiveFocus(Fw::MouseFocusReason); + if(widget->onMousePress(event.mousePos, event.mouseButton)) + break; + } + + break; + case Fw::MouseReleaseInputEvent: { + // release dragging widget + bool accepted = false; + if(m_draggingWidget && event.mouseButton == Fw::MouseLeftButton) + accepted = updateDraggingWidget(nullptr, event.mousePos); + + if(!accepted) { + m_mouseReceiver->propagateOnMouseEvent(event.mousePos, widgetList); + + // mouse release is always fired first on the pressed widget + if(m_pressedWidget) { + auto it = std::find(widgetList.begin(), widgetList.end(), m_pressedWidget); + if(it != widgetList.end()) + widgetList.erase(it); + widgetList.push_front(m_pressedWidget); + } + + for(const UIWidgetPtr& widget : widgetList) { + if(widget->onMouseRelease(event.mousePos, event.mouseButton)) + break; + } + } + + if(m_pressedWidget && event.mouseButton == Fw::MouseLeftButton) + updatePressedWidget(nullptr, event.mousePos, !accepted); + break; + } + case Fw::MouseMoveInputEvent: { + // start dragging when moving a pressed widget + if(m_pressedWidget && m_pressedWidget->isDraggable() && m_draggingWidget != m_pressedWidget) { + // only drags when moving more than 4 pixels + if((event.mousePos - m_pressedWidget->getLastClickPosition()).length() >= 4) + updateDraggingWidget(m_pressedWidget, event.mousePos - event.mouseMoved); + } + + // mouse move can change hovered widgets + updateHoveredWidget(true); + + // first fire dragging move + if(m_draggingWidget) { + if(m_draggingWidget->onDragMove(event.mousePos, event.mouseMoved)) + break; + } + + if (m_pressedWidget) { + if (m_pressedWidget->onMouseMove(event.mousePos, event.mouseMoved)) { + break; + } + } + + //m_mouseReceiver->propagateOnMouseMove(event.mousePos, event.mouseMoved, widgetList); + m_rootWidget->propagateOnMouseMove(event.mousePos, event.mouseMoved, widgetList); + for(const UIWidgetPtr& widget : widgetList) { + if(widget->onMouseMove(event.mousePos, event.mouseMoved)) + break; + } + break; + } + case Fw::MouseWheelInputEvent: + m_rootWidget->propagateOnMouseEvent(event.mousePos, widgetList); + for(const UIWidgetPtr& widget : widgetList) { + if(widget->onMouseWheel(event.mousePos, event.wheelDirection)) + break; + } + break; + default: + break; + }; +} + +void UIManager::updatePressedWidget(const UIWidgetPtr& newPressedWidget, const Point& clickedPos, bool fireClicks) +{ + UIWidgetPtr oldPressedWidget = m_pressedWidget; + m_pressedWidget = newPressedWidget; + + // when releasing mouse inside pressed widget area send onClick event + if(fireClicks && oldPressedWidget && oldPressedWidget->isEnabled() && oldPressedWidget->containsPoint(clickedPos)) + oldPressedWidget->onClick(clickedPos); + + if(newPressedWidget) + newPressedWidget->updateState(Fw::PressedState); + + if(oldPressedWidget) + oldPressedWidget->updateState(Fw::PressedState); +} + +bool UIManager::updateDraggingWidget(const UIWidgetPtr& draggingWidget, const Point& clickedPos) +{ + bool accepted = false; + + UIWidgetPtr oldDraggingWidget = m_draggingWidget; + m_draggingWidget = nullptr; + if(oldDraggingWidget) { + UIWidgetPtr droppedWidget; + if(!clickedPos.isNull()) { + auto clickedChildren = m_rootWidget->recursiveGetChildrenByPos(clickedPos); + for(const UIWidgetPtr& child : clickedChildren) { + if(child->onDrop(oldDraggingWidget, clickedPos)) { + droppedWidget = child; + break; + } + } + } + + accepted = oldDraggingWidget->onDragLeave(droppedWidget, clickedPos); + oldDraggingWidget->updateState(Fw::DraggingState); + } + + if(draggingWidget) { + if(draggingWidget->onDragEnter(clickedPos)) { + m_draggingWidget = draggingWidget; + draggingWidget->updateState(Fw::DraggingState); + accepted = true; + } + } + + return accepted; +} + +void UIManager::updateHoveredWidget(bool now) +{ + if(m_hoverUpdateScheduled && !now) + return; + + auto func = [this] { + if(!m_rootWidget) + return; + + m_hoverUpdateScheduled = false; + UIWidgetPtr hoveredWidget; + //if(!g_window.isMouseButtonPressed(Fw::MouseLeftButton) && !g_window.isMouseButtonPressed(Fw::MouseRightButton)) { + hoveredWidget = m_rootWidget->recursiveGetChildByPos(g_window.getMousePosition(), false); + if(hoveredWidget && !hoveredWidget->isEnabled()) + hoveredWidget = nullptr; + //} + + if(hoveredWidget != m_hoveredWidget) { + UIWidgetPtr oldHovered = m_hoveredWidget; + m_hoveredWidget = hoveredWidget; + if(oldHovered) { + oldHovered->updateState(Fw::HoverState); + oldHovered->onHoverChange(false); + } + if(hoveredWidget) { + hoveredWidget->updateState(Fw::HoverState); + hoveredWidget->onHoverChange(true); + } + } + }; + + if(now) + func(); + else { + m_hoverUpdateScheduled = true; + g_dispatcher.addEvent(func); + } +} + +void UIManager::onWidgetAppear(const UIWidgetPtr& widget) +{ + if(widget->containsPoint(g_window.getMousePosition())) + updateHoveredWidget(); +} + +void UIManager::onWidgetDisappear(const UIWidgetPtr& widget) +{ + if(widget->containsPoint(g_window.getMousePosition())) + updateHoveredWidget(); +} + +void UIManager::onWidgetDestroy(const UIWidgetPtr& widget) +{ + AutoStat s(STATS_MAIN, "UIManager::onWidgetDestroy", stdext::format("%s (%s)", widget->getId(), widget->getParent() ? widget->getParent()->getId() : "")); + + // release input grabs + if(m_keyboardReceiver == widget) + resetKeyboardReceiver(); + + if(m_mouseReceiver == widget) + resetMouseReceiver(); + + if(m_hoveredWidget == widget) + updateHoveredWidget(); + + if(m_pressedWidget == widget) + updatePressedWidget(nullptr); + + if(m_draggingWidget == widget) + updateDraggingWidget(nullptr); + + if (!g_extras.debugWidgets) + return; + + if(widget == m_rootWidget || !m_rootWidget) + return; + + m_destroyedWidgets.push_back(widget); + + if(m_checkEvent && !m_checkEvent->isExecuted()) + return; + + m_checkEvent = g_dispatcher.scheduleEvent([this] { + g_lua.collectGarbage(); + UIWidgetList backupList(std::move(m_destroyedWidgets)); + m_destroyedWidgets.clear(); + g_dispatcher.scheduleEvent([backupList] { + g_lua.collectGarbage(); + for(const UIWidgetPtr& widget : backupList) { + if(widget->ref_count() != 1) + g_logger.warning(stdext::format("widget '%s' (parent: '%s' (%s), source: '%s') destroyed but still have %d reference(s) left", widget->getId(), widget->getParent() ? widget->getParent()->getId() : "", widget->getParentId(), widget->getSource(), widget->getUseCount()-1)); + } + }, 1); + }, 1000); +} + +void UIManager::clearStyles() +{ + m_styles.clear(); +} + +bool UIManager::importStyle(std::string file) +{ + try { + file = g_resources.guessFilePath(file, "otui"); + + OTMLDocumentPtr doc = OTMLDocument::parse(file); + + for(const OTMLNodePtr& styleNode : doc->children()) + importStyleFromOTML(styleNode); + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Failed to import UI styles from '%s': %s", file, e.what())); + return false; + } +} + +bool UIManager::importStyleFromString(std::string data) +{ + try { + OTMLDocumentPtr doc = OTMLDocument::parseString(data, g_lua.getCurrentSourcePath()); + for(const OTMLNodePtr& styleNode : doc->children()) + importStyleFromOTML(styleNode); + return true; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("Failed to import UI styles from '%s': %s", g_lua.getCurrentSourcePath(), e.what())); + return false; + } +} + +void UIManager::importStyleFromOTML(const OTMLNodePtr& styleNode) +{ + std::string tag = styleNode->tag(); + std::vector split = stdext::split(tag, "<"); + if(split.size() != 2) + throw OTMLException(styleNode, "not a valid style declaration"); + + std::string name = split[0]; + std::string base = split[1]; + bool unique = false; + + stdext::trim(name); + stdext::trim(base); + + if(name[0] == '#') { + name = name.substr(1); + unique = true; + + styleNode->setTag(name); + styleNode->writeAt("__unique", true); + } + + OTMLNodePtr oldStyle = m_styles[name]; + + // Warn about redefined styles + /* + if(!g_app.isRunning() && (oldStyle && !oldStyle->valueAt("__unique", false))) { + auto it = m_styles.find(name); + if(it != m_styles.end()) + g_logger.warning(stdext::format("style '%s' is being redefined", name)); + } + */ + + if(!oldStyle || !oldStyle->valueAt("__unique", false) || unique) { + OTMLNodePtr originalStyle = getStyle(base); + if(!originalStyle) + stdext::throw_exception(stdext::format("base style '%s', is not defined", base)); + OTMLNodePtr style = originalStyle->clone(); + style->merge(styleNode); + style->setTag(name); + m_styles[name] = style; + } +} + +OTMLNodePtr UIManager::getStyle(const std::string& styleName) +{ + auto it = m_styles.find(styleName); + if(it != m_styles.end()) + return m_styles[styleName]; + + // styles starting with UI are automatically defined + if(stdext::starts_with(styleName, "UI")) { + OTMLNodePtr node = OTMLNode::create(styleName); + node->writeAt("__class", styleName); + m_styles[styleName] = node; + return node; + } + + return nullptr; +} + +std::string UIManager::getStyleClass(const std::string& styleName) +{ + OTMLNodePtr style = getStyle(styleName); + if(style && style->get("__class")) + return style->valueAt("__class"); + return ""; +} + + +UIWidgetPtr UIManager::loadUIFromString(const std::string& data, const UIWidgetPtr& parent) +{ + try { + std::stringstream sstream; + sstream.clear(std::ios::goodbit); + sstream.write(&data[0], data.length()); + sstream.seekg(0, std::ios::beg); + OTMLDocumentPtr doc = OTMLDocument::parse(sstream, "(string)"); + UIWidgetPtr widget; + for (const OTMLNodePtr& node : doc->children()) { + std::string tag = node->tag(); + + // import styles in these files too + if (tag.find("<") != std::string::npos) + importStyleFromOTML(node); + else { + if (widget) + stdext::throw_exception("cannot have multiple main widgets in otui files"); + widget = createWidgetFromOTML(node, parent); + } + } + + return widget; + } catch (stdext::exception& e) { + g_logger.error(stdext::format("failed to load UI from string: %s", e.what())); + return nullptr; + } +} + +UIWidgetPtr UIManager::loadUI(std::string file, const UIWidgetPtr& parent) +{ + try { + file = g_resources.guessFilePath(file, "otui"); + + OTMLDocumentPtr doc = OTMLDocument::parse(file); + UIWidgetPtr widget; + for(const OTMLNodePtr& node : doc->children()) { + std::string tag = node->tag(); + + // import styles in these files too + if(tag.find("<") != std::string::npos) + importStyleFromOTML(node); + else { + if(widget) + stdext::throw_exception("cannot have multiple main widgets in otui files"); + widget = createWidgetFromOTML(node, parent); + } + } + + return widget; + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to load UI from '%s': %s", file, e.what())); + return nullptr; + } +} + +UIWidgetPtr UIManager::createWidget(const std::string& styleName, const UIWidgetPtr& parent) +{ + OTMLNodePtr node = OTMLNode::create(styleName); + try { + return createWidgetFromOTML(node, parent); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("failed to create widget from style '%s': %s", styleName, e.what())); + return nullptr; + } +} + +UIWidgetPtr UIManager::createWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent) +{ + OTMLNodePtr originalStyleNode = getStyle(widgetNode->tag()); + if(!originalStyleNode) + stdext::throw_exception(stdext::format("'%s' is not a defined style", widgetNode->tag())); + + OTMLNodePtr styleNode = originalStyleNode->clone(); + styleNode->merge(widgetNode); + + std::string widgetType = styleNode->valueAt("__class"); + + // call widget creation from lua + UIWidgetPtr widget = g_lua.callGlobalField(widgetType, "create"); + if(parent) + parent->addChild(widget); + + if(widget) { + widget->callLuaField("onCreate"); + + widget->setStyleFromNode(styleNode); + + for(const OTMLNodePtr& childNode : styleNode->children()) { + if(!childNode->isUnique()) { + createWidgetFromOTML(childNode, widget); + styleNode->removeChild(childNode); + } + } + } else + stdext::throw_exception(stdext::format("unable to create widget of type '%s'", widgetType)); + + widget->callLuaField("onSetup"); + + return widget; +} diff --git a/src/framework/ui/uimanager.h b/src/framework/ui/uimanager.h new file mode 100644 index 0000000..a90e7b6 --- /dev/null +++ b/src/framework/ui/uimanager.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIMANAGER_H +#define UIMANAGER_H + +#include "declarations.h" +#include "uiwidget.h" +#include +#include + +//@bindsingleton g_ui +class UIManager +{ +public: + void init(); + void terminate(); + + void render(Fw::DrawPane drawPane); + void resize(const Size& size); + void inputEvent(const InputEvent& event); + + void updatePressedWidget(const UIWidgetPtr& newPressedWidget, const Point& clickedPos = Point(), bool fireClicks = true); + bool updateDraggingWidget(const UIWidgetPtr& draggingWidget, const Point& clickedPos = Point()); + void updateHoveredWidget(bool now = false); + + void clearStyles(); + bool importStyle(std::string file); + bool importStyleFromString(std::string data); + void importStyleFromOTML(const OTMLNodePtr& styleNode); + OTMLNodePtr getStyle(const std::string& styleName); + std::string getStyleClass(const std::string& styleName); + + UIWidgetPtr loadUIFromString(const std::string& data, const UIWidgetPtr& parent); + UIWidgetPtr loadUI(std::string file, const UIWidgetPtr& parent); + UIWidgetPtr displayUI(const std::string& file) { return loadUI(file, m_rootWidget); } + UIWidgetPtr createWidget(const std::string& styleName, const UIWidgetPtr& parent); + UIWidgetPtr createWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent); + + void setMouseReceiver(const UIWidgetPtr& widget) { m_mouseReceiver = widget; } + void setKeyboardReceiver(const UIWidgetPtr& widget) { m_keyboardReceiver = widget; } + void setDebugBoxesDrawing(bool enabled) { m_drawDebugBoxes = enabled; } + void resetMouseReceiver() { m_mouseReceiver = m_rootWidget; } + void resetKeyboardReceiver() { m_keyboardReceiver = m_rootWidget; } + UIWidgetPtr getMouseReceiver() { return m_mouseReceiver; } + UIWidgetPtr getKeyboardReceiver() { return m_keyboardReceiver; } + UIWidgetPtr getDraggingWidget() { return m_draggingWidget; } + UIWidgetPtr getHoveredWidget() { return m_hoveredWidget; } + UIWidgetPtr getPressedWidget() { return m_pressedWidget; } + UIWidgetPtr getRootWidget() { return m_rootWidget; } + bool isMouseGrabbed() { return m_mouseReceiver != m_rootWidget; } + bool isKeyboardGrabbed() { return m_keyboardReceiver != m_rootWidget; } + + bool isDrawingDebugBoxes() { return m_drawDebugBoxes; } + +protected: + void onWidgetAppear(const UIWidgetPtr& widget); + void onWidgetDisappear(const UIWidgetPtr& widget); + void onWidgetDestroy(const UIWidgetPtr& widget); + + friend class UIWidget; + +private: + UIWidgetPtr m_rootWidget; + UIWidgetPtr m_mouseReceiver; + UIWidgetPtr m_keyboardReceiver; + UIWidgetPtr m_draggingWidget; + UIWidgetPtr m_hoveredWidget; + UIWidgetPtr m_pressedWidget; + stdext::boolean m_hoverUpdateScheduled; + stdext::boolean m_drawDebugBoxes; + std::unordered_map m_styles; + UIWidgetList m_destroyedWidgets; + ScheduledEventPtr m_checkEvent; + +}; + +extern UIManager g_ui; + +#endif diff --git a/src/framework/ui/uitextedit.cpp b/src/framework/ui/uitextedit.cpp new file mode 100644 index 0000000..1b41651 --- /dev/null +++ b/src/framework/ui/uitextedit.cpp @@ -0,0 +1,931 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uitextedit.h" +#include "uimanager.h" +#include +#include +#include +#include +#include +#include +#include + +UITextEdit::UITextEdit() +{ + m_cursorPos = 0; + m_textAlign = Fw::AlignTopLeft; + m_textHidden = false; + m_shiftNavigation = false; + m_multiline = false; +#ifdef ANDROID + m_cursorVisible = false; +#else + m_cursorVisible = true; +#endif + m_cursorInRange = true; + m_maxLength = 0; + m_editable = true; + m_selectable = true; + m_autoScroll = true; + m_autoSubmit = false; + m_changeCursorImage = true; + m_selectionReference = 0; + m_selectionStart = 0; + m_selectionEnd = 0; + m_updatesEnabled = true; + m_selectionColor = Color::white; + m_selectionBackgroundColor = Color::black; + m_glyphsMustRecache = true; + blinkCursor(); +} + +void UITextEdit::drawSelf(Fw::DrawPane drawPane) +{ + if(drawPane != Fw::ForegroundPane) + return; + + drawBackground(m_rect); + drawBorder(m_rect); + drawImage(m_rect); + drawIcon(m_rect); + + int textLength = m_glyphsCoords.size(); + const TexturePtr& texture = m_font->getTexture(); + if(!texture) + return; + + bool glyphsMustRecache = m_glyphsMustRecache; + if(glyphsMustRecache) + m_glyphsMustRecache = false; + + if(m_color != Color::alpha) { + if(glyphsMustRecache) { + m_glyphsTextCoordsBuffer.clear(); + for(int i=0;iaddTextureCoords(m_glyphsTextCoordsBuffer, texture, m_color); + } + + if(hasSelection()) { + if(glyphsMustRecache) { + m_glyphsSelectCoordsBuffer.clear(); + for(int i=m_selectionStart;iaddFillCoords(m_glyphsSelectCoordsBuffer, m_selectionBackgroundColor); + g_drawQueue->addTextureCoords(m_glyphsSelectCoordsBuffer, texture, m_selectionColor); + } + + // render cursor + if(isExplicitlyEnabled() && m_cursorVisible && m_cursorInRange && isActive() && m_cursorPos >= 0) { + VALIDATE(m_cursorPos <= textLength); + // draw every 333ms + const int delay = 333; + int elapsed = g_clock.millis() - m_cursorTicks; + if(elapsed <= delay) { + Rect cursorRect; + // when cursor is at 0 + if(m_cursorPos == 0) + cursorRect = Rect(m_rect.left()+m_padding.left, m_rect.top()+m_padding.top, 1, m_font->getGlyphHeight()); + else + cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight()); + + if (hasSelection() && m_cursorPos >= m_selectionStart && m_cursorPos <= m_selectionEnd) + g_drawQueue->addFilledRect(cursorRect, m_selectionColor); + else + g_drawQueue->addFilledRect(cursorRect, m_color); + } else if(elapsed >= 2*delay) { + m_cursorTicks = g_clock.millis(); + } + } +} + +void UITextEdit::update(bool focusCursor) +{ + if(!m_updatesEnabled) + return; + + std::string text = getDisplayedText(); + m_drawText = text; + int textLength = text.length(); + + // prevent glitches + if(m_rect.isEmpty()) + return; + + // recache coords buffers + recacheGlyphs(); + + // map glyphs positions + Size textBoxSize; + const std::vector& glyphsPositions = m_font->calculateGlyphsPositions(text, m_textAlign, &textBoxSize); + const Rect *glyphsTextureCoords = m_font->getGlyphsTextureCoords(); + const Size *glyphsSize = m_font->getGlyphsSize(); + int glyph; + + // update rect size + if(!m_rect.isValid() || m_textHorizontalAutoResize || m_textVerticalAutoResize) { + textBoxSize += Size(m_padding.left + m_padding.right, m_padding.top + m_padding.bottom) + m_textOffset.toSize(); + Size size = getSize(); + if(size.width() <= 0 || (m_textHorizontalAutoResize && !m_textWrap)) + size.setWidth(textBoxSize.width()); + if(size.height() <= 0 || m_textVerticalAutoResize) + size.setHeight(textBoxSize.height()); + setSize(size); + } + + // resize just on demand + if(textLength != (int)m_glyphsCoords.size()) { + m_glyphsCoords.resize(textLength); + m_glyphsTexCoords.resize(textLength); + } + + Point oldTextAreaOffset = m_textVirtualOffset; + + if(textBoxSize.width() <= getPaddingRect().width()) + m_textVirtualOffset.x = 0; + if(textBoxSize.height() <= getPaddingRect().height()) + m_textVirtualOffset.y = 0; + + // readjust start view area based on cursor position + m_cursorInRange = false; + if(focusCursor && m_autoScroll) { + if(m_cursorPos > 0 && textLength > 0) { + VALIDATE(m_cursorPos <= textLength); + Rect virtualRect(m_textVirtualOffset, m_rect.size() - Size(m_padding.left+m_padding.right, 0)); // previous rendered virtual rect + int pos = m_cursorPos - 1; // element before cursor + glyph = (uchar)text[pos]; // glyph of the element before cursor + Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]); + + // if the cursor is not on the previous rendered virtual rect we need to update it + if(!virtualRect.contains(glyphRect.topLeft()) || !virtualRect.contains(glyphRect.bottomRight())) { + // calculate where is the first glyph visible + Point startGlyphPos; + startGlyphPos.y = std::max(glyphRect.bottom() - virtualRect.height(), 0); + startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0); + + // find that glyph + for(pos = 0; pos < textLength; ++pos) { + glyph = (uchar)text[pos]; + glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]); + glyphRect.setTop(std::max(glyphRect.top() - m_font->getYOffset() - m_font->getGlyphSpacing().height(), 0)); + glyphRect.setLeft(std::max(glyphRect.left() - m_font->getGlyphSpacing().width(), 0)); + + // first glyph entirely visible found + if(glyphRect.topLeft().x >= startGlyphPos.x && glyphRect.topLeft().y >= startGlyphPos.y) { + m_textVirtualOffset.x = glyphsPositions[pos].x; + m_textVirtualOffset.y = glyphsPositions[pos].y - m_font->getYOffset(); + break; + } + } + } + } else { + m_textVirtualOffset = Point(0,0); + } + m_cursorInRange = true; + } else { + if(m_cursorPos > 0 && textLength > 0) { + Rect virtualRect(m_textVirtualOffset, m_rect.size() - Size(2*m_padding.left+m_padding.right, 0) ); // previous rendered virtual rect + int pos = m_cursorPos - 1; // element before cursor + glyph = (uchar)text[pos]; // glyph of the element before cursor + Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]); + if(virtualRect.contains(glyphRect.topLeft()) && virtualRect.contains(glyphRect.bottomRight())) + m_cursorInRange = true; + } else { + m_cursorInRange = true; + } + } + + bool fireAreaUpdate = false; + if(oldTextAreaOffset != m_textVirtualOffset) + fireAreaUpdate = true; + + Rect textScreenCoords = m_rect; + textScreenCoords.expandLeft(-m_padding.left); + textScreenCoords.expandRight(-m_padding.right); + textScreenCoords.expandBottom(-m_padding.bottom); + textScreenCoords.expandTop(-m_padding.top); + m_drawArea = textScreenCoords; + + if(textScreenCoords.size() != m_textVirtualSize) { + m_textVirtualSize = textScreenCoords.size(); + fireAreaUpdate = true; + } + + Size totalSize = textBoxSize; + if(totalSize.width() < m_textVirtualSize.width()) + totalSize.setWidth(m_textVirtualSize.height()); + if(totalSize.height() < m_textVirtualSize.height()) + totalSize.setHeight(m_textVirtualSize.height()); + if(m_textTotalSize != totalSize) { + m_textTotalSize = totalSize; + fireAreaUpdate = true; + } + + if(m_textAlign & Fw::AlignBottom) { + m_drawArea.translate(0, textScreenCoords.height() - textBoxSize.height()); + } else if(m_textAlign & Fw::AlignVerticalCenter) { + m_drawArea.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2); + } else { // AlignTop + } + + if(m_textAlign & Fw::AlignRight) { + m_drawArea.translate(textScreenCoords.width() - textBoxSize.width(), 0); + } else if(m_textAlign & Fw::AlignHorizontalCenter) { + m_drawArea.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0); + } else { // AlignLeft + + } + + for(int i = 0; i < textLength; ++i) { + glyph = (uchar)text[i]; + m_glyphsCoords[i].clear(); + + // skip invalid glyphs + if(glyph < 32 && glyph != (uchar)'\n') + continue; + + // calculate initial glyph rect and texture coords + Rect glyphScreenCoords(glyphsPositions[i], glyphsSize[glyph]); + Rect glyphTextureCoords = glyphsTextureCoords[glyph]; + + // first translate to align position + if(m_textAlign & Fw::AlignBottom) { + glyphScreenCoords.translate(0, textScreenCoords.height() - textBoxSize.height()); + } else if(m_textAlign & Fw::AlignVerticalCenter) { + glyphScreenCoords.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2); + } else { // AlignTop + // nothing to do + } + + if(m_textAlign & Fw::AlignRight) { + glyphScreenCoords.translate(textScreenCoords.width() - textBoxSize.width(), 0); + } else if(m_textAlign & Fw::AlignHorizontalCenter) { + glyphScreenCoords.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0); + } else { // AlignLeft + // nothing to do + } + + // only render glyphs that are after startRenderPosition + if(glyphScreenCoords.bottom() < m_textVirtualOffset.y || glyphScreenCoords.right() < m_textVirtualOffset.x) + continue; + + // bound glyph topLeft to startRenderPosition + if(glyphScreenCoords.top() < m_textVirtualOffset.y) { + glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_textVirtualOffset.y - glyphScreenCoords.top())); + glyphScreenCoords.setTop(m_textVirtualOffset.y); + } + if(glyphScreenCoords.left() < m_textVirtualOffset.x) { + glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_textVirtualOffset.x - glyphScreenCoords.left())); + glyphScreenCoords.setLeft(m_textVirtualOffset.x); + } + + // subtract startInternalPos + glyphScreenCoords.translate(-m_textVirtualOffset); + + // translate rect to screen coords + glyphScreenCoords.translate(textScreenCoords.topLeft()); + + // only render if glyph rect is visible on screenCoords + if(!textScreenCoords.intersects(glyphScreenCoords)) + continue; + + // bound glyph bottomRight to screenCoords bottomRight + if(glyphScreenCoords.bottom() > textScreenCoords.bottom()) { + glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (textScreenCoords.bottom() - glyphScreenCoords.bottom())); + glyphScreenCoords.setBottom(textScreenCoords.bottom()); + } + if(glyphScreenCoords.right() > textScreenCoords.right()) { + glyphTextureCoords.setRight(glyphTextureCoords.right() + (textScreenCoords.right() - glyphScreenCoords.right())); + glyphScreenCoords.setRight(textScreenCoords.right()); + } + + // render glyph + m_glyphsCoords[i] = glyphScreenCoords; + m_glyphsTexCoords[i] = glyphTextureCoords; + } + + if(fireAreaUpdate) + onTextAreaUpdate(m_textVirtualOffset, m_textVirtualSize, m_textTotalSize); +} + +void UITextEdit::setCursorPos(int pos) +{ + if(pos < 0) + pos = m_text.length(); + + if(pos != m_cursorPos) { + if(pos < 0) + m_cursorPos = 0; + else if((uint)pos >= m_text.length()) + m_cursorPos = m_text.length(); + else + m_cursorPos = pos; + update(true); + } +} + +void UITextEdit::setSelection(int start, int end) +{ + if(start == m_selectionStart && end == m_selectionEnd) + return; + + if(start > end) + std::swap(start, end); + + if(end == -1) + end = m_text.length(); + + m_selectionStart = stdext::clamp(start, 0, (int)m_text.length()); + m_selectionEnd = stdext::clamp(end, 0, (int)m_text.length()); + recacheGlyphs(); +} + +void UITextEdit::setTextHidden(bool hidden) +{ + m_textHidden = true; + update(true); +} + +void UITextEdit::setTextVirtualOffset(const Point& offset) +{ + m_textVirtualOffset = offset; + update(); +} + +void UITextEdit::appendText(std::string text) +{ + if(hasSelection()) + del(); + + if(m_cursorPos >= 0) { + // replace characters that are now allowed + if(!m_multiline) + stdext::replace_all(text, "\n", " "); + stdext::replace_all(text, "\r", ""); + stdext::replace_all(text, "\t", " "); + + if(text.length() > 0) { + // only add text if textedit can add it + if(m_maxLength > 0 && m_text.length() + text.length() > m_maxLength) + return; + + // only ignore text append if it contains invalid characters + if(m_validCharacters.size() > 0) { + for(uint i = 0; i < text.size(); ++i) { + if(m_validCharacters.find(text[i]) == std::string::npos) + return; + } + } + + std::string tmp = m_text; + tmp.insert(m_cursorPos, text); + m_cursorPos += text.length(); + setText(tmp); + } + } +} + +void UITextEdit::appendCharacter(char c) +{ + if((c == '\n' && !m_multiline) || c == '\r') + return; + + if(hasSelection()) + del(); + + if(m_cursorPos >= 0) { + if(m_maxLength > 0 && m_text.length() + 1 > m_maxLength) + return; + + if(m_validCharacters.size() > 0 && m_validCharacters.find(c) == std::string::npos) + return; + + std::string tmp; + tmp = c; + std::string tmp2 = m_text; + tmp2.insert(m_cursorPos, tmp); + m_cursorPos++; + setText(tmp2); + } +} + +void UITextEdit::removeCharacter(bool right) +{ + std::string tmp = m_text; + if(m_cursorPos >= 0 && tmp.length() > 0) { + if((uint)m_cursorPos >= tmp.length()) { + tmp.erase(tmp.begin() + (--m_cursorPos)); + } else { + if(right) + tmp.erase(tmp.begin() + m_cursorPos); + else if(m_cursorPos > 0) + tmp.erase(tmp.begin() + --m_cursorPos); + } + setText(tmp); + } +} + +void UITextEdit::blinkCursor() +{ + m_cursorTicks = g_clock.millis(); +} + +void UITextEdit::del(bool right) +{ + if(hasSelection()) { + std::string tmp = m_text; + tmp.erase(m_selectionStart, m_selectionEnd - m_selectionStart); + + setCursorPos(m_selectionStart); + clearSelection(); + setText(tmp); + } else + removeCharacter(right); +} + +void UITextEdit::paste(const std::string& text) +{ + if(hasSelection()) + del(); + appendText(text); +} + +std::string UITextEdit::copy() +{ + std::string text; + if(hasSelection()) { + text = getSelection(); + g_window.setClipboardText(text); + } + return text; +} + +std::string UITextEdit::cut() +{ + std::string text = copy(); + del(); + return text; +} + +void UITextEdit::wrapText() +{ + setText(m_font->wrapText(m_text, getPaddingRect().width() - m_textOffset.x)); +} + +void UITextEdit::moveCursorHorizontally(bool right) +{ + if(right) { + if((uint)m_cursorPos+1 <= m_text.length()) + m_cursorPos++; + else + m_cursorPos = 0; + } else { + if(m_cursorPos-1 >= 0) + m_cursorPos--; + else + m_cursorPos = m_text.length(); + } + + blinkCursor(); + update(true); +} + +void UITextEdit::moveCursorVertically(bool up) +{ + if (up) { + int shifted = 0; + int i = m_cursorPos - 1; + int limit = 0; + bool nextLine = false; + for (; i > 0; --i) { + if (m_text[i] == '\n') { + if (nextLine) { + i += 1; + break; + } + nextLine = true; + limit = i; + } else if(!nextLine) { + shifted++; + } + } + i += shifted; + m_cursorPos = std::min(limit, i); + } else { + int shifted = 0; + int i = m_cursorPos - 1; + for (; i >= 0; --i) { + if (m_text[i] == '\n') { + break; + } else { + shifted++; + } + } + i = m_cursorPos; + + bool nextLine = false; + int limit = m_text.size(); + int moveTo = m_text.size(); + for (; i < (int)m_text.size(); ++i) { + if (m_text[i] == '\n') { + if (nextLine) { + limit = i; + break; + } + nextLine = true; + moveTo = i + 1; + } + } + moveTo += shifted; + m_cursorPos = std::min(limit, moveTo); + } + blinkCursor(); + update(true); +} + +int UITextEdit::getTextPos(Point pos) +{ + int textLength = m_text.length(); + + // find any glyph that is actually on the + int candidatePos = -1; + Rect firstGlyphRect, lastGlyphRect; + for(int i=0;igetYOffset() + m_font->getGlyphSpacing().height()); + clickGlyphRect.expandLeft(m_font->getGlyphSpacing().width()+1); + if(clickGlyphRect.contains(pos)) { + candidatePos = i; + break; + } + else if(pos.y >= clickGlyphRect.top() && pos.y <= clickGlyphRect.bottom()) { + if(pos.x <= clickGlyphRect.left()) { + candidatePos = i; + break; + } else if(pos.x >= clickGlyphRect.right()) + candidatePos = i+1; + } + } + + if(textLength > 0) { + if(pos.y < firstGlyphRect.top()) + return 0; + else if(pos.y > lastGlyphRect.bottom()) + return textLength; + } + + return candidatePos; +} + +std::string UITextEdit::getDisplayedText() +{ + std::string text; + if(m_textHidden) + text = std::string(m_text.length(), '*'); + else + text = m_text; + + if(m_textWrap && m_rect.isValid()) + text = m_font->wrapText(text, getPaddingRect().width() - m_textOffset.x); + + return text; +} + +std::string UITextEdit::getSelection() +{ + if(!hasSelection()) + return std::string(); + return m_text.substr(m_selectionStart, m_selectionEnd - m_selectionStart); +} + +void UITextEdit::updateText() +{ + if(m_cursorPos > (int)m_text.length()) + m_cursorPos = m_text.length(); + + // any text changes reset the selection + if(m_selectable) { + m_selectionEnd = 0; + m_selectionStart = 0; + } + + blinkCursor(); + update(true); +} + +void UITextEdit::onHoverChange(bool hovered) +{ + if(m_changeCursorImage) { + if(hovered && !g_mouse.isCursorChanged()) + g_mouse.pushCursor("text"); + else + g_mouse.popCursor("text"); + } +} + +void UITextEdit::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "text") { + setText(node->value()); + setCursorPos(m_text.length()); + } else if(node->tag() == "text-hidden") + setTextHidden(node->value()); + else if(node->tag() == "shift-navigation") + setShiftNavigation(node->value()); + else if(node->tag() == "multiline") + setMultiline(node->value()); + else if(node->tag() == "max-length") + setMaxLength(node->value()); + else if(node->tag() == "editable") + setEditable(node->value()); + else if(node->tag() == "selectable") + setSelectable(node->value()); + else if(node->tag() == "selection-color") + setSelectionColor(node->value()); + else if(node->tag() == "selection-background-color") + setSelectionBackgroundColor(node->value()); + else if(node->tag() == "selection") { + Point selectionRange = node->value(); + setSelection(selectionRange.x, selectionRange.y); + } + else if(node->tag() == "cursor-visible") + setCursorVisible(node->value()); + else if(node->tag() == "change-cursor-image") + setChangeCursorImage(node->value()); + else if (node->tag() == "auto-scroll") + setAutoScroll(node->value()); + else if (node->tag() == "text-auto-submit") + setAutoSubmit(node->value()); + } +} + +void UITextEdit::onGeometryChange(const Rect& oldRect, const Rect& newRect) +{ + update(true); + UIWidget::onGeometryChange(oldRect, newRect); +} + +void UITextEdit::onFocusChange(bool focused, Fw::FocusReason reason) +{ + if(focused) { + if(reason == Fw::KeyboardFocusReason) + setCursorPos(m_text.length()); + else + blinkCursor(); + update(true); + } else if(m_selectable) + clearSelection(); + UIWidget::onFocusChange(focused, reason); +} + +bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks) +{ + if(UIWidget::onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks)) + return true; + + if(keyboardModifiers == Fw::KeyboardNoModifier) { + if(keyCode == Fw::KeyDelete && m_editable) { // erase right character + if(hasSelection() || !m_text.empty()) { + del(true); + return true; + } + } else if(keyCode == Fw::KeyBackspace && m_editable) { // erase left character + if(hasSelection() || !m_text.empty()) { + del(false); + return true; + } + } else if(keyCode == Fw::KeyRight && !m_shiftNavigation) { // move cursor right + clearSelection(); + moveCursorHorizontally(true); + return true; + } else if(keyCode == Fw::KeyLeft && !m_shiftNavigation) { // move cursor left + clearSelection(); + moveCursorHorizontally(false); + return true; + } else if(keyCode == Fw::KeyHome) { // move cursor to first character + if(m_cursorPos != 0) { + clearSelection(); + setCursorPos(0); + return true; + } + } else if(keyCode == Fw::KeyEnd) { // move cursor to last character + if(m_cursorPos != (int)m_text.length()) { + clearSelection(); + setCursorPos(m_text.length()); + return true; + } + } else if(keyCode == Fw::KeyTab && !m_shiftNavigation) { + if (m_multiline) { + appendText(" "); + } else { + clearSelection(); + if (UIWidgetPtr parent = getParent()) + parent->focusNextChild(Fw::KeyboardFocusReason, true); + } + return true; + } else if(keyCode == Fw::KeyEnter && m_multiline && m_editable) { + appendCharacter('\n'); + return true; + } else if(keyCode == Fw::KeyUp && !m_shiftNavigation && m_multiline) { + moveCursorVertically(true); + return true; + } else if(keyCode == Fw::KeyDown && !m_shiftNavigation && m_multiline) { + moveCursorVertically(false); + return true; + } + } else if(keyboardModifiers == Fw::KeyboardCtrlModifier) { + if(keyCode == Fw::KeyV && m_editable) { + paste(g_window.getClipboardText()); + return true; + } else if(keyCode == Fw::KeyX && m_editable && m_selectable) { + if(hasSelection()) { + cut(); + return true; + } + } else if(keyCode == Fw::KeyC && m_selectable) { + if(hasSelection()) { + copy(); + return true; + } + } else if(keyCode == Fw::KeyA && m_selectable) { + if(m_text.length() > 0) { + selectAll(); + return true; + } + } + } else if(keyboardModifiers == Fw::KeyboardShiftModifier) { + if(keyCode == Fw::KeyTab && !m_shiftNavigation) { + if(UIWidgetPtr parent = getParent()) + parent->focusPreviousChild(Fw::KeyboardFocusReason, true); + return true; + } else if(keyCode == Fw::KeyRight || keyCode == Fw::KeyLeft || ((keyCode == Fw::KeyUp || keyCode == Fw::KeyDown) && m_multiline)) { + + int oldCursorPos = m_cursorPos; + + if(keyCode == Fw::KeyRight) // move cursor right + moveCursorHorizontally(true); + else if(keyCode == Fw::KeyLeft) // move cursor left + moveCursorHorizontally(false); + else if (keyCode == Fw::KeyUp && !m_shiftNavigation && m_multiline) + moveCursorVertically(true); + else if (keyCode == Fw::KeyDown && !m_shiftNavigation && m_multiline) + moveCursorVertically(false); + + if(m_shiftNavigation) + clearSelection(); + else { + if(!hasSelection()) + m_selectionReference = oldCursorPos; + setSelection(m_selectionReference, m_cursorPos); + } + return true; + } else if(keyCode == Fw::KeyHome) { // move cursor to first character + if(m_cursorPos != 0) { + setSelection(m_cursorPos, 0); + setCursorPos(0); + return true; + } + } else if(keyCode == Fw::KeyEnd) { // move cursor to last character + if(m_cursorPos != (int)m_text.length()) { + setSelection(m_cursorPos, m_text.length()); + setCursorPos(m_text.length()); + return true; + } + } + } + + return false; +} + +bool UITextEdit::onKeyText(const std::string& keyText) +{ + if(m_editable) { +#ifdef ANDROID + setText(keyText); + if (m_autoSubmit) { + InputEvent event; + event.reset(Fw::KeyDownInputEvent); + event.keyCode = Fw::KeyEnter; + g_ui.inputEvent(event); + event.reset(Fw::KeyUpInputEvent); + event.keyCode = Fw::KeyEnter; + g_ui.inputEvent(event); + } +#else + appendText(keyText); +#endif + return true; + } + return false; +} + +bool UITextEdit::onMousePress(const Point& mousePos, Fw::MouseButton button) +{ + if(UIWidget::onMousePress(mousePos, button)) + return true; + + if(button == Fw::MouseLeftButton) { +#ifdef ANDROID + if (m_editable) { + g_window.showTextEditor("Edit text", "", m_text, m_multiline ? 1 : 0); + return true; + } +#else + int pos = getTextPos(mousePos); + if(pos >= 0) { + setCursorPos(pos); + + if(m_selectable) { + m_selectionReference = pos; + setSelection(pos, pos); + } + } +#endif + return true; + } + return false; +} + +bool UITextEdit::onMouseRelease(const Point& mousePos, Fw::MouseButton button) +{ + return UIWidget::onMouseRelease(mousePos, button); +} + +bool UITextEdit::onMouseMove(const Point& mousePos, const Point& mouseMoved) +{ + if(UIWidget::onMouseMove(mousePos, mouseMoved)) + return true; + + if(m_selectable && isPressed()) { + int pos = getTextPos(mousePos); + if(pos >= 0 && m_selectionReference != -1) { + setSelection(m_selectionReference, pos); + setCursorPos(pos); + } + return true; + } + return false; +} + +bool UITextEdit::onDoubleClick(const Point& mousePos) +{ + if(UIWidget::onDoubleClick(mousePos)) + return true; + + int pos = getTextPos(mousePos); + if (m_selectable && pos >= 0 && m_text.length() > 0) { + m_selectionReference = -1; + int firstSpace = 0; + int lastSpace = m_text.length(); + for (int i = 0; i < pos && i < (int)m_text.length(); ++i) { + if (m_text[i] == ' ' || m_text[i] == '\t' || m_text[i] == '\n') { + firstSpace = i + 1; + } + } + for (int i = pos; i < (int)m_text.length(); ++i) { + if (m_text[i] == ' ' || m_text[i] == '\t' || m_text[i] == '\n') { + lastSpace = i; + break; + } + } + setSelection(firstSpace, lastSpace); + return true; + } + + return false; +} + +void UITextEdit::onTextAreaUpdate(const Point& offset, const Size& visibleSize, const Size& totalSize) +{ + callLuaField("onTextAreaUpdate", offset, visibleSize, totalSize); +} diff --git a/src/framework/ui/uitextedit.h b/src/framework/ui/uitextedit.h new file mode 100644 index 0000000..9c9a485 --- /dev/null +++ b/src/framework/ui/uitextedit.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UITEXTEDIT_H +#define UITEXTEDIT_H + +#include "uiwidget.h" + +// @bindclass +class UITextEdit : public UIWidget +{ +public: + UITextEdit(); + + void drawSelf(Fw::DrawPane drawPane); + +private: + void update(bool focusCursor = false); + +public: + void setCursorPos(int pos); + void setSelection(int start, int end); + void setCursorVisible(bool enable) { m_cursorVisible = enable; } + void setChangeCursorImage(bool enable) { m_changeCursorImage = enable; } + void setTextHidden(bool hidden); + void setValidCharacters(const std::string validCharacters) { m_validCharacters = validCharacters; } + void setShiftNavigation(bool enable) { m_shiftNavigation = enable; } + void setMultiline(bool enable) { m_multiline = enable; } + void setMaxLength(uint maxLength) { m_maxLength = maxLength; } + void setTextVirtualOffset(const Point& offset); + void setEditable(bool editable) { m_editable = editable; } + void setSelectable(bool selectable) { m_selectable = selectable; } + void setSelectionColor(const Color& color) { m_selectionColor = color; } + void setSelectionBackgroundColor(const Color& color) { m_selectionBackgroundColor = color; } + void setAutoScroll(bool autoScroll) { m_autoScroll = autoScroll; } + void setAutoSubmit(bool autoSubmit) { m_autoSubmit = autoSubmit; } + + void moveCursorHorizontally(bool right); + void moveCursorVertically(bool up); + void appendText(std::string text); + void appendCharacter(char c); + void removeCharacter(bool right); + void blinkCursor(); + + void del(bool right = false); + void paste(const std::string& text); + std::string copy(); + std::string cut(); + void selectAll() { setSelection(0, m_text.length()); } + void clearSelection() { setSelection(0, 0); } + + void wrapText(); + std::string getDisplayedText(); + std::string getSelection(); + int getTextPos(Point pos); + int getCursorPos() { return m_cursorPos; } + Point getTextVirtualOffset() { return m_textVirtualOffset; } + Size getTextVirtualSize() { return m_textVirtualSize; } + Size getTextTotalSize() { return m_textTotalSize; } + uint getMaxLength() { return m_maxLength; } + int getSelectionStart() { return m_selectionStart; } + int getSelectionEnd() { return m_selectionEnd; } + Color getSelectionColor() { return m_selectionColor; } + Color getSelectionBackgroundColor() { return m_selectionBackgroundColor; } + bool hasSelection() { return m_selectionEnd - m_selectionStart > 0; } + bool isCursorVisible() { return m_cursorVisible; } + bool isChangingCursorImage() { return m_changeCursorImage; } + bool isTextHidden() { return m_textHidden; } + bool isShiftNavigation() { return m_shiftNavigation; } + bool isMultiline() { return m_multiline; } + bool isEditable() { return m_editable; } + bool isSelectable() { return m_selectable; } + bool isAutoScrolling() { return m_autoScroll; } + +protected: + void updateText(); + + virtual void onHoverChange(bool hovered); + virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + virtual void onGeometryChange(const Rect& oldRect, const Rect& newRect); + virtual void onFocusChange(bool focused, Fw::FocusReason reason); + virtual bool onKeyText(const std::string& keyText); + virtual bool onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks); + virtual bool onMousePress(const Point& mousePos, Fw::MouseButton button); + virtual bool onMouseRelease(const Point& mousePos, Fw::MouseButton button); + virtual bool onMouseMove(const Point& mousePos, const Point& mouseMoved); + virtual bool onDoubleClick(const Point& mousePos); + virtual void onTextAreaUpdate(const Point& vitualOffset, const Size& virtualSize, const Size& totalSize); + +private: + void disableUpdates() { m_updatesEnabled = false; } + void enableUpdates() { m_updatesEnabled = true; } + void recacheGlyphs() { m_glyphsMustRecache = true; } + + Rect m_drawArea; + int m_cursorPos; + Point m_textVirtualOffset; + Size m_textVirtualSize; + Size m_textTotalSize; + ticks_t m_cursorTicks; + bool m_textHidden; + bool m_shiftNavigation; + bool m_multiline; + bool m_cursorInRange; + bool m_cursorVisible; + bool m_editable; + bool m_changeCursorImage; + std::string m_validCharacters; + uint m_maxLength; + bool m_updatesEnabled; + bool m_autoScroll; + bool m_autoSubmit; + + bool m_selectable; + int m_selectionReference; + int m_selectionStart; + int m_selectionEnd; + + Color m_selectionColor; + Color m_selectionBackgroundColor; + + std::vector m_glyphsCoords; + std::vector m_glyphsTexCoords; + + CoordsBuffer m_glyphsTextCoordsBuffer; + CoordsBuffer m_glyphsSelectCoordsBuffer; + bool m_glyphsMustRecache; +}; + +#endif diff --git a/src/framework/ui/uitranslator.cpp b/src/framework/ui/uitranslator.cpp new file mode 100644 index 0000000..abb9b8e --- /dev/null +++ b/src/framework/ui/uitranslator.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uitranslator.h" +#include +#include + +Fw::AlignmentFlag Fw::translateAlignment(std::string aligment) +{ + boost::to_lower(aligment); + boost::erase_all(aligment, " "); + if(aligment == "topleft") + return Fw::AlignTopLeft; + else if(aligment == "topright") + return Fw::AlignTopRight; + else if(aligment == "bottomleft") + return Fw::AlignBottomLeft; + else if(aligment == "bottomright") + return Fw::AlignBottomRight; + else if(aligment == "left") + return Fw::AlignLeftCenter; + else if(aligment == "right") + return Fw::AlignRightCenter; + else if(aligment == "top") + return Fw::AlignTopCenter; + else if(aligment == "bottom") + return Fw::AlignBottomCenter; + else if(aligment == "center") + return Fw::AlignCenter; + return Fw::AlignNone; +} + +Fw::AnchorEdge Fw::translateAnchorEdge(std::string anchorEdge) +{ + boost::to_lower(anchorEdge); + boost::erase_all(anchorEdge, " "); + if(anchorEdge == "left") + return Fw::AnchorLeft; + else if(anchorEdge == "right") + return Fw::AnchorRight; + else if(anchorEdge == "top") + return Fw::AnchorTop; + else if(anchorEdge == "bottom") + return Fw::AnchorBottom; + else if(anchorEdge == "horizontalcenter") + return Fw::AnchorHorizontalCenter; + else if(anchorEdge == "verticalcenter") + return Fw::AnchorVerticalCenter; + return Fw::AnchorNone; +} + +Fw::WidgetState Fw::translateState(std::string state) +{ + boost::to_lower(state); + boost::trim(state); + if(state == "active") + return Fw::ActiveState; + else if(state == "focus") + return Fw::FocusState; + else if(state == "hover") + return Fw::HoverState; + else if(state == "pressed") + return Fw::PressedState; + else if(state == "checked") + return Fw::CheckedState; + else if(state == "disabled") + return Fw::DisabledState; + else if(state == "on") + return Fw::OnState; + else if(state == "first") + return Fw::FirstState; + else if(state == "middle") + return Fw::MiddleState; + else if(state == "last") + return Fw::LastState; + else if(state == "alternate") + return Fw::AlternateState; + else if(state == "dragging") + return Fw::DraggingState; + else if (state == "hidden") + return Fw::HiddenState; + else if (state == "mobile") + return Fw::MobileState; + return Fw::InvalidState; +} + +Fw::AutoFocusPolicy Fw::translateAutoFocusPolicy(std::string policy) +{ + boost::to_lower(policy); + boost::trim(policy); + if(policy == "first") + return Fw::AutoFocusFirst; + else if(policy == "last") + return Fw::AutoFocusLast; + return Fw::AutoFocusNone; +} diff --git a/src/framework/ui/uitranslator.h b/src/framework/ui/uitranslator.h new file mode 100644 index 0000000..1c15836 --- /dev/null +++ b/src/framework/ui/uitranslator.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TRANSLATOR_H +#define TRANSLATOR_H + +#include "../const.h" +#include + +namespace Fw { + +AlignmentFlag translateAlignment(std::string aligment); +AnchorEdge translateAnchorEdge(std::string anchorEdge); +WidgetState translateState(std::string state); +AutoFocusPolicy translateAutoFocusPolicy(std::string policy); + +}; + +#endif diff --git a/src/framework/ui/uiverticallayout.cpp b/src/framework/ui/uiverticallayout.cpp new file mode 100644 index 0000000..28e420a --- /dev/null +++ b/src/framework/ui/uiverticallayout.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiverticallayout.h" +#include "uiwidget.h" +#include + +void UIVerticalLayout::applyStyle(const OTMLNodePtr& styleNode) +{ + UIBoxLayout::applyStyle(styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "align-bottom") + setAlignBottom(node->value()); + } +} + +bool UIVerticalLayout::internalUpdate() +{ + bool changed = false; + + UIWidgetPtr parentWidget = getParentWidget(); + if(!parentWidget) + return false; + + UIWidgetList widgets = parentWidget->getChildren(); + + if(m_alignBottom) + std::reverse(widgets.begin(), widgets.end()); + + Rect paddingRect = parentWidget->getPaddingRect(); + Point pos = (m_alignBottom) ? paddingRect .bottomLeft() : paddingRect.topLeft(); + int preferredHeight = 0; + int gap; + + for(const UIWidgetPtr& widget : widgets) { + if(!widget->isExplicitlyVisible()) + continue; + + Size size = widget->getSize(); + + gap = (m_alignBottom) ? -(widget->getMarginBottom()+widget->getHeight()) : widget->getMarginTop(); + pos.y += gap; + preferredHeight += gap; + + if(widget->isFixedSize()) { + // center it + if(widget->getTextAlign() & Fw::AlignLeft) { + pos.x = paddingRect.left() + widget->getMarginLeft(); + } else if(widget->getTextAlign() & Fw::AlignLeft) { + pos.x = paddingRect.bottom() - widget->getHeight() - widget->getMarginBottom(); + pos.x = std::max(pos.x, paddingRect.left()); + } else { + pos.x = paddingRect.left() + (paddingRect.width() - (widget->getMarginLeft() + widget->getWidth() + widget->getMarginRight()))/2; + pos.x = std::max(pos.x, paddingRect.left()); + } + } else { + // expand width + size.setWidth(paddingRect.width() - (widget->getMarginLeft() + widget->getMarginRight())); + pos.x = paddingRect.left() + (paddingRect.width() - size.width())/2; + } + + if(widget->setRect(Rect(pos - parentWidget->getVirtualOffset(), size))) + changed = true; + + gap = (m_alignBottom) ? -widget->getMarginTop() : (widget->getHeight() + widget->getMarginBottom()); + gap += m_spacing; + pos.y += gap; + preferredHeight += gap; + } + + preferredHeight -= m_spacing; + preferredHeight += parentWidget->getPaddingTop() + parentWidget->getPaddingBottom(); + + if(m_fitChildren && preferredHeight != parentWidget->getHeight()) { + // must set the preferred width later + g_dispatcher.addEvent([=] { + parentWidget->setHeight(preferredHeight); + }); + } + + return changed; +} diff --git a/src/framework/ui/uiverticallayout.h b/src/framework/ui/uiverticallayout.h new file mode 100644 index 0000000..64d2c31 --- /dev/null +++ b/src/framework/ui/uiverticallayout.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIVERTICALLAYOUT_H +#define UIVERTICALLAYOUT_H + +#include "uiboxlayout.h" + +// @bindclass +class UIVerticalLayout : public UIBoxLayout +{ +public: + UIVerticalLayout(UIWidgetPtr parentWidget) : UIBoxLayout(parentWidget) { } + + void applyStyle(const OTMLNodePtr& styleNode); + + void setAlignBottom(bool aliginBottom) { m_alignBottom = aliginBottom; update(); } + bool isAlignBottom() { return m_alignBottom; } + + bool isUIVerticalLayout() { return true; } + +protected: + bool internalUpdate(); + + stdext::boolean m_alignBottom; +}; + +#endif diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp new file mode 100644 index 0000000..fc1326b --- /dev/null +++ b/src/framework/ui/uiwidget.cpp @@ -0,0 +1,1772 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiwidget.h" +#include "uimanager.h" +#include "uianchorlayout.h" +#include "uitranslator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +UIWidget::UIWidget() +{ + // source for stats + m_source = g_lua.getSource(2); + // find correct source + int level = 3; + while((m_source.find("corelib") != std::string::npos || m_source.find("gamelib") != std::string::npos || m_source.find("[C]") != std::string::npos) && level < 8) { + std::string tmp_src = g_lua.getSource(level); + if (tmp_src.length() <= 3) break; + m_source = tmp_src; + level += 1; + } + + m_lastFocusReason = Fw::ActiveFocusReason; + m_states = Fw::DefaultState; + m_autoFocusPolicy = Fw::AutoFocusLast; + m_clickTimer.stop(); + m_autoRepeatDelay = 500; + + initBaseStyle(); + initText(); + initImage(); + + g_stats.addWidget(this); +} + +UIWidget::~UIWidget() +{ +#ifndef NDEBUG + VALIDATE(!g_app.isTerminated()); + if(!m_destroyed) + g_logger.warning(stdext::format("widget '%s' was not explicitly destroyed", m_id)); +#endif + + g_stats.removeWidget(this); +} + +void UIWidget::draw(const Rect& visibleRect, Fw::DrawPane drawPane) +{ + size_t drawQueueStart = g_drawQueue->size(); + + drawSelf(drawPane); + if (m_clipping) { + g_drawQueue->setClip(drawQueueStart, visibleRect); + } + + if(m_children.size() > 0) { + size_t drawQueueChildStart = g_drawQueue->size(); + drawChildren(visibleRect, drawPane); + if (m_clipping) { + g_drawQueue->setClip(drawQueueChildStart, visibleRect.intersection(getPaddingRect())); + } + } + + if (getOpacity() < 0.99f) { + g_drawQueue->setOpacity(drawQueueStart, getOpacity()); + } + + if (m_rotation < -0.1f || m_rotation > 0.1f) { + g_drawQueue->setRotation(drawQueueStart, m_rect.center(), m_rotation * (Fw::pi / 180.0)); + } +} + +void UIWidget::drawSelf(Fw::DrawPane drawPane) +{ + if(drawPane != Fw::ForegroundPane) + return; + + // draw style components in order + if(m_backgroundColor.aF() > Fw::MIN_ALPHA) { + Rect backgroundDestRect = m_rect; + backgroundDestRect.expand(-m_borderWidth.top, -m_borderWidth.right, -m_borderWidth.bottom, -m_borderWidth.left); + drawBackground(m_rect); + } + + drawImage(m_rect); + drawIcon(m_rect); + drawText(m_rect); + drawBorder(m_rect); +} + +void UIWidget::drawChildren(const Rect& visibleRect, Fw::DrawPane drawPane) +{ + // draw children + for(const UIWidgetPtr& child : m_children) { + // render only visible children with a valid rect inside parent rect + if(!child->isExplicitlyVisible() || !child->getRect().isValid() || child->getOpacity() < Fw::MIN_ALPHA) + continue; + + Rect childVisibleRect = visibleRect.intersection(child->getRect()); + if(!childVisibleRect.isValid()) + continue; + + child->draw(childVisibleRect, drawPane); + + // debug draw box + if(g_ui.isDrawingDebugBoxes() && drawPane == Fw::ForegroundPane) { + g_drawQueue->addBoundingRect(child->getRect(), 1, Color::green); + if (m_font) { + m_font->drawText(child->getId(), child->getPosition() + Point(2, 0), Color::red); + } + } + } +} + +void UIWidget::addChild(const UIWidgetPtr& child) +{ + if(!child) { + g_logger.traceWarning("attempt to add a null child into a UIWidget"); + return; + } + + if(child->isDestroyed()) { + g_logger.traceWarning("attemp to add a destroyed child into a UIWidget"); + return; + } + + if(hasChild(child)) { + g_logger.traceWarning("attempt to add a child again into a UIWidget"); + return; + } + + UIWidgetPtr oldLastChild = getLastChild(); + + m_children.push_back(child); + child->setParent(static_self_cast()); + + // create default layout + if(!m_layout) + m_layout = UIAnchorLayoutPtr(new UIAnchorLayout(static_self_cast())); + + // add to layout and updates it + m_layout->addWidget(child); + + // update new child states + child->updateStates(); + + // update old child index states + if(oldLastChild) { + oldLastChild->updateState(Fw::MiddleState); + oldLastChild->updateState(Fw::LastState); + } + + g_ui.onWidgetAppear(child); +} + +void UIWidget::onChildIdChange(const UIWidgetPtr& child) +{ + if (!hasChild(child)) { + g_logger.traceWarning("onChildIdChange: invalid child"); + return; + } +} + +void UIWidget::insertChild(int index, const UIWidgetPtr& child) +{ + if(!child) { + g_logger.traceWarning("attempt to insert a null child into a UIWidget"); + return; + } + + if(hasChild(child)) { + g_logger.traceWarning("attempt to insert a child again into a UIWidget"); + return; + } + + index = index <= 0 ? (m_children.size() + index) : index-1; + + if(!(index >= 0 && (uint)index <= m_children.size())) { + //g_logger.traceWarning("attempt to insert a child UIWidget into an invalid index, using nearest index..."); + index = stdext::clamp(index, 0, (int)m_children.size()); + } + + // retrieve child by index + auto it = m_children.begin() + index; + m_children.insert(it, child); + child->setParent(static_self_cast()); + + // create default layout if needed + if(!m_layout) + m_layout = UIAnchorLayoutPtr(new UIAnchorLayout(static_self_cast())); + + // add to layout and updates it + m_layout->addWidget(child); + + // update new child states + child->updateStates(); + updateChildrenIndexStates(); + + g_ui.onWidgetAppear(child); +} + +void UIWidget::removeChild(UIWidgetPtr child) +{ + // remove from children list + if(hasChild(child)) { + // defocus if needed + bool focusAnother = false; + if(m_focusedChild == child) { + focusChild(nullptr, Fw::ActiveFocusReason); + focusAnother = true; + } + + if(isChildLocked(child)) + unlockChild(child); + + auto it = std::find(m_children.begin(), m_children.end(), child); + m_children.erase(it); + + // reset child parent + VALIDATE(child->getParent() == static_self_cast()); + child->setParent(nullptr); + + m_layout->removeWidget(child); + + // update child states + child->updateStates(); + updateChildrenIndexStates(); + + if(m_autoFocusPolicy != Fw::AutoFocusNone && focusAnother && !m_focusedChild) + focusPreviousChild(Fw::ActiveFocusReason, true); + + g_ui.onWidgetDisappear(child); + } else + g_logger.traceError("attempt to remove an unknown child from a UIWidget"); +} + + +void UIWidget::focusChild(const UIWidgetPtr& child, Fw::FocusReason reason) +{ + if(m_destroyed) + return; + + if(child == m_focusedChild) + return; + + if(child && !hasChild(child)) { + g_logger.error("attempt to focus an unknown child in a UIWidget"); + return; + } + + UIWidgetPtr oldFocused = m_focusedChild; + m_focusedChild = child; + + if(child) { + child->setLastFocusReason(reason); + child->updateState(Fw::FocusState); + child->updateState(Fw::ActiveState); + + child->onFocusChange(true, reason); + } + + if(oldFocused) { + oldFocused->setLastFocusReason(reason); + oldFocused->updateState(Fw::FocusState); + oldFocused->updateState(Fw::ActiveState); + + oldFocused->onFocusChange(false, reason); + } + + onChildFocusChange(child, oldFocused, reason); +} + +void UIWidget::focusNextChild(Fw::FocusReason reason, bool rotate) +{ + if(m_destroyed) + return; + + UIWidgetPtr toFocus; + + if(rotate) { + UIWidgetList rotatedChildren(m_children); + + if(m_focusedChild) { + auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild); + if(focusedIt != rotatedChildren.end()) { + std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end()); + rotatedChildren.pop_front(); + } + } + + // finds next child to focus + for(const UIWidgetPtr& child : rotatedChildren) { + if(child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) { + toFocus = child; + break; + } + } + } else { + auto it = m_children.begin(); + if(m_focusedChild) + it = std::find(m_children.begin(), m_children.end(), m_focusedChild); + + for(; it != m_children.end(); ++it) { + const UIWidgetPtr& child = *it; + if(child != m_focusedChild && child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) { + toFocus = child; + break; + } + } + } + + if(toFocus && toFocus != m_focusedChild) + focusChild(toFocus, reason); +} + +void UIWidget::focusPreviousChild(Fw::FocusReason reason, bool rotate) +{ + if(m_destroyed) + return; + + UIWidgetPtr toFocus; + if(rotate) { + UIWidgetList rotatedChildren(m_children); + std::reverse(rotatedChildren.begin(), rotatedChildren.end()); + + if(m_focusedChild) { + auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild); + if(focusedIt != rotatedChildren.end()) { + std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end()); + rotatedChildren.pop_front(); + } + } + + // finds next child to focus + for(const UIWidgetPtr& child : rotatedChildren) { + if(child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) { + toFocus = child; + break; + } + } + } else { + auto it = m_children.rbegin(); + if(m_focusedChild) + it = std::find(m_children.rbegin(), m_children.rend(), m_focusedChild); + + for(; it != m_children.rend(); ++it) { + const UIWidgetPtr& child = *it; + if(child != m_focusedChild && child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) { + toFocus = child; + break; + } + } + } + + if(toFocus && toFocus != m_focusedChild) + focusChild(toFocus, reason); +} + +void UIWidget::lowerChild(UIWidgetPtr child) +{ + if(m_destroyed) + return; + + if(!child) + return; + + // remove and push child again + auto it = std::find(m_children.begin(), m_children.end(), child); + if(it == m_children.end()) { + g_logger.traceError("cannot find child"); + return; + } + + m_children.erase(it); + m_children.push_front(child); + updateChildrenIndexStates(); +} + +void UIWidget::raiseChild(UIWidgetPtr child) +{ + if(m_destroyed) + return; + + if(!child) + return; + + // remove and push child again + auto it = std::find(m_children.begin(), m_children.end(), child); + if(it == m_children.end()) { + g_logger.traceError("cannot find child"); + return; + } + m_children.erase(it); + m_children.push_back(child); + updateChildrenIndexStates(); +} + +void UIWidget::moveChildToIndex(const UIWidgetPtr& child, int index) +{ + if(m_destroyed) + return; + + if(!child) + return; + + if((uint)index - 1 >= m_children.size()) { + g_logger.traceError(stdext::format("moving %s to index %d on %s", child->getId(), index, m_id)); + return; + } + + // remove and push child again + auto it = std::find(m_children.begin(), m_children.end(), child); + if(it == m_children.end()) { + g_logger.traceError("cannot find child"); + return; + } + m_children.erase(it); + if (index >= (int)m_children.size() + 1) { + m_children.push_back(child); + } else { + m_children.insert(m_children.begin() + index - 1, child); + } + + updateChildrenIndexStates(); + updateLayout(); +} + +void UIWidget::reorderChildren(const std::vector& childrens) { + if (m_children.size() != childrens.size()) { + g_logger.error("Invalid parameter for reorderChildren"); + return; + } + + m_children.clear(); + for (size_t i = 0; i < childrens.size(); ++i) { + m_children.push_back(childrens[i]); + } + + updateChildrenIndexStates(); + updateLayout(); +} + +void UIWidget::lockChild(const UIWidgetPtr& child) +{ + if(m_destroyed) + return; + + if(!child) + return; + + if(!hasChild(child)) { + g_logger.traceError("cannot find child"); + return; + } + + // prevent double locks + if(isChildLocked(child)) + unlockChild(child); + + // disable all other children + for(const UIWidgetPtr& otherChild : m_children) { + if(otherChild == child) + child->setEnabled(true); + else + otherChild->setEnabled(false); + } + + m_lockedChildren.push_front(child); + + // lock child focus + if(child->isFocusable()) + focusChild(child, Fw::ActiveFocusReason); +} + +void UIWidget::unlockChild(const UIWidgetPtr& child) +{ + if(m_destroyed) + return; + + if(!child) + return; + + if(!hasChild(child)) { + g_logger.traceError("cannot find child"); + return; + } + + auto it = std::find(m_lockedChildren.begin(), m_lockedChildren.end(), child); + if(it == m_lockedChildren.end()) + return; + + m_lockedChildren.erase(it); + + // find new child to lock + UIWidgetPtr lockedChild; + if(m_lockedChildren.size() > 0) { + lockedChild = m_lockedChildren.front(); + VALIDATE(hasChild(lockedChild)); + } + + for(const UIWidgetPtr& otherChild : m_children) { + // lock new child + if(lockedChild) { + if(otherChild == lockedChild) + lockedChild->setEnabled(true); + else + otherChild->setEnabled(false); + } + // else unlock all + else + otherChild->setEnabled(true); + } + + if(lockedChild) { + if(lockedChild->isFocusable()) + focusChild(lockedChild, Fw::ActiveFocusReason); + } +} + +void UIWidget::mergeStyle(const OTMLNodePtr& styleNode) +{ + applyStyle(styleNode); + std::string name = m_style->tag(); + std::string source = m_style->source(); + m_style->merge(styleNode); + m_style->setTag(name); + m_style->setSource(source); + updateStyle(); +} + +void UIWidget::applyStyle(const OTMLNodePtr& styleNode) +{ + if(m_destroyed) + return; + + if(styleNode->size() == 0) + return; + + m_loadingStyle = true; + try { + // translate ! style tags + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag()[0] == '!') { + std::string tag = node->tag().substr(1); + std::string code = stdext::format("tostring(%s)", node->value()); + std::string origin = std::string("@") + node->source() + ": [" + node->tag() + "]"; + g_lua.evaluateExpression(code, origin); + std::string value = g_lua.popString(); + + node->setTag(tag); + node->setValue(value); + } + } + + onStyleApply(styleNode->tag(), styleNode); + callLuaField("onStyleApply", styleNode->tag(), styleNode); + + if(m_firstOnStyle) { + UIWidgetPtr parent = getParent(); + if(isFocusable() && isExplicitlyVisible() && isExplicitlyEnabled() && + parent && ((!parent->getFocusedChild() && parent->getAutoFocusPolicy() == Fw::AutoFocusFirst) || + parent->getAutoFocusPolicy() == Fw::AutoFocusLast)) { + focus(); + } + } + + m_firstOnStyle = false; + } catch(stdext::exception& e) { + g_logger.traceError(stdext::format("failed to apply style to widget '%s': %s", m_id, e.what())); + } + m_loadingStyle = false; +} + +void UIWidget::addAnchor(Fw::AnchorEdge anchoredEdge, const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge) +{ + if(m_destroyed) + return; + + if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) + anchorLayout->addAnchor(static_self_cast(), anchoredEdge, hookedWidgetId, hookedEdge); + else + g_logger.traceError(stdext::format("cannot add anchors to widget '%s': the parent doesn't use anchors layout", m_id)); +} + +void UIWidget::removeAnchor(Fw::AnchorEdge anchoredEdge) +{ + addAnchor(anchoredEdge, "none", Fw::AnchorNone); +} + +void UIWidget::centerIn(const std::string& hookedWidgetId) +{ + if(m_destroyed) + return; + + if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) { + anchorLayout->addAnchor(static_self_cast(), Fw::AnchorHorizontalCenter, hookedWidgetId, Fw::AnchorHorizontalCenter); + anchorLayout->addAnchor(static_self_cast(), Fw::AnchorVerticalCenter, hookedWidgetId, Fw::AnchorVerticalCenter); + } else + g_logger.traceError(stdext::format("cannot add anchors to widget '%s': the parent doesn't use anchors layout", m_id)); +} + +void UIWidget::fill(const std::string& hookedWidgetId) +{ + if(m_destroyed) + return; + + if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) { + anchorLayout->addAnchor(static_self_cast(), Fw::AnchorLeft, hookedWidgetId, Fw::AnchorLeft); + anchorLayout->addAnchor(static_self_cast(), Fw::AnchorRight, hookedWidgetId, Fw::AnchorRight); + anchorLayout->addAnchor(static_self_cast(), Fw::AnchorTop, hookedWidgetId, Fw::AnchorTop); + anchorLayout->addAnchor(static_self_cast(), Fw::AnchorBottom, hookedWidgetId, Fw::AnchorBottom); + } else + g_logger.traceError(stdext::format("cannot add anchors to widget '%s': the parent doesn't use anchors layout", m_id)); +} + +void UIWidget::breakAnchors() +{ + if(m_destroyed) + return; + + if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) + anchorLayout->removeAnchors(static_self_cast()); +} + +void UIWidget::updateParentLayout() +{ + if(m_destroyed) + return; + + if(UIWidgetPtr parent = getParent()) + parent->updateLayout(); + else + updateLayout(); +} + +void UIWidget::updateLayout() +{ + if(m_destroyed) + return; + + if(m_layout) + m_layout->update(); + + // children can affect the parent layout + if(UIWidgetPtr parent = getParent()) + if(UILayoutPtr parentLayout = parent->getLayout()) + parentLayout->updateLater(); +} + +void UIWidget::lock() +{ + if(m_destroyed) + return; + + if(UIWidgetPtr parent = getParent()) + parent->lockChild(static_self_cast()); +} + +void UIWidget::unlock() +{ + if(m_destroyed) + return; + + if(UIWidgetPtr parent = getParent()) + parent->unlockChild(static_self_cast()); +} + +void UIWidget::focus() +{ + if(m_destroyed) + return; + + if(!m_focusable) + return; + + if(UIWidgetPtr parent = getParent()) + parent->focusChild(static_self_cast(), Fw::ActiveFocusReason); +} + +void UIWidget::recursiveFocus(Fw::FocusReason reason) +{ + if(m_destroyed) + return; + + if(UIWidgetPtr parent = getParent()) { + if(m_focusable) + parent->focusChild(static_self_cast(), reason); + parent->recursiveFocus(reason); + } +} + +void UIWidget::lower() +{ + if(m_destroyed) + return; + + UIWidgetPtr parent = getParent(); + if(parent) + parent->lowerChild(static_self_cast()); +} + +void UIWidget::raise() +{ + if(m_destroyed) + return; + + UIWidgetPtr parent = getParent(); + if(parent) + parent->raiseChild(static_self_cast()); +} + +void UIWidget::grabMouse() +{ + if(m_destroyed) + return; + + g_ui.setMouseReceiver(static_self_cast()); +} + +void UIWidget::ungrabMouse() +{ + if(g_ui.getMouseReceiver() == static_self_cast()) + g_ui.resetMouseReceiver(); +} + +void UIWidget::grabKeyboard() +{ + if(m_destroyed) + return; + + g_ui.setKeyboardReceiver(static_self_cast()); +} + +void UIWidget::ungrabKeyboard() +{ + if(g_ui.getKeyboardReceiver() == static_self_cast()) + g_ui.resetKeyboardReceiver(); +} + +void UIWidget::bindRectToParent() +{ + if(m_destroyed) + return; + + Rect boundRect = m_rect; + UIWidgetPtr parent = getParent(); + if(parent) { + Rect parentRect = parent->getPaddingRect(); + boundRect.bind(parentRect); + } + + setRect(boundRect); +} + +void UIWidget::internalDestroy() +{ + if (!getText().empty()) { + setText("", true); + } + + m_destroyed = true; + m_visible = false; + m_enabled = false; + m_focusedChild = nullptr; + if(m_layout) { + m_layout->setParent(nullptr); + m_layout = nullptr; + } + m_parent = nullptr; + m_lockedChildren.clear(); + + for(const UIWidgetPtr& child : m_children) + child->internalDestroy(); + m_children.clear(); + + callLuaField("onDestroy"); + + releaseLuaFieldsTable(); + + g_ui.onWidgetDestroy(static_self_cast()); +} + +void UIWidget::destroy() +{ + if(m_destroyed) + g_logger.warning(stdext::format("attempt to destroy widget '%s' two times", m_id)); + + // hold itself reference + UIWidgetPtr self = static_self_cast(); + m_destroyed = true; + + // remove itself from parent + if(UIWidgetPtr parent = getParent()) + parent->removeChild(self); + internalDestroy(); +} + +void UIWidget::destroyChildren() +{ + UILayoutPtr layout = getLayout(); + if(layout) + layout->disableUpdates(); + + m_focusedChild = nullptr; + m_lockedChildren.clear(); + while (!m_children.empty()) { + UIWidgetPtr child = m_children.front(); + m_children.pop_front(); + child->setParent(nullptr); + m_layout->removeWidget(child); + child->destroy(); + } + + if(layout) + layout->enableUpdates(); +} + +void UIWidget::setId(const std::string& id) +{ + if(id != m_id) { + m_id = id; + callLuaField("onIdChange", id); + if (m_parent) { + m_parent->onChildIdChange(static_self_cast()); + } + } +} + +void UIWidget::setParent(const UIWidgetPtr& parent) +{ + // remove from old parent + UIWidgetPtr oldParent = getParent(); + + // the parent is already the same + if(oldParent == parent) + return; + + UIWidgetPtr self = static_self_cast(); + if(oldParent && oldParent->hasChild(self)) + oldParent->removeChild(self); + + // reset parent + m_parent.reset(); + + // set new parent + if(parent) { + m_parent = parent; + m_parentId = parent->getId(); + + // add to parent if needed + if(!parent->hasChild(self)) + parent->addChild(self); + } +} + +void UIWidget::setLayout(const UILayoutPtr& layout) +{ + if(!layout) + stdext::throw_exception("attempt to set a nil layout to a widget"); + + if(m_layout) + m_layout->disableUpdates(); + + layout->setParent(static_self_cast()); + layout->disableUpdates(); + + for(const UIWidgetPtr& child : m_children) { + if(m_layout) + m_layout->removeWidget(child); + layout->addWidget(child); + } + + if(m_layout) { + m_layout->enableUpdates(); + m_layout->setParent(nullptr); + m_layout->update(); + } + + layout->enableUpdates(); + m_layout = layout; +} + +bool UIWidget::setRect(const Rect& rect) +{ + /* + if(rect.width() > 8192 || rect.height() > 8192) { + g_logger.error(stdext::format("attempt to set huge rect size (%s) for %s", stdext::to_string(rect), m_id)); + return false; + } + */ + // only update if the rect really changed + Rect oldRect = m_rect; + if(rect == oldRect) + return false; + + m_rect = rect; + + // updates own layout + updateLayout(); + + // avoid massive update events + if(!m_updateEventScheduled) { + UIWidgetPtr self = static_self_cast(); + g_dispatcher.addEvent([self, oldRect]() { + self->m_updateEventScheduled = false; + if(oldRect != self->getRect()) + self->onGeometryChange(oldRect, self->getRect()); + }); + m_updateEventScheduled = true; + } + + // update hovered widget when moved behind mouse area + if(containsPoint(g_window.getMousePosition())) + g_ui.updateHoveredWidget(); + + return true; +} + +void UIWidget::setStyle(const std::string& styleName) +{ + OTMLNodePtr styleNode = g_ui.getStyle(styleName); + if(!styleNode) { + g_logger.traceError(stdext::format("unable to retrieve style '%s': not a defined style", styleName)); + return; + } + styleNode = styleNode->clone(); + applyStyle(styleNode); + m_style = styleNode; + updateStyle(); +} + +void UIWidget::setStyleFromNode(const OTMLNodePtr& styleNode) +{ + applyStyle(styleNode); + m_style = styleNode; + updateStyle(); +} + +void UIWidget::setEnabled(bool enabled) +{ + if(enabled != m_enabled) { + m_enabled = enabled; + + updateState(Fw::DisabledState); + updateState(Fw::ActiveState); + } +} + +void UIWidget::setVisible(bool visible) +{ + if(m_visible != visible) { + m_visible = visible; + + // hiding a widget make it lose focus + if(!visible && isFocused()) { + if(UIWidgetPtr parent = getParent()) + parent->focusPreviousChild(Fw::ActiveFocusReason, true); + } + + // visibility can change change parent layout + updateParentLayout(); + + updateState(Fw::ActiveState); + updateState(Fw::HiddenState); + + // visibility can change the current hovered widget + if(visible) + g_ui.onWidgetAppear(static_self_cast()); + else + g_ui.onWidgetDisappear(static_self_cast()); + } +} + +void UIWidget::setOn(bool on) +{ + setState(Fw::OnState, on); +} + +void UIWidget::setChecked(bool checked) +{ + if(setState(Fw::CheckedState, checked)) + callLuaField("onCheckChange", checked); +} + +void UIWidget::setFocusable(bool focusable) +{ + if(m_focusable != focusable) { + m_focusable = focusable; + + // make parent focus another child + if(UIWidgetPtr parent = getParent()) { + if(!focusable && isFocused()) { + parent->focusPreviousChild(Fw::ActiveFocusReason, true); + } else if(focusable && !parent->getFocusedChild() && parent->getAutoFocusPolicy() != Fw::AutoFocusNone) { + focus(); + } + } + } +} + +void UIWidget::setPhantom(bool phantom) +{ + m_phantom = phantom; +} + +void UIWidget::setDraggable(bool draggable) +{ + m_draggable = draggable; +} + +void UIWidget::setFixedSize(bool fixed) +{ + m_fixedSize = fixed; + updateParentLayout(); +} + +void UIWidget::setLastFocusReason(Fw::FocusReason reason) +{ + m_lastFocusReason = reason; +} + +void UIWidget::setAutoFocusPolicy(Fw::AutoFocusPolicy policy) +{ + m_autoFocusPolicy = policy; +} + +void UIWidget::setVirtualOffset(const Point& offset) +{ + m_virtualOffset = offset; + if(m_layout) + m_layout->update(); +} + +bool UIWidget::isAnchored() +{ + if(UIWidgetPtr parent = getParent()) + if(UIAnchorLayoutPtr anchorLayout = parent->getAnchoredLayout()) + return anchorLayout->hasAnchors(static_self_cast()); + return false; +} + +bool UIWidget::isChildLocked(const UIWidgetPtr& child) +{ + auto it = std::find(m_lockedChildren.begin(), m_lockedChildren.end(), child); + return it != m_lockedChildren.end(); +} + +bool UIWidget::hasChild(const UIWidgetPtr& child) +{ + auto it = std::find(m_children.begin(), m_children.end(), child); + if(it != m_children.end()) + return true; + return false; +} + +int UIWidget::getChildIndex(const UIWidgetPtr& child) +{ + int index = 1; + for(auto it = m_children.begin(); it != m_children.end(); ++it) { + if(*it == child) + return index; + ++index; + } + return -1; +} + +Rect UIWidget::getPaddingRect() +{ + Rect rect = m_rect; + rect.expand(-m_padding.top, -m_padding.right, -m_padding.bottom, -m_padding.left); + return rect; +} + +Rect UIWidget::getMarginRect() +{ + Rect rect = m_rect; + rect.expand(m_margin.top, m_margin.right, m_margin.bottom, m_margin.left); + return rect; +} + +Rect UIWidget::getChildrenRect() +{ + Rect childrenRect; + for(const UIWidgetPtr& child : m_children) { + if(!child->isExplicitlyVisible() || !child->getRect().isValid()) + continue; + Rect marginRect = child->getMarginRect(); + if(!childrenRect.isValid()) + childrenRect = marginRect; + else + childrenRect = childrenRect.united(marginRect); + } + + Rect myClippingRect = getPaddingRect(); + if(!childrenRect.isValid()) + childrenRect = myClippingRect; + else { + if(childrenRect.width() < myClippingRect.width()) + childrenRect.setWidth(myClippingRect.width()); + if(childrenRect.height() < myClippingRect.height()) + childrenRect.setHeight(myClippingRect.height()); + } + return childrenRect; +} + +UIAnchorLayoutPtr UIWidget::getAnchoredLayout() +{ + UIWidgetPtr parent = getParent(); + if(!parent) + return nullptr; + + UILayoutPtr layout = parent->getLayout(); + if(layout->isUIAnchorLayout()) + return layout->static_self_cast(); + return nullptr; +} + +UIWidgetPtr UIWidget::getRootParent() +{ + if(UIWidgetPtr parent = getParent()) + return parent->getRootParent(); + else + return static_self_cast(); +} + +UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild) +{ + auto it = std::find(m_children.begin(), m_children.end(), relativeChild); + if(it != m_children.end() && ++it != m_children.end()) + return *it; + return nullptr; +} + +UIWidgetPtr UIWidget::getChildBefore(const UIWidgetPtr& relativeChild) +{ + auto it = std::find(m_children.rbegin(), m_children.rend(), relativeChild); + if(it != m_children.rend() && ++it != m_children.rend()) + return *it; + return nullptr; +} + +UIWidgetPtr UIWidget::getChildById(const std::string& childId) +{ + for(const UIWidgetPtr& child : m_children) { + if(child->getId() == childId) + return child; + } + return nullptr; +} + +UIWidgetPtr UIWidget::getChildByPos(const Point& childPos) +{ + if(!containsPaddingPoint(childPos)) + return nullptr; + + for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const UIWidgetPtr& child = (*it); + if(child->isExplicitlyVisible() && child->containsPoint(childPos)) + return child; + } + + return nullptr; +} + +UIWidgetPtr UIWidget::getChildByIndex(int index) +{ + index = index <= 0 ? (m_children.size() + index) : index-1; + if(index >= 0 && (uint)index < m_children.size()) + return m_children.at(index); + return nullptr; +} + +UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& id) +{ + UIWidgetPtr widget = getChildById(id); + if(!widget) { + for(const UIWidgetPtr& child : m_children) { + widget = child->recursiveGetChildById(id); + if(widget) + break; + } + } + return widget; +} + +UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos, bool wantsPhantom) +{ + if(!containsPaddingPoint(childPos)) + return nullptr; + + for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const UIWidgetPtr& child = (*it); + if(child->isExplicitlyVisible() && child->containsPoint(childPos)) { + UIWidgetPtr subChild = child->recursiveGetChildByPos(childPos, wantsPhantom); + if(subChild) + return subChild; + else if(wantsPhantom || !child->isPhantom()) + return child; + } + } + return nullptr; +} + +UIWidgetList UIWidget::recursiveGetChildren() +{ + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + UIWidgetList subChildren = child->recursiveGetChildren(); + if(!subChildren.empty()) + children.insert(children.end(), subChildren.begin(), subChildren.end()); + children.push_back(child); + } + return children; +} + +UIWidgetList UIWidget::recursiveGetChildrenByPos(const Point& childPos) +{ + UIWidgetList children; + if(!containsPaddingPoint(childPos)) + return children; + + for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const UIWidgetPtr& child = (*it); + if(child->isExplicitlyVisible() && child->containsPoint(childPos)) { + UIWidgetList subChildren = child->recursiveGetChildrenByPos(childPos); + if(!subChildren.empty()) + children.insert(children.end(), subChildren.begin(), subChildren.end()); + children.push_back(child); + } + } + return children; +} + +UIWidgetList UIWidget::recursiveGetChildrenByMarginPos(const Point& childPos) +{ + UIWidgetList children; + if(!containsPaddingPoint(childPos)) + return children; + + for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const UIWidgetPtr& child = (*it); + if(child->isExplicitlyVisible() && child->containsMarginPoint(childPos)) { + UIWidgetList subChildren = child->recursiveGetChildrenByMarginPos(childPos); + if(!subChildren.empty()) + children.insert(children.end(), subChildren.begin(), subChildren.end()); + children.push_back(child); + } + } + return children; +} + +UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id) +{ + UIWidgetPtr widget = getChildById(id); + if(!widget) { + if(UIWidgetPtr parent = getParent()) + widget = parent->backwardsGetWidgetById(id); + } + return widget; +} + +bool UIWidget::setState(Fw::WidgetState state, bool on) +{ + if(state == Fw::InvalidState) + return false; + + int oldStates = m_states; + if(on) + m_states |= state; + else + m_states &= ~state; + + if(oldStates != m_states) { + updateStyle(); + return true; + } + return false; +} + +bool UIWidget::hasState(Fw::WidgetState state) +{ + if(state == Fw::InvalidState) + return false; + return (m_states & state); +} + +void UIWidget::updateState(Fw::WidgetState state) +{ + if(m_destroyed) + return; + + bool newStatus = true; + bool oldStatus = hasState(state); + bool updateChildren = false; + + switch(state) { + case Fw::ActiveState: { + UIWidgetPtr widget = static_self_cast(); + UIWidgetPtr parent; + do { + parent = widget->getParent(); + if(!widget->isExplicitlyEnabled() || + ((parent && parent->getFocusedChild() != widget))) { + newStatus = false; + break; + } + } while((widget = parent)); + + updateChildren = newStatus != oldStatus; + break; + } + case Fw::FocusState: { + newStatus = (getParent() && getParent()->getFocusedChild() == static_self_cast()); + break; + } + case Fw::HoverState: { + newStatus = (g_ui.getHoveredWidget() == static_self_cast() && isEnabled()); + break; + } + case Fw::PressedState: { + newStatus = (g_ui.getPressedWidget() == static_self_cast()); + break; + } + case Fw::DraggingState: { + newStatus = (g_ui.getDraggingWidget() == static_self_cast()); + break; + } + case Fw::DisabledState: { + bool enabled = true; + UIWidgetPtr widget = static_self_cast(); + do { + if(!widget->isExplicitlyEnabled()) { + enabled = false; + break; + } + } while((widget = widget->getParent())); + newStatus = !enabled; + updateChildren = newStatus != oldStatus; + break; + } + case Fw::FirstState: { + newStatus = (getParent() && getParent()->getFirstChild() == static_self_cast()); + break; + } + case Fw::MiddleState: { + newStatus = (getParent() && getParent()->getFirstChild() != static_self_cast() && getParent()->getLastChild() != static_self_cast()); + break; + } + case Fw::LastState: { + newStatus = (getParent() && getParent()->getLastChild() == static_self_cast()); + break; + } + case Fw::AlternateState: { + newStatus = (getParent() && (getParent()->getChildIndex(static_self_cast()) % 2) == 1); + break; + } + case Fw::HiddenState: { + bool visible = true; + UIWidgetPtr widget = static_self_cast(); + do { + if(!widget->isExplicitlyVisible()) { + visible = false; + break; + } + } while((widget = widget->getParent())); + newStatus = !visible; + updateChildren = newStatus != oldStatus; + break; + } + case Fw::MobileState: + { + newStatus = g_app.isMobile(); + break; + } + default: + return; + } + + if(updateChildren) { + // do a backup of children list, because it may change while looping it + UIWidgetList children = m_children; + for(const UIWidgetPtr& child : children) + child->updateState(state); + } + + if(setState(state, newStatus)) { + // disabled widgets cannot have hover state + if(state == Fw::DisabledState && !newStatus && isHovered()) { + g_ui.updateHoveredWidget(); + } else if(state == Fw::HiddenState) { + onVisibilityChange(!newStatus); + } + } +} + +void UIWidget::updateStates() +{ + if(m_destroyed) + return; + + for(int state = 1; state != Fw::LastWidgetState; state <<= 1) + updateState((Fw::WidgetState)state); +} + +void UIWidget::updateChildrenIndexStates() +{ + if(m_destroyed) + return; + + for(const UIWidgetPtr& child : m_children) { + child->updateState(Fw::FirstState); + child->updateState(Fw::MiddleState); + child->updateState(Fw::LastState); + child->updateState(Fw::AlternateState); + } +} + +void UIWidget::updateStyle() +{ + if(m_destroyed) + return; + + if(m_loadingStyle && !m_updateStyleScheduled) { + UIWidgetPtr self = static_self_cast(); + g_dispatcher.addEvent([self] { + self->m_updateStyleScheduled = false; + self->updateStyle(); + }); + m_updateStyleScheduled = true; + return; + } + + if(!m_style) + return; + + OTMLNodePtr newStateStyle = OTMLNode::create(); + + // copy only the changed styles from default style + if(m_stateStyle) { + for(OTMLNodePtr node : m_stateStyle->children()) { + if(OTMLNodePtr otherNode = m_style->get(node->tag())) + newStateStyle->addChild(otherNode->clone()); + } + } + + // checks for states combination + for(const OTMLNodePtr& style : m_style->children()) { + if(stdext::starts_with(style->tag(), "$")) { + std::string statesStr = style->tag().substr(1); + std::vector statesSplit = stdext::split(statesStr, " "); + + bool match = true; + for(std::string stateStr : statesSplit) { + if(stateStr.length() == 0) + continue; + + bool notstate = (stateStr[0] == '!'); + if(notstate) + stateStr = stateStr.substr(1); + + bool stateOn = hasState(Fw::translateState(stateStr)); + if((!notstate && !stateOn) || (notstate && stateOn)) + match = false; + } + + // merge states styles + if(match) { + newStateStyle->merge(style); + } + } + } + + //TODO: prevent setting already set proprieties + + applyStyle(newStateStyle); + m_stateStyle = newStateStyle; +} + +void UIWidget::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + if(m_destroyed) + return; + + // first set id + if(const OTMLNodePtr& node = styleNode->get("id")) + setId(node->value()); + + parseBaseStyle(styleNode); + parseImageStyle(styleNode); + parseTextStyle(styleNode); +} + +void UIWidget::onGeometryChange(const Rect& oldRect, const Rect& newRect) +{ + if(m_textWrap && oldRect.size() != newRect.size()) + updateText(); + + // move children that is outside the parent rect to inside again + for(const UIWidgetPtr& child : m_children) { + if(!child->isAnchored() && child->isVisible()) + child->bindRectToParent(); + } + + callLuaField("onGeometryChange", oldRect, newRect); +} + +void UIWidget::onLayoutUpdate() +{ + callLuaField("onLayoutUpdate"); +} + +void UIWidget::onFocusChange(bool focused, Fw::FocusReason reason) +{ + callLuaField("onFocusChange", focused, reason); +} + +void UIWidget::onChildFocusChange(const UIWidgetPtr& focusedChild, const UIWidgetPtr& unfocusedChild, Fw::FocusReason reason) +{ + callLuaField("onChildFocusChange", focusedChild, unfocusedChild, reason); +} + +void UIWidget::onHoverChange(bool hovered) +{ + callLuaField("onHoverChange", hovered); +} + +void UIWidget::onVisibilityChange(bool visible) +{ + if(!isAnchored()) + bindRectToParent(); + callLuaField("onVisibilityChange", visible); +} + +bool UIWidget::onDragEnter(const Point& mousePos) +{ + return callLuaField("onDragEnter", mousePos); +} + +bool UIWidget::onDragLeave(UIWidgetPtr droppedWidget, const Point& mousePos) +{ + return callLuaField("onDragLeave", droppedWidget, mousePos); +} + +bool UIWidget::onDragMove(const Point& mousePos, const Point& mouseMoved) +{ + return callLuaField("onDragMove", mousePos, mouseMoved); +} + +bool UIWidget::onDrop(UIWidgetPtr draggedWidget, const Point& mousePos) +{ + return callLuaField("onDrop", draggedWidget, mousePos); +} + +bool UIWidget::onKeyText(const std::string& keyText) +{ + return callLuaField("onKeyText", keyText); +} + +bool UIWidget::onKeyDown(uchar keyCode, int keyboardModifiers) +{ + return callLuaField("onKeyDown", keyCode, keyboardModifiers); +} + +bool UIWidget::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks) +{ + return callLuaField("onKeyPress", keyCode, keyboardModifiers, autoRepeatTicks); +} + +bool UIWidget::onKeyUp(uchar keyCode, int keyboardModifiers) +{ + return callLuaField("onKeyUp", keyCode, keyboardModifiers); +} + +bool UIWidget::onMousePress(const Point& mousePos, Fw::MouseButton button) +{ + if(button == Fw::MouseLeftButton) { + if(m_clickTimer.running() && m_clickTimer.ticksElapsed() <= 200) { + if(onDoubleClick(mousePos)) + return true; + m_clickTimer.stop(); + } else + m_clickTimer.restart(); + m_lastClickPosition = mousePos; + } + + if (button == Fw::MouseTouch) + return callLuaField("onTouchPress", mousePos, button); + return callLuaField("onMousePress", mousePos, button); +} + +bool UIWidget::onMouseRelease(const Point& mousePos, Fw::MouseButton button) +{ + if (button == Fw::MouseTouch) + return callLuaField("onTouchRelease", mousePos, button); + return callLuaField("onMouseRelease", mousePos, button); +} + +bool UIWidget::onMouseMove(const Point& mousePos, const Point& mouseMoved) +{ + return callLuaField("onMouseMove", mousePos, mouseMoved); +} + +bool UIWidget::onMouseWheel(const Point& mousePos, Fw::MouseWheelDirection direction) +{ + return callLuaField("onMouseWheel", mousePos, direction); +} + +bool UIWidget::onClick(const Point& mousePos) +{ + return callLuaField("onClick", mousePos); +} + +bool UIWidget::onDoubleClick(const Point& mousePos) +{ + return callLuaField("onDoubleClick", mousePos); +} + +bool UIWidget::propagateOnKeyText(const std::string& keyText) +{ + // do a backup of children list, because it may change while looping it + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded + if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) + continue; + + // key events go only to containers or focused child + if(child->isFocused()) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->propagateOnKeyText(keyText)) + return true; + } + + return onKeyText(keyText); +} + +bool UIWidget::propagateOnKeyDown(uchar keyCode, int keyboardModifiers) +{ + // do a backup of children list, because it may change while looping it + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded + if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) + continue; + + // key events go only to containers or focused child + if(child->isFocused()) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->propagateOnKeyDown(keyCode, keyboardModifiers)) + return true; + } + + return onKeyDown(keyCode, keyboardModifiers); +} + +bool UIWidget::propagateOnKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks) +{ + // do a backup of children list, because it may change while looping it + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded + if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) + continue; + + // key events go only to containers or focused child + if(child->isFocused()) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->propagateOnKeyPress(keyCode, keyboardModifiers, autoRepeatTicks)) + return true; + } + + if(autoRepeatTicks == 0 || autoRepeatTicks >= m_autoRepeatDelay) + return onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks); + else + return false; +} + +bool UIWidget::propagateOnKeyUp(uchar keyCode, int keyboardModifiers) +{ + // do a backup of children list, because it may change while looping it + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded + if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) + continue; + + // key events go only to focused child + if(child->isFocused()) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->propagateOnKeyUp(keyCode, keyboardModifiers)) + return true; + } + + return onKeyUp(keyCode, keyboardModifiers); +} + +bool UIWidget::propagateOnMouseEvent(const Point& mousePos, UIWidgetList& widgetList) +{ + bool ret = false; + if(containsPaddingPoint(mousePos)) { + for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const UIWidgetPtr& child = *it; + if(child->isExplicitlyEnabled() && child->isExplicitlyVisible() && child->containsPoint(mousePos)) { + if(child->propagateOnMouseEvent(mousePos, widgetList)) { + ret = true; + break; + } + } + } + } + + widgetList.push_back(static_self_cast()); + + if(!isPhantom()) + ret = true; + return ret; +} + +bool UIWidget::propagateOnMouseMove(const Point& mousePos, const Point& mouseMoved, UIWidgetList& widgetList) +{ + if (containsPaddingPoint(mousePos)) { + for (auto it = m_children.begin(); it != m_children.end(); ++it) { + const UIWidgetPtr& child = *it; + if (child->isExplicitlyVisible() && child->isExplicitlyEnabled() && child->containsPoint(mousePos)) + child->propagateOnMouseMove(mousePos, mouseMoved, widgetList); + + widgetList.push_back(static_self_cast()); + } + } + + return true; +} diff --git a/src/framework/ui/uiwidget.h b/src/framework/ui/uiwidget.h new file mode 100644 index 0000000..a1ce51d --- /dev/null +++ b/src/framework/ui/uiwidget.h @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UIWIDGET_H +#define UIWIDGET_H + +#include "declarations.h" +#include "uilayout.h" + +#include +#include +#include +#include +#include +#include + +template +struct EdgeGroup { + EdgeGroup() { top = right = bottom = left = T(0); } + void set(T value) { top = right = bottom = left = value; } + T top; + T right; + T bottom; + T left; +}; + +// @bindclass +class UIWidget : public LuaObject +{ +// widget core +public: + UIWidget(); + virtual ~UIWidget(); + + virtual void draw(const Rect& visibleRect, Fw::DrawPane drawPane); + +protected: + virtual void drawSelf(Fw::DrawPane drawPane); + virtual void drawChildren(const Rect& visibleRect, Fw::DrawPane drawPane); + + friend class UIManager; + + std::string m_id; + std::string m_source; + Rect m_rect; + Point m_virtualOffset; + stdext::boolean m_enabled; + stdext::boolean m_visible; + stdext::boolean m_focusable; + stdext::boolean m_fixedSize; + stdext::boolean m_phantom; + stdext::boolean m_draggable; + stdext::boolean m_destroyed; + stdext::boolean m_clipping; + UILayoutPtr m_layout; + UIWidgetPtr m_parent; + std::string m_parentId; + UIWidgetList m_children; + UIWidgetList m_lockedChildren; + std::map m_childrenShortcuts; + UIWidgetPtr m_focusedChild; + OTMLNodePtr m_style; + Timer m_clickTimer; + Fw::FocusReason m_lastFocusReason; + Fw::AutoFocusPolicy m_autoFocusPolicy; + +public: + void addChild(const UIWidgetPtr& child); + void onChildIdChange(const UIWidgetPtr& child); + void insertChild(int index, const UIWidgetPtr& child); + void removeChild(UIWidgetPtr child); + void focusChild(const UIWidgetPtr& child, Fw::FocusReason reason); + void focusNextChild(Fw::FocusReason reason, bool rotate = false); + void focusPreviousChild(Fw::FocusReason reason, bool rotate = false); + void lowerChild(UIWidgetPtr child); + void raiseChild(UIWidgetPtr child); + void moveChildToIndex(const UIWidgetPtr& child, int index); + void reorderChildren(const std::vector& childrens); + void lockChild(const UIWidgetPtr& child); + void unlockChild(const UIWidgetPtr& child); + void mergeStyle(const OTMLNodePtr& styleNode); + void applyStyle(const OTMLNodePtr& styleNode); + void addAnchor(Fw::AnchorEdge anchoredEdge, const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge); + void removeAnchor(Fw::AnchorEdge anchoredEdge); + void fill(const std::string& hookedWidgetId); + void centerIn(const std::string& hookedWidgetId); + void breakAnchors(); + void updateParentLayout(); + void updateLayout(); + void lock(); + void unlock(); + void focus(); + void recursiveFocus(Fw::FocusReason reason); + void lower(); + void raise(); + void grabMouse(); + void ungrabMouse(); + void grabKeyboard(); + void ungrabKeyboard(); + void bindRectToParent(); + void destroy(); + void destroyChildren(); + + void setId(const std::string& id); + void setParent(const UIWidgetPtr& parent); + void setLayout(const UILayoutPtr& layout); + bool setRect(const Rect& rect); + void setStyle(const std::string& styleName); + void setStyleFromNode(const OTMLNodePtr& styleNode); + void setEnabled(bool enabled); + void setVisible(bool visible); + void setOn(bool on); + void setChecked(bool checked); + void setFocusable(bool focusable); + void setPhantom(bool phantom); + void setDraggable(bool draggable); + void setFixedSize(bool fixed); + void setClipping(bool clipping) { m_clipping = clipping; } + void setLastFocusReason(Fw::FocusReason reason); + void setAutoFocusPolicy(Fw::AutoFocusPolicy policy); + void setAutoRepeatDelay(int delay) { m_autoRepeatDelay = delay; } + void setVirtualOffset(const Point& offset); + + bool isAnchored(); + bool isChildLocked(const UIWidgetPtr& child); + bool hasChild(const UIWidgetPtr& child); + int getChildIndex(const UIWidgetPtr& child); + Rect getPaddingRect(); + Rect getMarginRect(); + Rect getChildrenRect(); + UIAnchorLayoutPtr getAnchoredLayout(); + UIWidgetPtr getRootParent(); + UIWidgetPtr getChildAfter(const UIWidgetPtr& relativeChild); + UIWidgetPtr getChildBefore(const UIWidgetPtr& relativeChild); + UIWidgetPtr getChildById(const std::string& childId); + UIWidgetPtr getChildByPos(const Point& childPos); + UIWidgetPtr getChildByIndex(int index); + UIWidgetPtr recursiveGetChildById(const std::string& id); + UIWidgetPtr recursiveGetChildByPos(const Point& childPos, bool wantsPhantom); + UIWidgetList recursiveGetChildren(); + UIWidgetList recursiveGetChildrenByPos(const Point& childPos); + UIWidgetList recursiveGetChildrenByMarginPos(const Point& childPos); + UIWidgetPtr backwardsGetWidgetById(const std::string& id); + +private: + stdext::boolean m_updateEventScheduled; + stdext::boolean m_loadingStyle; + + +// state managment +protected: + bool setState(Fw::WidgetState state, bool on); + bool hasState(Fw::WidgetState state); + +private: + void internalDestroy(); + void updateState(Fw::WidgetState state); + void updateStates(); + void updateChildrenIndexStates(); + void updateStyle(); + + stdext::boolean m_updateStyleScheduled; + stdext::boolean m_firstOnStyle; + OTMLNodePtr m_stateStyle; + int m_states; + + +// event processing +protected: + virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + virtual void onGeometryChange(const Rect& oldRect, const Rect& newRect); + virtual void onLayoutUpdate(); + virtual void onFocusChange(bool focused, Fw::FocusReason reason); + virtual void onChildFocusChange(const UIWidgetPtr& focusedChild, const UIWidgetPtr& unfocusedChild, Fw::FocusReason reason); + virtual void onHoverChange(bool hovered); + virtual void onVisibilityChange(bool visible); + virtual bool onDragEnter(const Point& mousePos); + virtual bool onDragLeave(UIWidgetPtr droppedWidget, const Point& mousePos); + virtual bool onDragMove(const Point& mousePos, const Point& mouseMoved); + virtual bool onDrop(UIWidgetPtr draggedWidget, const Point& mousePos); + virtual bool onKeyText(const std::string& keyText); + virtual bool onKeyDown(uchar keyCode, int keyboardModifiers); + virtual bool onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks); + virtual bool onKeyUp(uchar keyCode, int keyboardModifiers); + virtual bool onMousePress(const Point& mousePos, Fw::MouseButton button); + virtual bool onMouseRelease(const Point& mousePos, Fw::MouseButton button); + virtual bool onMouseMove(const Point& mousePos, const Point& mouseMoved); + virtual bool onMouseWheel(const Point& mousePos, Fw::MouseWheelDirection direction); + virtual bool onClick(const Point& mousePos); + virtual bool onDoubleClick(const Point& mousePos); + + friend class UILayout; + + bool propagateOnKeyText(const std::string& keyText); + bool propagateOnKeyDown(uchar keyCode, int keyboardModifiers); + bool propagateOnKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks); + bool propagateOnKeyUp(uchar keyCode, int keyboardModifiers); + bool propagateOnMouseEvent(const Point& mousePos, UIWidgetList& widgetList); + bool propagateOnMouseMove(const Point& mousePos, const Point& mouseMoved, UIWidgetList& widgetList); + + +// function shortcuts +public: + void resize(int width, int height) { setRect(Rect(getPosition(), Size(width, height))); } + void move(int x, int y) { setRect(Rect(x, y, getSize())); } + void rotate(float degrees) { setRotation(degrees); } + void hide() { setVisible(false); } + void show() { setVisible(true); } + void disable() { setEnabled(false); } + void enable() { setEnabled(true); } + + bool isActive() { return hasState(Fw::ActiveState); } + bool isEnabled() { return !hasState(Fw::DisabledState); } + bool isDisabled() { return hasState(Fw::DisabledState); } + bool isFocused() { return hasState(Fw::FocusState); } + bool isHovered() { return hasState(Fw::HoverState); } + bool isPressed() { return hasState(Fw::PressedState); } + bool isFirst() { return hasState(Fw::FirstState); } + bool isMiddle() { return hasState(Fw::MiddleState); } + bool isLast() { return hasState(Fw::LastState); } + bool isAlternate() { return hasState(Fw::AlternateState); } + bool isChecked() { return hasState(Fw::CheckedState); } + bool isOn() { return hasState(Fw::OnState); } + bool isDragging() { return hasState(Fw::DraggingState); } + bool isVisible() { return !hasState(Fw::HiddenState); } + bool isHidden() { return hasState(Fw::HiddenState); } + bool isExplicitlyEnabled() { return m_enabled; } + bool isExplicitlyVisible() { return m_visible; } + bool isFocusable() { return m_focusable; } + bool isPhantom() { return m_phantom; } + bool isDraggable() { return m_draggable; } + bool isFixedSize() { return m_fixedSize; } + bool isClipping() { return m_clipping; } + bool isDestroyed() { return m_destroyed; } + + bool hasChildren() { return m_children.size() > 0; } + bool containsMarginPoint(const Point& point) { return getMarginRect().contains(point); } + bool containsPaddingPoint(const Point& point) { return getPaddingRect().contains(point); } + bool containsPoint(const Point& point) { return m_rect.contains(point); } + + std::string getId() { return m_id; } + std::string getSource() { return m_source; } + UIWidgetPtr getParent() { return m_parent; } + std::string getParentId() { return m_parentId; } + UIWidgetPtr getFocusedChild() { return m_focusedChild; } + UIWidgetList getChildren() { return m_children; } + UIWidgetPtr getFirstChild() { return getChildByIndex(1); } + UIWidgetPtr getLastChild() { return getChildByIndex(-1); } + UILayoutPtr getLayout() { return m_layout; } + OTMLNodePtr getStyle() { return m_style; } + int getChildCount() { return m_children.size(); } + Fw::FocusReason getLastFocusReason() { return m_lastFocusReason; } + Fw::AutoFocusPolicy getAutoFocusPolicy() { return m_autoFocusPolicy; } + int getAutoRepeatDelay() { return m_autoRepeatDelay; } + Point getVirtualOffset() { return m_virtualOffset; } + std::string getStyleName() { return m_style->tag(); } + Point getLastClickPosition() { return m_lastClickPosition; } + + // for stats only + bool isRootChild() + { + return m_isRootChild; + } + + void setRootChild(bool v) + { + m_isRootChild = v; + } + + +// base style +private: + void initBaseStyle(); + void parseBaseStyle(const OTMLNodePtr& styleNode); + +protected: + void drawBackground(const Rect& screenCoords); + void drawBorder(const Rect& screenCoords); + void drawIcon(const Rect& screenCoords); + + Color m_color; + Color m_backgroundColor; + Rect m_backgroundRect; + TexturePtr m_icon; + Color m_iconColor; + Rect m_iconRect; + Rect m_iconClipRect; + Fw::AlignmentFlag m_iconAlign; + EdgeGroup m_borderColor; + EdgeGroup m_borderWidth; + EdgeGroup m_margin; + EdgeGroup m_padding; + float m_opacity; + float m_rotation; + int m_autoRepeatDelay; + Point m_lastClickPosition; + bool m_isRootChild = false; // for stats + +public: + void setX(int x) { move(x, getY()); } + void setY(int y) { move(getX(), y); } + void setWidth(int width) { resize(width, getHeight()); } + void setHeight(int height) { resize(getWidth(), height); } + void setSize(const Size& size) { resize(size.width(), size.height()); } + void setPosition(const Point& pos) { move(pos.x, pos.y); } + void setColor(const Color& color) { m_color = color; } + void setBackgroundColor(const Color& color) { m_backgroundColor = color; } + void setBackgroundOffsetX(int x) { m_backgroundRect.setX(x); } + void setBackgroundOffsetY(int y) { m_backgroundRect.setX(y); } + void setBackgroundOffset(const Point& pos) { m_backgroundRect.move(pos); } + void setBackgroundWidth(int width) { m_backgroundRect.setWidth(width); } + void setBackgroundHeight(int height) { m_backgroundRect.setHeight(height); } + void setBackgroundSize(const Size& size) { m_backgroundRect.resize(size); } + void setBackgroundRect(const Rect& rect) { m_backgroundRect = rect; } + void setIcon(const std::string& iconFile); + void setIconColor(const Color& color) { m_iconColor = color; } + void setIconOffsetX(int x) { m_iconOffset.x = x; } + void setIconOffsetY(int y) { m_iconOffset.y = y; } + void setIconOffset(const Point& pos) { m_iconOffset = pos; } + void setIconWidth(int width) { m_iconRect.setWidth(width); } + void setIconHeight(int height) { m_iconRect.setHeight(height); } + void setIconSize(const Size& size) { m_iconRect.resize(size); } + void setIconRect(const Rect& rect) { m_iconRect = rect; } + void setIconClip(const Rect& rect) { m_iconClipRect = rect; } + void setIconAlign(Fw::AlignmentFlag align) { m_iconAlign = align; } + void setBorderWidth(int width) { m_borderWidth.set(width); updateLayout(); } + void setBorderWidthTop(int width) { m_borderWidth.top = width; } + void setBorderWidthRight(int width) { m_borderWidth.right = width; } + void setBorderWidthBottom(int width) { m_borderWidth.bottom = width; } + void setBorderWidthLeft(int width) { m_borderWidth.left = width; } + void setBorderColor(const Color& color) { m_borderColor.set(color); updateLayout(); } + void setBorderColorTop(const Color& color) { m_borderColor.top = color; } + void setBorderColorRight(const Color& color) { m_borderColor.right = color; } + void setBorderColorBottom(const Color& color) { m_borderColor.bottom = color; } + void setBorderColorLeft(const Color& color) { m_borderColor.left = color; } + void setMargin(int margin) { m_margin.set(margin); updateParentLayout(); } + void setMarginHorizontal(int margin) { m_margin.right = m_margin.left = margin; updateParentLayout(); } + void setMarginVertical(int margin) { m_margin.bottom = m_margin.top = margin; updateParentLayout(); } + void setMarginTop(int margin) { m_margin.top = margin; updateParentLayout(); } + void setMarginRight(int margin) { m_margin.right = margin; updateParentLayout(); } + void setMarginBottom(int margin) { m_margin.bottom = margin; updateParentLayout(); } + void setMarginLeft(int margin) { m_margin.left = margin; updateParentLayout(); } + void setPadding(int padding) { m_padding.top = m_padding.right = m_padding.bottom = m_padding.left = padding; updateLayout(); } + void setPaddingHorizontal(int padding) { m_padding.right = m_padding.left = padding; updateLayout(); } + void setPaddingVertical(int padding) { m_padding.bottom = m_padding.top = padding; updateLayout(); } + void setPaddingTop(int padding) { m_padding.top = padding; updateLayout(); } + void setPaddingRight(int padding) { m_padding.right = padding; updateLayout(); } + void setPaddingBottom(int padding) { m_padding.bottom = padding; updateLayout(); } + void setPaddingLeft(int padding) { m_padding.left = padding; updateLayout(); } + void setOpacity(float opacity) { m_opacity = stdext::clamp(opacity, 0.0f, 1.0f); } + void setRotation(float degrees) { m_rotation = degrees; } + + int getX() { return m_rect.x(); } + int getY() { return m_rect.y(); } + Point getPosition() { return m_rect.topLeft(); } + int getWidth() { return m_rect.width(); } + int getHeight() { return m_rect.height(); } + Size getSize() { return m_rect.size(); } + Rect getRect() { return m_rect; } + Color getColor() { return m_color; } + Color getBackgroundColor() { return m_backgroundColor; } + int getBackgroundOffsetX() { return m_backgroundRect.x(); } + int getBackgroundOffsetY() { return m_backgroundRect.y(); } + Point getBackgroundOffset() { return m_backgroundRect.topLeft(); } + int getBackgroundWidth() { return m_backgroundRect.width(); } + int getBackgroundHeight() { return m_backgroundRect.height(); } + Size getBackgroundSize() { return m_backgroundRect.size(); } + Rect getBackgroundRect() { return m_backgroundRect; } + Color getIconColor() { return m_iconColor; } + int getIconOffsetX() { return m_iconRect.x(); } + int getIconOffsetY() { return m_iconRect.y(); } + Point getIconOffset() { return m_iconRect.topLeft(); } + int getIconWidth() { return m_iconRect.width(); } + int getIconHeight() { return m_iconRect.height(); } + Size getIconSize() { return m_iconRect.size(); } + Rect getIconRect() { return m_iconRect; } + Rect getIconClip() { return m_iconClipRect; } + Fw::AlignmentFlag getIconAlign() { return m_iconAlign; } + Color getBorderTopColor() { return m_borderColor.top; } + Color getBorderRightColor() { return m_borderColor.right; } + Color getBorderBottomColor() { return m_borderColor.bottom; } + Color getBorderLeftColor() { return m_borderColor.left; } + int getBorderTopWidth() { return m_borderWidth.top; } + int getBorderRightWidth() { return m_borderWidth.right; } + int getBorderBottomWidth() { return m_borderWidth.bottom; } + int getBorderLeftWidth() { return m_borderWidth.left; } + int getMarginTop() { return m_margin.top; } + int getMarginRight() { return m_margin.right; } + int getMarginBottom() { return m_margin.bottom; } + int getMarginLeft() { return m_margin.left; } + int getPaddingTop() { return m_padding.top; } + int getPaddingRight() { return m_padding.right; } + int getPaddingBottom() { return m_padding.bottom; } + int getPaddingLeft() { return m_padding.left; } + float getOpacity() { return m_opacity; } + float getRotation() { return m_rotation; } + +// image +private: + void initImage(); + void parseImageStyle(const OTMLNodePtr& styleNode); + + void updateImageCache() { m_imageMustRecache = true; } + void configureBorderImage() { m_imageBordered = true; updateImageCache(); } + + CoordsBuffer m_imageCoordsBuffer; + Rect m_imageCachedScreenCoords; + stdext::boolean m_imageMustRecache; + stdext::boolean m_imageBordered; + +protected: + void drawImage(const Rect& screenCoords); + + TexturePtr m_imageTexture; + Rect m_imageClipRect; + Rect m_imageRect; + Color m_imageColor; + Point m_iconOffset; + stdext::boolean m_imageFixedRatio; + stdext::boolean m_imageRepeated; + stdext::boolean m_imageSmooth; + stdext::boolean m_imageAutoResize; + EdgeGroup m_imageBorder; + +public: + void setQRCode(const std::string& code, int border); + void setImageSource(const std::string& source); + void setImageSourceBase64(const std::string & data); + void setImageClip(const Rect& clipRect) { m_imageClipRect = clipRect; updateImageCache(); } + void setImageOffsetX(int x) { m_imageRect.setX(x); updateImageCache(); } + void setImageOffsetY(int y) { m_imageRect.setY(y); updateImageCache(); } + void setImageOffset(const Point& pos) { m_imageRect.move(pos); updateImageCache(); } + void setImageWidth(int width) { m_imageRect.setWidth(width); updateImageCache(); } + void setImageHeight(int height) { m_imageRect.setHeight(height); updateImageCache(); } + void setImageSize(const Size& size) { m_imageRect.resize(size); updateImageCache(); } + void setImageRect(const Rect& rect) { m_imageRect = rect; updateImageCache(); } + void setImageColor(const Color& color) { m_imageColor = color; updateImageCache(); } + void setImageFixedRatio(bool fixedRatio) { m_imageFixedRatio = fixedRatio; updateImageCache(); } + void setImageRepeated(bool repeated) { m_imageRepeated = repeated; updateImageCache(); } + void setImageSmooth(bool smooth) { m_imageSmooth = smooth; } + void setImageAutoResize(bool autoResize) { m_imageAutoResize = autoResize; } + void setImageBorderTop(int border) { m_imageBorder.top = border; configureBorderImage(); } + void setImageBorderRight(int border) { m_imageBorder.right = border; configureBorderImage(); } + void setImageBorderBottom(int border) { m_imageBorder.bottom = border; configureBorderImage(); } + void setImageBorderLeft(int border) { m_imageBorder.left = border; configureBorderImage(); } + void setImageBorder(int border) { m_imageBorder.set(border); configureBorderImage(); } + + Rect getImageClip() { return m_imageClipRect; } + int getImageOffsetX() { return m_imageRect.x(); } + int getImageOffsetY() { return m_imageRect.y(); } + Point getImageOffset() { return m_imageRect.topLeft(); } + int getImageWidth() { return m_imageRect.width(); } + int getImageHeight() { return m_imageRect.height(); } + Size getImageSize() { return m_imageRect.size(); } + Rect getImageRect() { return m_imageRect; } + Color getImageColor() { return m_imageColor; } + bool isImageFixedRatio() { return m_imageFixedRatio; } + bool isImageSmooth() { return m_imageSmooth; } + bool isImageAutoResize() { return m_imageAutoResize; } + int getImageBorderTop() { return m_imageBorder.top; } + int getImageBorderRight() { return m_imageBorder.right; } + int getImageBorderBottom() { return m_imageBorder.bottom; } + int getImageBorderLeft() { return m_imageBorder.left; } + int getImageTextureWidth() { return m_imageTexture ? m_imageTexture->getWidth() : 0; } + int getImageTextureHeight() { return m_imageTexture ? m_imageTexture->getHeight() : 0; } + +// text related +private: + void initText(); + void parseTextStyle(const OTMLNodePtr& styleNode); + + stdext::boolean m_textMustRecache; + Rect m_textCachedScreenCoords; + +protected: + virtual void updateText(); + void drawText(const Rect& screenCoords); + + virtual void onTextChange(const std::string& text, const std::string& oldText); + virtual void onFontChange(const std::string& font); + + std::string m_text; + std::string m_drawText; + Fw::AlignmentFlag m_textAlign; + Point m_textOffset; + stdext::boolean m_textWrap; + stdext::boolean m_textVerticalAutoResize; + stdext::boolean m_textHorizontalAutoResize; + stdext::boolean m_textOnlyUpperCase; + BitmapFontPtr m_font; + std::vector> m_textColors; + std::vector> m_drawTextColors; + +public: + void resizeToText() { setSize(getTextSize()); } + void clearText() { setText(""); } + + void setText(std::string text, bool dontFireLuaCall = false); + void setColoredText(const std::vector& texts, bool dontFireLuaCall = false); + void setTextAlign(Fw::AlignmentFlag align) { m_textAlign = align; updateText(); } + void setTextOffset(const Point& offset) { m_textOffset = offset; updateText(); } + void setTextWrap(bool textWrap) { m_textWrap = textWrap; updateText(); } + void setTextAutoResize(bool textAutoResize) { m_textHorizontalAutoResize = textAutoResize; m_textVerticalAutoResize = textAutoResize; updateText(); } + void setTextHorizontalAutoResize(bool textAutoResize) { m_textHorizontalAutoResize = textAutoResize; updateText(); } + void setTextVerticalAutoResize(bool textAutoResize) { m_textVerticalAutoResize = textAutoResize; updateText(); } + void setTextOnlyUpperCase(bool textOnlyUpperCase) { m_textOnlyUpperCase = textOnlyUpperCase; setText(m_text); } + void setFont(const std::string& fontName); + + std::string getText() { return m_text; } + std::string getDrawText() { return m_drawText; } + Fw::AlignmentFlag getTextAlign() { return m_textAlign; } + Point getTextOffset() { return m_textOffset; } + bool getTextWrap() { return m_textWrap; } + std::string getFont() { return m_font->getName(); } + Size getTextSize() { return m_font->calculateTextRectSize(m_drawText); } +}; + +#endif diff --git a/src/framework/ui/uiwidgetbasestyle.cpp b/src/framework/ui/uiwidgetbasestyle.cpp new file mode 100644 index 0000000..7c88beb --- /dev/null +++ b/src/framework/ui/uiwidgetbasestyle.cpp @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiwidget.h" +#include "uihorizontallayout.h" +#include "uiverticallayout.h" +#include "uigridlayout.h" +#include "uianchorlayout.h" +#include "uitranslator.h" + +#include +#include +#include + +void UIWidget::initBaseStyle() +{ + m_backgroundColor = Color::alpha; + m_borderColor.set(Color::black); + m_iconColor = Color::white; + m_color = Color::white; + m_opacity = 1.0f; + m_rotation = 0.0f; + m_iconAlign = Fw::AlignNone; + + // generate an unique id, this is need because anchored layouts find widgets by id + static unsigned long id = 1; + m_id = std::string("widget") + std::to_string(id++); +} + +void UIWidget::parseBaseStyle(const OTMLNodePtr& styleNode) +{ + // parse lua variables and callbacks first + for(const OTMLNodePtr& node : styleNode->children()) { + // lua functions + if(stdext::starts_with(node->tag(), "@")) { + // load once + if(m_firstOnStyle) { + std::string funcName = node->tag().substr(1); + std::string funcOrigin = std::string("@") + node->source() + ": [" + node->tag() + "]"; + g_lua.loadFunction(node->value(), funcOrigin); + luaSetField(funcName); + } + // lua fields value + } else if(stdext::starts_with(node->tag(), "&")) { + std::string fieldName = node->tag().substr(1); + std::string fieldOrigin = std::string("@") + node->source() + ": [" + node->tag() + "]"; + + g_lua.evaluateExpression(node->value(), fieldOrigin); + luaSetField(fieldName); + } + } + // load styles used by all widgets + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "color") + setColor(node->value()); + else if(node->tag() == "x") + setX(node->value()); + else if(node->tag() == "y") + setY(node->value()); + else if(node->tag() == "pos") + setPosition(node->value()); + else if(node->tag() == "width") + setWidth(node->value()); + else if(node->tag() == "height") + setHeight(node->value()); + else if(node->tag() == "rect") + setRect(node->value()); + else if(node->tag() == "background") + setBackgroundColor(node->value()); + else if(node->tag() == "background-color") + setBackgroundColor(node->value()); + else if(node->tag() == "background-offset-x") + setBackgroundOffsetX(node->value()); + else if(node->tag() == "background-offset-y") + setBackgroundOffsetY(node->value()); + else if(node->tag() == "background-offset") + setBackgroundOffset(node->value()); + else if(node->tag() == "background-width") + setBackgroundWidth(node->value()); + else if(node->tag() == "background-height") + setBackgroundHeight(node->value()); + else if(node->tag() == "background-size") + setBackgroundSize(node->value()); + else if(node->tag() == "background-rect") + setBackgroundRect(node->value()); + else if(node->tag() == "icon") + setIcon(stdext::resolve_path(node->value(), node->source())); + else if(node->tag() == "icon-source") + setIcon(stdext::resolve_path(node->value(), node->source())); + else if(node->tag() == "icon-color") + setIconColor(node->value()); + else if(node->tag() == "icon-offset-x") + setIconOffsetX(node->value()); + else if(node->tag() == "icon-offset-y") + setIconOffsetY(node->value()); + else if(node->tag() == "icon-offset") + setIconOffset(node->value()); + else if(node->tag() == "icon-width") + setIconWidth(node->value()); + else if(node->tag() == "icon-height") + setIconHeight(node->value()); + else if(node->tag() == "icon-size") + setIconSize(node->value()); + else if(node->tag() == "icon-rect") + setIconRect(node->value()); + else if(node->tag() == "icon-clip") + setIconClip(node->value()); + else if(node->tag() == "icon-align") + setIconAlign(Fw::translateAlignment(node->value())); + else if(node->tag() == "opacity") + setOpacity(node->value()); + else if (node->tag() == "rotation") + setRotation(node->value()); + else if(node->tag() == "enabled") + setEnabled(node->value()); + else if(node->tag() == "visible") + setVisible(node->value()); + else if(node->tag() == "checked") + setChecked(node->value()); + else if(node->tag() == "draggable") + setDraggable(node->value()); + else if(node->tag() == "on") + setOn(node->value()); + else if(node->tag() == "focusable") + setFocusable(node->value()); + else if(node->tag() == "auto-focus") + setAutoFocusPolicy(Fw::translateAutoFocusPolicy(node->value())); + else if(node->tag() == "phantom") + setPhantom(node->value()); + else if(node->tag() == "size") + setSize(node->value()); + else if(node->tag() == "fixed-size") + setFixedSize(node->value()); + else if(node->tag() == "clipping") + setClipping(node->value()); + else if(node->tag() == "border") { + auto split = stdext::split(node->value(), " "); + if(split.size() == 2) { + setBorderWidth(stdext::safe_cast(split[0])); + setBorderColor(stdext::safe_cast(split[1])); + } else + throw OTMLException(node, "border param must have its width followed by its color"); + } + else if(node->tag() == "border-width") + setBorderWidth(node->value()); + else if(node->tag() == "border-width-top") + setBorderWidthTop(node->value()); + else if(node->tag() == "border-width-right") + setBorderWidthRight(node->value()); + else if(node->tag() == "border-width-bottom") + setBorderWidthBottom(node->value()); + else if(node->tag() == "border-width-left") + setBorderWidthLeft(node->value()); + else if(node->tag() == "border-color") + setBorderColor(node->value()); + else if(node->tag() == "border-color-top") + setBorderColorTop(node->value()); + else if(node->tag() == "border-color-right") + setBorderColorRight(node->value()); + else if(node->tag() == "border-color-bottom") + setBorderColorBottom(node->value()); + else if(node->tag() == "border-color-left") + setBorderColorLeft(node->value()); + else if(node->tag() == "margin-top") + setMarginTop(node->value()); + else if(node->tag() == "margin-right") + setMarginRight(node->value()); + else if(node->tag() == "margin-bottom") + setMarginBottom(node->value()); + else if(node->tag() == "margin-left") + setMarginLeft(node->value()); + else if(node->tag() == "margin") { + std::string marginDesc = node->value(); + std::vector split = stdext::split(marginDesc, " "); + if(split.size() == 4) { + setMarginTop(stdext::safe_cast(split[0])); + setMarginRight(stdext::safe_cast(split[1])); + setMarginBottom(stdext::safe_cast(split[2])); + setMarginLeft(stdext::safe_cast(split[3])); + } else if(split.size() == 3) { + int marginTop = stdext::safe_cast(split[0]); + int marginHorizontal = stdext::safe_cast(split[1]); + int marginBottom = stdext::safe_cast(split[2]); + setMarginTop(marginTop); + setMarginRight(marginHorizontal); + setMarginBottom(marginBottom); + setMarginLeft(marginHorizontal); + } else if(split.size() == 2) { + int marginVertical = stdext::safe_cast(split[0]); + int marginHorizontal = stdext::safe_cast(split[1]); + setMarginTop(marginVertical); + setMarginRight(marginHorizontal); + setMarginBottom(marginVertical); + setMarginLeft(marginHorizontal); + } else if(split.size() == 1) { + int margin = stdext::safe_cast(split[0]); + setMarginTop(margin); + setMarginRight(margin); + setMarginBottom(margin); + setMarginLeft(margin); + } + } + else if(node->tag() == "padding-top") + setPaddingTop(node->value()); + else if(node->tag() == "padding-right") + setPaddingRight(node->value()); + else if(node->tag() == "padding-bottom") + setPaddingBottom(node->value()); + else if(node->tag() == "padding-left") + setPaddingLeft(node->value()); + else if(node->tag() == "padding") { + std::string paddingDesc = node->value(); + std::vector split = stdext::split(paddingDesc, " "); + if(split.size() == 4) { + setPaddingTop(stdext::safe_cast(split[0])); + setPaddingRight(stdext::safe_cast(split[1])); + setPaddingBottom(stdext::safe_cast(split[2])); + setPaddingLeft(stdext::safe_cast(split[3])); + } else if(split.size() == 3) { + int paddingTop = stdext::safe_cast(split[0]); + int paddingHorizontal = stdext::safe_cast(split[1]); + int paddingBottom = stdext::safe_cast(split[2]); + setPaddingTop(paddingTop); + setPaddingRight(paddingHorizontal); + setPaddingBottom(paddingBottom); + setPaddingLeft(paddingHorizontal); + } else if(split.size() == 2) { + int paddingVertical = stdext::safe_cast(split[0]); + int paddingHorizontal = stdext::safe_cast(split[1]); + setPaddingTop(paddingVertical); + setPaddingRight(paddingHorizontal); + setPaddingBottom(paddingVertical); + setPaddingLeft(paddingHorizontal); + } else if(split.size() == 1) { + int padding = stdext::safe_cast(split[0]); + setPaddingTop(padding); + setPaddingRight(padding); + setPaddingBottom(padding); + setPaddingLeft(padding); + } + } + // layouts + else if(node->tag() == "layout") { + std::string layoutType; + if(node->hasValue()) + layoutType = node->value(); + else + layoutType = node->valueAt("type", ""); + + if(!layoutType.empty()) { + UILayoutPtr layout; + if(layoutType == "horizontalBox") + layout = UIHorizontalLayoutPtr(new UIHorizontalLayout(static_self_cast())); + else if(layoutType == "verticalBox") + layout = UIVerticalLayoutPtr(new UIVerticalLayout(static_self_cast())); + else if(layoutType == "grid") + layout = UIGridLayoutPtr(new UIGridLayout(static_self_cast())); + else if(layoutType == "anchor") + layout = UIAnchorLayoutPtr(new UIAnchorLayout(static_self_cast())); + else + throw OTMLException(node, "cannot determine layout type"); + setLayout(layout); + } + + if(node->hasChildren()) + m_layout->applyStyle(node); + } + // anchors + else if(stdext::starts_with(node->tag(), "anchors.")) { + UIWidgetPtr parent = getParent(); + if(!parent) { + if(m_firstOnStyle) + throw OTMLException(node, "cannot create anchor, there is no parent widget!"); + else + continue; + } + + UILayoutPtr layout = parent->getLayout(); + UIAnchorLayoutPtr anchorLayout; + if(layout->isUIAnchorLayout()) + anchorLayout = layout->static_self_cast(); + + if(!anchorLayout) + throw OTMLException(node, "cannot create anchor, the parent widget doesn't use anchor layout!"); + + std::string what = node->tag().substr(8); + if(what == "fill") { + fill(node->value()); + } else if(what == "centerIn") { + centerIn(node->value()); + } else { + Fw::AnchorEdge anchoredEdge = Fw::translateAnchorEdge(what); + + if(node->value() == "none") { + removeAnchor(anchoredEdge); + } else { + std::vector split = stdext::split(node->value(), "."); + if(split.size() != 2) + throw OTMLException(node, "invalid anchor description"); + + std::string hookedWidgetId = split[0]; + Fw::AnchorEdge hookedEdge = Fw::translateAnchorEdge(split[1]); + + if(anchoredEdge == Fw::AnchorNone) + throw OTMLException(node, "invalid anchor edge"); + + if(hookedEdge == Fw::AnchorNone) + throw OTMLException(node, "invalid anchor target edge"); + + addAnchor(anchoredEdge, hookedWidgetId, hookedEdge); + } + } + } + } +} + +void UIWidget::drawBackground(const Rect& screenCoords) +{ + if(m_backgroundColor.aF() > 0.0f) { + Rect drawRect = screenCoords; + drawRect.translate(m_backgroundRect.topLeft()); + if(m_backgroundRect.isValid()) + drawRect.resize(m_backgroundRect.size()); + g_drawQueue->addFilledRect(drawRect, m_backgroundColor); + } +} + +void UIWidget::drawBorder(const Rect& screenCoords) +{ + // top + if(m_borderWidth.top > 0) { + Rect borderRect(screenCoords.topLeft(), screenCoords.width(), m_borderWidth.top); + g_drawQueue->addFilledRect(borderRect, m_borderColor.top); + } + + // right + if(m_borderWidth.right > 0) { + Rect borderRect(screenCoords.topRight() - Point(m_borderWidth.right - 1, 0), m_borderWidth.right, screenCoords.height()); + g_drawQueue->addFilledRect(borderRect, m_borderColor.right); + } + + // bottom + if(m_borderWidth.bottom > 0) { + Rect borderRect(screenCoords.bottomLeft() - Point(0, m_borderWidth.bottom - 1), screenCoords.width(), m_borderWidth.bottom); + g_drawQueue->addFilledRect(borderRect, m_borderColor.bottom); + } + + // left + if(m_borderWidth.left > 0) { + Rect borderRect(screenCoords.topLeft(), m_borderWidth.left, screenCoords.height()); + g_drawQueue->addFilledRect(borderRect, m_borderColor.left); + } +} + +void UIWidget::drawIcon(const Rect& screenCoords) +{ + if(m_icon) { + Rect drawRect; + if(m_iconRect.isValid()) { + drawRect = screenCoords; + drawRect.translate(m_iconRect.topLeft()); + drawRect.resize(m_iconRect.size()); + } else { + drawRect.resize(m_iconClipRect.size()); + + if(m_iconAlign == Fw::AlignNone) + drawRect.moveCenter(screenCoords.center()); + else + drawRect.alignIn(screenCoords, m_iconAlign); + } + drawRect.translate(m_iconOffset); + g_drawQueue->addTexturedRect(drawRect, m_icon, m_iconClipRect, m_iconColor); + } +} + +void UIWidget::setIcon(const std::string& iconFile) +{ + if(iconFile.empty()) + m_icon = nullptr; + else + m_icon = g_textures.getTexture(iconFile); + if(m_icon && !m_iconClipRect.isValid()) + m_iconClipRect = Rect(0, 0, m_icon->getSize()); +} diff --git a/src/framework/ui/uiwidgetimage.cpp b/src/framework/ui/uiwidgetimage.cpp new file mode 100644 index 0000000..2ecfb1c --- /dev/null +++ b/src/framework/ui/uiwidgetimage.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiwidget.h" +#include +#include +#include +#include +#include +#include + +void UIWidget::initImage() +{ +} + +void UIWidget::parseImageStyle(const OTMLNodePtr& styleNode) +{ + for(const OTMLNodePtr& node : styleNode->children()) { + if (node->tag() == "qr" || node->tag() == "qr-code") + setQRCode(node->value(), 1); + else if(node->tag() == "image-source") + setImageSource(stdext::resolve_path(node->value(), node->source())); + else if(node->tag() == "image-source-base64") + setImageSourceBase64(node->value()); + else if(node->tag() == "image-offset-x") + setImageOffsetX(node->value()); + else if(node->tag() == "image-offset-y") + setImageOffsetY(node->value()); + else if(node->tag() == "image-offset") + setImageOffset(node->value()); + else if(node->tag() == "image-width") + setImageWidth(node->value()); + else if(node->tag() == "image-height") + setImageHeight(node->value()); + else if(node->tag() == "image-size") + setImageSize(node->value()); + else if(node->tag() == "image-rect") + setImageRect(node->value()); + else if(node->tag() == "image-clip") + setImageClip(node->value()); + else if(node->tag() == "image-fixed-ratio") + setImageFixedRatio(node->value()); + else if(node->tag() == "image-repeated") + setImageRepeated(node->value()); + else if(node->tag() == "image-smooth") + setImageSmooth(node->value()); + else if(node->tag() == "image-color") + setImageColor(node->value()); + else if(node->tag() == "image-border-top") + setImageBorderTop(node->value()); + else if(node->tag() == "image-border-right") + setImageBorderRight(node->value()); + else if(node->tag() == "image-border-bottom") + setImageBorderBottom(node->value()); + else if(node->tag() == "image-border-left") + setImageBorderLeft(node->value()); + else if(node->tag() == "image-border") + setImageBorder(node->value()); + else if(node->tag() == "image-auto-resize") + setImageAutoResize(node->value()); + } +} + +void UIWidget::drawImage(const Rect& screenCoords) +{ + if(!m_imageTexture || !screenCoords.isValid()) + return; + + // cache vertex buffers + if(m_imageCachedScreenCoords != screenCoords || m_imageMustRecache) { + m_imageCoordsBuffer.clear(); + m_imageCachedScreenCoords = screenCoords; + m_imageMustRecache = false; + + Rect drawRect = screenCoords; + drawRect.translate(m_imageRect.topLeft()); + if(m_imageRect.isValid()) + drawRect.resize(m_imageRect.size()); + + Rect clipRect = m_imageClipRect.isValid() ? m_imageClipRect : Rect(0, 0, m_imageTexture->getSize()); + + if(!m_imageBordered) { + if(m_imageFixedRatio) { + Size textureSize = m_imageTexture->getSize(); + + Size textureClipSize = drawRect.size(); + textureClipSize.scale(textureSize, Fw::KeepAspectRatio); + + Point texCoordsOffset; + if(textureSize.height() > textureClipSize.height()) + texCoordsOffset.y = (textureSize.height() - textureClipSize.height())/2; + else if(textureSize.width() > textureClipSize.width()) + texCoordsOffset.x = (textureSize.width() - textureClipSize.width())/2; + + Rect textureClipRect(texCoordsOffset, textureClipSize); + + m_imageCoordsBuffer.addRect(drawRect, textureClipRect); + } else { + if(m_imageRepeated) + m_imageCoordsBuffer.addRepeatedRects(drawRect, clipRect); + else + m_imageCoordsBuffer.addRect(drawRect, clipRect); + } + } else { + int top = m_imageBorder.top; + int bottom = m_imageBorder.bottom; + int left = m_imageBorder.left; + int right = m_imageBorder.right; + + // calculates border coords + const Rect clip = clipRect; + Rect leftBorder(clip.left(), clip.top() + top, left, clip.height() - top - bottom); + Rect rightBorder(clip.right() - right + 1, clip.top() + top, right, clip.height() - top - bottom); + Rect topBorder(clip.left() + left, clip.top(), clip.width() - right - left, top); + Rect bottomBorder(clip.left() + left, clip.bottom() - bottom + 1, clip.width() - right - left, bottom); + Rect topLeftCorner(clip.left(), clip.top(), left, top); + Rect topRightCorner(clip.right() - right + 1, clip.top(), right, top); + Rect bottomLeftCorner(clip.left(), clip.bottom() - bottom + 1, left, bottom); + Rect bottomRightCorner(clip.right() - right + 1, clip.bottom() - bottom + 1, right, bottom); + Rect center(clip.left() + left, clip.top() + top, clip.width() - right - left, clip.height() - top - bottom); + Size bordersSize(leftBorder.width() + rightBorder.width(), topBorder.height() + bottomBorder.height()); + Size centerSize = drawRect.size() - bordersSize; + Rect rectCoords; + + // first the center + if(centerSize.area() > 0) { + rectCoords = Rect(drawRect.left() + leftBorder.width(), drawRect.top() + topBorder.height(), centerSize); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, center); + } + // top left corner + rectCoords = Rect(drawRect.topLeft(), topLeftCorner.size()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, topLeftCorner); + // top + rectCoords = Rect(drawRect.left() + topLeftCorner.width(), drawRect.topLeft().y, centerSize.width(), topBorder.height()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, topBorder); + // top right corner + rectCoords = Rect(drawRect.left() + topLeftCorner.width() + centerSize.width(), drawRect.top(), topRightCorner.size()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, topRightCorner); + // left + rectCoords = Rect(drawRect.left(), drawRect.top() + topLeftCorner.height(), leftBorder.width(), centerSize.height()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, leftBorder); + // right + rectCoords = Rect(drawRect.left() + leftBorder.width() + centerSize.width(), drawRect.top() + topRightCorner.height(), rightBorder.width(), centerSize.height()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, rightBorder); + // bottom left corner + rectCoords = Rect(drawRect.left(), drawRect.top() + topLeftCorner.height() + centerSize.height(), bottomLeftCorner.size()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, bottomLeftCorner); + // bottom + rectCoords = Rect(drawRect.left() + bottomLeftCorner.width(), drawRect.top() + topBorder.height() + centerSize.height(), centerSize.width(), bottomBorder.height()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, bottomBorder); + // bottom right corner + rectCoords = Rect(drawRect.left() + bottomLeftCorner.width() + centerSize.width(), drawRect.top() + topRightCorner.height() + centerSize.height(), bottomRightCorner.size()); + m_imageCoordsBuffer.addRepeatedRects(rectCoords, bottomRightCorner); + } + } + + g_drawQueue->addTextureCoords(m_imageCoordsBuffer, m_imageTexture, m_imageColor); +} + +void UIWidget::setQRCode(const std::string& code, int border) +{ + m_imageTexture = TexturePtr(new Texture(Image::fromQRCode(code, border))); + + if (m_imageTexture && (!m_rect.isValid() || m_imageAutoResize)) { + Size size = getSize(); + Size imageSize = m_imageTexture->getSize(); + if (size.width() <= 0 || m_imageAutoResize) + size.setWidth(imageSize.width()); + if (size.height() <= 0 || m_imageAutoResize) + size.setHeight(imageSize.height()); + setSize(size); + } + + m_imageMustRecache = true; +} + +void UIWidget::setImageSource(const std::string& source) +{ + if(source.empty()) + m_imageTexture = nullptr; + else + m_imageTexture = g_textures.getTexture(source); + + if(m_imageTexture && (!m_rect.isValid() || m_imageAutoResize)) { + Size size = getSize(); + Size imageSize = m_imageTexture->getSize(); + if(size.width() <= 0 || m_imageAutoResize) + size.setWidth(imageSize.width()); + if(size.height() <= 0 || m_imageAutoResize) + size.setHeight(imageSize.height()); + setSize(size); + } + + m_imageMustRecache = true; +} + +void UIWidget::setImageSourceBase64(const std::string& data) { + if (data.size() % 4 != 0 || data.empty()) { + m_imageTexture = nullptr; + m_imageMustRecache = true; + return; + } + + std::stringstream stream; + std::string decoded = g_crypt.base64Decode(data); + stream.write(decoded.c_str(), decoded.size()); + m_imageTexture = g_textures.loadTexture(stream, "base64"); + if(m_imageTexture && (!m_rect.isValid() || m_imageAutoResize)) { + Size size = getSize(); + Size imageSize = m_imageTexture->getSize(); + if(size.width() <= 0 || m_imageAutoResize) + size.setWidth(imageSize.width()); + if(size.height() <= 0 || m_imageAutoResize) + size.setHeight(imageSize.height()); + setSize(size); + } + + m_imageMustRecache = true; +} diff --git a/src/framework/ui/uiwidgettext.cpp b/src/framework/ui/uiwidgettext.cpp new file mode 100644 index 0000000..d3b1e8c --- /dev/null +++ b/src/framework/ui/uiwidgettext.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uiwidget.h" +#include "uitranslator.h" +#include +#include +#include + +void UIWidget::initText() +{ + m_font = g_fonts.getDefaultFont(); + m_textAlign = Fw::AlignCenter; +} + +void UIWidget::updateText() +{ + if (m_textWrap && m_rect.isValid()) { + m_drawTextColors = m_textColors; + m_drawText = m_font->wrapText(m_text, getWidth() - m_textOffset.x, &m_drawTextColors); + } else { + m_drawText = m_text; + m_drawTextColors = m_textColors; + } + + // update rect size + if(!m_rect.isValid() || m_textHorizontalAutoResize || m_textVerticalAutoResize) { + Size textBoxSize = getTextSize(); + textBoxSize += Size(m_padding.left + m_padding.right, m_padding.top + m_padding.bottom) + m_textOffset.toSize(); + Size size = getSize(); + if(size.width() <= 0 || (m_textHorizontalAutoResize && !m_textWrap)) + size.setWidth(textBoxSize.width()); + if(size.height() <= 0 || m_textVerticalAutoResize) + size.setHeight(textBoxSize.height()); + setSize(size); + } + + m_textMustRecache = true; +} + +void UIWidget::parseTextStyle(const OTMLNodePtr& styleNode) +{ + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "text") + setText(node->value()); + else if(node->tag() == "text-align") + setTextAlign(Fw::translateAlignment(node->value())); + else if(node->tag() == "text-offset") + setTextOffset(node->value()); + else if(node->tag() == "text-wrap") + setTextWrap(node->value()); + else if(node->tag() == "text-auto-resize") + setTextAutoResize(node->value()); + else if(node->tag() == "text-horizontal-auto-resize") + setTextHorizontalAutoResize(node->value()); + else if(node->tag() == "text-vertical-auto-resize") + setTextVerticalAutoResize(node->value()); + else if(node->tag() == "text-only-upper-case") + setTextOnlyUpperCase(node->value()); + else if(node->tag() == "font") + setFont(node->value()); + } +} + +void UIWidget::drawText(const Rect& screenCoords) +{ + if(m_drawText.length() == 0 || m_color.aF() == 0.0f) + return; + + if(screenCoords != m_textCachedScreenCoords || m_textMustRecache) { + Rect coords = Rect(screenCoords.topLeft() + m_textOffset, screenCoords.bottomRight()); + m_textMustRecache = false; + m_textCachedScreenCoords = coords; + } + + if (!m_drawTextColors.empty()) { + m_font->drawColoredText(m_drawText, m_textCachedScreenCoords, m_textAlign, m_drawTextColors); + } else { + m_font->drawText(m_drawText, m_textCachedScreenCoords, m_textAlign, m_color); + } +} + +void UIWidget::onTextChange(const std::string& text, const std::string& oldText) +{ + callLuaField("onTextChange", text, oldText); +} + +void UIWidget::onFontChange(const std::string& font) +{ + callLuaField("onFontChange", font); +} + +void UIWidget::setText(std::string text, bool dontFireLuaCall) +{ + if(m_textOnlyUpperCase) + stdext::toupper(text); + + m_textColors.clear(); + m_drawTextColors.clear(); + + if(m_text == text) + return; + + std::string oldText = m_text; + m_text = text; + updateText(); + + if(!dontFireLuaCall) { + onTextChange(text, oldText); + } +} + +void UIWidget::setColoredText(const std::vector& texts, bool dontFireLuaCall) +{ + m_textColors.clear(); + m_drawTextColors.clear(); + + std::string text = ""; + for(size_t i = 0, p = 0; i < texts.size() - 1; i += 2) { + Color c(Color::white); + stdext::cast(texts[i + 1], c); + text += texts[i]; + for (auto& c : texts[i]) { + if ((uint8)c >= 32) + p += 1; + } + m_textColors.push_back(std::make_pair(p, c)); + } + + if (m_textOnlyUpperCase) + stdext::toupper(text); + + std::string oldText = m_text; + m_text = text; + updateText(); + + if (!dontFireLuaCall) { + onTextChange(text, oldText); + } +} + +void UIWidget::setFont(const std::string& fontName) +{ + m_font = g_fonts.getFont(fontName); + updateText(); + onFontChange(fontName); +} diff --git a/src/framework/util/color.cpp b/src/framework/util/color.cpp new file mode 100644 index 0000000..96515ff --- /dev/null +++ b/src/framework/util/color.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "color.h" + +// NOTE: AABBGGRR order +const Color Color::alpha = 0x00000000U; +const Color Color::white = 0xffffffff; +const Color Color::black = 0xff000000; +const Color Color::red = 0xff0000ff; +const Color Color::darkRed = 0xff000080; +const Color Color::green = 0xff00ff00; +const Color Color::darkGreen = 0xff008000; +const Color Color::blue = 0xffff0000; +const Color Color::darkBlue = 0xff800000; +const Color Color::pink = 0xffff00ff; +const Color Color::darkPink = 0xff800080; +const Color Color::yellow = 0xff00ffff; +const Color Color::darkYellow = 0xff008080; +const Color Color::teal = 0xffffff00; +const Color Color::darkTeal = 0xff808000; +const Color Color::gray = 0xffa0a0a0; +const Color Color::darkGray = 0xff808080; +const Color Color::lightGray = 0xffc0c0c0; +const Color Color::orange = 0xff008cff; + +Color::Color(const std::string& coltext) +{ + std::stringstream ss(coltext); + ss >> *this; +} + +Color Color::getOutfitColor(int color) +{ + static const int HSI_SI_VALUES = 7; + static const int HSI_H_STEPS = 19; + + if (color >= HSI_H_STEPS * HSI_SI_VALUES) + color = 0; + + float loc1 = 0, loc2 = 0, loc3 = 0; + if (color % HSI_H_STEPS != 0) { + loc1 = color % HSI_H_STEPS * 1.0 / 18.0; + loc2 = 1; + loc3 = 1; + + switch (int(color / HSI_H_STEPS)) { + case 0: + loc2 = 0.25; + loc3 = 1.00; + break; + case 1: + loc2 = 0.25; + loc3 = 0.75; + break; + case 2: + loc2 = 0.50; + loc3 = 0.75; + break; + case 3: + loc2 = 0.667; + loc3 = 0.75; + break; + case 4: + loc2 = 1.00; + loc3 = 1.00; + break; + case 5: + loc2 = 1.00; + loc3 = 0.75; + break; + case 6: + loc2 = 1.00; + loc3 = 0.50; + break; + } + } else { + loc1 = 0; + loc2 = 0; + loc3 = 1 - (float)color / HSI_H_STEPS / (float)HSI_SI_VALUES; + } + + if (loc3 == 0) + return Color(0, 0, 0); + + if (loc2 == 0) { + int loc7 = int(loc3 * 255); + return Color(loc7, loc7, loc7); + } + + float red = 0, green = 0, blue = 0; + + if (loc1 < 1.0 / 6.0) { + red = loc3; + blue = loc3 * (1 - loc2); + green = blue + (loc3 - blue) * 6 * loc1; + } else if (loc1 < 2.0 / 6.0) { + green = loc3; + blue = loc3 * (1 - loc2); + red = green - (loc3 - blue) * (6 * loc1 - 1); + } else if (loc1 < 3.0 / 6.0) { + green = loc3; + red = loc3 * (1 - loc2); + blue = red + (loc3 - red) * (6 * loc1 - 2); + } else if (loc1 < 4.0 / 6.0) { + blue = loc3; + red = loc3 * (1 - loc2); + green = blue - (loc3 - red) * (6 * loc1 - 3); + } else if (loc1 < 5.0 / 6.0) { + blue = loc3; + green = loc3 * (1 - loc2); + red = green + (loc3 - green) * (6 * loc1 - 4); + } else { + red = loc3; + green = loc3 * (1 - loc2); + blue = red - (loc3 - green) * (6 * loc1 - 5); + } + return Color(int(red * 255), int(green * 255), int(blue * 255)); +} + +std::string Color::toHex() +{ + std::stringstream ss; + ss << *this; + return ss.str(); +} \ No newline at end of file diff --git a/src/framework/util/color.h b/src/framework/util/color.h new file mode 100644 index 0000000..964b5b5 --- /dev/null +++ b/src/framework/util/color.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef COLOR_H +#define COLOR_H + +#include "../stdext/types.h" +#include "../stdext/cast.h" +#include "../stdext/string.h" +#include "../const.h" +#include +#include +#include + +class Color +{ +public: + Color() : m_r(1.0f), m_g(1.0f), m_b(1.0f), m_a(1.0f) { } + Color(uint32 rgba) { setRGBA(rgba); } + Color(uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) : m_r(r/255.0f), m_g(g/255.0f), m_b(b/255.0f), m_a(a/255.0f) { } + Color(int r, int g, int b, int a = 0xFF) : m_r(r/255.0f), m_g(g/255.0f), m_b(b/255.0f), m_a(a/255.0f) { } + Color(float r, float g, float b, float a = 1.0f) : m_r(r), m_g(g), m_b(b), m_a(a) { } + Color(const std::string& coltext); + + uint8 a() const { return 255.0f * m_a; } + uint8 b() const { return 255.0f * m_b; } + uint8 g() const { return 255.0f * m_g; } + uint8 r() const { return 255.0f * m_r; } + + float aF() const { return m_a; } + float bF() const { return m_b; } + float gF() const { return m_g; } + float rF() const { return m_r; } + + void setRed(int r) { m_r = 0.003921f * r; } + void setGreen(int g) { m_g = 0.003921f * g; } + void setBlue(int b) { m_b = 0.003921f * b; } + void setAlpha(int a) { m_a = 0.003921f * a; } + + void setRed(float r) { m_r = r; } + void setGreen(float g) { m_g = g; } + void setBlue(float b) { m_b = b; } + void setAlpha(float a) { m_a = a; } + + void setRGBA(uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) { m_r = r/255.0f; m_g = g/255.0f; m_b = b/255.0f; m_a = a/255.0f; } + void setRGBA(uint32 rgba) { setRGBA((rgba >> 0) & 0xff, (rgba >> 8) & 0xff, (rgba >> 16) & 0xff, (rgba >> 24) & 0xff); } + + Color opacity(float opacity) const { + return Color(m_r, m_g, m_b, m_a * opacity); + } + + Color operator+(const Color& other) const { return Color(m_r + other.m_r, m_g + other.m_g, m_b + other.m_b, m_a + other.m_a); } + Color operator-(const Color& other) const { return Color(m_r - other.m_r, m_g - other.m_g, m_b - other.m_b, m_a - other.m_a); } + + Color operator*(float v) const { return Color(m_r*v, m_g*v, m_b*v, m_a*v); } + Color operator/(float v) const { return Color(m_r/v, m_g/v, m_b/v, m_a/v); } + + Color& operator=(uint32_t rgba) { setRGBA(rgba); return *this; } + + Color operator+=(const Color& other) const { + return Color(std::min(1.0f, m_r + other.m_r), std::min(1.0f, m_g + other.m_g), + std::min(1.0f, m_b + other.m_b), std::min(1.0f, m_a + other.m_a)); + } + + Color& operator=(const Color& other) { m_r = other.m_r; m_g = other.m_g; m_b = other.m_b; m_a = other.m_a; return *this; } + bool operator==(const Color& other) const { + if (std::abs(other.m_r - m_r) > 0.001) return false; + if (std::abs(other.m_g - m_g) > 0.001) return false; + if (std::abs(other.m_b - m_b) > 0.001) return false; + if (std::abs(other.m_a - m_a) > 0.001) return false; + return true; + } + bool operator!=(const Color& other) const { return !(other == *this); } + + std::string toHex(); + + static uint8 to8bit(const Color& color) { + uint8 c = 0; + c += (color.r() / 51) * 36; + c += (color.g() / 51) * 6; + c += (color.b() / 51); + return c; + } + + static Color from8bit(int color) { + if(color >= 216 || color <= 0) + return Color(0, 0, 0); + + int r = int(color / 36) % 6 * 51; + int g = int(color / 6) % 6 * 51; + int b = color % 6 * 51; + return Color(r, g, b); + } + + static Color getOutfitColor(int color); + + static const Color alpha; + static const Color white; + static const Color black; + static const Color red; + static const Color darkRed; + static const Color green; + static const Color darkGreen; + static const Color blue; + static const Color darkBlue; + static const Color pink; + static const Color darkPink; + static const Color yellow; + static const Color darkYellow; + static const Color teal; + static const Color darkTeal; + static const Color gray; + static const Color darkGray; + static const Color lightGray; + static const Color orange; + +private: + float m_r; + float m_g; + float m_b; + float m_a; +}; + +inline std::ostream& operator<<(std::ostream& out, const Color& color) +{ + using namespace std; + out << "#" << hex << setfill('0') + << setw(2) << (int)color.r() + << setw(2) << (int)color.g() + << setw(2) << (int)color.b() + << setw(2) << (int)color.a(); + out << dec << setfill(' '); + return out; +} + +inline std::istream& operator>>(std::istream& in, Color& color) +{ + using namespace std; + std::string tmp; + + if(in.get() == '#') { + in >> tmp; + + if(tmp.length() == 6 || tmp.length() == 8) { + color.setRed((uint8)stdext::hex_to_dec(tmp.substr(0, 2))); + color.setGreen((uint8)stdext::hex_to_dec(tmp.substr(2, 2))); + color.setBlue((uint8)stdext::hex_to_dec(tmp.substr(4, 2))); + if(tmp.length() == 8) + color.setAlpha((uint8)stdext::hex_to_dec(tmp.substr(6, 2))); + else + color.setAlpha(255); + } else + in.seekg(-(std::streampos)tmp.length()-1, ios_base::cur); + } else { + in.unget(); + in >> tmp; + + if(tmp == "alpha") { + color = Color::alpha; + } else if(tmp == "black") { + color = Color::black; + } else if(tmp == "white") { + color = Color::white; + } else if(tmp == "red") { + color = Color::red; + } else if(tmp == "darkRed") { + color = Color::darkRed; + } else if(tmp == "green") { + color = Color::green; + } else if(tmp == "darkGreen") { + color = Color::darkGreen; + } else if(tmp == "blue") { + color = Color::blue; + } else if(tmp == "darkBlue") { + color = Color::darkBlue; + } else if(tmp == "pink") { + color = Color::pink; + } else if(tmp == "darkPink") { + color = Color::darkPink; + } else if(tmp == "yellow") { + color = Color::yellow; + } else if(tmp == "darkYellow") { + color = Color::darkYellow; + } else if(tmp == "teal") { + color = Color::teal; + } else if(tmp == "darkTeal") { + color = Color::darkTeal; + } else if(tmp == "gray") { + color = Color::gray; + } else if(tmp == "darkGray") { + color = Color::darkGray; + } else if(tmp == "lightGray") { + color = Color::lightGray; + } else if(tmp == "orange") { + color = Color::orange; + } else { + in.seekg(-tmp.length(), ios_base::cur); + } + } + return in; +} + +#endif diff --git a/src/framework/util/extras.cpp b/src/framework/util/extras.cpp new file mode 100644 index 0000000..52f9d71 --- /dev/null +++ b/src/framework/util/extras.cpp @@ -0,0 +1,6 @@ +#include "extras.h" +#include +#include +#include + +Extras g_extras; diff --git a/src/framework/util/extras.h b/src/framework/util/extras.h new file mode 100644 index 0000000..46c7fe5 --- /dev/null +++ b/src/framework/util/extras.h @@ -0,0 +1,86 @@ +#ifndef EXTRAS_H +#define EXTRAS_H + +#include +#include +#include +#include + +constexpr bool default_value = true; + +#define DEFINE_OPTION(option, description) { m_options[ #option ] = std::make_pair(description, &(this -> option )); } + +class Extras { +public: + Extras() { + DEFINE_OPTION(limitedPolling, "Limited polling"); + + DEFINE_OPTION(debugWalking, "Debug walking"); + DEFINE_OPTION(debugPredictiveWalking, "Debug predictive walking"); + DEFINE_OPTION(debugPathfinding, "Debug path finding"); + DEFINE_OPTION(debugRender, "Debug render"); + DEFINE_OPTION(debugProxy, "Debug proxy"); + DEFINE_OPTION(showPredictions, "Show predictions"); + DEFINE_OPTION(debugWidgets, "Debug widgets"); + + DEFINE_OPTION(disablePredictiveWalking, "Disable predictive walking"); + } + + bool botDetection = default_value; + + bool newMapViewRendering = default_value; + bool limitedPolling = default_value; + + bool debugWalking = false; + bool debugPredictiveWalking = false; + bool debugPathfinding = false; + bool debugRender = false; + bool debugProxy = false; + bool disablePredictiveWalking = false; + bool showPredictions = false; + bool debugWidgets = false; + + int testMode = 0; + + void set(const std::string& key, bool value) { + auto it = m_options.find(key); + if (it == m_options.end()) { + g_logger.fatal(std::string("Invalid extraOptions key:") + key); + return; + } + *(it->second.second) = value; + } + + bool get(const std::string& key) { + auto it = m_options.find(key); + if (it == m_options.end()) { + g_logger.fatal(std::string("Invalid extraOptions key:") + key); + return false; + } + return *(it->second.second); + } + + std::string getDescription(const std::string& key) { + auto it = m_options.find(key); + if (it == m_options.end()) { + g_logger.fatal(std::string("Invalid extraOptions key:") + key); + return ""; + } + return it->second.first; + } + + std::vector getAll() { + std::vector ret; + for (auto& it : m_options) + ret.push_back(it.first); + return ret; + } + +private: + std::map> m_options; + std::deque framerRenderTimes; +}; + +extern Extras g_extras; + +#endif \ No newline at end of file diff --git a/src/framework/util/framecounter.h b/src/framework/util/framecounter.h new file mode 100644 index 0000000..0607434 --- /dev/null +++ b/src/framework/util/framecounter.h @@ -0,0 +1,26 @@ +#include +#include +#include "../stdext/time.h" + +class FrameCounter { +public: + void addFrame() // not thread-safe + { + ticks_t now = stdext::millis(); + m_framesList.push_back(now); + m_frames += 1; + while (m_framesList.front() + 1000 < now) { + m_framesList.pop_front(); + m_frames -= 1; + } + } + + int getFps() // thread safe + { + return m_frames.load(); + } + +private: + std::list m_framesList; + std::atomic_int m_frames; +}; diff --git a/src/framework/util/matrix.h b/src/framework/util/matrix.h new file mode 100644 index 0000000..d9aaf76 --- /dev/null +++ b/src/framework/util/matrix.h @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MATRIX_H +#define MATRIX_H + +#include +#include +#include +#include + +template +class Matrix +{ +public: + Matrix() { setIdentity(); } + Matrix(int) {} // construct without initializing identity matrix + Matrix(const Matrix& other) = default; + template + Matrix(const std::initializer_list& values) { *this = values; } + template + Matrix(const U *values) { *this = values; } + + void setIdentity(); + bool isIdentity() const; + void fill(T value); + + Matrix transposed() const; + typename std::enable_if::type transpose() { *this = transposed(); } + + T *data() { return m[0]; } + const T *data() const { return m[0]; } + + T& operator()(int row, int column) { return m[row-1][column-1]; } + T operator()(int row, int column) const { return m[row-1][column-1]; } + + Matrix& operator=(const Matrix& other) = default; + template + Matrix& operator=(const std::initializer_list& values); + template + Matrix& operator=(const U *values); + Matrix& operator+=(const Matrix& other); + Matrix& operator-=(const Matrix& other); + Matrix& operator*=(T factor); + Matrix& operator/=(T divisor); + bool operator==(const Matrix& other) const; + bool operator!=(const Matrix& other) const; + +private: + T m[N][M]; +}; + +template +void Matrix::setIdentity() { + for(int i=0;i +bool Matrix::isIdentity() const { + for(int i=0;i +void Matrix::fill(T value) { + for(int i=0;i +Matrix Matrix::transposed() const { + Matrix result(1); + for(int i=0;i +template +Matrix& Matrix::operator=(const std::initializer_list& values) { + auto it = values.begin(); + for(int i=0;i +template +Matrix& Matrix::operator=(const U *values) { + for(int i=0;i +Matrix& Matrix::operator+=(const Matrix& other) { + for(int i=0;i +Matrix& Matrix::operator-=(const Matrix& other) { + for(int i=0;i +Matrix& Matrix::operator*=(T factor) { + for(int i=0;i +Matrix& Matrix::operator/=(T divisor) { + for(int i=0;i +bool Matrix::operator==(const Matrix& other) const +{ + for(int i=0;i +bool Matrix::operator!=(const Matrix& other) const +{ + return !(*this == other); +} + +template +std::ostream& operator<<(std::ostream& out, const Matrix& mat) +{ + for(int i=1;i<=N;++i) { + for(int j=1;j<=M;++j) { + out << mat(i,j); + if(j != M) + out << " "; + } + out << "\n"; + } + return out; +} + +template +std::istream& operator>>(std::istream& in, Matrix& mat) +{ + for(int i=0;i> mat(i,j); + return in; +} + +// faster comparing for 3x3 matrixes +template<> +inline bool Matrix<3,3,float>::operator==(const Matrix<3,3,float>& other) const +{ + return m[0][0] == other.m[0][0] && m[1][1] == other.m[1][1] && + m[2][1] == other.m[2][1] && m[2][0] == other.m[2][0] && + m[1][2] == other.m[1][2] && m[0][2] == other.m[0][2] && + m[1][0] == other.m[1][0] && m[0][1] == other.m[0][1] && + m[2][2] == other.m[2][2]; +} + +template +Matrix operator*(const Matrix& a, const Matrix& b) +{ + static_assert(N==P, "N==P"); + Matrix c(1); + for(int i=1;i<=M;++i) { + for(int j=1;j<=Q;++j) { + T sum = 0; + for(int k=1;k<=N;++k) + sum += a(i,k) * b(k,j); + c(i,j) = sum; + } + } + return c; +} + +template +Matrix operator+(const Matrix& a, const Matrix& b) { Matrix c(a); c += b; return c; } + +template +Matrix operator-(const Matrix& a, const Matrix& b) { Matrix c(a); c -= b; return c; } + +template +Matrix operator*(const Matrix& a, float b) { Matrix c(a); c *= b; return c; } + +template +Matrix operator/(const Matrix& a, float b) { Matrix c = a; c /= b; return c; } + +typedef Matrix<4,4> Matrix4x4; +typedef Matrix<3,3> Matrix3x3; +typedef Matrix<2,2> Matrix2x2; + +typedef Matrix4x4 Matrix4; +typedef Matrix3x3 Matrix3; +typedef Matrix2x2 Matrix2; + +#endif diff --git a/src/framework/util/point.h b/src/framework/util/point.h new file mode 100644 index 0000000..1bb4cd0 --- /dev/null +++ b/src/framework/util/point.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef POINT_H +#define POINT_H + +#include "../stdext/types.h" +#include +#include + +template +class TSize; + +template +class TPoint +{ +public: + TPoint() : x(0), y(0) {} + TPoint(T x, T y) : x(x), y(y) { } + TPoint(const TPoint& other) : x(other.x), y(other.y) { } + + bool isNull() const { return x==0 && y==0; } + TSize toSize() const { return TSize(x, y); } + + TPoint operator-() const { return TPoint(-x, -y); } + + TPoint operator+(const TPoint& other) const { return TPoint(x + other.x, y + other.y); } + TPoint& operator+=(const TPoint& other) { x+=other.x; y+=other.y; return *this; } + TPoint operator-(const TPoint& other) const { return TPoint(x - other.x, y - other.y); } + TPoint& operator-=(const TPoint& other) { x-=other.x; y-=other.y; return *this; } + TPoint operator*(const TPoint& other) const { return TPoint(x * other.x, y * other.y); } + TPoint& operator*=(const TPoint& other) { x*=other.x; y*=other.y; return *this; } + TPoint operator/(const TPoint& other) const { return TPoint(x/other.x, y/other.y); } + TPoint& operator/=(const TPoint& other) { x/=other.x; y/=other.y; return *this; } + + TPoint operator+(T other) const { return TPoint(x + other, y + other); } + TPoint& operator+=(T other) { x+=other; y+=other; return *this; } + TPoint operator-(T other) const { return TPoint(x - other, y - other); } + TPoint& operator-=(T other) { x-=other; y-=other; return *this; } + TPoint operator*(float v) const { return TPoint(x*v, y*v); } + TPoint& operator*=(float v) { x*=v; y*=v; return *this; } + TPoint operator/(float v) const { return TPoint(x/v, y/v); } + TPoint& operator/=(float v) { x/=v; y/=v; return *this; } + + TPoint operator&(int a) { return TPoint(x & a, y & a); } + TPoint& operator&=(int a) { x &= a; y &= a; return *this; } + + bool operator<=(const TPoint&other) const { return x<=other.x || (x==other.x && y<=other.y); } + bool operator>=(const TPoint&other) const { return x>=other.x || (x==other.x && y>=other.y); } + bool operator<(const TPoint&other) const { return x(const TPoint&other) const { return x>other.x || (x==other.x && y>other.y); } + + TPoint& operator=(const TPoint& other) { x = other.x; y = other.y; return *this; } + bool operator==(const TPoint& other) const { return other.x==x && other.y==y; } + bool operator!=(const TPoint& other) const { return other.x!=x || other.y!=y; } + + float length() const { return sqrt((float)(x*x + y*y)); } + T manhattanLength() const { return std::abs(x) + std::abs(y); } + + float distanceFrom(const TPoint& other) const { + return TPoint(x - other.x, y - other.y).getLength(); + } + + T x, y; +}; + +typedef TPoint Point; +typedef TPoint PointF; + +template +std::ostream& operator<<(std::ostream& out, const TPoint& point) +{ + out << point.x << " " << point.y; + return out; +} + +template +std::istream& operator>>(std::istream& in, TPoint& point) +{ + in >> point.x; + in >> point.y; + return in; +} + +#endif diff --git a/src/framework/util/qrcodegen.c b/src/framework/util/qrcodegen.c new file mode 100644 index 0000000..9da795e --- /dev/null +++ b/src/framework/util/qrcodegen.c @@ -0,0 +1,1022 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include +#include +#include +#include +#include "qrcodegen.h" + +#ifndef QRCODEGEN_TEST + #define testable static // Keep functions private +#else + #define testable // Expose private functions +#endif + + +/*---- Forward declarations for private functions ----*/ + +// Regarding all public and private functions defined in this source file: +// - They require all pointer/array arguments to be not null unless the array length is zero. +// - They only read input scalar/array arguments, write to output pointer/array +// arguments, and return scalar values; they are "pure" functions. +// - They don't read mutable global variables or write to any global variables. +// - They don't perform I/O, read the clock, print to console, etc. +// - They allocate a small and constant amount of stack memory. +// - They don't allocate or free any memory on the heap. +// - They don't recurse or mutually recurse. All the code +// could be inlined into the top-level public functions. +// - They run in at most quadratic time with respect to input arguments. +// Most functions run in linear time, and some in constant time. +// There are no unbounded loops or non-obvious termination conditions. +// - They are completely thread-safe if the caller does not give the +// same writable buffer to concurrent calls to these functions. + +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); + +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); +testable int getNumRawDataModules(int ver); + +testable void reedSolomonComputeDivisor(int degree, uint8_t result[]); +testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]); +testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y); + +testable void initializeFunctionModules(int version, uint8_t qrcode[]); +static void drawWhiteFunctionModules(uint8_t qrcode[], int version); +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]); +testable int getAlignmentPatternPositions(int version, uint8_t result[7]); +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]); + +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); +static long getPenaltyScore(const uint8_t qrcode[]); +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize); +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize); +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]); + +testable bool getModule(const uint8_t qrcode[], int x, int y); +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack); +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack); +static bool getBit(int x, int i); + +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars); +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); +static int numCharCountBits(enum qrcodegen_Mode mode, int version); + + + +/*---- Private tables of constants ----*/ + +// The set of all legal characters in alphanumeric mode, where each character +// value maps to the index in the string. For checking text and encoding segments. +static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +// For generating error correction codes. +testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above + +// For generating error correction codes. +testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +// For automatic mask pattern selection. +static const int PENALTY_N1 = 3; +static const int PENALTY_N2 = 3; +static const int PENALTY_N3 = 40; +static const int PENALTY_N4 = 10; + + + +/*---- High-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + size_t textLen = strlen(text); + if (textLen == 0) + return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + size_t bufLen = (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); + + struct qrcodegen_Segment seg; + if (qrcodegen_isNumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeNumeric(text, tempBuffer); + } else if (qrcodegen_isAlphanumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeAlphanumeric(text, tempBuffer); + } else { + if (textLen > bufLen) + goto fail; + for (size_t i = 0; i < textLen; i++) + tempBuffer[i] = (uint8_t)text[i]; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, textLen); + if (seg.bitLength == -1) + goto fail; + seg.numChars = (int)textLen; + seg.data = tempBuffer; + } + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + +fail: + qrcode[0] = 0; // Set size to invalid value for safety + return false; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + struct qrcodegen_Segment seg; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); + if (seg.bitLength == -1) { + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + seg.numChars = (int)dataLen; + seg.data = dataAndTemp; + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode); +} + + +// Appends the given number of low-order bits of the given value to the given byte-based +// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { + assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); + for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) + buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); +} + + + +/*---- Low-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { + return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true, tempBuffer, qrcode); +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = getTotalBits(segs, len, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) + ecl = (enum qrcodegen_Ecc)i; + } + + // Concatenate all segments to create the data bit string + memset(qrcode, 0, (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); + int bitLen = 0; + for (size_t i = 0; i < len; i++) { + const struct qrcodegen_Segment *seg = &segs[i]; + appendBitsToBuffer((unsigned int)seg->mode, 4, qrcode, &bitLen); + appendBitsToBuffer((unsigned int)seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen); + for (int j = 0; j < seg->bitLength; j++) { + int bit = (seg->data[j >> 3] >> (7 - (j & 7))) & 1; + appendBitsToBuffer((unsigned int)bit, 1, qrcode, &bitLen); + } + } + assert(bitLen == dataUsedBits); + + // Add terminator and pad up to a byte if applicable + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + assert(bitLen <= dataCapacityBits); + int terminatorBits = dataCapacityBits - bitLen; + if (terminatorBits > 4) + terminatorBits = 4; + appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); + appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); + assert(bitLen % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBitsToBuffer(padByte, 8, qrcode, &bitLen); + + // Draw function and data codeword modules + addEccAndInterleave(qrcode, version, ecl, tempBuffer); + initializeFunctionModules(version, qrcode); + drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); + drawWhiteFunctionModules(qrcode, version); + initializeFunctionModules(version, tempBuffer); + + // Handle masking + if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i; + applyMask(tempBuffer, qrcode, msk); + drawFormatBits(ecl, msk, qrcode); + long penalty = getPenaltyScore(qrcode); + if (penalty < minPenalty) { + mask = msk; + minPenalty = penalty; + } + applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR + } + } + assert(0 <= (int)mask && (int)mask <= 7); + applyMask(tempBuffer, qrcode, mask); + drawFormatBits(ecl, mask, qrcode); + return true; +} + + + +/*---- Error correction code generation functions ----*/ + +// Appends error correction bytes to each block of the given data array, then interleaves +// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains +// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will +// be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { + // Calculate parameter numbers + assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int dataLen = getNumDataCodewords(version, ecl); + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; + + // Split data into blocks, calculate ECC, and interleave + // (not concatenate) the bytes into a single sequence + uint8_t rsdiv[qrcodegen_REED_SOLOMON_DEGREE_MAX]; + reedSolomonComputeDivisor(blockEccLen, rsdiv); + const uint8_t *dat = data; + for (int i = 0; i < numBlocks; i++) { + int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); + uint8_t *ecc = &data[dataLen]; // Temporary storage + reedSolomonComputeRemainder(dat, datLen, rsdiv, blockEccLen, ecc); + for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data + if (j == shortBlockDataLen) + k -= numShortBlocks; + result[k] = dat[j]; + } + for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC + result[k] = ecc[j]; + dat += datLen; + } +} + + +// Returns the number of 8-bit codewords that can be used for storing data (not ECC), +// for the given version number and error correction level. The result is in the range [9, 2956]. +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { + int v = version, e = (int)ecl; + assert(0 <= e && e < 4); + return getNumRawDataModules(v) / 8 + - ECC_CODEWORDS_PER_BLOCK [e][v] + * NUM_ERROR_CORRECTION_BLOCKS[e][v]; +} + + +// Returns the number of data bits that can be stored in a QR Code of the given version number, after +// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. +// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +testable int getNumRawDataModules(int ver) { + assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + assert(208 <= result && result <= 29648); + return result; +} + + + +/*---- Reed-Solomon ECC generator functions ----*/ + +// Computes a Reed-Solomon ECC generator polynomial for the given degree, storing in result[0 : degree]. +// This could be implemented as a lookup table over all possible parameter values, instead of as an algorithm. +testable void reedSolomonComputeDivisor(int degree, uint8_t result[]) { + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + memset(result, 0, (size_t)degree * sizeof(result[0])); + result[degree - 1] = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < degree; j++) { + result[j] = reedSolomonMultiply(result[j], root); + if (j + 1 < degree) + result[j] ^= result[j + 1]; + } + root = reedSolomonMultiply(root, 0x02); + } +} + + +// Computes the Reed-Solomon error correction codeword for the given data and divisor polynomials. +// The remainder when data[0 : dataLen] is divided by divisor[0 : degree] is stored in result[0 : degree]. +// All polynomials are in big endian, and the generator has an implicit leading 1 term. +testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]) { + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, (size_t)degree * sizeof(result[0])); + for (int i = 0; i < dataLen; i++) { // Polynomial division + uint8_t factor = data[i] ^ result[0]; + memmove(&result[0], &result[1], (size_t)(degree - 1) * sizeof(result[0])); + result[degree - 1] = 0; + for (int j = 0; j < degree; j++) + result[j] ^= reedSolomonMultiply(generator[j], factor); + } +} + +#undef qrcodegen_REED_SOLOMON_DEGREE_MAX + + +// Returns the product of the two given field elements modulo GF(2^8/0x11D). +// All inputs are valid. This could be implemented as a 256*256 lookup table. +testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + uint8_t z = 0; + for (int i = 7; i >= 0; i--) { + z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); + z ^= ((y >> i) & 1) * x; + } + return z; +} + + + +/*---- Drawing function modules ----*/ + +// Clears the given QR Code grid with white modules for the given +// version's size, then marks every function module as black. +testable void initializeFunctionModules(int version, uint8_t qrcode[]) { + // Initialize QR Code + int qrsize = version * 4 + 17; + memset(qrcode, 0, (size_t)((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); + qrcode[0] = (uint8_t)qrsize; + + // Fill horizontal and vertical timing patterns + fillRectangle(6, 0, 1, qrsize, qrcode); + fillRectangle(0, 6, qrsize, 1, qrcode); + + // Fill 3 finder patterns (all corners except bottom right) and format bits + fillRectangle(0, 0, 9, 9, qrcode); + fillRectangle(qrsize - 8, 0, 8, 9, qrcode); + fillRectangle(0, qrsize - 8, 9, 8, qrcode); + + // Fill numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); + } + } + + // Fill version blocks + if (version >= 7) { + fillRectangle(qrsize - 11, 0, 3, 6, qrcode); + fillRectangle(0, qrsize - 11, 6, 3, qrcode); + } +} + + +// Draws white function modules and possibly some black modules onto the given QR Code, without changing +// non-function modules. This does not draw the format bits. This requires all function modules to be previously +// marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules. +static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { + // Draw horizontal and vertical timing patterns + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 7; i < qrsize - 7; i += 2) { + setModule(qrcode, 6, i, false); + setModule(qrcode, i, 6, false); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = abs(dx); + if (abs(dy) > dist) + dist = abs(dy); + if (dist == 2 || dist == 4) { + setModuleBounded(qrcode, 3 + dx, 3 + dy, false); + setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false); + setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false); + } + } + } + + // Draw numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) + continue; // Don't draw on the three finder corners + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) + setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0); + } + } + } + + // Draw version blocks + if (version >= 7) { + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = (long)version << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 3; j++) { + int k = qrsize - 11 + j; + setModule(qrcode, k, i, (bits & 1) != 0); + setModule(qrcode, i, k, (bits & 1) != 0); + bits >>= 1; + } + } + } +} + + +// Draws two copies of the format bits (with its own error correction code) based +// on the given mask and error correction level. This always draws all modules of +// the format bits, unlike drawWhiteFunctionModules() which might skip black modules. +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { + // Calculate error correction code and pack bits + assert(0 <= (int)mask && (int)mask <= 7); + static const int table[] = {1, 0, 3, 2}; + int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setModule(qrcode, 8, i, getBit(bits, i)); + setModule(qrcode, 8, 7, getBit(bits, 6)); + setModule(qrcode, 8, 8, getBit(bits, 7)); + setModule(qrcode, 7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setModule(qrcode, 14 - i, 8, getBit(bits, i)); + + // Draw second copy + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 0; i < 8; i++) + setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i)); + setModule(qrcode, 8, qrsize - 8, true); // Always black +} + + +// Calculates and stores an ascending list of positions of alignment patterns +// for this version number, returning the length of the list (in the range [0,7]). +// Each position is in the range [0,177), and are used on both the x and y axes. +// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { + if (version == 1) + return 0; + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step) + result[i] = (uint8_t)pos; + result[0] = 6; + return numAlign; +} + + +// Sets every pixel in the range [left : left + width] * [top : top + height] to black. +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) { + for (int dy = 0; dy < height; dy++) { + for (int dx = 0; dx < width; dx++) + setModule(qrcode, left + dx, top + dy, true); + } +} + + + +/*---- Drawing data modules and masking ----*/ + +// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of +// the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < qrsize; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate + if (!getModule(qrcode, x, y) && i < dataLen * 8) { + bool black = getBit(data[i >> 3], 7 - (i & 7)); + setModule(qrcode, x, y, black); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/white by the constructor and are left unchanged by this method + } + } + } + assert(i == dataLen * 8); +} + + +// XORs the codeword modules in this QR Code with the given mask pattern. +// The function modules must be marked and the codeword bits must be drawn +// before masking. Due to the arithmetic of XOR, calling applyMask() with +// the same mask value a second time will undo the mask. A final well-formed +// QR Code needs exactly one (not zero, two, etc.) mask applied. +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { + assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO + int qrsize = qrcodegen_getSize(qrcode); + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(functionModules, x, y)) + continue; + bool invert; + switch ((int)mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: assert(false); return; + } + bool val = getModule(qrcode, x, y); + setModule(qrcode, x, y, val ^ invert); + } + } +} + + +// Calculates and returns the penalty score based on state of the given QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +static long getPenaltyScore(const uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < qrsize; y++) { + bool runColor = false; + int runX = 0; + int runHistory[7] = {0}; + int padRun = qrsize; // Add white border to initial run + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; + runColor = getModule(qrcode, x, y); + runX = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory, qrsize) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < qrsize; x++) { + bool runColor = false; + int runY = 0; + int runHistory[7] = {0}; + int padRun = qrsize; // Add white border to initial run + for (int y = 0; y < qrsize; y++) { + if (getModule(qrcode, x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; + runColor = getModule(qrcode, x, y); + runY = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory, qrsize) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < qrsize - 1; y++) { + for (int x = 0; x < qrsize - 1; x++) { + bool color = getModule(qrcode, x, y); + if ( color == getModule(qrcode, x + 1, y) && + color == getModule(qrcode, x, y + 1) && + color == getModule(qrcode, x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of black and white modules + int black = 0; + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y)) + black++; + } + } + int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = (int)((labs(black * 20L - total * 10L) + total - 1) / total) - 1; + result += k * PENALTY_N4; + return result; +} + + +// Can only be called immediately after a white run is added, and +// returns either 0, 1, or 2. A helper function for getPenaltyScore(). +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize) { + int n = runHistory[1]; + assert(n <= qrsize * 3); + bool core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + // The maximum QR Code size is 177, hence the black run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); +} + + +// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize) { + if (currentRunColor) { // Terminate black run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += qrsize; // Add white border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory, qrsize); +} + + +// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]) { + memmove(&runHistory[1], &runHistory[0], 6 * sizeof(runHistory[0])); + runHistory[0] = currentRunLength; +} + + + +/*---- Basic QR Code information ----*/ + +// Public function - see documentation comment in header file. +int qrcodegen_getSize(const uint8_t qrcode[]) { + assert(qrcode != NULL); + int result = qrcode[0]; + assert((qrcodegen_VERSION_MIN * 4 + 17) <= result + && result <= (qrcodegen_VERSION_MAX * 4 + 17)); + return result; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { + assert(qrcode != NULL); + int qrsize = qrcode[0]; + return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y); +} + + +// Gets the module at the given coordinates, which must be in bounds. +testable bool getModule(const uint8_t qrcode[], int x, int y) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + return getBit(qrcode[(index >> 3) + 1], index & 7); +} + + +// Sets the module at the given coordinates, which must be in bounds. +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + int bitIndex = index & 7; + int byteIndex = (index >> 3) + 1; + if (isBlack) + qrcode[byteIndex] |= 1 << bitIndex; + else + qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; +} + + +// Sets the module at the given coordinates, doing nothing if out of bounds. +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + if (0 <= x && x < qrsize && 0 <= y && y < qrsize) + setModule(qrcode, x, y, isBlack); +} + + +// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14. +static bool getBit(int x, int i) { + return ((x >> i) & 1) != 0; +} + + + +/*---- Segment handling ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_isAlphanumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_isNumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (*text < '0' || *text > '9') + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) { + int temp = calcSegmentBitLength(mode, numChars); + if (temp == -1) + return SIZE_MAX; + assert(0 <= temp && temp <= INT16_MAX); + return ((size_t)temp + 7) / 8; +} + + +// Returns the number of data bits needed to represent a segment +// containing the given number of characters using the given mode. Notes: +// - Returns -1 on failure, i.e. numChars > INT16_MAX or +// the number of needed bits exceeds INT16_MAX (i.e. 32767). +// - Otherwise, all valid results are in the range [0, INT16_MAX]. +// - For byte mode, numChars measures the number of bytes, not Unicode code points. +// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned. +// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { + // All calculations are designed to avoid overflow on all platforms + if (numChars > (unsigned int)INT16_MAX) + return -1; + long result = (long)numChars; + if (mode == qrcodegen_Mode_NUMERIC) + result = (result * 10 + 2) / 3; // ceil(10/3 * n) + else if (mode == qrcodegen_Mode_ALPHANUMERIC) + result = (result * 11 + 1) / 2; // ceil(11/2 * n) + else if (mode == qrcodegen_Mode_BYTE) + result *= 8; + else if (mode == qrcodegen_Mode_KANJI) + result *= 13; + else if (mode == qrcodegen_Mode_ECI && numChars == 0) + result = 3 * 8; + else { // Invalid argument + assert(false); + return -1; + } + assert(result >= 0); + if (result > INT16_MAX) + return -1; + return (int)result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { + assert(data != NULL || len == 0); + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_BYTE; + result.bitLength = calcSegmentBitLength(result.mode, len); + assert(result.bitLength != -1); + result.numChars = (int)len; + if (len > 0) + memcpy(buf, data, len * sizeof(buf[0])); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { + assert(digits != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(digits); + result.mode = qrcodegen_Mode_NUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *digits != '\0'; digits++) { + char c = *digits; + assert('0' <= c && c <= '9'); + accumData = accumData * 10 + (unsigned int)(c - '0'); + accumCount++; + if (accumCount == 3) { + appendBitsToBuffer(accumData, 10, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { + assert(text != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(text); + result.mode = qrcodegen_Mode_ALPHANUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *text != '\0'; text++) { + const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); + assert(temp != NULL); + accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + appendBitsToBuffer(accumData, 11, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + appendBitsToBuffer(accumData, 6, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_ECI; + result.numChars = 0; + result.bitLength = 0; + if (assignVal < 0) + assert(false); + else if (assignVal < (1 << 7)) { + memset(buf, 0, 1 * sizeof(buf[0])); + appendBitsToBuffer((unsigned int)assignVal, 8, buf, &result.bitLength); + } else if (assignVal < (1 << 14)) { + memset(buf, 0, 2 * sizeof(buf[0])); + appendBitsToBuffer(2, 2, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)assignVal, 14, buf, &result.bitLength); + } else if (assignVal < 1000000L) { + memset(buf, 0, 3 * sizeof(buf[0])); + appendBitsToBuffer(6, 3, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)(assignVal >> 10), 11, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)(assignVal & 0x3FF), 10, buf, &result.bitLength); + } else + assert(false); + result.data = buf; + return result; +} + + +// Calculates the number of bits needed to encode the given segments at the given version. +// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too +// many characters to fit its length field, or the total bits exceeds INT16_MAX. +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { + assert(segs != NULL || len == 0); + long result = 0; + for (size_t i = 0; i < len; i++) { + int numChars = segs[i].numChars; + int bitLength = segs[i].bitLength; + assert(0 <= numChars && numChars <= INT16_MAX); + assert(0 <= bitLength && bitLength <= INT16_MAX); + int ccbits = numCharCountBits(segs[i].mode, version); + assert(0 <= ccbits && ccbits <= 16); + if (numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + result += 4L + ccbits + bitLength; + if (result > INT16_MAX) + return -1; // The sum might overflow an int type + } + assert(0 <= result && result <= INT16_MAX); + return (int)result; +} + + +// Returns the bit width of the character count field for a segment in the given mode +// in a QR Code at the given version number. The result is in the range [0, 16]. +static int numCharCountBits(enum qrcodegen_Mode mode, int version) { + assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int i = (version + 7) / 17; + switch (mode) { + case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } + case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; } + case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } + case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } + case qrcodegen_Mode_ECI : return 0; + default: assert(false); return -1; // Dummy value + } +} \ No newline at end of file diff --git a/src/framework/util/qrcodegen.h b/src/framework/util/qrcodegen.h new file mode 100644 index 0000000..c9353f2 --- /dev/null +++ b/src/framework/util/qrcodegen.h @@ -0,0 +1,311 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * This library creates QR Code symbols, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * A QR Code structure is an immutable square grid of black and white cells. + * The library provides functions to create a QR Code from text or binary data. + * The library covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). + * - Low level: Custom-make the list of segments and call + * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). + * (Note that all ways require supplying the desired error correction level and various byte buffers.) + */ + + +/*---- Enum and struct types----*/ + +/* + * The error correction level in a QR Code symbol. + */ +enum qrcodegen_Ecc { + // Must be declared in ascending order of error protection + // so that an internal qrcodegen function works properly + qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords +}; + + +/* + * The mask pattern used in a QR Code symbol. + */ +enum qrcodegen_Mask { + // A special value to tell the QR Code encoder to + // automatically select an appropriate mask pattern + qrcodegen_Mask_AUTO = -1, + // The eight actual mask patterns + qrcodegen_Mask_0 = 0, + qrcodegen_Mask_1, + qrcodegen_Mask_2, + qrcodegen_Mask_3, + qrcodegen_Mask_4, + qrcodegen_Mask_5, + qrcodegen_Mask_6, + qrcodegen_Mask_7, +}; + + +/* + * Describes how a segment's data bits are interpreted. + */ +enum qrcodegen_Mode { + qrcodegen_Mode_NUMERIC = 0x1, + qrcodegen_Mode_ALPHANUMERIC = 0x2, + qrcodegen_Mode_BYTE = 0x4, + qrcodegen_Mode_KANJI = 0x8, + qrcodegen_Mode_ECI = 0x7, +}; + + +/* + * A segment of character/binary/control data in a QR Code symbol. + * The mid-level way to create a segment is to take the payload data + * and call a factory function such as qrcodegen_makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and initialize a qrcodegen_Segment struct with appropriate values. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + * Moreover, the maximum allowed bit length is 32767 because + * the largest QR Code (version 40) has 31329 modules. + */ +struct qrcodegen_Segment { + // The mode indicator of this segment. + enum qrcodegen_Mode mode; + + // The length of this segment's unencoded data. Measured in characters for + // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + // Always zero or positive. Not the same as the data's bit length. + int numChars; + + // The data bits of this segment, packed in bitwise big endian. + // Can be null if the bit length is zero. + uint8_t *data; + + // The number of valid data bits used in the buffer. Requires + // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. + // The character count (numChars) must agree with the mode and the bit buffer length. + int bitLength; +}; + + + +/*---- Macro constants and functions ----*/ + +#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard +#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard + +// Calculates the number of bytes needed to store any QR Code up to and including the given version number, +// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' +// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). +// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. +#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1) + +// The worst-case number of bytes needed to store one QR Code, up to and including +// version 40. This value equals 3918, which is just under 4 kilobytes. +// Use this more convenient value to avoid calculating tighter memory bounds for buffers. +#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX) + + + +/*---- Functions (high level) to generate QR Codes ----*/ + +/* + * Encodes the given text string to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input text must be encoded in UTF-8 and contain no NULs. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays tempBuffer and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, tempBuffer contains no useful data. + * - If successful, the resulting QR Code may use numeric, + * alphanumeric, or byte mode to encode the text. + * - In the most optimistic case, a QR Code at version 40 with low ECC + * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string + * up to 4296 characters, or any digit string up to 7089 characters. + * These numbers represent the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/* + * Encodes the given binary data to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input array range dataAndTemp[0 : dataLen] should normally be + * valid UTF-8 text, but is not required by the QR Code standard. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays dataAndTemp and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, the contents of dataAndTemp may have changed, + * and does not represent useful data anymore. + * - If successful, the resulting QR Code will use byte mode to encode the data. + * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte + * sequence up to length 2953. This is the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/*---- Functions (low level) to generate QR Codes ----*/ + +/* + * Renders a QR Code representing the given segments at the given error correction level. + * The smallest possible QR Code version is automatically chosen for the output. Returns true if + * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level + * of the result may be higher than the ecl argument if it can be done without increasing the version. + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Renders a QR Code representing the given segments with the given encoding parameters. + * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or + * qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +bool qrcodegen_isAlphanumeric(const char *text); + + +/* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +bool qrcodegen_isNumeric(const char *text); + + +/* + * Returns the number of bytes (uint8_t) needed for the data buffer of a segment + * containing the given number of characters using the given mode. Notes: + * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or + * the number of needed bits exceeds INT16_MAX (i.e. 32767). + * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096. + * - It is okay for the user to allocate more bytes for the buffer than needed. + * - For byte mode, numChars measures the number of bytes, not Unicode code points. + * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned. + * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. + */ +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars); + + +/* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte arrays are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]); + + +/* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]); + + +/* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]); + + +/* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); + + +/*---- Functions to extract raw data from QR Codes ----*/ + +/* + * Returns the side length of the given QR Code, assuming that encoding succeeded. + * The result is in the range [21, 177]. Note that the length of the array buffer + * is related to the side length - every 'uint8_t qrcode[]' must have length at least + * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). + */ +int qrcodegen_getSize(const uint8_t qrcode[]); + + +/* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for white or true for black. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (white) is returned. + */ +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y); + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/framework/util/rect.h b/src/framework/util/rect.h new file mode 100644 index 0000000..5548363 --- /dev/null +++ b/src/framework/util/rect.h @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef RECT_H +#define RECT_H + +#include "../stdext/types.h" +#include "../const.h" +#include + +template +class TPoint; + +template +class TSize; + +template +class TRect +{ +public: + TRect() : x1(0), y1(0), x2(-1), y2(-1) { } + TRect(T x, T y, T width, T height) : x1(x), y1(y), x2(x+width-1), y2(y+height-1) { } + TRect(const TPoint& topLeft, const TPoint& bottomRight) : x1(topLeft.x), y1(topLeft.y), x2(bottomRight.x), y2(bottomRight.y) { } + TRect(const TRect& other) : x1(other.x1), y1(other.y1), x2(other.x2), y2(other.y2) { } + TRect(T x, T y, const TSize& size) : x1(x), y1(y), x2(x+size.width()-1), y2(y+size.height()-1) { } + TRect(const TPoint& topLeft, const TSize& size) : x1(topLeft.x), y1(topLeft.y), x2(x1+size.width()-1), y2(y1+size.height()-1) { } + TRect(const TPoint& topLeft, int width, int height) : x1(topLeft.x), y1(topLeft.y), x2(x1+width-1), y2(y1+height-1) { } + + bool isNull() const { return x2 == x1 - 1 && y2 == y1 - 1; } + bool isEmpty() const { return x1 > x2 || y1 > y2; } + bool isValid() const { return x1 <= x2 && y1 <= y2; } + + inline T left() const { return x1; } + inline T top() const { return y1; } + inline T right() const { return x2; } + inline T bottom() const { return y2; } + inline T horizontalCenter() const { return x1 + (x2 - x1)/2; } + inline T verticalCenter() const { return y1 + (y2 - y1)/2; } + inline T x() const { return x1; } + inline T y() const { return y1; } + TPoint topLeft() const { return TPoint(x1, y1); } + TPoint bottomRight() const { return TPoint(x2, y2); } + TPoint topRight() const { return TPoint(x2, y1); } + TPoint bottomLeft() const { return TPoint(x1, y2); } + TPoint topCenter() const { return TPoint((x1+x2)/2, y1); } + TPoint bottomCenter() const { return TPoint((x1+x2)/2, y2); } + TPoint centerLeft() const { return TPoint(x1, (y1+y2)/2); } + TPoint centerRight() const { return TPoint(x2, (y1+y2)/2); } + TPoint center() const { return TPoint((x1+x2)/2, (y1+y2)/2); } + T width() const { return x2 - x1 + 1; } + T height() const { return y2 - y1 + 1; } + TSize size() const { return TSize(width(), height()); } + void reset() { x1 = y1 = 0; x2 = y2 = -1; } + void clear() { x2 = x1 - 1; y2 = y1 - 1; } + + void setLeft(T pos) { x1 = pos; } + void setTop(T pos) { y1 = pos; } + void setRight(T pos) { x2 = pos; } + void setBottom(T pos) { y2 = pos; } + void setX(T x) { x1 = x; } + void setY(T y) { y1 = y; } + void setTopLeft(const TPoint &p) { x1 = p.x; y1 = p.y; } + void setBottomRight(const TPoint &p) { x2 = p.x; y2 = p.y; } + void setTopRight(const TPoint &p) { x2 = p.x; y1 = p.y; } + void setBottomLeft(const TPoint &p) { x1 = p.x; y2 = p.y; } + void setWidth(T width) { x2 = x1 + width - 1; } + void setHeight(T height) { y2 = y1 + height- 1; } + void setSize(const TSize& size) { x2 = x1 + size.width() - 1; y2 = y1 + size.height() - 1; } + void setRect(T x, T y, T width, T height) { x1 = x; y1 = y; x2 = (x + width - 1); y2 = (y + height - 1); } + void setCoords(int left, int top, int right, int bottom) { x1 = left; y1 = top; x2 = right; y2 = bottom; } + + void expandLeft(T add) { x1 -= add; } + void expandTop(T add) { y1 -= add; } + void expandRight(T add) { x2 += add; } + void expandBottom(T add) { y2 += add; } + void expand(T top, T right, T bottom, T left) { x1 -= left; y1 -= top; x2 += right; y2 += bottom; } + void expand(T add) { x1 -= add; y1 -= add; x2 += add; y2 += add; } + + void translate(T x, T y) { x1 += x; y1 += y; x2 += x; y2 += y; } + void translate(const TPoint &p) { x1 += p.x; y1 += p.y; x2 += p.x; y2 += p.y; } + void resize(const TSize& size) { x2 = x1 + size.width() - 1; y2 = y1 + size.height() - 1; } + void resize(T width, T height) { x2 = x1 + width - 1; y2 = y1 + height - 1; } + void move(T x, T y) { x2 += x - x1; y2 += y - y1; x1 = x; y1 = y; } + void move(const TPoint &p) { x2 += p.x - x1; y2 += p.y - y1; x1 = p.x; y1 = p.y; } + void moveLeft(T pos) { x2 += (pos - x1); x1 = pos; } + void moveTop(T pos) { y2 += (pos - y1); y1 = pos; } + void moveRight(T pos) { x1 += (pos - x2); x2 = pos; } + void moveBottom(T pos) { y1 += (pos - y2); y2 = pos; } + void moveTopLeft(const TPoint &p) { moveLeft(p.x); moveTop(p.y); } + void moveBottomRight(const TPoint &p) { moveRight(p.x); moveBottom(p.y); } + void moveTopRight(const TPoint &p) { moveRight(p.x); moveTop(p.y); } + void moveBottomLeft(const TPoint &p) { moveLeft(p.x); moveBottom(p.y); } + void moveTopCenter(const TPoint &p) { moveHorizontalCenter(p.x); moveTop(p.y); } + void moveBottomCenter(const TPoint &p) { moveHorizontalCenter(p.x); moveBottom(p.y); } + void moveCenterLeft(const TPoint &p) { moveLeft(p.x); moveVerticalCenter(p.y); } + void moveCenterRight(const TPoint &p) { moveRight(p.x); moveVerticalCenter(p.y); } + + TRect translated(int x, int y) const { return TRect(TPoint(x1 + x, y1 + y), TPoint(x2 + x, y2 + y)); } + TRect translated(const TPoint &p) const { return TRect(TPoint(x1 + p.x, y1 + p.y), TPoint(x2 + p.x, y2 + p.y)); } + + TRect expanded(T add) const { return TRect(TPoint(x1 - add, y1 - add), TPoint(x2 + add, y2 + add)); } + + void moveCenter(const TPoint &p) { + T w = x2 - x1; + T h = y2 - y1; + x1 = p.x - w/2; + y1 = p.y - h/2; + x2 = x1 + w; + y2 = y1 + h; + } + void moveHorizontalCenter(T x) { + T w = x2 - x1; + x1 = x - w/2; + x2 = x1 + w; + } + void moveVerticalCenter(T y) { + T h = y2 - y1; + y1 = y - h/2; + y2 = y1 + h; + } + + bool contains(const TPoint &p, bool insideOnly = false) const { + T l, r; + if(x2 < x1 - 1) { + l = x2; + r = x1; + } else { + l = x1; + r = x2; + } + if(insideOnly) { + if(p.x <= l || p.x >= r) + return false; + } else { + if(p.x < l || p.x > r) + return false; + } + T t, b; + if(y2 < y1 - 1) { + t = y2; + b = y1; + } else { + t = y1; + b = y2; + } + if(insideOnly) { + if(p.y <= t || p.y >= b) + return false; + } else { + if(p.y < t || p.y > b) + return false; + } + return true; + } + + bool contains(const TRect &r, bool insideOnly = false) const { + if(contains(r.topLeft(), insideOnly) && contains(r.bottomRight(), insideOnly)) + return true; + return false; + } + + bool intersects(const TRect &r) const { + if(isNull() || r.isNull()) + return false; + + int l1 = x1; + int r1 = x1; + if(x2 - x1 + 1 < 0) + l1 = x2; + else + r1 = x2; + + int l2 = r.x1; + int r2 = r.x1; + if(r.x2 - r.x1 + 1 < 0) + l2 = r.x2; + else + r2 = r.x2; + + if(l1 > r2 || l2 > r1) + return false; + + int t1 = y1; + int b1 = y1; + if(y2 - y1 + 1 < 0) + t1 = y2; + else + b1 = y2; + + int t2 = r.y1; + int b2 = r.y1; + if(r.y2 - r.y1 + 1 < 0) + t2 = r.y2; + else + b2 = r.y2; + + if(t1 > b2 || t2 > b1) + return false; + + return true; + } + + TRect united(const TRect &r) const { + TRect tmp; + tmp.x1 = std::min(x1, r.x1); + tmp.x2 = std::max(x2, r.x2); + tmp.y1 = std::min(y1, r.y1); + tmp.y2 = std::max(y2, r.y2); + return tmp; + } + + TRect intersection(const TRect &r) const { + if(isNull()) + return r; + if(r.isNull()) + return *this; + + int l1 = x1; + int r1 = x1; + if(x2 - x1 + 1 < 0) + l1 = x2; + else + r1 = x2; + + int l2 = r.x1; + int r2 = r.x1; + if(r.x2 - r.x1 + 1 < 0) + l2 = r.x2; + else + r2 = r.x2; + + int t1 = y1; + int b1 = y1; + if(y2 - y1 + 1 < 0) + t1 = y2; + else + b1 = y2; + + int t2 = r.y1; + int b2 = r.y1; + if(r.y2 - r.y1 + 1 < 0) + t2 = r.y2; + else + b2 = r.y2; + + TRect tmp; + tmp.x1 = std::max(l1, l2); + tmp.x2 = std::min(r1, r2); + tmp.y1 = std::max(t1, t2); + tmp.y2 = std::min(b1, b2); + return tmp; + } + + void bind(const TRect &r) { + if(isNull() || r.isNull()) + return; + + if(right() > r.right()) + moveRight(r.right()); + if(bottom() > r.bottom()) + moveBottom(r.bottom()); + if(left() < r.left()) + moveLeft(r.left()); + if(top() < r.top()) + moveTop(r.top()); + } + + void alignIn(const TRect &r, Fw::AlignmentFlag align) { + if(align == Fw::AlignTopLeft) + moveTopLeft(r.topLeft()); + else if(align == Fw::AlignTopRight) + moveTopRight(r.topRight()); + else if(align == Fw::AlignTopCenter) + moveTopCenter(r.topCenter()); + else if(align == Fw::AlignBottomLeft) + moveBottomLeft(r.bottomLeft()); + else if(align == Fw::AlignBottomRight) + moveBottomRight(r.bottomRight()); + else if(align == Fw::AlignBottomCenter) + moveBottomCenter(r.bottomCenter()); + else if(align == Fw::AlignLeftCenter) + moveCenterLeft(r.centerLeft()); + else if(align == Fw::AlignCenter) + moveCenter(r.center()); + else if(align == Fw::AlignRightCenter) + moveCenterRight(r.centerRight()); + } + + TRect& operator=(const TRect& other) { x1 = other.x1; y1 = other.y1; x2 = other.x2; y2 = other.y2; return *this; } + bool operator==(const TRect& other) const { return (x1 == other.x1 && y1 == other.y1 && x2 == other.x2 && y2 == other.y2); } + bool operator!=(const TRect& other) const { return (x1 != other.x1 || y1 != other.y1 || x2 != other.x2 || y2 != other.y2); } + + TRect& operator|=(const TRect& other) { *this = united(other); return *this; } + TRect& operator&=(const TRect& other) { *this = intersection(other); return *this; } + + TRect operator+(TPoint other) const { return translated(other); } + TRect& operator+=(TPoint other) { x1 += other.x; x2 += other.x; y1 += other.y; y2 += other.y; return *this; } + + TRect operator*(float num) const { return TRect(x1 * num, y1 * num, (x2 - x1) * num, (y2 - y1) * num); } + +private: + T x1, y1, x2, y2; +}; + +typedef TRect Rect; +typedef TRect RectF; + +template +std::ostream& operator<<(std::ostream& out, const TRect& rect) +{ + out << rect.left() << " " << rect.top() << " " << rect.width() << " " << rect.height(); + return out; +} + +template +std::istream& operator>>(std::istream& in, TRect& rect) +{ + T x, y , w, h; + in >> x >> y >> w >> h; + rect.setRect(x,y,w,h); + return in; +} + +#endif diff --git a/src/framework/util/size.h b/src/framework/util/size.h new file mode 100644 index 0000000..40f15ba --- /dev/null +++ b/src/framework/util/size.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2010-2017 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SIZE_H +#define SIZE_H + +#include "point.h" +#include "../const.h" + +template +class TSize +{ +public: + TSize() : wd(-1), ht(-1) {}; + TSize(T width, T height) : wd(width), ht(height) { }; + TSize(const TSize& other) : wd(other.wd), ht(other.ht) { }; + + TPoint toPoint() const { return TPoint(wd, ht); } + + bool isNull() const { return wd==0 && ht==0; } + bool isEmpty() const { return wd<1 || ht<1; } + bool isValid() const { return wd>=0 && ht>=0; } + + T width() const { return wd; } + T height() const { return ht; } + + void resize(T w, T h) { wd = w; ht = h; } + void setWidth(T w) { wd = w; } + void setHeight(T h) { ht = h; } + + TSize operator-() const { return TSize(-wd, -ht); } + TSize operator+(const TSize& other) const { return TSize(wd + other.wd, ht + other.ht); } + TSize& operator+=(const TSize& other) { wd+=other.wd; ht+=other.ht; return *this; } + TSize operator-(const TSize& other) const { return TSize(wd - other.wd, ht - other.ht); } + TSize& operator-=(const TSize& other) { wd-=other.wd; ht-=other.ht; return *this; } + TSize operator*(const TSize& other) const { return TSize((T)other.wd*wd, (T)ht*other.ht); } + TSize& operator*=(const TSize& other) { wd=(T)other.wd*wd; ht=(T)ht*other.ht; return *this; } + TSize operator/(const TSize& other) const { return TSize((T)wd/other.wd, (T)ht/other.ht); } + TSize& operator/=(const TSize& other) { (T)wd/=other.wd; (T)ht/=other.ht; return *this; } + TSize operator*(const float v) const { return TSize((T)wd*v, (T)ht*v); } + TSize& operator*=(const float v) { wd=(T)wd*v; ht=(T)ht*v; return *this; } + TSize operator/(const float v) const { return TSize((T)wd/v, (T)ht/v); } + TSize& operator/=(const float v) { wd/=v; ht/=v; return *this; } + + bool operator<=(const TSize&other) const { return wd<=other.wd || ht<=other.ht; } + bool operator>=(const TSize&other) const { return wd>=other.wd || ht>=other.ht; } + bool operator<(const TSize&other) const { return wd(const TSize&other) const { return wd>other.wd || ht>other.ht; } + + TSize& operator=(const TSize& other) { wd = other.wd; ht = other.ht; return *this; } + bool operator==(const TSize& other) const { return other.wd==wd && other.ht==ht; } + bool operator!=(const TSize& other) const { return other.wd!=wd || other.ht!=ht; } + + TSize expandedTo(const TSize& other) const { return TSize(std::max(wd, other.wd), std::max(ht, other.ht)); } + TSize boundedTo(const TSize& other) const { return TSize(std::min(wd, other.wd), std::min(ht, other.ht)); } + + void scale(const TSize& s, Fw::AspectRatioMode mode) { + if(mode == Fw::IgnoreAspectRatio || wd == 0 || ht == 0) { + wd = s.wd; + ht = s.ht; + } else { + bool useHeight; + T rw = (s.ht * wd) / ht; + + if(mode == Fw::KeepAspectRatio) + useHeight = (rw <= s.wd); + else // mode == Fw::KeepAspectRatioByExpanding + useHeight = (rw >= s.wd); + + if(useHeight) { + wd = rw; + ht = s.ht; + } else { + ht = (s.wd * ht)/wd; + wd = s.wd; + } + } + } + void scale(int w, int h, Fw::AspectRatioMode mode) { scale(TSize(w, h), mode); } + + float ratio() const { return (float)wd/ht; } + T area() const { return wd*ht; } + +private: + T wd, ht; +}; + +typedef TSize Size; +typedef TSize SizeF; + +template +std::ostream& operator<<(std::ostream& out, const TSize& size) +{ + out << size.width() << " " << size.height(); + return out; +} + +template +std::istream& operator>>(std::istream& in, TSize& size) +{ + T w, h; + in >> w >> h; + size.resize(w, h); + return in; +} + +#endif diff --git a/src/framework/xml/tinystr.cpp b/src/framework/xml/tinystr.cpp new file mode 100644 index 0000000..84dd8bd --- /dev/null +++ b/src/framework/xml/tinystr.cpp @@ -0,0 +1,112 @@ +/* +www.sourceforge.net/projects/tinyxml + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include + +#ifndef TIXML_USE_STL + +#include "tinystr.h" + +// Error value for find primitive +const TiXmlString::size_type TiXmlString::npos = static_cast< TiXmlString::size_type >(-1); + + +// Null rep. +TiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, { '\0' } }; + + +void TiXmlString::reserve (size_type cap) +{ + if (cap > capacity()) + { + TiXmlString tmp; + tmp.init(length(), cap); + memcpy(tmp.start(), data(), length()); + swap(tmp); + } +} + + +TiXmlString& TiXmlString::assign(const char* str, size_type len) +{ + size_type cap = capacity(); + if (len > cap || cap > 3*(len + 8)) + { + TiXmlString tmp; + tmp.init(len); + memcpy(tmp.start(), str, len); + swap(tmp); + } + else + { + memmove(start(), str, len); + set_size(len); + } + return *this; +} + + +TiXmlString& TiXmlString::append(const char* str, size_type len) +{ + size_type newsize = length() + len; + if (newsize > capacity()) + { + reserve (newsize + capacity()); + } + memmove(finish(), str, len); + set_size(newsize); + return *this; +} + + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b) +{ + TiXmlString tmp; + tmp.reserve(a.length() + b.length()); + tmp += a; + tmp += b; + return tmp; +} + +TiXmlString operator + (const TiXmlString & a, const char* b) +{ + TiXmlString tmp; + TiXmlString::size_type b_len = static_cast( strlen(b) ); + tmp.reserve(a.length() + b_len); + tmp += a; + tmp.append(b, b_len); + return tmp; +} + +TiXmlString operator + (const char* a, const TiXmlString & b) +{ + TiXmlString tmp; + TiXmlString::size_type a_len = static_cast( strlen(a) ); + tmp.reserve(a_len + b.length()); + tmp.append(a, a_len); + tmp += b; + return tmp; +} + + +#endif // TIXML_USE_STL diff --git a/src/framework/xml/tinystr.h b/src/framework/xml/tinystr.h new file mode 100644 index 0000000..ac0c2ab --- /dev/null +++ b/src/framework/xml/tinystr.h @@ -0,0 +1,304 @@ +/* +www.sourceforge.net/projects/tinyxml + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TIXML_USE_STL + +#ifndef TIXML_STRING_INCLUDED +#define TIXML_STRING_INCLUDED + +#include + +/* The support for explicit isn't that universal, and it isn't really + required - it is used to check that the TiXmlString class isn't incorrectly + used. Be nice to old compilers and macro it here: +*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + #define TIXML_EXPLICIT explicit +#elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + #define TIXML_EXPLICIT explicit +#else + #define TIXML_EXPLICIT +#endif + + +/* + TiXmlString is an emulation of a subset of the std::string template. + Its purpose is to allow compiling TinyXML on compilers with no or poor STL support. + Only the member functions relevant to the TinyXML project have been implemented. + The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase + a string and there's no more room, we allocate a buffer twice as big as we need. +*/ +class TiXmlString +{ + public : + // The size type used + typedef size_t size_type; + + // Error value for find primitive + static const size_type npos; // = -1; + + + // TiXmlString empty constructor + TiXmlString () : rep_(&nullrep_) + { + } + + // TiXmlString copy constructor + TiXmlString ( const TiXmlString & copy) : rep_(0) + { + init(copy.length()); + memcpy(start(), copy.data(), length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0) + { + init( static_cast( strlen(copy) )); + memcpy(start(), copy, length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0) + { + init(len); + memcpy(start(), str, len); + } + + // TiXmlString destructor + ~TiXmlString () + { + quit(); + } + + TiXmlString& operator = (const char * copy) + { + return assign( copy, (size_type)strlen(copy)); + } + + TiXmlString& operator = (const TiXmlString & copy) + { + return assign(copy.start(), copy.length()); + } + + + // += operator. Maps to append + TiXmlString& operator += (const char * suffix) + { + return append(suffix, static_cast( strlen(suffix) )); + } + + // += operator. Maps to append + TiXmlString& operator += (char single) + { + return append(&single, 1); + } + + // += operator. Maps to append + TiXmlString& operator += (const TiXmlString & suffix) + { + return append(suffix.data(), suffix.length()); + } + + + // Convert a TiXmlString into a null-terminated char * + const char * c_str () const { return rep_->str; } + + // Convert a TiXmlString into a char * (need not be null terminated). + const char * data () const { return rep_->str; } + + // Return the length of a TiXmlString + size_type length () const { return rep_->size; } + + // Alias for length() + size_type size () const { return rep_->size; } + + // Checks if a TiXmlString is empty + bool empty () const { return rep_->size == 0; } + + // Return capacity of string + size_type capacity () const { return rep_->capacity; } + + + // single char extraction + const char& at (size_type index) const + { + VALIDATE( index < length() ); + return rep_->str[ index ]; + } + + // [] operator + char& operator [] (size_type index) const + { + VALIDATE( index < length() ); + return rep_->str[ index ]; + } + + // find a char in a string. Return TiXmlString::npos if not found + size_type find (char lookup) const + { + return find(lookup, 0); + } + + // find a char in a string from an offset. Return TiXmlString::npos if not found + size_type find (char tofind, size_type offset) const + { + if (offset >= length()) return npos; + + for (const char* p = c_str() + offset; *p != '\0'; ++p) + { + if (*p == tofind) return static_cast< size_type >( p - c_str() ); + } + return npos; + } + + void clear () + { + //Lee: + //The original was just too strange, though correct: + // TiXmlString().swap(*this); + //Instead use the quit & re-init: + quit(); + init(0,0); + } + + /* Function to reserve a big amount of data when we know we'll need it. Be aware that this + function DOES NOT clear the content of the TiXmlString if any exists. + */ + void reserve (size_type cap); + + TiXmlString& assign (const char* str, size_type len); + + TiXmlString& append (const char* str, size_type len); + + void swap (TiXmlString& other) + { + Rep* r = rep_; + rep_ = other.rep_; + other.rep_ = r; + } + + private: + + void init(size_type sz) { init(sz, sz); } + void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; } + char* start() const { return rep_->str; } + char* finish() const { return rep_->str + rep_->size; } + + struct Rep + { + size_type size, capacity; + char str[1]; + }; + + void init(size_type sz, size_type cap) + { + if (cap) + { + // Lee: the original form: + // rep_ = static_cast(operator new(sizeof(Rep) + cap)); + // doesn't work in some cases of new being overloaded. Switching + // to the normal allocation, although use an 'int' for systems + // that are overly picky about structure alignment. + const size_type bytesNeeded = sizeof(Rep) + cap; + const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); + rep_ = reinterpret_cast( new int[ intsNeeded ] ); + + rep_->str[ rep_->size = sz ] = '\0'; + rep_->capacity = cap; + } + else + { + rep_ = &nullrep_; + } + } + + void quit() + { + if (rep_ != &nullrep_) + { + // The rep_ is really an array of ints. (see the allocator, above). + // Cast it back before delete, so the compiler won't incorrectly call destructors. + delete [] ( reinterpret_cast( rep_ ) ); + } + } + + Rep * rep_; + static Rep nullrep_; + +} ; + + +inline bool operator == (const TiXmlString & a, const TiXmlString & b) +{ + return ( a.length() == b.length() ) // optimization on some platforms + && ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare +} +inline bool operator < (const TiXmlString & a, const TiXmlString & b) +{ + return strcmp(a.c_str(), b.c_str()) < 0; +} + +inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); } +inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; } +inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); } +inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); } + +inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; } +inline bool operator == (const char* a, const TiXmlString & b) { return b == a; } +inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); } +inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); } + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b); +TiXmlString operator + (const TiXmlString & a, const char* b); +TiXmlString operator + (const char* a, const TiXmlString & b); + + +/* + TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString. + Only the operators that we need for TinyXML have been developped. +*/ +class TiXmlOutStream : public TiXmlString +{ +public : + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const TiXmlString & in) + { + *this += in; + return *this; + } + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const char * in) + { + *this += in; + return *this; + } + +} ; + +#endif // TIXML_STRING_INCLUDED +#endif // TIXML_USE_STL diff --git a/src/framework/xml/tinyxml.cpp b/src/framework/xml/tinyxml.cpp new file mode 100644 index 0000000..8d52ed3 --- /dev/null +++ b/src/framework/xml/tinyxml.cpp @@ -0,0 +1,1690 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ +#include + +#include + +#ifdef TIXML_USE_STL +#include +#include +#endif + +#include "tinyxml.h" + +FILE* TiXmlFOpen( const char* filename, const char* mode ); + +bool TiXmlBase::condenseWhiteSpace = true; + +// Microsoft compiler security +FILE* TiXmlFOpen( const char* filename, const char* mode ) +{ + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + FILE* fp = 0; + errno_t err = fopen_s( &fp, filename, mode ); + if ( !err && fp ) + return fp; + return 0; + #else + return fopen( filename, mode ); + #endif +} + +void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString ) +{ + int i=0; + + while( i<(int)str.length() ) + { + unsigned char c = (unsigned char) str[i]; + + if ( c == '&' + && i < ( (int)str.length() - 2 ) + && str[i+1] == '#' + && str[i+2] == 'x' ) + { + // Hexadecimal character reference. + // Pass through unchanged. + // © -- copyright symbol, for example. + // + // The -1 is a bug fix from Rob Laveaux. It keeps + // an overflow from happening if there is no ';'. + // There are actually 2 ways to exit this loop - + // while fails (error case) and break (semicolon found). + // However, there is no mechanism (currently) for + // this function to return an error. + while ( i<(int)str.length()-1 ) + { + outString->append( str.c_str() + i, 1 ); + ++i; + if ( str[i] == ';' ) + break; + } + } + else if ( c == '&' ) + { + outString->append( entity[0].str, entity[0].strLength ); + ++i; + } + else if ( c == '<' ) + { + outString->append( entity[1].str, entity[1].strLength ); + ++i; + } + else if ( c == '>' ) + { + outString->append( entity[2].str, entity[2].strLength ); + ++i; + } + else if ( c == '\"' ) + { + outString->append( entity[3].str, entity[3].strLength ); + ++i; + } + else if ( c == '\'' ) + { + outString->append( entity[4].str, entity[4].strLength ); + ++i; + } + else if ( c < 32 ) + { + // Easy pass at non-alpha/numeric/symbol + // Below 32 is symbolic. + char buf[ 32 ]; + + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) ); + #else + sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); + #endif + + //*ME: warning C4267: convert 'size_t' to 'int' + //*ME: Int-Cast to make compiler happy ... + outString->append( buf, (int)strlen( buf ) ); + ++i; + } + else + { + //char realc = (char) c; + //outString->append( &realc, 1 ); + *outString += (char) c; // somewhat more efficient function call. + ++i; + } + } +} + + +TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() +{ + parent = 0; + type = _type; + firstChild = 0; + lastChild = 0; + prev = 0; + next = 0; +} + + +TiXmlNode::~TiXmlNode() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } +} + + +void TiXmlNode::CopyTo( TiXmlNode* target ) const +{ + target->SetValue (value.c_str() ); + target->userData = userData; + target->location = location; +} + + +void TiXmlNode::Clear() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } + + firstChild = 0; + lastChild = 0; +} + + +TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) +{ + VALIDATE( node->parent == 0 || node->parent == this ); + VALIDATE( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); + + if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + delete node; + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + node->parent = this; + + node->prev = lastChild; + node->next = 0; + + if ( lastChild ) + lastChild->next = node; + else + firstChild = node; // it was an empty list. + + lastChild = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) +{ + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + + return LinkEndChild( node ); +} + + +TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) +{ + if ( !beforeThis || beforeThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->next = beforeThis; + node->prev = beforeThis->prev; + if ( beforeThis->prev ) + { + beforeThis->prev->next = node; + } + else + { + VALIDATE( firstChild == beforeThis ); + firstChild = node; + } + beforeThis->prev = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) +{ + if ( !afterThis || afterThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->prev = afterThis; + node->next = afterThis->next; + if ( afterThis->next ) + { + afterThis->next->prev = node; + } + else + { + VALIDATE( lastChild == afterThis ); + lastChild = node; + } + afterThis->next = node; + return node; +} + + +TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) +{ + if ( !replaceThis ) + return 0; + + if ( replaceThis->parent != this ) + return 0; + + if ( withThis.ToDocument() ) { + // A document can never be a child. Thanks to Noam. + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = withThis.Clone(); + if ( !node ) + return 0; + + node->next = replaceThis->next; + node->prev = replaceThis->prev; + + if ( replaceThis->next ) + replaceThis->next->prev = node; + else + lastChild = node; + + if ( replaceThis->prev ) + replaceThis->prev->next = node; + else + firstChild = node; + + delete replaceThis; + node->parent = this; + return node; +} + + +bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) +{ + if ( !removeThis ) { + return false; + } + + if ( removeThis->parent != this ) + { + VALIDATE( 0 ); + return false; + } + + if ( removeThis->next ) + removeThis->next->prev = removeThis->prev; + else + lastChild = removeThis->prev; + + if ( removeThis->prev ) + removeThis->prev->next = removeThis->next; + else + firstChild = removeThis->next; + + delete removeThis; + return true; +} + +const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = firstChild; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = lastChild; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild(); + } + else + { + VALIDATE( previous->parent == this ); + return previous->NextSibling(); + } +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild( val ); + } + else + { + VALIDATE( previous->parent == this ); + return previous->NextSibling( val ); + } +} + + +const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = next; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = prev; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + +void TiXmlElement::RemoveAttribute(const std::string &name) +{ + TiXmlAttribute* node = attributeSet.Find(name); + if ( node ) + { + attributeSet.Remove( node ); + delete node; + } +} + +const TiXmlElement* TiXmlNode::FirstChildElement() const +{ + const TiXmlNode* node; + + for ( node = FirstChild(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = FirstChild( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement() const +{ + const TiXmlNode* node; + + for ( node = NextSibling(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = NextSibling( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlDocument* TiXmlNode::GetDocument() const +{ + const TiXmlNode* node; + + for( node = this; node; node = node->parent ) + { + if ( node->ToDocument() ) + return node->ToDocument(); + } + return 0; +} + +TiXmlElement::TiXmlElement( const std::string& _value ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} + +TiXmlElement::TiXmlElement( const TiXmlElement& copy) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + copy.CopyTo( this ); +} + + +TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) +{ + ClearThis(); + base.CopyTo( this ); + return *this; +} + + +TiXmlElement::~TiXmlElement() +{ + ClearThis(); +} + + +void TiXmlElement::ClearThis() +{ + Clear(); + while( attributeSet.First() ) + { + TiXmlAttribute* node = attributeSet.First(); + attributeSet.Remove( node ); + delete node; + } +} + +std::string TiXmlElement::Attribute( const std::string& name ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( attrib ) + return attrib->ValueStr(); + return std::string(); +} + +std::string TiXmlElement::Attribute( const std::string& name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + std::string result; + + if ( attrib ) { + result = attrib->ValueStr(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} + +std::string TiXmlElement::Attribute( const std::string& name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + std::string result = 0; + + if ( attrib ) { + result = attrib->ValueStr(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} + +void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); + if ( attrib ) { + attrib->SetValue( _value ); + } +} + + +void TiXmlElement::Print( FILE* cfile, int depth ) const +{ + int i; + VALIDATE( cfile ); + for ( i=0; iNext() ) + { + fprintf( cfile, " " ); + attrib->Print( cfile, depth ); + } + + // There are 3 different formatting approaches: + // 1) An element without children is printed as a node + // 2) An element with only a text child is printed as text + // 3) An element with children is printed on multiple lines. + TiXmlNode* node; + if ( !firstChild ) + { + fprintf( cfile, " />" ); + } + else if ( firstChild == lastChild && firstChild->ToText() ) + { + fprintf( cfile, ">" ); + firstChild->Print( cfile, depth + 1 ); + fprintf( cfile, "", value.c_str() ); + } + else + { + fprintf( cfile, ">" ); + + for ( node = firstChild; node; node=node->NextSibling() ) + { + if ( !node->ToText() ) + { + fprintf( cfile, "\n" ); + } + node->Print( cfile, depth+1 ); + } + fprintf( cfile, "\n" ); + for( i=0; i", value.c_str() ); + } +} + + +void TiXmlElement::CopyTo( TiXmlElement* target ) const +{ + // superclass: + TiXmlNode::CopyTo( target ); + + // Element class: + // Clone the attributes, then clone the children. + const TiXmlAttribute* attribute = 0; + for( attribute = attributeSet.First(); + attribute; + attribute = attribute->Next() ) + { + target->SetAttribute( attribute->Name(), attribute->Value() ); + } + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + +bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this, attributeSet.First() ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +TiXmlNode* TiXmlElement::Clone() const +{ + TiXmlElement* clone = new TiXmlElement( Value() ); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +const char* TiXmlElement::GetText() const +{ + const TiXmlNode* child = this->FirstChild(); + if ( child ) { + const TiXmlText* childText = child->ToText(); + if ( childText ) { + return childText->Value(); + } + } + return 0; +} + + +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + ClearError(); +} + +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} + + +#ifdef TIXML_USE_STL +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} +#endif + + +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) +{ + return LoadFile( Value(), encoding ); +} + + +bool TiXmlDocument::SaveFile() const +{ + return SaveFile( Value() ); +} + +bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) +{ + TIXML_STRING filename( _filename ); + value = filename; + + // reading in binary mode so that tinyxml can normalize the EOL + FILE* file = TiXmlFOpen( value.c_str (), "rb" ); + + if ( file ) + { + bool result = LoadFile( file, encoding ); + fclose( file ); + return result; + } + else + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } +} + +bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) +{ + if ( !file ) + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Delete the existing data: + Clear(); + location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = 0; + fseek( file, 0, SEEK_END ); + length = ftell( file ); + fseek( file, 0, SEEK_SET ); + + // Strange case, but good to handle up front. + if ( length <= 0 ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Subtle bug here. TinyXml did use fgets. But from the XML spec: + // 2.11 End-of-Line Handling + // + // + // ...the XML processor MUST behave as if it normalized all line breaks in external + // parsed entities (including the document entity) on input, before parsing, by translating + // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to + // a single #xA character. + // + // + // It is not clear fgets does that, and certainly isn't clear it works cross platform. + // Generally, you expect fgets to translate from the convention of the OS to the c/unix + // convention, and not work generally. + + /* + while( fgets( buf, sizeof(buf), file ) ) + { + data += buf; + } + */ + + char* buf = new char[ length+1 ]; + buf[0] = 0; + + if ( fread( buf, length, 1, file ) != 1 ) { + delete [] buf; + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; + + buf[length] = 0; + while( *p ) { + VALIDATE( p < (buf+length) ); + VALIDATE( q <= (buf+length) ); + VALIDATE( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; + } + } + else { + *q++ = *p++; + } + } + VALIDATE( q <= (buf+length) ); + *q = 0; + + Parse( buf, 0, encoding ); + + delete [] buf; + return !Error(); +} + + +bool TiXmlDocument::SaveFile( const char * filename ) const +{ + // The old c stuff lives on... + FILE* fp = TiXmlFOpen( filename, "w" ); + if ( fp ) + { + bool result = SaveFile( fp ); + fclose( fp ); + return result; + } + return false; +} + + +bool TiXmlDocument::SaveFile( FILE* fp ) const +{ + if ( useMicrosoftBOM ) + { + const unsigned char TIXML_UTF_LEAD_0 = 0xefU; + const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; + const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + fputc( TIXML_UTF_LEAD_0, fp ); + fputc( TIXML_UTF_LEAD_1, fp ); + fputc( TIXML_UTF_LEAD_2, fp ); + } + Print( fp, 0 ); + return (ferror(fp) == 0); +} + + +void TiXmlDocument::CopyTo( TiXmlDocument* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->error = error; + target->errorId = errorId; + target->errorDesc = errorDesc; + target->tabsize = tabsize; + target->errorLocation = errorLocation; + target->useMicrosoftBOM = useMicrosoftBOM; + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + + +TiXmlNode* TiXmlDocument::Clone() const +{ + TiXmlDocument* clone = new TiXmlDocument(); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlDocument::Print( FILE* cfile, int depth ) const +{ + VALIDATE( cfile ); + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + node->Print( cfile, depth ); + fprintf( cfile, "\n" ); + } +} + + +bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +const TiXmlAttribute* TiXmlAttribute::Next() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} + +/* +TiXmlAttribute* TiXmlAttribute::Next() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} +*/ + +const TiXmlAttribute* TiXmlAttribute::Previous() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} + +/* +TiXmlAttribute* TiXmlAttribute::Previous() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} +*/ + +void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + TIXML_STRING n, v; + + EncodeString( name, &n ); + EncodeString( value, &v ); + + if (value.find ('\"') == TIXML_STRING::npos) { + if ( cfile ) { + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; + } + } + else { + if ( cfile ) { + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; + } + } +} + + +int TiXmlAttribute::QueryIntValue( int* ival ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +int TiXmlAttribute::QueryDoubleValue( double* dval ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +void TiXmlAttribute::SetIntValue( int _value ) +{ + char buf [64]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); + #else + sprintf (buf, "%d", _value); + #endif + SetValue (buf); +} + +void TiXmlAttribute::SetDoubleValue( double _value ) +{ + char buf [256]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); + #else + sprintf (buf, "%g", _value); + #endif + SetValue (buf); +} + +int TiXmlAttribute::IntValue() const +{ + return atoi (value.c_str ()); +} + +double TiXmlAttribute::DoubleValue() const +{ + return atof (value.c_str ()); +} + + +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) +{ + Clear(); + base.CopyTo( this ); + return *this; +} + + +void TiXmlComment::Print( FILE* cfile, int depth ) const +{ + VALIDATE( cfile ); + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlComment::CopyTo( TiXmlComment* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlComment::Clone() const +{ + TiXmlComment* clone = new TiXmlComment(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlText::Print( FILE* cfile, int depth ) const +{ + VALIDATE( cfile ); + if ( cdata ) + { + int i; + fprintf( cfile, "\n" ); + for ( i=0; i\n", value.c_str() ); // unformatted output + } + else + { + TIXML_STRING buffer; + EncodeString( value, &buffer ); + fprintf( cfile, "%s", buffer.c_str() ); + } +} + + +void TiXmlText::CopyTo( TiXmlText* target ) const +{ + TiXmlNode::CopyTo( target ); + target->cdata = cdata; +} + + +bool TiXmlText::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlText::Clone() const +{ + TiXmlText* clone = 0; + clone = new TiXmlText( "" ); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlDeclaration::TiXmlDeclaration( const char * _version, + const char * _encoding, + const char * _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} + + +#ifdef TIXML_USE_STL +TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} +#endif + + +TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + copy.CopyTo( this ); +} + + +TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + if ( cfile ) fprintf( cfile, "" ); + if ( str ) (*str) += "?>"; +} + + +void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->version = version; + target->encoding = encoding; + target->standalone = standalone; +} + + +bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlDeclaration::Clone() const +{ + TiXmlDeclaration* clone = new TiXmlDeclaration(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlUnknown::Print( FILE* cfile, int depth ) const +{ + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlUnknown::Clone() const +{ + TiXmlUnknown* clone = new TiXmlUnknown(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlAttributeSet::TiXmlAttributeSet() +{ + sentinel.next = &sentinel; + sentinel.prev = &sentinel; +} + + +TiXmlAttributeSet::~TiXmlAttributeSet() +{ + VALIDATE( sentinel.next == &sentinel ); + VALIDATE( sentinel.prev == &sentinel ); +} + + +void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) +{ + #ifdef TIXML_USE_STL + VALIDATE( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. + #else + VALIDATE( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. + #endif + + addMe->next = &sentinel; + addMe->prev = sentinel.prev; + + sentinel.prev->next = addMe; + sentinel.prev = addMe; +} + +void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) +{ + TiXmlAttribute* node; + + for( node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node == removeMe ) + { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; + return; + } + } + VALIDATE( 0 ); // we tried to remove a non-linked attribute. +} + + +#ifdef TIXML_USE_STL +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node->name == name ) + return node; + } + return 0; +} + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); + } + return attrib; +} +#endif + + +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( strcmp( node->name.c_str(), name ) == 0 ) + return node; + } + return 0; +} + + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); + } + return attrib; +} + + +#ifdef TIXML_USE_STL +std::istream& operator>> (std::istream & in, TiXmlNode & base) +{ + TIXML_STRING tag; + tag.reserve( 8 * 1000 ); + base.StreamIn( &in, &tag ); + + base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); + return in; +} +#endif + + +#ifdef TIXML_USE_STL +std::ostream& operator<< (std::ostream & out, const TiXmlNode & base) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out << printer.Str(); + + return out; +} + + +std::string& operator<< (std::string& out, const TiXmlNode& base ) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out.append( printer.Str() ); + + return out; +} +#endif + + +TiXmlHandle TiXmlHandle::FirstChild() const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement() const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild(); + for ( i=0; + child && iNextSibling(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild( value ); + for ( i=0; + child && iNextSibling( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement(); + for ( i=0; + child && iNextSiblingElement(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement( value ); + for ( i=0; + child && iNextSiblingElement( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +bool TiXmlPrinter::VisitEnter( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitExit( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) +{ + DoIndent(); + buffer += "<"; + buffer += element.Value(); + + for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ) + { + buffer += " "; + attrib->Print( 0, 0, &buffer ); + } + + if ( !element.FirstChild() ) + { + buffer += " />"; + DoLineBreak(); + } + else + { + buffer += ">"; + if ( element.FirstChild()->ToText() + && element.LastChild() == element.FirstChild() + && element.FirstChild()->ToText()->CDATA() == false ) + { + simpleTextPrint = true; + // no DoLineBreak()! + } + else + { + DoLineBreak(); + } + } + ++depth; + return true; +} + + +bool TiXmlPrinter::VisitExit( const TiXmlElement& element ) +{ + --depth; + if ( !element.FirstChild() ) + { + // nothing. + } + else + { + if ( simpleTextPrint ) + { + simpleTextPrint = false; + } + else + { + DoIndent(); + } + buffer += ""; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlText& text ) +{ + if ( text.CDATA() ) + { + DoIndent(); + buffer += ""; + DoLineBreak(); + } + else if ( simpleTextPrint ) + { + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + } + else + { + DoIndent(); + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration ) +{ + DoIndent(); + declaration.Print( 0, 0, &buffer ); + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlComment& comment ) +{ + DoIndent(); + buffer += ""; + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown ) +{ + DoIndent(); + buffer += "<"; + buffer += unknown.Value(); + buffer += ">"; + DoLineBreak(); + return true; +} + diff --git a/src/framework/xml/tinyxml.h b/src/framework/xml/tinyxml.h new file mode 100644 index 0000000..421ee3b --- /dev/null +++ b/src/framework/xml/tinyxml.h @@ -0,0 +1,1721 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TINYXML_INCLUDED +#define TINYXML_INCLUDED + +#define TIXML_USE_STL // use STL strings instead + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4530 ) +#pragma warning( disable : 4786 ) +#endif + +#include +#include +#include +#include + +#include +#include +#include + +// Help out windows: +#if defined( _DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#ifdef TIXML_USE_STL + #include + #include + #include + #define TIXML_STRING std::string +#else + #include "tinystr.h" + #define TIXML_STRING TiXmlString +#endif + +// Deprecated library function hell. Compilers want to use the +// new safe versions. This probably doesn't fully address the problem, +// but it gets closer. There are too many compilers for me to fully +// test. If you get compilation troubles, undefine TIXML_SAFE +#define TIXML_SAFE + +#ifdef TIXML_SAFE + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + // Microsoft visual studio, version 2005 and higher. + #define TIXML_SNPRINTF _snprintf_s + #define TIXML_SSCANF sscanf_s + #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + //#pragma message( "Using _sn* functions." ) + #define TIXML_SNPRINTF _snprintf + #define TIXML_SSCANF sscanf + #elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #else + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #endif +#endif + +class TiXmlDocument; +class TiXmlElement; +class TiXmlComment; +class TiXmlUnknown; +class TiXmlAttribute; +class TiXmlText; +class TiXmlDeclaration; +class TiXmlParsingData; + +const int TIXML_MAJOR_VERSION = 2; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 2; + +/* Internal structure for tracking location of items + in the XML file. +*/ +struct TiXmlCursor +{ + TiXmlCursor() { Clear(); } + void Clear() { row = col = -1; } + + int row; // 0 based. + int col; // 0 based. +}; + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a TiXmlVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its sibilings will be Visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. + + You should never change the document from a callback. + + @sa TiXmlNode::Accept() +*/ +class TiXmlVisitor +{ +public: + virtual ~TiXmlVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } + /// Visit a document. + virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } + + /// Visit an element. + virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } + /// Visit an element. + virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } + + /// Visit a declaration + virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } + /// Visit a text node + virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } + /// Visit a comment node + virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } + /// Visit an unknown node + virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } +}; + +// Only used by Attribute::Query functions +enum +{ + TIXML_SUCCESS, + TIXML_NO_ATTRIBUTE, + TIXML_WRONG_TYPE +}; + + +// Used by the parsing routines. +enum TiXmlEncoding +{ + TIXML_ENCODING_UNKNOWN, + TIXML_ENCODING_UTF8, + TIXML_ENCODING_LEGACY +}; +static const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; +/** TiXmlBase is a base class for every class in TinyXml. + It does little except to establish that TinyXml classes + can be printed and provide some utility functions. + + In XML, the document and elements can contain + other elements and other types of nodes. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + A Decleration contains: Attributes (not on tree) + @endverbatim +*/ +class TiXmlBase +{ + friend class TiXmlNode; + friend class TiXmlElement; + friend class TiXmlDocument; + +public: + TiXmlBase( const TiXmlBase& ) = delete; + void operator=( const TiXmlBase& base ) = delete; + + TiXmlBase() : userData(0) {} + virtual ~TiXmlBase() {} + + /** All TinyXml classes can print themselves to a filestream + or the string class (TiXmlString in non-STL mode, std::string + in STL mode.) Either or both cfile and str can be null. + + This is a formatted print, and will insert + tabs and newlines. + + (For an unformatted stream, use the << operator.) + */ + virtual void Print( FILE* cfile, int depth ) const = 0; + + /** The world does not agree on whether white space should be kept or + not. In order to make everyone happy, these global, static functions + are provided to set whether or not TinyXml will condense all white space + into a single space or not. The default is to condense. Note changing this + value is not thread safe. + */ + static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } + + /// Return the current white space setting. + static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } + + /** Return the position, in the original source file, of this node or attribute. + The row and column are 1-based. (That is the first row and first column is + 1,1). If the returns values are 0 or less, then the parser does not have + a row and column value. + + Generally, the row and column value will be set when the TiXmlDocument::Load(), + TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set + when the DOM was created from operator>>. + + The values reflect the initial load. Once the DOM is modified programmatically + (by adding or changing nodes and attributes) the new values will NOT update to + reflect changes in the document. + + There is a minor performance cost to computing the row and column. Computation + can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. + + @sa TiXmlDocument::SetTabSize() + */ + int Row() const { return location.row + 1; } + int Column() const { return location.col + 1; } ///< See Row() + + void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. + void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. + const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. + + // Table that returs, for a given lead byte, the total number of bytes + // in the UTF-8 sequence. + static const int utf8ByteTable[256]; + + virtual const char* Parse( const char* p, + TiXmlParsingData* data, + TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; + + /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, + or they will be transformed into entities! + */ + static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); + + enum + { + TIXML_NO_ERROR = 0, + TIXML_ERROR, + TIXML_ERROR_OPENING_FILE, + TIXML_ERROR_PARSING_ELEMENT, + TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, + TIXML_ERROR_READING_ELEMENT_VALUE, + TIXML_ERROR_READING_ATTRIBUTES, + TIXML_ERROR_PARSING_EMPTY, + TIXML_ERROR_READING_END_TAG, + TIXML_ERROR_PARSING_UNKNOWN, + TIXML_ERROR_PARSING_COMMENT, + TIXML_ERROR_PARSING_DECLARATION, + TIXML_ERROR_DOCUMENT_EMPTY, + TIXML_ERROR_EMBEDDED_NULL, + TIXML_ERROR_PARSING_CDATA, + TIXML_ERROR_DOCUMENT_TOP_ONLY, + + TIXML_ERROR_STRING_COUNT + }; + +protected: + + static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + + inline static bool IsWhiteSpace( char c ) + { + return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); + } + inline static bool IsWhiteSpace( int c ) + { + if ( c < 256 ) + return IsWhiteSpace( (char) c ); + return false; // Again, only truly correct for English/Latin...but usually works. + } + + #ifdef TIXML_USE_STL + static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); + static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); + #endif + + /* Reads an XML name into the string provided. Returns + a pointer just past the last character of the name, + or 0 if the function has an error. + */ + static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); + + /* Reads text. Returns a pointer past the given end tag. + Wickedly complex options, but it keeps the (sensitive) code in one place. + */ + static const char* ReadText( const char* in, // where to start + TIXML_STRING* text, // the string read + bool ignoreWhiteSpace, // whether to keep the white space + const char* endTag, // what ends this text + bool ignoreCase, // whether to ignore case in the end tag + TiXmlEncoding encoding ); // the current encoding + + // If an entity has been found, transform it into a character. + static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); + + // Get a character, while interpreting entities. + // The length can be from 0 to 4 bytes. + inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) + { + VALIDATE( p ); + if ( encoding == TIXML_ENCODING_UTF8 ) + { + *length = utf8ByteTable[ *((const unsigned char*)p) ]; + VALIDATE( *length >= 0 && *length < 5 ); + } + else + { + *length = 1; + } + + if ( *length == 1 ) + { + if ( *p == '&' ) + return GetEntity( p, _value, length, encoding ); + *_value = *p; + return p+1; + } + else if ( *length ) + { + //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), + // and the null terminator isn't needed + for( int i=0; p[i] && i<*length; ++i ) { + _value[i] = p[i]; + } + return p + (*length); + } + else + { + // Not valid text. + return 0; + } + } + + // Return true if the next characters in the stream are any of the endTag sequences. + // Ignore case only works for english, and should only be relied on when comparing + // to English words: StringEqual( p, "version", true ) is fine. + static bool StringEqual( const char* p, + const char* endTag, + bool ignoreCase, + TiXmlEncoding encoding ); + + static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; + + TiXmlCursor location; + + /// Field containing a generic user pointer + void* userData; + + // None of these methods are reliable for any language except English. + // Good for approximation, not great for accuracy. + static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); + static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); + inline static int ToLower( int v, TiXmlEncoding encoding ) + { + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( v < 128 ) return tolower( v ); + return v; + } + else + { + return tolower( v ); + } + } + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + +private: + struct Entity + { + const char* str; + unsigned int strLength; + char chr; + }; + enum + { + NUM_ENTITY = 5, + MAX_ENTITY_LENGTH = 6 + + }; + static Entity entity[ NUM_ENTITY ]; + static bool condenseWhiteSpace; +}; + + +/** The parent class for everything in the Document Object Model. + (Except for attributes). + Nodes have siblings, a parent, and children. A node can be + in a document, or stand on its own. The type of a TiXmlNode + can be queried, and it can be cast to its more defined type. +*/ +class TiXmlNode : public TiXmlBase +{ + friend class TiXmlDocument; + friend class TiXmlElement; + +public: + TiXmlNode( const TiXmlNode& ) = delete; + void operator=( const TiXmlNode& base ) = delete; + + #ifdef TIXML_USE_STL + + /** An input stream operator, for every class. Tolerant of newlines and + formatting, but doesn't expect them. + */ + friend std::istream& operator >> (std::istream& in, TiXmlNode& base); + + /** An output stream operator, for every class. Note that this outputs + without any newlines or formatting, as opposed to Print(), which + includes tabs and new lines. + + The operator<< and operator>> are not completely symmetric. Writing + a node to a stream is very well defined. You'll get a nice stream + of output, without any extra whitespace or newlines. + + But reading is not as well defined. (As it always is.) If you create + a TiXmlElement (for example) and read that from an input stream, + the text needs to define an element or junk will result. This is + true of all input streams, but it's worth keeping in mind. + + A TiXmlDocument will read nodes until it reads a root element, and + all the children of that root element. + */ + friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); + + /// Appends the XML node or attribute to a std::string. + friend std::string& operator<< (std::string& out, const TiXmlNode& base ); + + #endif + + /** The types of XML nodes supported by TinyXml. (All the + unsupported types are picked up by UNKNOWN.) + */ + enum NodeType + { + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT + }; + + virtual ~TiXmlNode(); + + /** The meaning of 'value' changes for the specific type of + TiXmlNode. + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + + The subclasses will wrap this function. + */ + const char *Value() const { return value.c_str (); } + + #ifdef TIXML_USE_STL + /** Return Value() as a std::string. If you only use STL, + this is more efficient than calling Value(). + Only available in STL mode. + */ + const std::string& ValueStr() const { return value; } + #endif + + const TIXML_STRING& ValueTStr() const { return value; } + + /** Changes the value of the node. Defined as: + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + void SetValue(const char * _value) { value = _value;} + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Delete all the children of this node. Does not affect 'this'. + void Clear(); + + /// One step up the DOM. + TiXmlNode* Parent() { return parent; } + const TiXmlNode* Parent() const { return parent; } + + const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. + TiXmlNode* FirstChild() { return firstChild; } + const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. + /// The first child of this node with the matching 'value'. Will be null if none found. + TiXmlNode* FirstChild( const char * _value ) { + // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) + // call the method, cast the return back to non-const. + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); + } + const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. + TiXmlNode* LastChild() { return lastChild; } + + const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. + TiXmlNode* LastChild( const char * _value ) { + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. + #endif + + /** An alternate way to walk the children of a node. + One way to iterate over nodes is: + @verbatim + for( child = parent->FirstChild(); child; child = child->NextSibling() ) + @endverbatim + + IterateChildren does the same thing with the syntax: + @verbatim + child = 0; + while( child = parent->IterateChildren( child ) ) + @endverbatim + + IterateChildren takes the previous child as input and finds + the next one. If the previous child is null, it returns the + first. IterateChildren will return null when done. + */ + const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); + } + + /// This flavor of IterateChildren searches for children with a particular 'value' + const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + #endif + + /** Add a new node related to this. Adds a child past the LastChild. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); + + + /** Add a new node related to this. Adds a child past the LastChild. + + NOTE: the node to be added is passed by pointer, and will be + henceforth owned (and deleted) by tinyXml. This method is efficient + and avoids an extra copy, but should be used with care as it + uses a different memory model than the other insert functions. + + @sa InsertEndChild + */ + TiXmlNode* LinkEndChild( TiXmlNode* addThis ); + + /** Add a new node related to this. Adds a child before the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); + + /** Add a new node related to this. Adds a child after the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); + + /** Replace a child of this node. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); + + /// Delete a child of this node. + bool RemoveChild( TiXmlNode* removeThis ); + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling() const { return prev; } + TiXmlNode* PreviousSibling() { return prev; } + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling( const char * ) const; + TiXmlNode* PreviousSibling( const char *_prev ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Navigate to a sibling node. + const TiXmlNode* NextSibling() const { return next; } + TiXmlNode* NextSibling() { return next; } + + /// Navigate to a sibling node with the given 'value'. + const TiXmlNode* NextSibling( const char * ) const; + TiXmlNode* NextSibling( const char* _next ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement() const; + TiXmlElement* NextSiblingElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement( const char * ) const; + TiXmlElement* NextSiblingElement( const char *_next ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement() const; + TiXmlElement* FirstChildElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); + } + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement( const char * _value ) const; + TiXmlElement* FirstChildElement( const char * _value ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /** Query the type (as an enumerated value, above) of this node. + The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, + TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. + */ + int Type() const { return type; } + + /** Return a pointer to the Document this node lives in. + Returns null if not in a document. + */ + const TiXmlDocument* GetDocument() const; + TiXmlDocument* GetDocument() { + return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); + } + + /// Returns true if this node has no children. + bool NoChildren() const { return !firstChild; } + + virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + /** Create an exact duplicate of this node and return it. The memory must be deleted + by the caller. + */ + virtual TiXmlNode* Clone() const = 0; + + /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the TiXmlVisitor interface. + + This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + TiXmlPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( TiXmlVisitor* visitor ) const = 0; + +protected: + TiXmlNode( NodeType _type ); + + // Copy to the allocated object. Shared functionality between Clone, Copy constructor, + // and the assignment operator. + void CopyTo( TiXmlNode* target ) const; + + #ifdef TIXML_USE_STL + // The real work of the input operator. + virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; + #endif + + // Figure out what is at *p, and parse it. Returns null if it is not an xml node. + TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); + + TiXmlNode* parent; + NodeType type; + + TiXmlNode* firstChild; + TiXmlNode* lastChild; + + TIXML_STRING value; + + TiXmlNode* prev; + TiXmlNode* next; +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not TiXmlNodes, since they are not + part of the tinyXML document object model. There are other + suggested ways to look at this problem. +*/ +class TiXmlAttribute : public TiXmlBase +{ + friend class TiXmlAttributeSet; + +public: + /// Construct an empty attribute. + TiXmlAttribute() : TiXmlBase() + { + document = 0; + prev = next = 0; + } + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlAttribute( const std::string& _name, const std::string& _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + #endif + + /// Construct an attribute with a name and value. + TiXmlAttribute( const char * _name, const char * _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + + const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. + const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. + #ifdef TIXML_USE_STL + const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. + #endif + int IntValue() const; ///< Return the value of this attribute, converted to an integer. + double DoubleValue() const; ///< Return the value of this attribute, converted to a double. + + // Get the tinyxml string representation + const TIXML_STRING& NameTStr() const { return name; } + + /** QueryIntValue examines the value string. It is an alternative to the + IntValue() method with richer error checking. + If the value is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. + + A specialized but useful call. Note that for success it returns 0, + which is the opposite of almost all other TinyXml calls. + */ + int QueryIntValue( int* _value ) const; + /// QueryDoubleValue examines the value string. See QueryIntValue(). + int QueryDoubleValue( double* _value ) const; + + void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. + void SetValue( const char* _value ) { value = _value; } ///< Set the value. + + void SetIntValue( int _value ); ///< Set the value from an integer. + void SetDoubleValue( double _value ); ///< Set the value from a double. + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetName( const std::string& _name ) { name = _name; } + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Get the next sibling attribute in the DOM. Returns null at end. + const TiXmlAttribute* Next() const; + TiXmlAttribute* Next() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); + } + + /// Get the previous sibling attribute in the DOM. Returns null at beginning. + const TiXmlAttribute* Previous() const; + TiXmlAttribute* Previous() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); + } + + bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } + bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } + bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } + + /* Attribute parsing starts: first letter of the name + returns: the next char after the value end quote + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + // Prints this Attribute to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + + // [internal use] + // Set the document pointer so the attribute can report errors. + void SetDocument( TiXmlDocument* doc ) { document = doc; } + +private: + TiXmlAttribute( const TiXmlAttribute& ); // not implemented. + void operator=( const TiXmlAttribute& base ); // not allowed. + + TiXmlDocument* document; // A pointer back to a document, for error reporting. + TIXML_STRING name; + TIXML_STRING value; + TiXmlAttribute* prev; + TiXmlAttribute* next; +}; + + +/* A class used to manage a group of attributes. + It is only used internally, both by the ELEMENT and the DECLARATION. + + The set can be changed transparent to the Element and Declaration + classes that use it, but NOT transparent to the Attribute + which has to implement a next() and previous() method. Which makes + it a bit problematic and prevents the use of STL. + + This version is implemented with circular lists because: + - I like circular lists + - it demonstrates some independence from the (typical) doubly linked list. +*/ +class TiXmlAttributeSet +{ +public: + TiXmlAttributeSet(); + ~TiXmlAttributeSet(); + + void Add( TiXmlAttribute* attribute ); + void Remove( TiXmlAttribute* attribute ); + + const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif + + +private: + //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), + //*ME: this class must be also use a hidden/disabled copy-constructor !!! + TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed + void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) + + TiXmlAttribute sentinel; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TiXmlElement : public TiXmlNode +{ +public: + TiXmlElement( const std::string& _value ); + TiXmlElement( const TiXmlElement& ); + TiXmlElement& operator=( const TiXmlElement& base ); + + virtual ~TiXmlElement(); + + template + inline T readType(const std::string& str) const + { + T ret; + int r = QueryValueAttribute(str, &ret); + if(r == TIXML_NO_ATTRIBUTE || r == TIXML_WRONG_TYPE) + return T(); + return ret; + } + + /** Template form of the attribute query which will try to read the + attribute into the specified type. Very easy, very powerful, but + be careful to make sure to call this with the correct type. + + NOTE: This method doesn't work correctly for 'string' types that contain spaces. + + @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE + */ + template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + std::stringstream sstream( node->ValueStr() ); + sstream >> *outValue; + if ( !sstream.fail() ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; + } + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + *outValue = node->ValueStr(); + return TIXML_SUCCESS; + } + + std::string Attribute( const std::string& name ) const; + std::string Attribute( const std::string& name, int* i ) const; + std::string Attribute( const std::string& name, double* d ) const; + + void SetAttribute( const std::string& name, const std::string& _value ); + void SetAttribute( const std::string& name, int _value) { SetAttribute(name, stdext::to_string(_value)); } + + void RemoveAttribute( const std::string& name ); + + const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. + TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } + const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. + TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the TiXmlText child + and accessing it directly. + + If the first child of 'this' is a TiXmlText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + + WARNING: GetText() accesses a child node - don't become confused with the + similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are + safe type casts on the referenced node. + */ + const char* GetText() const; + + /// Creates a new Element and returns it - the returned element is a copy. + virtual TiXmlNode* Clone() const; + // Print the Element to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: next char past '<' + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + + void CopyTo( TiXmlElement* target ) const; + void ClearThis(); // like clear, but initializes 'this' object as well + + // Used to be public [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + /* [internal use] + Reads the "value" of the element -- another element, or text. + This should terminate with the current end tag. + */ + const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + +private: + TiXmlAttributeSet attributeSet; +}; + + +/** An XML comment. +*/ +class TiXmlComment : public TiXmlNode +{ +public: + /// Constructs an empty comment. + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} + /// Construct a comment from text. + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { + SetValue( _value ); + } + TiXmlComment( const TiXmlComment& ); + TiXmlComment& operator=( const TiXmlComment& base ); + + virtual ~TiXmlComment() {} + + /// Returns a copy of this Comment. + virtual TiXmlNode* Clone() const; + // Write this Comment to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: at the ! of the !-- + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlComment* target ) const; + + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif +// virtual void StreamOut( TIXML_OSTREAM * out ) const; + +private: + +}; + + +/** XML text. A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCDATA() and query it with CDATA(). +*/ +class TiXmlText : public TiXmlNode +{ + friend class TiXmlElement; +public: + /** Constructor for text element. By default, it is treated as + normal, encoded text. If you want it be output as a CDATA text + element, set the parameter _cdata to 'true' + */ + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + virtual ~TiXmlText() {} + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + #endif + + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } + + // Write this text object to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /// Queries whether this represents text using a CDATA section. + bool CDATA() const { return cdata; } + /// Turns on or off a CDATA representation of text. + void SetCDATA( bool _cdata ) { cdata = _cdata; } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + /// [internal use] Creates a new Element and returns it. + virtual TiXmlNode* Clone() const; + void CopyTo( TiXmlText* target ) const; + + bool Blank() const; // returns true if all white space and new lines + // [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + bool cdata; // true if this should be input and output as a CDATA style text element +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXml will happily read or write files without a declaration, + however. There are 3 possible attributes to the declaration: + version, encoding, and standalone. + + Note: In this version of the code, the attributes are + handled as special cases, not generic attributes, simply + because there can only be at most 3 and they are always the same. +*/ +class TiXmlDeclaration : public TiXmlNode +{ +public: + /// Construct an empty declaration. + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} + +#ifdef TIXML_USE_STL + /// Constructor. + TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ); +#endif + + /// Construct. + TiXmlDeclaration( const char* _version, + const char* _encoding, + const char* _standalone ); + + TiXmlDeclaration( const TiXmlDeclaration& copy ); + TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); + + virtual ~TiXmlDeclaration() {} + + /// Version. Will return an empty string if none was found. + const char *Version() const { return version.c_str (); } + /// Encoding. Will return an empty string if none was found. + const char *Encoding() const { return encoding.c_str (); } + /// Is this a standalone document? + const char *Standalone() const { return standalone.c_str (); } + + /// Creates a copy of this Declaration and returns it. + virtual TiXmlNode* Clone() const; + // Print this declaration to a FILE stream. + virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlDeclaration* target ) const; + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + + TIXML_STRING version; + TIXML_STRING encoding; + TIXML_STRING standalone; +}; + + +/** Any tag that tinyXml doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into TiXmlUnknowns. +*/ +class TiXmlUnknown : public TiXmlNode +{ +public: + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} + virtual ~TiXmlUnknown() {} + + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } + + /// Creates a copy of this Unknown and returns it. + virtual TiXmlNode* Clone() const; + // Print this Unknown to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected: + void CopyTo( TiXmlUnknown* target ) const; + + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + +}; + + +/** Always the top level node. A document binds together all the + XML pieces. It can be saved, loaded, and printed to the screen. + The 'value' of a document node is the xml file name. +*/ +class TiXmlDocument : public TiXmlNode +{ +public: + /// Create an empty document, that has no name. + TiXmlDocument(); + /// Create a document with a name. The name of the document is also the filename of the xml. + TiXmlDocument( const char * documentName ); + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlDocument( const std::string& documentName ); + #endif + + TiXmlDocument( const TiXmlDocument& copy ); + TiXmlDocument& operator=( const TiXmlDocument& copy ); + + virtual ~TiXmlDocument() {} + + /** Load a file using the current document value. + Returns true if successful. Will delete any existing + document data before loading. + */ + bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the current document value. Returns true if successful. + bool SaveFile() const; + /// Load a file using the given filename. Returns true if successful. + bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given filename. Returns true if successful. + bool SaveFile( const char * filename ) const; + /** Load a file using the given FILE*. Returns true if successful. Note that this method + doesn't stream - the entire object pointed at by the FILE* + will be interpreted as an XML file. TinyXML doesn't stream in XML from the current + file location. Streaming may be added in the future. + */ + bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given FILE*. Returns true if successful. + bool SaveFile( FILE* ) const; + + #ifdef TIXML_USE_STL + bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. + { + return LoadFile( filename.c_str(), encoding ); + } + bool SaveFile( const std::string& filename ) const ///< STL std::string version. + { + return SaveFile( filename.c_str() ); + } + #endif + + /** Parse the given null terminated block of xml data. Passing in an encoding to this + method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml + to use that encoding, regardless of what TinyXml might otherwise try to detect. + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + + /** Get the root element -- the only top level element -- of the document. + In well formed XML, there should only be one. TinyXml is tolerant of + multiple elements at the document level. + */ + const TiXmlElement* RootElement() const { return FirstChildElement(); } + TiXmlElement* RootElement() { return FirstChildElement(); } + + /** If an error occurs, Error will be set to true. Also, + - The ErrorId() will contain the integer identifier of the error (not generally useful) + - The ErrorDesc() method will return the name of the error. (very useful) + - The ErrorRow() and ErrorCol() will return the location of the error (if known) + */ + bool Error() const { return error; } + + /// Contains a textual (english) description of the error if one occurs. + const char * ErrorDesc() const { return errorDesc.c_str (); } + + /** Generally, you probably want the error string ( ErrorDesc() ). But if you + prefer the ErrorId, this function will fetch it. + */ + int ErrorId() const { return errorId; } + + /** Returns the location (if known) of the error. The first column is column 1, + and the first row is row 1. A value of 0 means the row and column wasn't applicable + (memory errors, for example, have no row/column) or the parser lost the error. (An + error in the error reporting, in that case.) + + @sa SetTabSize, Row, Column + */ + int ErrorRow() const { return errorLocation.row+1; } + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + + /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) + to report the correct values for row and column. It does not change the output + or input in any way. + + By calling this method, with a tab size + greater than 0, the row and column of each node and attribute is stored + when the file is loaded. Very useful for tracking the DOM back in to + the source file. + + The tab size is required for calculating the location of nodes. If not + set, the default of 4 is used. The tabsize is set per document. Setting + the tabsize to 0 disables row/column tracking. + + Note that row and column tracking is not supported when using operator>>. + + The tab size needs to be enabled before the parse or load. Correct usage: + @verbatim + TiXmlDocument doc; + doc.SetTabSize( 8 ); + doc.Load( "myfile.xml" ); + @endverbatim + + @sa Row, Column + */ + void SetTabSize( int _tabsize ) { tabsize = _tabsize; } + + int TabSize() const { return tabsize; } + + /** If you have handled the error, it can be reset with this call. The error + state is automatically cleared if you Parse a new XML block. + */ + void ClearError() { error = false; + errorId = 0; + errorDesc = ""; + errorLocation.row = errorLocation.col = 0; + //errorLocation.last = 0; + } + + /** Write the document to standard out using formatted printing ("pretty print"). */ + void Print() const { Print( stdout, 0 ); } + + /* Write the document to a string using formatted printing ("pretty print"). This + will allocate a character array (new char[]) and return it as a pointer. The + calling code pust call delete[] on the return char* to avoid a memory leak. + */ + //char* PrintToMemory() const; + + /// Print this Document to a FILE stream. + virtual void Print( FILE* cfile, int depth = 0 ) const; + // [internal use] + void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + + virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + // [internal use] + virtual TiXmlNode* Clone() const; + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + void CopyTo( TiXmlDocument* target ) const; + + bool error; + int errorId; + TIXML_STRING errorDesc; + int tabsize; + TiXmlCursor errorLocation; + bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. +}; + + +/** + A TiXmlHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + TiXmlElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity + of such code. A TiXmlHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + TiXmlHandle docHandle( &document ); + TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + TiXmlHandle handleCopy = handle; + @endverbatim + + What they should not be used for is iteration: + + @verbatim + int i=0; + while ( true ) + { + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); + if ( !child ) + break; + // do something + ++i; + } + @endverbatim + + It seems reasonable, but it is in fact two embedded while loops. The Child method is + a linear walk to find the element, so this code would iterate much more than it needs + to. Instead, prefer: + + @verbatim + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); + + for( child; child; child=child->NextSiblingElement() ) + { + // do something + } + @endverbatim +*/ +class TiXmlHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } + /// Copy constructor + TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } + + /// Return a handle to the first child node. + TiXmlHandle FirstChild() const; + /// Return a handle to the first child node with the given name. + TiXmlHandle FirstChild( const char * value ) const; + /// Return a handle to the first child element. + TiXmlHandle FirstChildElement() const; + /// Return a handle to the first child element with the given name. + TiXmlHandle FirstChildElement( const char * value ) const; + + /** Return a handle to the "index" child with the given name. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( const char* value, int index ) const; + /** Return a handle to the "index" child. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( int index ) const; + /** Return a handle to the "index" child element with the given name. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( const char* value, int index ) const; + /** Return a handle to the "index" child element. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( int index ) const; + + #ifdef TIXML_USE_STL + TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } + TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } + + TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } + TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } + #endif + + /** Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* ToNode() const { return node; } + /** Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } + /** Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } + /** Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } + + /** @deprecated use ToNode. + Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* Node() const { return ToNode(); } + /** @deprecated use ToElement. + Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* Element() const { return ToElement(); } + /** @deprecated use ToText() + Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* Text() const { return ToText(); } + /** @deprecated use ToUnknown() + Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* Unknown() const { return ToUnknown(); } + +private: + TiXmlNode* node; +}; + + +/** Print to memory functionality. The TiXmlPrinter is useful when you need to: + + -# Print to memory (especially in non-STL mode) + -# Control formatting (line endings, etc.) + + When constructed, the TiXmlPrinter is in its default "pretty printing" mode. + Before calling Accept() you can call methods to control the printing + of the XML document. After TiXmlNode::Accept() is called, the printed document can + be accessed via the CStr(), Str(), and Size() methods. + + TiXmlPrinter uses the Visitor API. + @verbatim + TiXmlPrinter printer; + printer.SetIndent( "\t" ); + + doc.Accept( &printer ); + fprintf( stdout, "%s", printer.CStr() ); + @endverbatim +*/ +class TiXmlPrinter : public TiXmlVisitor +{ +public: + TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), + buffer(), indent( " " ), lineBreak( "\n" ) {} + + virtual bool VisitEnter( const TiXmlDocument& doc ); + virtual bool VisitExit( const TiXmlDocument& doc ); + + virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); + virtual bool VisitExit( const TiXmlElement& element ); + + virtual bool Visit( const TiXmlDeclaration& declaration ); + virtual bool Visit( const TiXmlText& text ); + virtual bool Visit( const TiXmlComment& comment ); + virtual bool Visit( const TiXmlUnknown& unknown ); + + /** Set the indent characters for printing. By default 4 spaces + but tab (\t) is also useful, or null/empty string for no indentation. + */ + void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } + /// Query the indention string. + const char* Indent() { return indent.c_str(); } + /** Set the line breaking string. By default set to newline (\n). + Some operating systems prefer other characters, or can be + set to the null/empty string for no indenation. + */ + void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } + /// Query the current line breaking string. + const char* LineBreak() { return lineBreak.c_str(); } + + /** Switch over to "stream printing" which is the most dense formatting without + linebreaks. Common when the XML is needed for network transmission. + */ + void SetStreamPrinting() { indent = ""; + lineBreak = ""; + } + /// Return the result. + const char* CStr() { return buffer.c_str(); } + /// Return the length of the result string. + size_t Size() { return buffer.size(); } + + #ifdef TIXML_USE_STL + /// Return the result. + const std::string& Str() { return buffer; } + #endif + +private: + void DoIndent() { + for( int i=0; i +#include "tinyxml.h" + +// The goal of the seperate error file is to make the first +// step towards localization. tinyxml (currently) only supports +// english error messages, but the could now be translated. +// +// It also cleans up the code a bit. +// + +const char* TiXmlBase::errorString[ TiXmlBase::TIXML_ERROR_STRING_COUNT ] = +{ + "No error", + "Error", + "Failed to open file", + "Error parsing Element.", + "Failed to read Element name", + "Error reading Element value.", + "Error reading Attributes.", + "Error: empty tag.", + "Error reading end tag.", + "Error parsing Unknown.", + "Error parsing Comment.", + "Error parsing Declaration.", + "Error document empty.", + "Error null (0) or unexpected EOF found in input stream.", + "Error parsing CDATA.", + "Error when TiXmlDocument added to document, because TiXmlDocument can only be at the root.", +}; diff --git a/src/framework/xml/tinyxmlparser.cpp b/src/framework/xml/tinyxmlparser.cpp new file mode 100644 index 0000000..9a972b0 --- /dev/null +++ b/src/framework/xml/tinyxmlparser.cpp @@ -0,0 +1,1644 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include + +#include +#include + +#include "tinyxml.h" + +//#define DEBUG_PARSER +#if defined( DEBUG_PARSER ) +# if defined( DEBUG ) && defined( _MSC_VER ) +# include +# define TIXML_LOG OutputDebugString +# else +# define TIXML_LOG printf +# endif +#endif + +// Note tha "PutString" hardcodes the same list. This +// is less flexible than it appears. Changing the entries +// or order will break putstring. +TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = +{ + { "&", 5, '&' }, + { "<", 4, '<' }, + { ">", 4, '>' }, + { """, 6, '\"' }, + { "'", 6, '\'' } +}; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// Including the basic of this table, which determines the #bytes in the +// sequence from the lead byte. 1 placed for invalid sequences -- +// although the result will be junk, pass it through as much as possible. +// Beware of the non-characters in UTF-8: +// ef bb bf (Microsoft "lead bytes") +// ef bf be +// ef bf bf + +const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +const int TiXmlBase::utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte + 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; + + +void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) + *length = 1; + else if ( input < 0x800 ) + *length = 2; + else if ( input < 0x10000 ) + *length = 3; + else if ( input < 0x200000 ) + *length = 4; + else + { *length = 0; return; } // This code won't covert this correctly anyway. + + output += *length; + + // Scary scary fall throughs. + switch (*length) + { + /* FALLTHROUGH */ + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + /* FALLTHROUGH */ + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + /* FALLTHROUGH */ + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + /* FALLTHROUGH */ + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + } +} + + +/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalpha( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalpha( anyByte ); +// } +} + + +/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalnum( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalnum( anyByte ); +// } +} + + +class TiXmlParsingData +{ + friend class TiXmlDocument; + public: + void Stamp( const char* now, TiXmlEncoding encoding ); + + const TiXmlCursor& Cursor() const { return cursor; } + + private: + // Only used by the document! + TiXmlParsingData( const char* start, int _tabsize, int row, int col ) + { + VALIDATE( start ); + stamp = start; + tabsize = _tabsize; + cursor.row = row; + cursor.col = col; + } + + TiXmlCursor cursor; + const char* stamp; + int tabsize; +}; + + +void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) +{ + VALIDATE( now ); + + // Do nothing if the tabsize is 0. + if ( tabsize < 1 ) + { + return; + } + + // Get the current row, column. + int row = cursor.row; + int col = cursor.col; + const char* p = stamp; + VALIDATE( p ); + + while ( p < now ) + { + // Treat p as unsigned, so we have a happy compiler. + const unsigned char* pU = (const unsigned char*)p; + + // Code contributed by Fletcher Dunn: (modified by lee) + switch (*pU) { + case 0: + // We *should* never get here, but in case we do, don't + // advance past the terminating null character, ever + return; + + case '\r': + // bump down to the next line + ++row; + col = 0; + // Eat the character + ++p; + + // Check for \r\n sequence, and treat this as a single character + if (*p == '\n') { + ++p; + } + break; + + case '\n': + // bump down to the next line + ++row; + col = 0; + + // Eat the character + ++p; + + // Check for \n\r sequence, and treat this as a single + // character. (Yes, this bizarre thing does occur still + // on some arcane platforms...) + if (*p == '\r') { + ++p; + } + break; + + case '\t': + // Eat the character + ++p; + + // Skip to next tab stop + col = (col / tabsize + 1) * tabsize; + break; + + case TIXML_UTF_LEAD_0: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( *(p+1) && *(p+2) ) + { + // In these cases, don't advance the column. These are + // 0-width spaces. + if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) + p += 3; + else + { p +=3; ++col; } // A normal character. + } + } + else + { + ++p; + ++col; + } + break; + + default: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // Eat the 1 to 4 byte utf8 character. + int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)]; + if ( step == 0 ) + step = 1; // Error case from bad encoding, but handle gracefully. + p += step; + + // Just advance one column, of course. + ++col; + } + else + { + ++p; + ++col; + } + break; + } + } + cursor.row = row; + cursor.col = col; + VALIDATE( cursor.row >= -1 ); + VALIDATE( cursor.col >= -1 ); + stamp = p; + VALIDATE( stamp ); +} + + +const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) +{ + if ( !p || !*p ) + { + return 0; + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + while ( *p ) + { + const unsigned char* pU = (const unsigned char*)p; + + // Skip the stupid Microsoft UTF-8 Byte order marks + if ( *(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==TIXML_UTF_LEAD_1 + && *(pU+2)==TIXML_UTF_LEAD_2 ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbeU ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbfU ) + { + p += 3; + continue; + } + + if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. + ++p; + else + break; + } + } + else + { + while ( *p && IsWhiteSpace( *p ) ) + ++p; + } + + return p; +} + +#ifdef TIXML_USE_STL +/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ) +{ + for( ;; ) + { + if ( !in->good() ) return false; + + int c = in->peek(); + // At this scope, we can't get to a document. So fail silently. + if ( !IsWhiteSpace( c ) || c <= 0 ) + return true; + + *tag += (char) in->get(); + } +} + +/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag ) +{ + //VALIDATE( character > 0 && character < 128 ); // else it won't work in utf-8 + while ( in->good() ) + { + int c = in->peek(); + if ( c == character ) + return true; + if ( c <= 0 ) // Silent failure: can't get document at this scope + return false; + + in->get(); + *tag += (char) c; + } + return false; +} +#endif + +// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The +// "assign" optimization removes over 10% of the execution time. +// +const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) +{ + // Oddly, not supported on some comilers, + //name->clear(); + // So use this: + *name = ""; + VALIDATE( p ); + + // Names start with letters or underscores. + // Of course, in unicode, tinyxml has no idea what a letter *is*. The + // algorithm is generous. + // + // After that, they can be letters, underscores, numbers, + // hyphens, or colons. (Colons are valid ony for namespaces, + // but tinyxml can't tell namespaces from names.) + if ( p && *p + && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) + { + const char* start = p; + while( p && *p + && ( IsAlphaNum( (unsigned char ) *p, encoding ) + || *p == '_' + || *p == '-' + || *p == '.' + || *p == ':' ) ) + { + //(*name) += *p; // expensive + ++p; + } + if ( p-start > 0 ) { + name->assign( start, p-start ); + } + return p; + } + return 0; +} + +const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) +{ + // Presume an entity, and pull it out. + TIXML_STRING ent; + int i; + *length = 0; + + if ( *(p+1) && *(p+1) == '#' && *(p+2) ) + { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) + { + // Hexadecimal. + if ( !*(p+3) ) return 0; + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != 'x' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else if ( *q >= 'a' && *q <= 'f' ) + ucs += mult * (*q - 'a' + 10); + else if ( *q >= 'A' && *q <= 'F' ) + ucs += mult * (*q - 'A' + 10 ); + else + return 0; + mult *= 16; + --q; + } + } + else + { + // Decimal. + if ( !*(p+2) ) return 0; + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != '#' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else + return 0; + mult *= 10; + --q; + } + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + } + else + { + *value = (char)ucs; + *length = 1; + } + return p + delta + 1; + } + + // Now try to match it. + for( i=0; iappend( cArr, len ); + } + } + else + { + bool whitespace = false; + + // Remove leading white space: + p = SkipWhiteSpace( p, encoding ); + while ( p && *p + && !StringEqual( p, endTag, caseInsensitive, encoding ) ) + { + if ( *p == '\r' || *p == '\n' ) + { + whitespace = true; + ++p; + } + else if ( IsWhiteSpace( *p ) ) + { + whitespace = true; + ++p; + } + else + { + // If we've found whitespace, add it before the + // new character. Any whitespace just becomes a space. + if ( whitespace ) + { + (*text) += ' '; + whitespace = false; + } + int len; + char cArr[4] = { 0, 0, 0, 0 }; + p = GetChar( p, cArr, &len, encoding ); + if ( len == 1 ) + (*text) += cArr[0]; // more efficient + else + text->append( cArr, len ); + } + } + } + if ( p && *p ) + p += strlen( endTag ); + return ( p && *p ) ? p : 0; +} + +#ifdef TIXML_USE_STL + +void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + // The basic issue with a document is that we don't know what we're + // streaming. Read something presumed to be a tag (and hope), then + // identify it, and call the appropriate stream method on the tag. + // + // This "pre-streaming" will never read the closing ">" so the + // sub-tag can orient itself. + + if ( !StreamTo( in, '<', tag ) ) + { + SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + while ( in->good() ) + { + int tagIndex = (int) tag->length(); + while ( in->good() && in->peek() != '>' ) + { + int c = in->get(); + if ( c <= 0 ) + { + SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + break; + } + (*tag) += (char) c; + } + + if ( in->good() ) + { + // We now have something we presume to be a node of + // some sort. Identify it, and call the node to + // continue streaming. + TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); + + if ( node ) + { + node->StreamIn( in, tag ); + bool isElement = node->ToElement() != 0; + delete node; + node = 0; + + // If this is the root element, we're done. Parsing will be + // done by the >> operator. + if ( isElement ) + { + return; + } + } + else + { + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + } + } + // We should have returned sooner. + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); +} + +#endif + +const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) +{ + ClearError(); + + // Parse away, at the document level. Since a document + // contains nothing but other tags, most of what happens + // here is skipping white space. + if ( !p || !*p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + // Note that, for a document, this needs to come + // before the while space skip, so that parsing + // starts from the pointer we are given. + location.Clear(); + if ( prevData ) + { + location.row = prevData->cursor.row; + location.col = prevData->cursor.col; + } + else + { + location.row = 0; + location.col = 0; + } + TiXmlParsingData data( p, TabSize(), location.row, location.col ); + location = data.Cursor(); + + if ( encoding == TIXML_ENCODING_UNKNOWN ) + { + // Check for the Microsoft UTF-8 lead bytes. + const unsigned char* pU = (const unsigned char*)p; + if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0 + && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1 + && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 ) + { + encoding = TIXML_ENCODING_UTF8; + useMicrosoftBOM = true; + } + } + + p = SkipWhiteSpace( p, encoding ); + if ( !p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + while ( p && *p ) + { + TiXmlNode* node = Identify( p, encoding ); + if ( node ) + { + p = node->Parse( p, &data, encoding ); + LinkEndChild( node ); + } + else + { + break; + } + + // Did we get encoding info? + if ( encoding == TIXML_ENCODING_UNKNOWN + && node->ToDeclaration() ) + { + TiXmlDeclaration* dec = node->ToDeclaration(); + const char* enc = dec->Encoding(); + VALIDATE( enc ); + + if ( *enc == 0 ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice + else + encoding = TIXML_ENCODING_LEGACY; + } + + p = SkipWhiteSpace( p, encoding ); + } + + // Was this empty? + if ( !firstChild ) { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding ); + return 0; + } + + // All is well. + return p; +} + +void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + // The first error in a chain is more accurate - don't set again! + if ( error ) + return; + + VALIDATE( err > 0 && err < TIXML_ERROR_STRING_COUNT ); + error = true; + errorId = err; + errorDesc = errorString[ errorId ]; + + errorLocation.Clear(); + if ( pError && data ) + { + data->Stamp( pError, encoding ); + errorLocation = data->Cursor(); + } +} + + +TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) +{ + TiXmlNode* returnNode = 0; + + p = SkipWhiteSpace( p, encoding ); + if( !p || !*p || *p != '<' ) + { + return 0; + } + + p = SkipWhiteSpace( p, encoding ); + + if ( !p || !*p ) + { + return 0; + } + + // What is this thing? + // - Elements start with a letter or underscore, but xml is reserved. + // - Comments: "; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // [ 1475201 ] TinyXML parses entities in comments + // Oops - ReadText doesn't work, because we don't want to parse the entities. + // p = ReadText( p, &value, false, endTag, false, encoding ); + // + // from the XML spec: + /* + [Definition: Comments may appear anywhere in a document outside other markup; in addition, + they may appear within the document type declaration at places allowed by the grammar. + They are not part of the document's character data; an XML processor MAY, but need not, + make it possible for an application to retrieve the text of comments. For compatibility, + the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity + references MUST NOT be recognized within comments. + + An example of a comment: + + + */ + + value = ""; + // Keep all the white space. + while ( p && *p && !StringEqual( p, endTag, false, encoding ) ) + { + value.append( p, 1 ); + ++p; + } + if ( p && *p ) + p += strlen( endTag ); + + return p; +} + + +const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) return 0; + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + // Read the name, the '=' and the value. + const char* pErr = p; + p = ReadName( p, &name, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); + return 0; + } + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p || *p != '=' ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + ++p; // skip '=' + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + const char* end; + const char SINGLE_QUOTE = '\''; + const char DOUBLE_QUOTE = '\"'; + + if ( *p == SINGLE_QUOTE ) + { + ++p; + end = "\'"; // single quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else if ( *p == DOUBLE_QUOTE ) + { + ++p; + end = "\""; // double quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else + { + // All attribute values should be in single or double quotes. + // But this is such a common error that the parser will try + // its best, even without them. + value = ""; + while ( p && *p // existence + && !IsWhiteSpace( *p ) // whitespace + && *p != '/' && *p != '>' ) // tag end + { + if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { + // [ 1451649 ] Attribute values with trailing quotes not handled correctly + // We did not have an opening quote but seem to have a + // closing one. Give up and throw an error. + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + value += *p; + ++p; + } + } + return p; +} + +#ifdef TIXML_USE_STL +void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->peek(); + if ( !cdata && (c == '<' ) ) + { + return; + } + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + (*tag) += (char) c; + in->get(); // "commits" the peek made above + + if ( cdata && c == '>' && tag->size() >= 3 ) { + size_t len = tag->size(); + if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) { + // terminator of cdata. + return; + } + } + } +} +#endif + +const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + value = ""; + TiXmlDocument* document = GetDocument(); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + + const char* const startTag = ""; + + if ( cdata || StringEqual( p, startTag, false, encoding ) ) + { + cdata = true; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // Keep all the white space, ignore the encoding, etc. + while ( p && *p + && !StringEqual( p, endTag, false, encoding ) + ) + { + value += *p; + ++p; + } + + TIXML_STRING dummy; + p = ReadText( p, &dummy, false, endTag, false, encoding ); + return p; + } + else + { + bool ignoreWhite = true; + + const char* end = "<"; + p = ReadText( p, &value, ignoreWhite, end, false, encoding ); + if ( p && *p ) + return p-1; // don't truncate the '<' + return 0; + } +} + +#ifdef TIXML_USE_STL +void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c; + + if ( c == '>' ) + { + // All is well. + return; + } + } +} +#endif + +const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) +{ + p = SkipWhiteSpace( p, _encoding ); + // Find the beginning, find the end, and look for + // the stuff in-between. + TiXmlDocument* document = GetDocument(); + if ( !p || !*p || !StringEqual( p, "SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); + return 0; + } + if ( data ) + { + data->Stamp( p, _encoding ); + location = data->Cursor(); + } + p += 5; + + version = ""; + encoding = ""; + standalone = ""; + + while ( p && *p ) + { + if ( *p == '>' ) + { + ++p; + return p; + } + + p = SkipWhiteSpace( p, _encoding ); + if ( StringEqual( p, "version", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + version = attrib.Value(); + } + else if ( StringEqual( p, "encoding", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + encoding = attrib.Value(); + } + else if ( StringEqual( p, "standalone", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + standalone = attrib.Value(); + } + else + { + // Read over whatever it is. + while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) + ++p; + } + } + return 0; +} + +bool TiXmlText::Blank() const +{ + for ( unsigned i=0; i + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, const char* argv[]) { + std::vector args(argv, argv + argc); + +#ifdef CRASH_HANDLER + installCrashHandler(); +#endif + + // initialize resources + g_resources.init(argv[0]); + std::string compactName = g_resources.getCompactName(); + g_logger.setLogFile(compactName + ".log"); + + // setup application name and version + g_app.setName("OTClientV8"); + g_app.setCompactName(compactName); + g_app.setVersion("2.4"); + +#ifdef WITH_ENCRYPTION + if (std::find(args.begin(), args.end(), "--encrypt") != args.end()) { + g_lua.init(); + g_resources.encrypt(args.size() >= 3 ? args[2] : ""); + std::cout << "Encryption complete" << std::endl; +#ifdef WIN32 + MessageBoxA(NULL, "Encryption complete", "Success", 0); +#endif + return 0; + } +#endif + + if (g_resources.launchCorrect(g_app.getName(), g_app.getCompactName())) { + return 0; // started other executable + } + + // initialize application framework and otclient + g_app.init(args); + g_client.init(args); + g_http.init(); + + // find script init.lua and run it + g_resources.setupWriteDir(g_app.getName(), g_app.getCompactName()); + g_resources.setup(); + + if (!g_lua.safeRunScript("init.lua")) { + if (g_resources.isLoadedFromArchive() && !g_resources.isLoadedFromMemory() && + g_resources.loadDataFromSelf(true)) { + g_logger.error("Unable to run script init.lua! Trying to run version from memory."); + if (!g_lua.safeRunScript("init.lua")) { + g_resources.deleteFile("data.zip"); // remove incorrect data.zip + g_logger.fatal("Unable to run script init.lua from binary file!\nTry to run client again."); + } + } else { + g_logger.fatal("Unable to run script init.lua!"); + } + } + +#ifdef WIN32 + // support for progdn proxy system, if you don't have this dll nothing will happen + // however, it is highly recommended to use otcv8 proxy system + LoadLibraryA("progdn32.dll"); +#endif + + // the run application main loop + g_app.run(); + +#ifdef CRASH_HANDLER + uninstallCrashHandler(); +#endif + + // unload modules + g_app.deinit(); + + // terminate everything and free memory + g_http.terminate(); + g_client.terminate(); + g_app.terminate(); + return 0; +} + +#ifdef ANDROID +#include + +android_app* g_androidState = nullptr; +void android_main(struct android_app* state) +{ + g_mainThreadId = g_dispatcherThreadId = std::this_thread::get_id(); + g_androidState = state; + + state->userData = nullptr; + state->onAppCmd = +[](android_app* app, int32_t cmd) -> void { + return g_androidWindow.handleCmd(cmd); + }; + state->onInputEvent = +[](android_app* app, AInputEvent* event) -> int32_t { + return g_androidWindow.handleInput(event); + }; + state->activity->callbacks->onNativeWindowResized = +[](ANativeActivity* activity, ANativeWindow* window) -> void { + g_graphicsDispatcher.scheduleEventEx("updateWindowSize", [] { + g_androidWindow.updateSize(); + }, 500); + }; + state->activity->callbacks->onContentRectChanged = +[](ANativeActivity* activity, const ARect* rect) -> void { + g_graphicsDispatcher.scheduleEventEx("updateWindowSize", [] { + g_androidWindow.updateSize(); + }, 500); + }; + + bool terminated = false; + g_window.setOnClose([&] { + terminated = true; + }); + while(!g_window.isVisible() && !terminated) + g_window.poll(); // init window + // run app + const char* args[] = { "otclientv8.apk" }; + main(1, args); + std::exit(0); // required! +} +#endif