From 3eeae12c498d1a4dbe969462d2ba841f77ee3ccb Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Sun, 2 Jan 2011 01:48:38 +0200 Subject: Initial commit. This code based on mana client http://www.gitorious.org/mana/mana and my private repository. --- src/CMakeLists.txt | 723 ++++++ src/Makefile.am | 589 +++++ src/SDLMain.h | 11 + src/SDLMain.m | 385 +++ src/SDL_gfxBlitFunc.h | 128 + src/actor.cpp | 64 + src/actor.h | 128 + src/actorsprite.cpp | 491 ++++ src/actorsprite.h | 255 ++ src/actorspritelistener.h | 42 + src/actorspritemanager.cpp | 1081 +++++++++ src/actorspritemanager.h | 253 ++ src/animatedsprite.cpp | 241 ++ src/animatedsprite.h | 97 + src/animationparticle.cpp | 54 + src/animationparticle.h | 48 + src/avatar.cpp | 62 + src/avatar.h | 189 ++ src/being.cpp | 2009 +++++++++++++++ src/being.h | 770 ++++++ src/channel.cpp | 40 + src/channel.h | 89 + src/channelmanager.cpp | 91 + src/channelmanager.h | 48 + src/chatlog.cpp | 201 ++ src/chatlog.h | 78 + src/client.cpp | 1986 +++++++++++++++ src/client.h | 317 +++ src/commandhandler.cpp | 1124 +++++++++ src/commandhandler.h | 281 +++ src/compoundsprite.cpp | 408 ++++ src/compoundsprite.h | 112 + src/configlistener.h | 48 + src/configuration.cpp | 438 ++++ src/configuration.h | 302 +++ src/defaults.cpp | 253 ++ src/defaults.h | 42 + src/dropshortcut.cpp | 172 ++ src/dropshortcut.h | 155 ++ src/effectmanager.cpp | 107 + src/effectmanager.h | 61 + src/emoteshortcut.cpp | 89 + src/emoteshortcut.h | 125 + src/enet/ChangeLog | 86 + src/enet/LICENSE | 7 + src/enet/README | 15 + src/enet/callbacks.c | 47 + src/enet/callbacks.h | 27 + src/enet/compress.c | 654 +++++ src/enet/design.txt | 117 + src/enet/enet.h | 540 +++++ src/enet/host.c | 479 ++++ src/enet/list.c | 75 + src/enet/list.h | 43 + src/enet/packet.c | 159 ++ src/enet/peer.c | 816 +++++++ src/enet/protocol.c | 1671 +++++++++++++ src/enet/protocol.h | 196 ++ src/enet/time.h | 18 + src/enet/types.h | 13 + src/enet/unix.c | 443 ++++ src/enet/unix.h | 45 + src/enet/utility.h | 12 + src/enet/win32.c | 348 +++ src/enet/win32.h | 58 + src/equipment.h | 94 + src/event.cpp | 142 ++ src/event.h | 175 ++ src/filefilter.txt | 26 + src/filefilter_more.txt | 12 + src/flooritem.cpp | 133 + src/flooritem.h | 92 + src/game.cpp | 1501 ++++++++++++ src/game.h | 110 + src/graphics.cpp | 426 ++++ src/graphics.h | 241 ++ src/gui/beingpopup.cpp | 142 ++ src/gui/beingpopup.h | 59 + src/gui/botcheckerwindow.cpp | 413 ++++ src/gui/botcheckerwindow.h | 95 + src/gui/buy.cpp | 323 +++ src/gui/buy.h | 151 ++ src/gui/buysell.cpp | 137 ++ src/gui/buysell.h | 78 + src/gui/changeemaildialog.cpp | 167 ++ src/gui/changeemaildialog.h | 78 + src/gui/changepassworddialog.cpp | 157 ++ src/gui/changepassworddialog.h | 73 + src/gui/charcreatedialog.cpp | 372 +++ src/gui/charcreatedialog.h | 122 + src/gui/charselectdialog.cpp | 456 ++++ src/gui/charselectdialog.h | 113 + src/gui/chat.cpp | 1350 +++++++++++ src/gui/chat.h | 318 +++ src/gui/confirmdialog.cpp | 112 + src/gui/confirmdialog.h | 57 + src/gui/connectiondialog.cpp | 65 + src/gui/connectiondialog.h | 62 + src/gui/debugwindow.cpp | 248 ++ src/gui/debugwindow.h | 72 + src/gui/editdialog.cpp | 73 + src/gui/editdialog.h | 66 + src/gui/emotepopup.cpp | 214 ++ src/gui/emotepopup.h | 121 + src/gui/equipmentwindow.cpp | 260 ++ src/gui/equipmentwindow.h | 98 + src/gui/focushandler.cpp | 99 + src/gui/focushandler.h | 77 + src/gui/gui.cpp | 310 +++ src/gui/gui.h | 148 ++ src/gui/help.cpp | 106 + src/gui/help.h | 76 + src/gui/inventorywindow.cpp | 503 ++++ src/gui/inventorywindow.h | 155 ++ src/gui/itemamount.cpp | 432 ++++ src/gui/itemamount.h | 124 + src/gui/itempopup.cpp | 238 ++ src/gui/itempopup.h | 71 + src/gui/killstats.cpp | 424 ++++ src/gui/killstats.h | 132 + src/gui/login.cpp | 231 ++ src/gui/login.h | 90 + src/gui/minimap.cpp | 292 +++ src/gui/minimap.h | 69 + src/gui/ministatus.cpp | 228 ++ src/gui/ministatus.h | 94 + src/gui/npcdialog.cpp | 483 ++++ src/gui/npcdialog.h | 232 ++ src/gui/npcpostdialog.cpp | 128 + src/gui/npcpostdialog.h | 70 + src/gui/okdialog.cpp | 81 + src/gui/okdialog.h | 57 + src/gui/outfitwindow.cpp | 914 +++++++ src/gui/outfitwindow.h | 135 ++ src/gui/palette.cpp | 274 +++ src/gui/palette.h | 191 ++ src/gui/popupmenu.cpp | 1286 ++++++++++ src/gui/popupmenu.h | 149 ++ src/gui/quitdialog.cpp | 204 ++ src/gui/quitdialog.h | 77 + src/gui/register.cpp | 258 ++ src/gui/register.h | 110 + src/gui/sdlinput.cpp | 432 ++++ src/gui/sdlinput.h | 188 ++ src/gui/sell.cpp | 333 +++ src/gui/sell.h | 145 ++ src/gui/serverdialog.cpp | 768 ++++++ src/gui/serverdialog.h | 204 ++ src/gui/setup.cpp | 178 ++ src/gui/setup.h | 82 + src/gui/setup_audio.cpp | 179 ++ src/gui/setup_audio.h | 53 + src/gui/setup_chat.cpp | 306 +++ src/gui/setup_chat.h | 92 + src/gui/setup_colors.cpp | 443 ++++ src/gui/setup_colors.h | 96 + src/gui/setup_joystick.cpp | 101 + src/gui/setup_joystick.h | 48 + src/gui/setup_keyboard.cpp | 210 ++ src/gui/setup_keyboard.h | 81 + src/gui/setup_other.cpp | 426 ++++ src/gui/setup_other.h | 125 + src/gui/setup_players.cpp | 505 ++++ src/gui/setup_players.h | 95 + src/gui/setup_theme.cpp | 239 ++ src/gui/setup_theme.h | 74 + src/gui/setup_video.cpp | 819 +++++++ src/gui/setup_video.h | 136 ++ src/gui/shopwindow.cpp | 788 ++++++ src/gui/shopwindow.h | 173 ++ src/gui/shortcutwindow.cpp | 152 ++ src/gui/shortcutwindow.h | 71 + src/gui/skilldialog.cpp | 523 ++++ src/gui/skilldialog.h | 91 + src/gui/socialwindow.cpp | 1306 ++++++++++ src/gui/socialwindow.h | 159 ++ src/gui/specialswindow.cpp | 257 ++ src/gui/specialswindow.h | 73 + src/gui/speechbubble.cpp | 91 + src/gui/speechbubble.h | 58 + src/gui/spellpopup.cpp | 105 + src/gui/spellpopup.h | 67 + src/gui/statuspopup.cpp | 543 +++++ src/gui/statuspopup.h | 78 + src/gui/statuswindow.cpp | 880 +++++++ src/gui/statuswindow.h | 99 + src/gui/textcommandeditor.cpp | 390 +++ src/gui/textcommandeditor.h | 105 + src/gui/textdialog.cpp | 94 + src/gui/textdialog.h | 74 + src/gui/textpopup.cpp | 99 + src/gui/textpopup.h | 68 + src/gui/theme.cpp | 791 ++++++ src/gui/theme.h | 273 +++ src/gui/trade.cpp | 420 ++++ src/gui/trade.h | 170 ++ src/gui/truetypefont.cpp | 336 +++ src/gui/truetypefont.h | 104 + src/gui/unregisterdialog.cpp | 145 ++ src/gui/unregisterdialog.h | 68 + src/gui/updatewindow.cpp | 672 +++++ src/gui/updatewindow.h | 210 ++ src/gui/userpalette.cpp | 292 +++ src/gui/userpalette.h | 222 ++ src/gui/viewport.cpp | 763 ++++++ src/gui/viewport.h | 298 +++ src/gui/whoisonline.cpp | 550 +++++ src/gui/whoisonline.h | 139 ++ src/gui/widgets/avatarlistbox.cpp | 346 +++ src/gui/widgets/avatarlistbox.h | 70 + src/gui/widgets/battletab.cpp | 54 + src/gui/widgets/battletab.h | 47 + src/gui/widgets/browserbox.cpp | 534 ++++ src/gui/widgets/browserbox.h | 205 ++ src/gui/widgets/button.cpp | 227 ++ src/gui/widgets/button.h | 94 + src/gui/widgets/channeltab.cpp | 132 + src/gui/widgets/channeltab.h | 62 + src/gui/widgets/chattab.cpp | 431 ++++ src/gui/widgets/chattab.h | 173 ++ src/gui/widgets/checkbox.cpp | 187 ++ src/gui/widgets/checkbox.h | 92 + src/gui/widgets/container.cpp | 33 + src/gui/widgets/container.h | 43 + src/gui/widgets/desktop.cpp | 157 ++ src/gui/widgets/desktop.h | 73 + src/gui/widgets/dropdown.cpp | 303 +++ src/gui/widgets/dropdown.h | 97 + src/gui/widgets/dropshortcutcontainer.cpp | 303 +++ src/gui/widgets/dropshortcutcontainer.h | 88 + src/gui/widgets/emoteshortcutcontainer.cpp | 259 ++ src/gui/widgets/emoteshortcutcontainer.h | 84 + src/gui/widgets/flowcontainer.cpp | 88 + src/gui/widgets/flowcontainer.h | 73 + src/gui/widgets/icon.cpp | 60 + src/gui/widgets/icon.h | 66 + src/gui/widgets/inttextfield.cpp | 112 + src/gui/widgets/inttextfield.h | 76 + src/gui/widgets/itemcontainer.cpp | 475 ++++ src/gui/widgets/itemcontainer.h | 195 ++ src/gui/widgets/itemlinkhandler.cpp | 66 + src/gui/widgets/itemlinkhandler.h | 47 + src/gui/widgets/itemshortcutcontainer.cpp | 375 +++ src/gui/widgets/itemshortcutcontainer.h | 93 + src/gui/widgets/label.cpp | 38 + src/gui/widgets/label.h | 52 + src/gui/widgets/layout.cpp | 362 +++ src/gui/widgets/layout.h | 319 +++ src/gui/widgets/layouthelper.cpp | 63 + src/gui/widgets/layouthelper.h | 90 + src/gui/widgets/linkhandler.h | 42 + src/gui/widgets/listbox.cpp | 146 ++ src/gui/widgets/listbox.h | 78 + src/gui/widgets/passwordfield.cpp | 36 + src/gui/widgets/passwordfield.h | 46 + src/gui/widgets/playerbox.cpp | 120 + src/gui/widgets/playerbox.h | 74 + src/gui/widgets/popup.cpp | 174 ++ src/gui/widgets/popup.h | 174 ++ src/gui/widgets/progressbar.cpp | 225 ++ src/gui/widgets/progressbar.h | 139 ++ src/gui/widgets/progressindicator.cpp | 78 + src/gui/widgets/progressindicator.h | 45 + src/gui/widgets/radiobutton.cpp | 163 ++ src/gui/widgets/radiobutton.h | 85 + src/gui/widgets/resizegrip.cpp | 82 + src/gui/widgets/resizegrip.h | 60 + src/gui/widgets/scrollarea.cpp | 445 ++++ src/gui/widgets/scrollarea.h | 151 ++ src/gui/widgets/setuptab.cpp | 31 + src/gui/widgets/setuptab.h | 64 + src/gui/widgets/shopitems.cpp | 118 + src/gui/widgets/shopitems.h | 120 + src/gui/widgets/shoplistbox.cpp | 185 ++ src/gui/widgets/shoplistbox.h | 104 + src/gui/widgets/shortcutcontainer.cpp | 67 + src/gui/widgets/shortcutcontainer.h | 115 + src/gui/widgets/slider.cpp | 298 +++ src/gui/widgets/slider.h | 98 + src/gui/widgets/spellshortcutcontainer.cpp | 285 +++ src/gui/widgets/spellshortcutcontainer.h | 88 + src/gui/widgets/tab.cpp | 196 ++ src/gui/widgets/tab.h | 80 + src/gui/widgets/tabbedarea.cpp | 221 ++ src/gui/widgets/tabbedarea.h | 129 + src/gui/widgets/table.cpp | 585 +++++ src/gui/widgets/table.h | 195 ++ src/gui/widgets/tablemodel.cpp | 173 ++ src/gui/widgets/tablemodel.h | 149 ++ src/gui/widgets/textbox.cpp | 149 ++ src/gui/widgets/textbox.h | 70 + src/gui/widgets/textfield.cpp | 306 +++ src/gui/widgets/textfield.h | 110 + src/gui/widgets/textpreview.cpp | 82 + src/gui/widgets/textpreview.h | 130 + src/gui/widgets/tradetab.cpp | 59 + src/gui/widgets/tradetab.h | 50 + src/gui/widgets/vertcontainer.cpp | 53 + src/gui/widgets/vertcontainer.h | 52 + src/gui/widgets/whispertab.cpp | 164 ++ src/gui/widgets/whispertab.h | 67 + src/gui/widgets/window.cpp | 924 +++++++ src/gui/widgets/window.h | 441 ++++ src/gui/widgets/windowcontainer.cpp | 40 + src/gui/widgets/windowcontainer.h | 59 + src/gui/windowmenu.cpp | 285 +++ src/gui/windowmenu.h | 82 + src/gui/worldselectdialog.cpp | 139 ++ src/gui/worldselectdialog.h | 73 + src/guichanfwd.h | 101 + src/guild.cpp | 296 +++ src/guild.h | 199 ++ src/imageparticle.cpp | 91 + src/imageparticle.h | 61 + src/imagesprite.cpp | 54 + src/imagesprite.h | 79 + src/inventory.cpp | 225 ++ src/inventory.h | 160 ++ src/item.cpp | 94 + src/item.h | 167 ++ src/itemshortcut.cpp | 155 ++ src/itemshortcut.h | 146 ++ src/joystick.cpp | 150 ++ src/joystick.h | 114 + src/keyboardconfig.cpp | 449 ++++ src/keyboardconfig.h | 333 +++ src/listener.cpp | 43 + src/listener.h | 45 + src/localplayer.cpp | 3633 ++++++++++++++++++++++++++++ src/localplayer.h | 577 +++++ src/log.cpp | 213 ++ src/log.h | 109 + src/main.cpp | 261 ++ src/main.h | 109 + src/mana.rc | 23 + src/map.cpp | 1654 +++++++++++++ src/map.h | 611 +++++ src/mumblemanager.cpp | 273 +++ src/mumblemanager.h | 57 + src/net/adminhandler.h | 60 + src/net/beinghandler.h | 43 + src/net/buysellhandler.h | 47 + src/net/charhandler.cpp | 37 + src/net/charhandler.h | 112 + src/net/chathandler.h | 71 + src/net/download.cpp | 355 +++ src/net/download.h | 123 + src/net/gamehandler.h | 62 + src/net/generalhandler.h | 50 + src/net/guildhandler.h | 76 + src/net/inventoryhandler.h | 72 + src/net/logindata.h | 95 + src/net/loginhandler.h | 112 + src/net/manaserv/adminhandler.cpp | 93 + src/net/manaserv/adminhandler.h | 64 + src/net/manaserv/attributes.cpp | 411 ++++ src/net/manaserv/attributes.h | 72 + src/net/manaserv/beinghandler.cpp | 385 +++ src/net/manaserv/beinghandler.h | 73 + src/net/manaserv/buysellhandler.cpp | 132 + src/net/manaserv/buysellhandler.h | 57 + src/net/manaserv/charhandler.cpp | 406 ++++ src/net/manaserv/charhandler.h | 119 + src/net/manaserv/chathandler.cpp | 472 ++++ src/net/manaserv/chathandler.h | 139 ++ src/net/manaserv/connection.cpp | 113 + src/net/manaserv/connection.h | 89 + src/net/manaserv/defines.h | 77 + src/net/manaserv/effecthandler.cpp | 80 + src/net/manaserv/effecthandler.h | 44 + src/net/manaserv/gamehandler.cpp | 154 ++ src/net/manaserv/gamehandler.h | 74 + src/net/manaserv/generalhandler.cpp | 211 ++ src/net/manaserv/generalhandler.h | 78 + src/net/manaserv/guildhandler.cpp | 360 +++ src/net/manaserv/guildhandler.h | 84 + src/net/manaserv/internal.cpp | 27 + src/net/manaserv/internal.h | 30 + src/net/manaserv/inventoryhandler.cpp | 219 ++ src/net/manaserv/inventoryhandler.h | 109 + src/net/manaserv/itemhandler.cpp | 90 + src/net/manaserv/itemhandler.h | 40 + src/net/manaserv/loginhandler.cpp | 479 ++++ src/net/manaserv/loginhandler.h | 99 + src/net/manaserv/messagehandler.cpp | 36 + src/net/manaserv/messagehandler.h | 44 + src/net/manaserv/messagein.cpp | 62 + src/net/manaserv/messagein.h | 49 + src/net/manaserv/messageout.cpp | 65 + src/net/manaserv/messageout.h | 59 + src/net/manaserv/network.cpp | 178 ++ src/net/manaserv/network.h | 75 + src/net/manaserv/npchandler.cpp | 237 ++ src/net/manaserv/npchandler.h | 89 + src/net/manaserv/partyhandler.cpp | 197 ++ src/net/manaserv/partyhandler.h | 82 + src/net/manaserv/playerhandler.cpp | 440 ++++ src/net/manaserv/playerhandler.h | 85 + src/net/manaserv/protocol.h | 392 +++ src/net/manaserv/specialhandler.cpp | 70 + src/net/manaserv/specialhandler.h | 56 + src/net/manaserv/tradehandler.cpp | 237 ++ src/net/manaserv/tradehandler.h | 82 + src/net/messagehandler.h | 50 + src/net/messagein.cpp | 227 ++ src/net/messagein.h | 118 + src/net/messageout.cpp | 92 + src/net/messageout.h | 91 + src/net/net.cpp | 198 ++ src/net/net.h | 81 + src/net/npchandler.h | 66 + src/net/packetcounters.cpp | 128 + src/net/packetcounters.h | 55 + src/net/partyhandler.h | 82 + src/net/playerhandler.h | 75 + src/net/serverinfo.h | 117 + src/net/specialhandler.h | 45 + src/net/tmwa/adminhandler.cpp | 135 ++ src/net/tmwa/adminhandler.h | 69 + src/net/tmwa/beinghandler.cpp | 1075 ++++++++ src/net/tmwa/beinghandler.h | 53 + src/net/tmwa/buysellhandler.cpp | 231 ++ src/net/tmwa/buysellhandler.h | 59 + src/net/tmwa/charserverhandler.cpp | 386 +++ src/net/tmwa/charserverhandler.h | 87 + src/net/tmwa/chathandler.cpp | 552 +++++ src/net/tmwa/chathandler.h | 87 + src/net/tmwa/gamehandler.cpp | 196 ++ src/net/tmwa/gamehandler.h | 85 + src/net/tmwa/generalhandler.cpp | 285 +++ src/net/tmwa/generalhandler.h | 83 + src/net/tmwa/gui/guildtab.cpp | 150 ++ src/net/tmwa/gui/guildtab.h | 57 + src/net/tmwa/gui/partytab.cpp | 242 ++ src/net/tmwa/gui/partytab.h | 57 + src/net/tmwa/guildhandler.cpp | 780 ++++++ src/net/tmwa/guildhandler.h | 84 + src/net/tmwa/inventoryhandler.cpp | 609 +++++ src/net/tmwa/inventoryhandler.h | 177 ++ src/net/tmwa/itemhandler.cpp | 93 + src/net/tmwa/itemhandler.h | 40 + src/net/tmwa/loginhandler.cpp | 342 +++ src/net/tmwa/loginhandler.h | 105 + src/net/tmwa/messagehandler.cpp | 48 + src/net/tmwa/messagehandler.h | 59 + src/net/tmwa/messagein.cpp | 85 + src/net/tmwa/messagein.h | 52 + src/net/tmwa/messageout.cpp | 142 ++ src/net/tmwa/messageout.h | 65 + src/net/tmwa/network.cpp | 483 ++++ src/net/tmwa/network.h | 136 ++ src/net/tmwa/npchandler.cpp | 249 ++ src/net/tmwa/npchandler.h | 90 + src/net/tmwa/partyhandler.cpp | 547 +++++ src/net/tmwa/partyhandler.h | 85 + src/net/tmwa/playerhandler.cpp | 752 ++++++ src/net/tmwa/playerhandler.h | 74 + src/net/tmwa/protocol.h | 327 +++ src/net/tmwa/specialhandler.cpp | 273 +++ src/net/tmwa/specialhandler.h | 57 + src/net/tmwa/token.h | 43 + src/net/tmwa/tradehandler.cpp | 347 +++ src/net/tmwa/tradehandler.h | 65 + src/net/tradehandler.h | 67 + src/net/worldinfo.h | 39 + src/opengl1graphics.cpp | 603 +++++ src/opengl1graphics.h | 133 + src/openglgraphics.cpp | 950 ++++++++ src/openglgraphics.h | 146 ++ src/particle.cpp | 450 ++++ src/particle.h | 331 +++ src/particlecontainer.cpp | 189 ++ src/particlecontainer.h | 121 + src/particleemitter.cpp | 568 +++++ src/particleemitter.h | 153 ++ src/particleemitterprop.h | 116 + src/party.cpp | 258 ++ src/party.h | 168 ++ src/playerinfo.cpp | 330 +++ src/playerinfo.h | 238 ++ src/playerrelations.cpp | 496 ++++ src/playerrelations.h | 251 ++ src/position.cpp | 45 + src/position.h | 55 + src/properties.h | 126 + src/resources/action.cpp | 52 + src/resources/action.h | 51 + src/resources/ambientlayer.cpp | 126 + src/resources/ambientlayer.h | 59 + src/resources/ambientoverlay.cpp | 126 + src/resources/ambientoverlay.h | 60 + src/resources/animation.cpp | 46 + src/resources/animation.h | 90 + src/resources/beinginfo.cpp | 115 + src/resources/beinginfo.h | 161 ++ src/resources/colordb.cpp | 115 + src/resources/colordb.h | 51 + src/resources/dye.cpp | 320 +++ src/resources/dye.h | 107 + src/resources/emotedb.cpp | 222 ++ src/resources/emotedb.h | 64 + src/resources/image.cpp | 797 ++++++ src/resources/image.h | 308 +++ src/resources/imageloader.cpp | 114 + src/resources/imageloader.h | 69 + src/resources/imageset.cpp | 64 + src/resources/imageset.h | 72 + src/resources/imagewriter.cpp | 111 + src/resources/imagewriter.h | 31 + src/resources/itemdb.cpp | 463 ++++ src/resources/itemdb.h | 79 + src/resources/iteminfo.cpp | 66 + src/resources/iteminfo.h | 242 ++ src/resources/mapreader.cpp | 723 ++++++ src/resources/mapreader.h | 78 + src/resources/monsterdb.cpp | 199 ++ src/resources/monsterdb.h | 39 + src/resources/music.cpp | 84 + src/resources/music.h | 81 + src/resources/npcdb.cpp | 127 + src/resources/npcdb.h | 39 + src/resources/resource.cpp | 58 + src/resources/resource.h | 81 + src/resources/resourcemanager.cpp | 658 +++++ src/resources/resourcemanager.h | 265 ++ src/resources/soundeffect.cpp | 58 + src/resources/soundeffect.h | 75 + src/resources/specialdb.cpp | 132 + src/resources/specialdb.h | 72 + src/resources/spritedef.cpp | 339 +++ src/resources/spritedef.h | 176 ++ src/resources/wallpaper.cpp | 172 ++ src/resources/wallpaper.h | 50 + src/rotationalparticle.cpp | 82 + src/rotationalparticle.h | 48 + src/shopitem.cpp | 95 + src/shopitem.h | 140 ++ src/simpleanimation.cpp | 212 ++ src/simpleanimation.h | 86 + src/sound.cpp | 344 +++ src/sound.h | 130 + src/spellmanager.cpp | 346 +++ src/spellmanager.h | 76 + src/spellshortcut.cpp | 71 + src/spellshortcut.h | 91 + src/sprite.h | 110 + src/statuseffect.cpp | 205 ++ src/statuseffect.h | 112 + src/text.cpp | 194 ++ src/text.h | 113 + src/textcommand.cpp | 115 + src/textcommand.h | 173 ++ src/textmanager.cpp | 170 ++ src/textmanager.h | 82 + src/textparticle.cpp | 69 + src/textparticle.h | 54 + src/textrenderer.h | 78 + src/tileset.h | 53 + src/units.cpp | 248 ++ src/units.h | 46 + src/utils/base64.cpp | 170 ++ src/utils/base64.h | 36 + src/utils/copynpaste.cpp | 324 +++ src/utils/copynpaste.h | 33 + src/utils/dtor.h | 54 + src/utils/gettext.h | 44 + src/utils/mathutils.h | 114 + src/utils/mkdir.cpp | 118 + src/utils/mkdir.h | 26 + src/utils/mutex.h | 97 + src/utils/sha256.cpp | 294 +++ src/utils/sha256.h | 35 + src/utils/specialfolder.cpp | 78 + src/utils/specialfolder.h | 30 + src/utils/stringutils.cpp | 360 +++ src/utils/stringutils.h | 159 ++ src/utils/xml.cpp | 164 ++ src/utils/xml.h | 101 + src/variabledata.h | 113 + src/vector.cpp | 28 + src/vector.h | 199 ++ src/winver.h | 6 + src/winver.h.in | 6 + 583 files changed, 121893 insertions(+) create mode 100644 src/CMakeLists.txt create mode 100644 src/Makefile.am create mode 100644 src/SDLMain.h create mode 100644 src/SDLMain.m create mode 100644 src/SDL_gfxBlitFunc.h create mode 100644 src/actor.cpp create mode 100644 src/actor.h create mode 100644 src/actorsprite.cpp create mode 100644 src/actorsprite.h create mode 100644 src/actorspritelistener.h create mode 100644 src/actorspritemanager.cpp create mode 100644 src/actorspritemanager.h create mode 100644 src/animatedsprite.cpp create mode 100644 src/animatedsprite.h create mode 100644 src/animationparticle.cpp create mode 100644 src/animationparticle.h create mode 100644 src/avatar.cpp create mode 100644 src/avatar.h create mode 100644 src/being.cpp create mode 100644 src/being.h create mode 100644 src/channel.cpp create mode 100644 src/channel.h create mode 100644 src/channelmanager.cpp create mode 100644 src/channelmanager.h create mode 100644 src/chatlog.cpp create mode 100644 src/chatlog.h create mode 100644 src/client.cpp create mode 100644 src/client.h create mode 100644 src/commandhandler.cpp create mode 100644 src/commandhandler.h create mode 100644 src/compoundsprite.cpp create mode 100644 src/compoundsprite.h create mode 100644 src/configlistener.h create mode 100644 src/configuration.cpp create mode 100644 src/configuration.h create mode 100644 src/defaults.cpp create mode 100644 src/defaults.h create mode 100644 src/dropshortcut.cpp create mode 100644 src/dropshortcut.h create mode 100644 src/effectmanager.cpp create mode 100644 src/effectmanager.h create mode 100644 src/emoteshortcut.cpp create mode 100644 src/emoteshortcut.h create mode 100644 src/enet/ChangeLog create mode 100644 src/enet/LICENSE create mode 100644 src/enet/README create mode 100644 src/enet/callbacks.c create mode 100644 src/enet/callbacks.h create mode 100644 src/enet/compress.c create mode 100644 src/enet/design.txt create mode 100644 src/enet/enet.h create mode 100644 src/enet/host.c create mode 100644 src/enet/list.c create mode 100644 src/enet/list.h create mode 100644 src/enet/packet.c create mode 100644 src/enet/peer.c create mode 100644 src/enet/protocol.c create mode 100644 src/enet/protocol.h create mode 100644 src/enet/time.h create mode 100644 src/enet/types.h create mode 100644 src/enet/unix.c create mode 100644 src/enet/unix.h create mode 100644 src/enet/utility.h create mode 100644 src/enet/win32.c create mode 100644 src/enet/win32.h create mode 100644 src/equipment.h create mode 100644 src/event.cpp create mode 100644 src/event.h create mode 100644 src/filefilter.txt create mode 100644 src/filefilter_more.txt create mode 100644 src/flooritem.cpp create mode 100644 src/flooritem.h create mode 100644 src/game.cpp create mode 100644 src/game.h create mode 100644 src/graphics.cpp create mode 100644 src/graphics.h create mode 100644 src/gui/beingpopup.cpp create mode 100644 src/gui/beingpopup.h create mode 100644 src/gui/botcheckerwindow.cpp create mode 100644 src/gui/botcheckerwindow.h create mode 100644 src/gui/buy.cpp create mode 100644 src/gui/buy.h create mode 100644 src/gui/buysell.cpp create mode 100644 src/gui/buysell.h create mode 100644 src/gui/changeemaildialog.cpp create mode 100644 src/gui/changeemaildialog.h create mode 100644 src/gui/changepassworddialog.cpp create mode 100644 src/gui/changepassworddialog.h create mode 100644 src/gui/charcreatedialog.cpp create mode 100644 src/gui/charcreatedialog.h create mode 100644 src/gui/charselectdialog.cpp create mode 100644 src/gui/charselectdialog.h create mode 100644 src/gui/chat.cpp create mode 100644 src/gui/chat.h create mode 100644 src/gui/confirmdialog.cpp create mode 100644 src/gui/confirmdialog.h create mode 100644 src/gui/connectiondialog.cpp create mode 100644 src/gui/connectiondialog.h create mode 100644 src/gui/debugwindow.cpp create mode 100644 src/gui/debugwindow.h create mode 100644 src/gui/editdialog.cpp create mode 100644 src/gui/editdialog.h create mode 100644 src/gui/emotepopup.cpp create mode 100644 src/gui/emotepopup.h create mode 100644 src/gui/equipmentwindow.cpp create mode 100644 src/gui/equipmentwindow.h create mode 100644 src/gui/focushandler.cpp create mode 100644 src/gui/focushandler.h create mode 100644 src/gui/gui.cpp create mode 100644 src/gui/gui.h create mode 100644 src/gui/help.cpp create mode 100644 src/gui/help.h create mode 100644 src/gui/inventorywindow.cpp create mode 100644 src/gui/inventorywindow.h create mode 100644 src/gui/itemamount.cpp create mode 100644 src/gui/itemamount.h create mode 100644 src/gui/itempopup.cpp create mode 100644 src/gui/itempopup.h create mode 100644 src/gui/killstats.cpp create mode 100644 src/gui/killstats.h create mode 100644 src/gui/login.cpp create mode 100644 src/gui/login.h create mode 100644 src/gui/minimap.cpp create mode 100644 src/gui/minimap.h create mode 100644 src/gui/ministatus.cpp create mode 100644 src/gui/ministatus.h create mode 100644 src/gui/npcdialog.cpp create mode 100644 src/gui/npcdialog.h create mode 100644 src/gui/npcpostdialog.cpp create mode 100644 src/gui/npcpostdialog.h create mode 100644 src/gui/okdialog.cpp create mode 100644 src/gui/okdialog.h create mode 100644 src/gui/outfitwindow.cpp create mode 100644 src/gui/outfitwindow.h create mode 100644 src/gui/palette.cpp create mode 100644 src/gui/palette.h create mode 100644 src/gui/popupmenu.cpp create mode 100644 src/gui/popupmenu.h create mode 100644 src/gui/quitdialog.cpp create mode 100644 src/gui/quitdialog.h create mode 100644 src/gui/register.cpp create mode 100644 src/gui/register.h create mode 100644 src/gui/sdlinput.cpp create mode 100644 src/gui/sdlinput.h create mode 100644 src/gui/sell.cpp create mode 100644 src/gui/sell.h create mode 100644 src/gui/serverdialog.cpp create mode 100644 src/gui/serverdialog.h create mode 100644 src/gui/setup.cpp create mode 100644 src/gui/setup.h create mode 100644 src/gui/setup_audio.cpp create mode 100644 src/gui/setup_audio.h create mode 100644 src/gui/setup_chat.cpp create mode 100644 src/gui/setup_chat.h create mode 100644 src/gui/setup_colors.cpp create mode 100644 src/gui/setup_colors.h create mode 100644 src/gui/setup_joystick.cpp create mode 100644 src/gui/setup_joystick.h create mode 100644 src/gui/setup_keyboard.cpp create mode 100644 src/gui/setup_keyboard.h create mode 100644 src/gui/setup_other.cpp create mode 100644 src/gui/setup_other.h create mode 100644 src/gui/setup_players.cpp create mode 100644 src/gui/setup_players.h create mode 100644 src/gui/setup_theme.cpp create mode 100644 src/gui/setup_theme.h create mode 100644 src/gui/setup_video.cpp create mode 100644 src/gui/setup_video.h create mode 100644 src/gui/shopwindow.cpp create mode 100644 src/gui/shopwindow.h create mode 100644 src/gui/shortcutwindow.cpp create mode 100644 src/gui/shortcutwindow.h create mode 100644 src/gui/skilldialog.cpp create mode 100644 src/gui/skilldialog.h create mode 100644 src/gui/socialwindow.cpp create mode 100644 src/gui/socialwindow.h create mode 100644 src/gui/specialswindow.cpp create mode 100644 src/gui/specialswindow.h create mode 100644 src/gui/speechbubble.cpp create mode 100644 src/gui/speechbubble.h create mode 100644 src/gui/spellpopup.cpp create mode 100644 src/gui/spellpopup.h create mode 100644 src/gui/statuspopup.cpp create mode 100644 src/gui/statuspopup.h create mode 100644 src/gui/statuswindow.cpp create mode 100644 src/gui/statuswindow.h create mode 100644 src/gui/textcommandeditor.cpp create mode 100644 src/gui/textcommandeditor.h create mode 100644 src/gui/textdialog.cpp create mode 100644 src/gui/textdialog.h create mode 100644 src/gui/textpopup.cpp create mode 100644 src/gui/textpopup.h create mode 100644 src/gui/theme.cpp create mode 100644 src/gui/theme.h create mode 100644 src/gui/trade.cpp create mode 100644 src/gui/trade.h create mode 100644 src/gui/truetypefont.cpp create mode 100644 src/gui/truetypefont.h create mode 100644 src/gui/unregisterdialog.cpp create mode 100644 src/gui/unregisterdialog.h create mode 100644 src/gui/updatewindow.cpp create mode 100644 src/gui/updatewindow.h create mode 100644 src/gui/userpalette.cpp create mode 100644 src/gui/userpalette.h create mode 100644 src/gui/viewport.cpp create mode 100644 src/gui/viewport.h create mode 100644 src/gui/whoisonline.cpp create mode 100644 src/gui/whoisonline.h create mode 100644 src/gui/widgets/avatarlistbox.cpp create mode 100644 src/gui/widgets/avatarlistbox.h create mode 100644 src/gui/widgets/battletab.cpp create mode 100644 src/gui/widgets/battletab.h create mode 100644 src/gui/widgets/browserbox.cpp create mode 100644 src/gui/widgets/browserbox.h create mode 100644 src/gui/widgets/button.cpp create mode 100644 src/gui/widgets/button.h create mode 100644 src/gui/widgets/channeltab.cpp create mode 100644 src/gui/widgets/channeltab.h create mode 100644 src/gui/widgets/chattab.cpp create mode 100644 src/gui/widgets/chattab.h create mode 100644 src/gui/widgets/checkbox.cpp create mode 100644 src/gui/widgets/checkbox.h create mode 100644 src/gui/widgets/container.cpp create mode 100644 src/gui/widgets/container.h create mode 100644 src/gui/widgets/desktop.cpp create mode 100644 src/gui/widgets/desktop.h create mode 100644 src/gui/widgets/dropdown.cpp create mode 100644 src/gui/widgets/dropdown.h create mode 100644 src/gui/widgets/dropshortcutcontainer.cpp create mode 100644 src/gui/widgets/dropshortcutcontainer.h create mode 100644 src/gui/widgets/emoteshortcutcontainer.cpp create mode 100644 src/gui/widgets/emoteshortcutcontainer.h create mode 100644 src/gui/widgets/flowcontainer.cpp create mode 100644 src/gui/widgets/flowcontainer.h create mode 100644 src/gui/widgets/icon.cpp create mode 100644 src/gui/widgets/icon.h create mode 100644 src/gui/widgets/inttextfield.cpp create mode 100644 src/gui/widgets/inttextfield.h create mode 100644 src/gui/widgets/itemcontainer.cpp create mode 100644 src/gui/widgets/itemcontainer.h create mode 100644 src/gui/widgets/itemlinkhandler.cpp create mode 100644 src/gui/widgets/itemlinkhandler.h create mode 100644 src/gui/widgets/itemshortcutcontainer.cpp create mode 100644 src/gui/widgets/itemshortcutcontainer.h create mode 100644 src/gui/widgets/label.cpp create mode 100644 src/gui/widgets/label.h create mode 100644 src/gui/widgets/layout.cpp create mode 100644 src/gui/widgets/layout.h create mode 100644 src/gui/widgets/layouthelper.cpp create mode 100644 src/gui/widgets/layouthelper.h create mode 100644 src/gui/widgets/linkhandler.h create mode 100644 src/gui/widgets/listbox.cpp create mode 100644 src/gui/widgets/listbox.h create mode 100644 src/gui/widgets/passwordfield.cpp create mode 100644 src/gui/widgets/passwordfield.h create mode 100644 src/gui/widgets/playerbox.cpp create mode 100644 src/gui/widgets/playerbox.h create mode 100644 src/gui/widgets/popup.cpp create mode 100644 src/gui/widgets/popup.h create mode 100644 src/gui/widgets/progressbar.cpp create mode 100644 src/gui/widgets/progressbar.h create mode 100644 src/gui/widgets/progressindicator.cpp create mode 100644 src/gui/widgets/progressindicator.h create mode 100644 src/gui/widgets/radiobutton.cpp create mode 100644 src/gui/widgets/radiobutton.h create mode 100644 src/gui/widgets/resizegrip.cpp create mode 100644 src/gui/widgets/resizegrip.h create mode 100644 src/gui/widgets/scrollarea.cpp create mode 100644 src/gui/widgets/scrollarea.h create mode 100644 src/gui/widgets/setuptab.cpp create mode 100644 src/gui/widgets/setuptab.h create mode 100644 src/gui/widgets/shopitems.cpp create mode 100644 src/gui/widgets/shopitems.h create mode 100644 src/gui/widgets/shoplistbox.cpp create mode 100644 src/gui/widgets/shoplistbox.h create mode 100644 src/gui/widgets/shortcutcontainer.cpp create mode 100644 src/gui/widgets/shortcutcontainer.h create mode 100644 src/gui/widgets/slider.cpp create mode 100644 src/gui/widgets/slider.h create mode 100644 src/gui/widgets/spellshortcutcontainer.cpp create mode 100644 src/gui/widgets/spellshortcutcontainer.h create mode 100644 src/gui/widgets/tab.cpp create mode 100644 src/gui/widgets/tab.h create mode 100644 src/gui/widgets/tabbedarea.cpp create mode 100644 src/gui/widgets/tabbedarea.h create mode 100644 src/gui/widgets/table.cpp create mode 100644 src/gui/widgets/table.h create mode 100644 src/gui/widgets/tablemodel.cpp create mode 100644 src/gui/widgets/tablemodel.h create mode 100644 src/gui/widgets/textbox.cpp create mode 100644 src/gui/widgets/textbox.h create mode 100644 src/gui/widgets/textfield.cpp create mode 100644 src/gui/widgets/textfield.h create mode 100644 src/gui/widgets/textpreview.cpp create mode 100644 src/gui/widgets/textpreview.h create mode 100644 src/gui/widgets/tradetab.cpp create mode 100644 src/gui/widgets/tradetab.h create mode 100644 src/gui/widgets/vertcontainer.cpp create mode 100644 src/gui/widgets/vertcontainer.h create mode 100644 src/gui/widgets/whispertab.cpp create mode 100644 src/gui/widgets/whispertab.h create mode 100644 src/gui/widgets/window.cpp create mode 100644 src/gui/widgets/window.h create mode 100644 src/gui/widgets/windowcontainer.cpp create mode 100644 src/gui/widgets/windowcontainer.h create mode 100644 src/gui/windowmenu.cpp create mode 100644 src/gui/windowmenu.h create mode 100644 src/gui/worldselectdialog.cpp create mode 100644 src/gui/worldselectdialog.h create mode 100644 src/guichanfwd.h create mode 100644 src/guild.cpp create mode 100644 src/guild.h create mode 100644 src/imageparticle.cpp create mode 100644 src/imageparticle.h create mode 100644 src/imagesprite.cpp create mode 100644 src/imagesprite.h create mode 100644 src/inventory.cpp create mode 100644 src/inventory.h create mode 100644 src/item.cpp create mode 100644 src/item.h create mode 100644 src/itemshortcut.cpp create mode 100644 src/itemshortcut.h create mode 100644 src/joystick.cpp create mode 100644 src/joystick.h create mode 100644 src/keyboardconfig.cpp create mode 100644 src/keyboardconfig.h create mode 100644 src/listener.cpp create mode 100644 src/listener.h create mode 100644 src/localplayer.cpp create mode 100644 src/localplayer.h create mode 100644 src/log.cpp create mode 100644 src/log.h create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 src/mana.rc create mode 100644 src/map.cpp create mode 100644 src/map.h create mode 100644 src/mumblemanager.cpp create mode 100644 src/mumblemanager.h create mode 100644 src/net/adminhandler.h create mode 100644 src/net/beinghandler.h create mode 100644 src/net/buysellhandler.h create mode 100644 src/net/charhandler.cpp create mode 100644 src/net/charhandler.h create mode 100644 src/net/chathandler.h create mode 100644 src/net/download.cpp create mode 100644 src/net/download.h create mode 100644 src/net/gamehandler.h create mode 100644 src/net/generalhandler.h create mode 100644 src/net/guildhandler.h create mode 100644 src/net/inventoryhandler.h create mode 100644 src/net/logindata.h create mode 100644 src/net/loginhandler.h create mode 100644 src/net/manaserv/adminhandler.cpp create mode 100644 src/net/manaserv/adminhandler.h create mode 100644 src/net/manaserv/attributes.cpp create mode 100644 src/net/manaserv/attributes.h create mode 100644 src/net/manaserv/beinghandler.cpp create mode 100644 src/net/manaserv/beinghandler.h create mode 100644 src/net/manaserv/buysellhandler.cpp create mode 100644 src/net/manaserv/buysellhandler.h create mode 100644 src/net/manaserv/charhandler.cpp create mode 100644 src/net/manaserv/charhandler.h create mode 100644 src/net/manaserv/chathandler.cpp create mode 100644 src/net/manaserv/chathandler.h create mode 100644 src/net/manaserv/connection.cpp create mode 100644 src/net/manaserv/connection.h create mode 100644 src/net/manaserv/defines.h create mode 100644 src/net/manaserv/effecthandler.cpp create mode 100644 src/net/manaserv/effecthandler.h create mode 100644 src/net/manaserv/gamehandler.cpp create mode 100644 src/net/manaserv/gamehandler.h create mode 100644 src/net/manaserv/generalhandler.cpp create mode 100644 src/net/manaserv/generalhandler.h create mode 100644 src/net/manaserv/guildhandler.cpp create mode 100644 src/net/manaserv/guildhandler.h create mode 100644 src/net/manaserv/internal.cpp create mode 100644 src/net/manaserv/internal.h create mode 100644 src/net/manaserv/inventoryhandler.cpp create mode 100644 src/net/manaserv/inventoryhandler.h create mode 100644 src/net/manaserv/itemhandler.cpp create mode 100644 src/net/manaserv/itemhandler.h create mode 100644 src/net/manaserv/loginhandler.cpp create mode 100644 src/net/manaserv/loginhandler.h create mode 100644 src/net/manaserv/messagehandler.cpp create mode 100644 src/net/manaserv/messagehandler.h create mode 100644 src/net/manaserv/messagein.cpp create mode 100644 src/net/manaserv/messagein.h create mode 100644 src/net/manaserv/messageout.cpp create mode 100644 src/net/manaserv/messageout.h create mode 100644 src/net/manaserv/network.cpp create mode 100644 src/net/manaserv/network.h create mode 100644 src/net/manaserv/npchandler.cpp create mode 100644 src/net/manaserv/npchandler.h create mode 100644 src/net/manaserv/partyhandler.cpp create mode 100644 src/net/manaserv/partyhandler.h create mode 100644 src/net/manaserv/playerhandler.cpp create mode 100644 src/net/manaserv/playerhandler.h create mode 100644 src/net/manaserv/protocol.h create mode 100644 src/net/manaserv/specialhandler.cpp create mode 100644 src/net/manaserv/specialhandler.h create mode 100644 src/net/manaserv/tradehandler.cpp create mode 100644 src/net/manaserv/tradehandler.h create mode 100644 src/net/messagehandler.h create mode 100644 src/net/messagein.cpp create mode 100644 src/net/messagein.h create mode 100644 src/net/messageout.cpp create mode 100644 src/net/messageout.h create mode 100644 src/net/net.cpp create mode 100644 src/net/net.h create mode 100644 src/net/npchandler.h create mode 100644 src/net/packetcounters.cpp create mode 100644 src/net/packetcounters.h create mode 100644 src/net/partyhandler.h create mode 100644 src/net/playerhandler.h create mode 100644 src/net/serverinfo.h create mode 100644 src/net/specialhandler.h create mode 100644 src/net/tmwa/adminhandler.cpp create mode 100644 src/net/tmwa/adminhandler.h create mode 100644 src/net/tmwa/beinghandler.cpp create mode 100644 src/net/tmwa/beinghandler.h create mode 100644 src/net/tmwa/buysellhandler.cpp create mode 100644 src/net/tmwa/buysellhandler.h create mode 100644 src/net/tmwa/charserverhandler.cpp create mode 100644 src/net/tmwa/charserverhandler.h create mode 100644 src/net/tmwa/chathandler.cpp create mode 100644 src/net/tmwa/chathandler.h create mode 100644 src/net/tmwa/gamehandler.cpp create mode 100644 src/net/tmwa/gamehandler.h create mode 100644 src/net/tmwa/generalhandler.cpp create mode 100644 src/net/tmwa/generalhandler.h create mode 100644 src/net/tmwa/gui/guildtab.cpp create mode 100644 src/net/tmwa/gui/guildtab.h create mode 100644 src/net/tmwa/gui/partytab.cpp create mode 100644 src/net/tmwa/gui/partytab.h create mode 100644 src/net/tmwa/guildhandler.cpp create mode 100644 src/net/tmwa/guildhandler.h create mode 100644 src/net/tmwa/inventoryhandler.cpp create mode 100644 src/net/tmwa/inventoryhandler.h create mode 100644 src/net/tmwa/itemhandler.cpp create mode 100644 src/net/tmwa/itemhandler.h create mode 100644 src/net/tmwa/loginhandler.cpp create mode 100644 src/net/tmwa/loginhandler.h create mode 100644 src/net/tmwa/messagehandler.cpp create mode 100644 src/net/tmwa/messagehandler.h create mode 100644 src/net/tmwa/messagein.cpp create mode 100644 src/net/tmwa/messagein.h create mode 100644 src/net/tmwa/messageout.cpp create mode 100644 src/net/tmwa/messageout.h create mode 100644 src/net/tmwa/network.cpp create mode 100644 src/net/tmwa/network.h create mode 100644 src/net/tmwa/npchandler.cpp create mode 100644 src/net/tmwa/npchandler.h create mode 100644 src/net/tmwa/partyhandler.cpp create mode 100644 src/net/tmwa/partyhandler.h create mode 100644 src/net/tmwa/playerhandler.cpp create mode 100644 src/net/tmwa/playerhandler.h create mode 100644 src/net/tmwa/protocol.h create mode 100644 src/net/tmwa/specialhandler.cpp create mode 100644 src/net/tmwa/specialhandler.h create mode 100644 src/net/tmwa/token.h create mode 100644 src/net/tmwa/tradehandler.cpp create mode 100644 src/net/tmwa/tradehandler.h create mode 100644 src/net/tradehandler.h create mode 100644 src/net/worldinfo.h create mode 100644 src/opengl1graphics.cpp create mode 100644 src/opengl1graphics.h create mode 100644 src/openglgraphics.cpp create mode 100644 src/openglgraphics.h create mode 100644 src/particle.cpp create mode 100644 src/particle.h create mode 100644 src/particlecontainer.cpp create mode 100644 src/particlecontainer.h create mode 100644 src/particleemitter.cpp create mode 100644 src/particleemitter.h create mode 100644 src/particleemitterprop.h create mode 100644 src/party.cpp create mode 100644 src/party.h create mode 100644 src/playerinfo.cpp create mode 100644 src/playerinfo.h create mode 100644 src/playerrelations.cpp create mode 100644 src/playerrelations.h create mode 100644 src/position.cpp create mode 100644 src/position.h create mode 100644 src/properties.h create mode 100644 src/resources/action.cpp create mode 100644 src/resources/action.h create mode 100644 src/resources/ambientlayer.cpp create mode 100644 src/resources/ambientlayer.h create mode 100644 src/resources/ambientoverlay.cpp create mode 100644 src/resources/ambientoverlay.h create mode 100644 src/resources/animation.cpp create mode 100644 src/resources/animation.h create mode 100644 src/resources/beinginfo.cpp create mode 100644 src/resources/beinginfo.h create mode 100644 src/resources/colordb.cpp create mode 100644 src/resources/colordb.h create mode 100644 src/resources/dye.cpp create mode 100644 src/resources/dye.h create mode 100644 src/resources/emotedb.cpp create mode 100644 src/resources/emotedb.h create mode 100644 src/resources/image.cpp create mode 100644 src/resources/image.h create mode 100644 src/resources/imageloader.cpp create mode 100644 src/resources/imageloader.h create mode 100644 src/resources/imageset.cpp create mode 100644 src/resources/imageset.h create mode 100644 src/resources/imagewriter.cpp create mode 100644 src/resources/imagewriter.h create mode 100644 src/resources/itemdb.cpp create mode 100644 src/resources/itemdb.h create mode 100644 src/resources/iteminfo.cpp create mode 100644 src/resources/iteminfo.h create mode 100644 src/resources/mapreader.cpp create mode 100644 src/resources/mapreader.h create mode 100644 src/resources/monsterdb.cpp create mode 100644 src/resources/monsterdb.h create mode 100644 src/resources/music.cpp create mode 100644 src/resources/music.h create mode 100644 src/resources/npcdb.cpp create mode 100644 src/resources/npcdb.h create mode 100644 src/resources/resource.cpp create mode 100644 src/resources/resource.h create mode 100644 src/resources/resourcemanager.cpp create mode 100644 src/resources/resourcemanager.h create mode 100644 src/resources/soundeffect.cpp create mode 100644 src/resources/soundeffect.h create mode 100644 src/resources/specialdb.cpp create mode 100644 src/resources/specialdb.h create mode 100644 src/resources/spritedef.cpp create mode 100644 src/resources/spritedef.h create mode 100644 src/resources/wallpaper.cpp create mode 100644 src/resources/wallpaper.h create mode 100644 src/rotationalparticle.cpp create mode 100644 src/rotationalparticle.h create mode 100644 src/shopitem.cpp create mode 100644 src/shopitem.h create mode 100644 src/simpleanimation.cpp create mode 100644 src/simpleanimation.h create mode 100644 src/sound.cpp create mode 100644 src/sound.h create mode 100644 src/spellmanager.cpp create mode 100644 src/spellmanager.h create mode 100644 src/spellshortcut.cpp create mode 100644 src/spellshortcut.h create mode 100644 src/sprite.h create mode 100644 src/statuseffect.cpp create mode 100644 src/statuseffect.h create mode 100644 src/text.cpp create mode 100644 src/text.h create mode 100644 src/textcommand.cpp create mode 100644 src/textcommand.h create mode 100644 src/textmanager.cpp create mode 100644 src/textmanager.h create mode 100644 src/textparticle.cpp create mode 100644 src/textparticle.h create mode 100644 src/textrenderer.h create mode 100644 src/tileset.h create mode 100644 src/units.cpp create mode 100644 src/units.h create mode 100644 src/utils/base64.cpp create mode 100644 src/utils/base64.h create mode 100644 src/utils/copynpaste.cpp create mode 100644 src/utils/copynpaste.h create mode 100644 src/utils/dtor.h create mode 100644 src/utils/gettext.h create mode 100644 src/utils/mathutils.h create mode 100644 src/utils/mkdir.cpp create mode 100644 src/utils/mkdir.h create mode 100644 src/utils/mutex.h create mode 100644 src/utils/sha256.cpp create mode 100644 src/utils/sha256.h create mode 100644 src/utils/specialfolder.cpp create mode 100644 src/utils/specialfolder.h create mode 100644 src/utils/stringutils.cpp create mode 100644 src/utils/stringutils.h create mode 100644 src/utils/xml.cpp create mode 100644 src/utils/xml.h create mode 100644 src/variabledata.h create mode 100644 src/vector.cpp create mode 100644 src/vector.h create mode 100644 src/winver.h create mode 100644 src/winver.h.in (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..bb7b6e4b2 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,723 @@ +FIND_PACKAGE(SDL REQUIRED) +FIND_PACKAGE(SDL_image REQUIRED) +FIND_PACKAGE(SDL_mixer REQUIRED) +FIND_PACKAGE(SDL_net REQUIRED) +FIND_PACKAGE(SDL_ttf REQUIRED) +FIND_PACKAGE(SDL_gfx REQUIRED) +FIND_PACKAGE(CURL REQUIRED) +FIND_PACKAGE(LibXml2 REQUIRED) +FIND_PACKAGE(PhysFS REQUIRED) +FIND_PACKAGE(PNG REQUIRED) +FIND_PACKAGE(Gettext REQUIRED) + +IF (CMAKE_COMPILER_IS_GNUCXX) + # Help getting compilation warnings + SET(CMAKE_CXX_FLAGS "-Wall") + IF (WIN32) + # This includes enough debug information to get something useful + # from Dr. Mingw while keeping binary size down. Almost useless + # with gdb, though. + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -ggdb0 -gstabs2") + ENDIF() +ENDIF() + +IF (POLICY CMP0015) + CMAKE_POLICY(SET CMP0015 OLD) +ENDIF() + +SET(FLAGS "-DPACKAGE_VERSION=\\\"${VERSION}\\\"") +SET(FLAGS "${FLAGS} -DPKG_DATADIR=\\\"${PKG_DATADIR}/\\\"") +SET(FLAGS "${FLAGS} -DLOCALEDIR=\\\"${LOCALEDIR}/\\\"") + +IF (ENABLE_NLS) + SET(FLAGS "${FLAGS} -DENABLE_NLS=1") +ENDIF() + +IF (CMAKE_BUILD_TYPE) + STRING(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + IF(CMAKE_BUILD_TYPE_TOLOWER MATCHES debug OR + CMAKE_BUILD_TYPE_TOLOWER MATCHES relwithdebinfo) + SET(FLAGS "${FLAGS} -DDEBUG") + ENDIF() +ENDIF() + +IF (WIN32) + SET(EXTRA_LIBRARIES ws2_32 winmm) + FIND_PACKAGE(LibIntl REQUIRED) +ELSEIF (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + FIND_PACKAGE(LibIntl REQUIRED) +ELSEIF (CMAKE_SYSTEM_NAME STREQUAL SunOS) + # explicit linking to libintl is required on Solaris + SET(EXTRA_LIBRARIES intl) +ENDIF() + +SET(GUICHAN_COMPONENTS "SDL") +FIND_PACKAGE(Guichan REQUIRED ${GUICHAN_COMPONENTS}) + +IF (WITH_OPENGL) + FIND_PACKAGE(OpenGL REQUIRED) + INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR}) + SET(FLAGS "${FLAGS} -DUSE_OPENGL") +ENDIF (WITH_OPENGL) + +IF (USE_X11) + FIND_PACKAGE(X11 REQUIRED) + INCLUDE_DIRECTORIES(${X11_INCLUDE_DIR}) + SET(FLAGS "${FLAGS} -DUSE_X11") +ENDIF (USE_X11) + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${SDL_INCLUDE_DIR} + ${SDLIMAGE_INCLUDE_DIR} + ${SDLMIXER_INCLUDE_DIR} + ${SDLNET_INCLUDE_DIR} + ${SDLTTF_INCLUDE_DIR} + ${SDLGFX_INCLUDE_DIR} + ${PNG_INCLUDE_DIR} + ${PHYSFS_INCLUDE_DIR} + ${CURL_INCLUDE_DIR} + ${LIBXML2_INCLUDE_DIR} + ${GUICHAN_INCLUDE_DIR} + ) + +# enable rc-handling with mingw +# most likely this part can be kicked out with some later cmake version +IF (MINGW) + FIND_PATH(MINGW_INCLUDE_DIR windows.h $ENV{INCLUDE}) + IF (MINGW_INCLUDE_DIR) + MESSAGE(STATUS "Found mingw headers: ${MINGW_INCLUDE_DIR}") + INCLUDE_DIRECTORIES(${MINGW_INCLUDE_DIR}) + ELSE() + MESSAGE(FATAL_ERROR "Unable to find mingw headers. Required for windres") + ENDIF() + SET(CMAKE_RC_COMPILER_INIT windres) + ENABLE_LANGUAGE(RC) + SET(CMAKE_RC_COMPILE_OBJECT + " -O coff -o ") +ENDIF() + +# Fix some stuff that gets not hidden by mainline modules +MARK_AS_ADVANCED(PHYSFS_INCLUDE_DIR) +MARK_AS_ADVANCED(PHYSFS_LIBRARY) +MARK_AS_ADVANCED(SDLIMAGE_INCLUDE_DIR) +MARK_AS_ADVANCED(SDLIMAGE_LIBRARY) +MARK_AS_ADVANCED(SDLMAIN_LIBRARY) +MARK_AS_ADVANCED(SDLMIXER_INCLUDE_DIR) +MARK_AS_ADVANCED(SDLMIXER_LIBRARY) +MARK_AS_ADVANCED(SDLNET_INCLUDE_DIR) +MARK_AS_ADVANCED(SDLNET_LIBRARY) +MARK_AS_ADVANCED(SDL_INCLUDE_DIR) +MARK_AS_ADVANCED(SDL_LIBRARY) + +SET(SRCS + enet/callbacks.c + enet/callbacks.h + enet/compress.c + enet/enet.h + enet/host.c + enet/list.c + enet/list.h + enet/packet.c + enet/peer.c + enet/protocol.c + enet/protocol.h + enet/time.h + enet/types.h + enet/unix.c + enet/unix.h + enet/utility.h + enet/win32.c + enet/win32.h + gui/widgets/avatarlistbox.cpp + gui/widgets/avatarlistbox.h + gui/widgets/battletab.cpp + gui/widgets/battletab.h + gui/widgets/browserbox.cpp + gui/widgets/browserbox.h + gui/widgets/button.cpp + gui/widgets/button.h + gui/widgets/channeltab.cpp + gui/widgets/channeltab.h + gui/widgets/chattab.cpp + gui/widgets/chattab.h + gui/widgets/checkbox.cpp + gui/widgets/checkbox.h + gui/widgets/container.cpp + gui/widgets/container.h + gui/widgets/desktop.cpp + gui/widgets/desktop.h + gui/widgets/dropdown.cpp + gui/widgets/dropdown.h + gui/widgets/emoteshortcutcontainer.cpp + gui/widgets/emoteshortcutcontainer.h + gui/widgets/flowcontainer.cpp + gui/widgets/flowcontainer.h + gui/widgets/icon.cpp + gui/widgets/icon.h + gui/widgets/inttextfield.cpp + gui/widgets/inttextfield.h + gui/widgets/itemcontainer.cpp + gui/widgets/itemcontainer.h + gui/widgets/itemlinkhandler.cpp + gui/widgets/itemlinkhandler.h + gui/widgets/dropshortcutcontainer.cpp + gui/widgets/dropshortcutcontainer.h + gui/widgets/itemshortcutcontainer.cpp + gui/widgets/itemshortcutcontainer.h + gui/widgets/spellshortcutcontainer.cpp + gui/widgets/spellshortcutcontainer.h + gui/widgets/label.cpp + gui/widgets/label.h + gui/widgets/layout.cpp + gui/widgets/layout.h + gui/widgets/layouthelper.cpp + gui/widgets/layouthelper.h + gui/widgets/linkhandler.h + gui/widgets/listbox.cpp + gui/widgets/listbox.h + gui/widgets/passwordfield.cpp + gui/widgets/passwordfield.h + gui/widgets/playerbox.cpp + gui/widgets/playerbox.h + gui/widgets/popup.cpp + gui/widgets/popup.h + gui/widgets/progressbar.cpp + gui/widgets/progressbar.h + gui/widgets/progressindicator.cpp + gui/widgets/progressindicator.h + gui/widgets/radiobutton.cpp + gui/widgets/radiobutton.h + gui/widgets/resizegrip.cpp + gui/widgets/resizegrip.h + gui/widgets/scrollarea.cpp + gui/widgets/scrollarea.h + gui/widgets/setuptab.cpp + gui/widgets/setuptab.h + gui/widgets/shopitems.cpp + gui/widgets/shopitems.h + gui/widgets/shoplistbox.cpp + gui/widgets/shoplistbox.h + gui/widgets/shortcutcontainer.cpp + gui/widgets/shortcutcontainer.h + gui/widgets/slider.cpp + gui/widgets/slider.h + gui/widgets/tab.cpp + gui/widgets/tab.h + gui/widgets/tabbedarea.cpp + gui/widgets/tabbedarea.h + gui/widgets/table.cpp + gui/widgets/table.h + gui/widgets/tablemodel.cpp + gui/widgets/tablemodel.h + gui/widgets/textbox.cpp + gui/widgets/textbox.h + gui/widgets/textfield.cpp + gui/widgets/textfield.h + gui/widgets/textpreview.cpp + gui/widgets/textpreview.h + gui/widgets/tradetab.cpp + gui/widgets/tradetab.h + gui/widgets/vertcontainer.cpp + gui/widgets/vertcontainer.h + gui/widgets/whispertab.cpp + gui/widgets/whispertab.h + gui/widgets/window.cpp + gui/widgets/window.h + gui/widgets/windowcontainer.cpp + gui/widgets/windowcontainer.h + gui/beingpopup.cpp + gui/beingpopup.h + gui/buy.cpp + gui/buy.h + gui/buysell.cpp + gui/buysell.h + gui/changeemaildialog.cpp + gui/changeemaildialog.h + gui/changepassworddialog.cpp + gui/changepassworddialog.h + gui/charselectdialog.cpp + gui/charselectdialog.h + gui/charcreatedialog.cpp + gui/charcreatedialog.h + gui/chat.cpp + gui/chat.h + gui/confirmdialog.cpp + gui/confirmdialog.h + gui/connectiondialog.cpp + gui/connectiondialog.h + gui/debugwindow.cpp + gui/debugwindow.h + gui/emotepopup.cpp + gui/emotepopup.h + gui/equipmentwindow.cpp + gui/equipmentwindow.h + gui/focushandler.cpp + gui/focushandler.h + gui/gui.cpp + gui/gui.h + gui/help.cpp + gui/help.h + gui/inventorywindow.cpp + gui/inventorywindow.h + gui/itempopup.cpp + gui/itempopup.h + gui/spellpopup.cpp + gui/spellpopup.h + gui/statuspopup.cpp + gui/statuspopup.h + gui/killstats.cpp + gui/killstats.h + gui/itemamount.cpp + gui/itemamount.h + gui/login.cpp + gui/login.h + gui/minimap.cpp + gui/minimap.h + gui/ministatus.cpp + gui/ministatus.h + gui/npcdialog.cpp + gui/npcdialog.h + gui/npcpostdialog.cpp + gui/npcpostdialog.h + gui/okdialog.cpp + gui/okdialog.h + gui/editdialog.cpp + gui/editdialog.h + gui/outfitwindow.cpp + gui/outfitwindow.h + gui/botcheckerwindow.cpp + gui/botcheckerwindow.h + gui/textcommandeditor.cpp + gui/textcommandeditor.h + gui/palette.cpp + gui/palette.h + gui/popupmenu.cpp + gui/popupmenu.h + gui/quitdialog.cpp + gui/quitdialog.h + gui/register.cpp + gui/register.h + gui/sdlinput.cpp + gui/sdlinput.h + gui/sell.cpp + gui/sell.h + gui/serverdialog.cpp + gui/serverdialog.h + gui/setup.cpp + gui/setup.h + gui/setup_audio.cpp + gui/setup_audio.h + gui/setup_colors.cpp + gui/setup_colors.h + gui/setup_joystick.cpp + gui/setup_joystick.h + gui/setup_other.cpp + gui/setup_other.h + gui/setup_theme.cpp + gui/setup_theme.h + gui/setup_chat.cpp + gui/setup_chat.h + gui/setup_keyboard.cpp + gui/setup_keyboard.h + gui/setup_players.cpp + gui/setup_players.h + gui/setup_video.cpp + gui/setup_video.h + gui/shopwindow.cpp + gui/shopwindow.h + gui/shortcutwindow.cpp + gui/shortcutwindow.h + gui/skilldialog.cpp + gui/skilldialog.h + gui/socialwindow.cpp + gui/socialwindow.h + gui/speechbubble.cpp + gui/speechbubble.h + gui/specialswindow.cpp + gui/specialswindow.h + gui/statuswindow.cpp + gui/statuswindow.h + gui/textdialog.cpp + gui/textdialog.h + gui/textpopup.cpp + gui/textpopup.h + gui/theme.cpp + gui/theme.h + gui/trade.cpp + gui/trade.h + gui/truetypefont.cpp + gui/truetypefont.h + gui/unregisterdialog.cpp + gui/unregisterdialog.h + gui/updatewindow.cpp + gui/updatewindow.h + gui/userpalette.cpp + gui/userpalette.h + gui/viewport.cpp + gui/viewport.h + gui/whoisonline.cpp + gui/whoisonline.h + gui/windowmenu.cpp + gui/windowmenu.h + gui/worldselectdialog.cpp + gui/worldselectdialog.h + net/adminhandler.h + net/charhandler.cpp + net/charhandler.h + net/chathandler.h + net/download.cpp + net/download.h + net/gamehandler.h + net/generalhandler.h + net/guildhandler.h + net/inventoryhandler.h + net/logindata.h + net/loginhandler.h + net/messagehandler.h + net/messagein.cpp + net/messagein.h + net/messageout.cpp + net/messageout.h + net/npchandler.h + net/net.cpp + net/net.h + net/partyhandler.h + net/playerhandler.h + net/serverinfo.h + net/specialhandler.h + net/tradehandler.h + net/worldinfo.h + net/packetcounters.cpp + net/packetcounters.h + resources/action.cpp + resources/action.h + resources/ambientlayer.cpp + resources/ambientlayer.h + resources/ambientoverlay.cpp + resources/ambientoverlay.h + resources/animation.cpp + resources/animation.h + resources/beinginfo.cpp + resources/beinginfo.h + resources/colordb.cpp + resources/colordb.h + resources/dye.cpp + resources/dye.h + resources/emotedb.cpp + resources/emotedb.h + resources/image.cpp + resources/image.h + resources/imageloader.cpp + resources/imageloader.h + resources/imageset.h + resources/imageset.cpp + resources/imagewriter.cpp + resources/imagewriter.h + resources/itemdb.cpp + resources/itemdb.h + resources/iteminfo.h + resources/iteminfo.cpp + resources/mapreader.cpp + resources/mapreader.h + resources/monsterdb.cpp + resources/monsterdb.h + resources/music.cpp + resources/music.h + resources/npcdb.cpp + resources/npcdb.h + resources/resource.cpp + resources/resource.h + resources/resourcemanager.cpp + resources/resourcemanager.h + resources/soundeffect.h + resources/soundeffect.cpp + resources/specialdb.cpp + resources/specialdb.h + resources/spritedef.h + resources/spritedef.cpp + resources/wallpaper.cpp + resources/wallpaper.h + utils/base64.cpp + utils/base64.h + utils/copynpaste.cpp + utils/copynpaste.h + utils/dtor.h + utils/gettext.h + utils/mathutils.h + utils/sha256.cpp + utils/sha256.h + utils/stringutils.cpp + utils/stringutils.h + utils/mutex.h + utils/mkdir.cpp + utils/mkdir.h + utils/xml.cpp + utils/xml.h + actor.cpp + actor.h + actorsprite.cpp + actorsprite.h + actorspritelistener.h + actorspritemanager.cpp + actorspritemanager.h + animatedsprite.cpp + animatedsprite.h + animationparticle.cpp + animationparticle.h + avatar.cpp + avatar.h + being.cpp + being.h + spellmanager.cpp + spellmanager.h + chatlog.cpp + chatlog.h + client.cpp + client.h + channel.cpp + channel.h + channelmanager.cpp + channelmanager.h + commandhandler.cpp + commandhandler.h + compoundsprite.cpp + compoundsprite.h + configlistener.h + configuration.cpp + configuration.h + defaults.cpp + defaults.h + effectmanager.cpp + effectmanager.h + emoteshortcut.cpp + emoteshortcut.h + equipment.h + event.cpp + event.h + flooritem.cpp + flooritem.h + game.cpp + game.h + graphics.cpp + graphics.h + guichanfwd.h + guild.cpp + guild.h + imageparticle.cpp + imageparticle.h + imagesprite.cpp + imagesprite.h + inventory.cpp + inventory.h + item.cpp + item.h + itemshortcut.cpp + itemshortcut.h + dropshortcut.cpp + dropshortcut.h + spellshortcut.cpp + spellshortcut.h + textcommand.cpp + textcommand.h + joystick.cpp + joystick.h + keyboardconfig.cpp + keyboardconfig.h + listener.cpp + listener.h + localplayer.cpp + localplayer.h + log.cpp + log.h + main.cpp + main.h + map.cpp + map.h + opengl1graphics.cpp + opengl1graphics.h + openglgraphics.cpp + openglgraphics.h + particle.cpp + particle.h + particlecontainer.cpp + particlecontainer.h + particleemitter.cpp + particleemitter.h + particleemitterprop.h + party.cpp + party.h + playerinfo.cpp + playerinfo.h + playerrelations.cpp + playerrelations.h + position.cpp + position.h + properties.h + rotationalparticle.cpp + rotationalparticle.h + shopitem.cpp + shopitem.h + simpleanimation.cpp + simpleanimation.h + sound.cpp + sound.h + sprite.h + statuseffect.cpp + statuseffect.h + text.cpp + text.h + textmanager.cpp + textmanager.h + textparticle.cpp + textparticle.h + textrenderer.h + tileset.h + units.cpp + units.h + variabledata.h + vector.cpp + vector.h + mumblemanager.cpp + mumblemanager.h + ) + +SET(SRCS_TMWA + net/tmwa/gui/guildtab.cpp + net/tmwa/gui/guildtab.h + net/tmwa/gui/partytab.cpp + net/tmwa/gui/partytab.h + net/tmwa/adminhandler.cpp + net/tmwa/adminhandler.h + net/tmwa/beinghandler.cpp + net/tmwa/beinghandler.h + net/tmwa/buysellhandler.cpp + net/tmwa/buysellhandler.h + net/tmwa/charserverhandler.cpp + net/tmwa/charserverhandler.h + net/tmwa/chathandler.cpp + net/tmwa/chathandler.h + net/tmwa/gamehandler.cpp + net/tmwa/gamehandler.h + net/tmwa/generalhandler.cpp + net/tmwa/generalhandler.h + net/tmwa/guildhandler.cpp + net/tmwa/guildhandler.h + net/tmwa/inventoryhandler.cpp + net/tmwa/inventoryhandler.h + net/tmwa/itemhandler.cpp + net/tmwa/itemhandler.h + net/tmwa/loginhandler.cpp + net/tmwa/loginhandler.h + net/tmwa/messagehandler.cpp + net/tmwa/messagehandler.h + net/tmwa/messagein.cpp + net/tmwa/messagein.h + net/tmwa/messageout.cpp + net/tmwa/messageout.h + net/tmwa/network.cpp + net/tmwa/network.h + net/tmwa/npchandler.cpp + net/tmwa/npchandler.h + net/tmwa/partyhandler.cpp + net/tmwa/partyhandler.h + net/tmwa/playerhandler.cpp + net/tmwa/playerhandler.h + net/tmwa/protocol.h + net/tmwa/specialhandler.cpp + net/tmwa/specialhandler.h + net/tmwa/token.h + net/tmwa/tradehandler.cpp + net/tmwa/tradehandler.h + ) + +SET(SRCS_MANAPLUS + net/manaserv/attributes.cpp + net/manaserv/attributes.h + net/manaserv/adminhandler.cpp + net/manaserv/adminhandler.h + net/manaserv/beinghandler.cpp + net/manaserv/beinghandler.h + net/manaserv/buysellhandler.cpp + net/manaserv/buysellhandler.h + net/manaserv/charhandler.cpp + net/manaserv/charhandler.h + net/manaserv/chathandler.cpp + net/manaserv/chathandler.h + net/manaserv/connection.cpp + net/manaserv/connection.h + net/manaserv/defines.h + net/manaserv/effecthandler.cpp + net/manaserv/effecthandler.h + net/manaserv/gamehandler.cpp + net/manaserv/gamehandler.h + net/manaserv/generalhandler.cpp + net/manaserv/generalhandler.h + net/manaserv/guildhandler.cpp + net/manaserv/guildhandler.h + net/manaserv/internal.cpp + net/manaserv/internal.h + net/manaserv/inventoryhandler.cpp + net/manaserv/inventoryhandler.h + net/manaserv/itemhandler.h + net/manaserv/itemhandler.cpp + net/manaserv/loginhandler.cpp + net/manaserv/loginhandler.h + net/manaserv/messagehandler.cpp + net/manaserv/messagehandler.h + net/manaserv/messagein.cpp + net/manaserv/messagein.h + net/manaserv/messageout.cpp + net/manaserv/messageout.h + net/manaserv/network.cpp + net/manaserv/network.h + net/manaserv/npchandler.cpp + net/manaserv/npchandler.h + net/manaserv/partyhandler.cpp + net/manaserv/partyhandler.h + net/manaserv/playerhandler.cpp + net/manaserv/playerhandler.h + net/manaserv/protocol.h + net/manaserv/specialhandler.cpp + net/manaserv/specialhandler.h + net/manaserv/tradehandler.cpp + net/manaserv/tradehandler.h + ) + +IF (WIN32) + SET(SRCS_MANAPLUS + ${SRCS_MANAPLUS} + utils/specialfolder.cpp + utils/specialfolder.h + mana.rc + ) +ENDIF () + +SET (PROGRAMS manaplus) + +ADD_EXECUTABLE(manaplus WIN32 ${SRCS} ${SRCS_MANAPLUS} ${SRCS_TMWA}) + +TARGET_LINK_LIBRARIES(manaplus + ${SDLGFX_LIBRARIES} + ${SDL_LIBRARY} + ${SDLIMAGE_LIBRARY} + ${SDLMIXER_LIBRARY} + ${SDLNET_LIBRARY} + ${SDLTTF_LIBRARY} + ${PNG_LIBRARIES} + ${PHYSFS_LIBRARY} + ${CURL_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${GUICHAN_LIBRARIES} + ${OPENGL_LIBRARIES} + ${LIBINTL_LIBRARIES} + ${EXTRA_LIBRARIES}) +INSTALL(TARGETS manaplus RUNTIME DESTINATION ${PKG_BINDIR}) + +IF (CMAKE_SYSTEM_NAME STREQUAL SunOS) + # we expect the SMCgtxt package to be present on Solaris; + # the Solaris gettext is not API-compatible to GNU gettext + SET_TARGET_PROPERTIES(manaplus PROPERTIES LINK_FLAGS "-L/usr/local/lib") +ENDIF() + +SET_TARGET_PROPERTIES(manaplus PROPERTIES COMPILE_FLAGS "${FLAGS}") diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..311f0d8e2 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,589 @@ +AUTOMAKE_OPTIONS = subdir-objects + +bin_PROGRAMS = manaplus + +manaplus_CXXFLAGS = -DPKG_DATADIR=\""$(pkgdatadir)/"\" \ + -DLOCALEDIR=\""$(localedir)"\" \ + -Wall + +manaplus_SOURCES = enet/callbacks.c \ + enet/compress.c \ + enet/host.c \ + enet/list.c \ + enet/packet.c \ + enet/peer.c \ + enet/protocol.c \ + enet/unix.c \ + enet/win32.c \ + enet/callbacks.h \ + enet/enet.h \ + enet/list.h \ + enet/protocol.h \ + enet/time.h \ + enet/types.h \ + enet/unix.h \ + enet/utility.h \ + enet/win32.h + +manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \ + gui/widgets/avatarlistbox.h \ + gui/widgets/battletab.cpp \ + gui/widgets/battletab.h \ + gui/widgets/browserbox.cpp \ + gui/widgets/browserbox.h \ + gui/widgets/button.cpp \ + gui/widgets/button.h \ + gui/widgets/channeltab.cpp \ + gui/widgets/channeltab.h \ + gui/widgets/chattab.cpp \ + gui/widgets/chattab.h \ + gui/widgets/checkbox.cpp \ + gui/widgets/checkbox.h \ + gui/widgets/container.cpp \ + gui/widgets/container.h \ + gui/widgets/desktop.cpp \ + gui/widgets/desktop.h \ + gui/widgets/dropdown.cpp \ + gui/widgets/dropdown.h \ + gui/widgets/emoteshortcutcontainer.cpp \ + gui/widgets/emoteshortcutcontainer.h \ + gui/widgets/flowcontainer.cpp \ + gui/widgets/flowcontainer.h \ + gui/widgets/icon.cpp \ + gui/widgets/icon.h \ + gui/widgets/inttextfield.cpp \ + gui/widgets/inttextfield.h \ + gui/widgets/itemcontainer.cpp \ + gui/widgets/itemcontainer.h \ + gui/widgets/itemlinkhandler.cpp \ + gui/widgets/itemlinkhandler.h \ + gui/widgets/dropshortcutcontainer.cpp \ + gui/widgets/dropshortcutcontainer.h \ + gui/widgets/itemshortcutcontainer.cpp \ + gui/widgets/itemshortcutcontainer.h \ + gui/widgets/spellshortcutcontainer.cpp \ + gui/widgets/spellshortcutcontainer.h \ + gui/widgets/label.cpp \ + gui/widgets/label.h \ + gui/widgets/layout.cpp \ + gui/widgets/layout.h \ + gui/widgets/layouthelper.cpp \ + gui/widgets/layouthelper.h \ + gui/widgets/linkhandler.h \ + gui/widgets/listbox.cpp \ + gui/widgets/listbox.h \ + gui/widgets/passwordfield.cpp \ + gui/widgets/passwordfield.h \ + gui/widgets/playerbox.cpp \ + gui/widgets/playerbox.h \ + gui/widgets/popup.cpp \ + gui/widgets/popup.h \ + gui/widgets/progressbar.cpp \ + gui/widgets/progressbar.h \ + gui/widgets/progressindicator.cpp \ + gui/widgets/progressindicator.h \ + gui/widgets/radiobutton.cpp \ + gui/widgets/radiobutton.h \ + gui/widgets/resizegrip.cpp \ + gui/widgets/resizegrip.h \ + gui/widgets/scrollarea.cpp \ + gui/widgets/scrollarea.h \ + gui/widgets/setuptab.cpp \ + gui/widgets/setuptab.h \ + gui/widgets/shopitems.cpp \ + gui/widgets/shopitems.h \ + gui/widgets/shoplistbox.cpp \ + gui/widgets/shoplistbox.h \ + gui/widgets/shortcutcontainer.cpp \ + gui/widgets/shortcutcontainer.h \ + gui/widgets/slider.cpp \ + gui/widgets/slider.h \ + gui/widgets/tab.cpp \ + gui/widgets/tab.h \ + gui/widgets/tabbedarea.cpp \ + gui/widgets/tabbedarea.h \ + gui/widgets/table.cpp \ + gui/widgets/table.h \ + gui/widgets/tablemodel.cpp \ + gui/widgets/tablemodel.h \ + gui/widgets/textbox.cpp \ + gui/widgets/textbox.h \ + gui/widgets/textfield.cpp \ + gui/widgets/textfield.h \ + gui/widgets/textpreview.cpp \ + gui/widgets/textpreview.h \ + gui/widgets/tradetab.cpp \ + gui/widgets/tradetab.h \ + gui/widgets/vertcontainer.cpp \ + gui/widgets/vertcontainer.h \ + gui/widgets/whispertab.cpp \ + gui/widgets/whispertab.h \ + gui/widgets/window.cpp \ + gui/widgets/window.h \ + gui/widgets/windowcontainer.cpp \ + gui/widgets/windowcontainer.h \ + gui/beingpopup.cpp \ + gui/beingpopup.h \ + gui/buy.cpp \ + gui/buy.h \ + gui/buysell.cpp \ + gui/buysell.h \ + gui/changeemaildialog.cpp \ + gui/changeemaildialog.h \ + gui/changepassworddialog.cpp \ + gui/changepassworddialog.h \ + gui/charselectdialog.cpp \ + gui/charselectdialog.h \ + gui/charcreatedialog.cpp \ + gui/charcreatedialog.h \ + gui/chat.cpp \ + gui/chat.h \ + gui/confirmdialog.cpp \ + gui/confirmdialog.h \ + gui/connectiondialog.cpp \ + gui/connectiondialog.h \ + gui/debugwindow.cpp \ + gui/debugwindow.h \ + gui/emotepopup.cpp \ + gui/emotepopup.h \ + gui/equipmentwindow.cpp \ + gui/equipmentwindow.h \ + gui/focushandler.cpp \ + gui/focushandler.h \ + gui/gui.cpp \ + gui/gui.h \ + gui/help.cpp \ + gui/help.h \ + gui/inventorywindow.cpp \ + gui/inventorywindow.h \ + gui/itemamount.cpp \ + gui/itemamount.h \ + gui/itempopup.cpp \ + gui/itempopup.h \ + gui/spellpopup.cpp \ + gui/spellpopup.h \ + gui/statuspopup.cpp \ + gui/statuspopup.h \ + gui/killstats.cpp \ + gui/killstats.h \ + gui/login.cpp \ + gui/login.h \ + gui/minimap.cpp \ + gui/minimap.h \ + gui/ministatus.cpp \ + gui/ministatus.h \ + gui/npcdialog.cpp \ + gui/npcdialog.h \ + gui/npcpostdialog.cpp \ + gui/npcpostdialog.h \ + gui/okdialog.cpp \ + gui/okdialog.h \ + gui/editdialog.cpp \ + gui/editdialog.h \ + gui/outfitwindow.cpp \ + gui/outfitwindow.h \ + gui/botcheckerwindow.cpp \ + gui/botcheckerwindow.h \ + gui/textcommandeditor.cpp \ + gui/textcommandeditor.h \ + gui/palette.cpp \ + gui/palette.h \ + gui/popupmenu.cpp \ + gui/popupmenu.h \ + gui/quitdialog.cpp \ + gui/quitdialog.h \ + gui/register.cpp \ + gui/register.h \ + gui/sdlinput.cpp \ + gui/sdlinput.h \ + gui/sell.cpp \ + gui/sell.h \ + gui/serverdialog.cpp \ + gui/serverdialog.h \ + gui/setup.cpp \ + gui/setup.h \ + gui/setup_audio.cpp \ + gui/setup_audio.h \ + gui/setup_colors.cpp \ + gui/setup_colors.h \ + gui/setup_joystick.cpp \ + gui/setup_joystick.h \ + gui/setup_other.cpp \ + gui/setup_other.h \ + gui/setup_theme.cpp \ + gui/setup_theme.h \ + gui/setup_chat.cpp \ + gui/setup_chat.h \ + gui/setup_keyboard.cpp \ + gui/setup_keyboard.h \ + gui/setup_players.cpp \ + gui/setup_players.h \ + gui/setup_video.cpp \ + gui/setup_video.h \ + gui/shopwindow.cpp \ + gui/shopwindow.h \ + gui/shortcutwindow.cpp \ + gui/shortcutwindow.h \ + gui/skilldialog.cpp \ + gui/skilldialog.h \ + gui/socialwindow.cpp \ + gui/socialwindow.h \ + gui/speechbubble.cpp \ + gui/speechbubble.h \ + gui/specialswindow.cpp \ + gui/specialswindow.h \ + gui/statuswindow.cpp \ + gui/statuswindow.h \ + gui/textdialog.cpp \ + gui/textdialog.h \ + gui/textpopup.cpp \ + gui/textpopup.h \ + gui/theme.cpp \ + gui/theme.h \ + gui/trade.cpp \ + gui/trade.h \ + gui/truetypefont.cpp \ + gui/truetypefont.h \ + gui/unregisterdialog.cpp \ + gui/unregisterdialog.h \ + gui/updatewindow.cpp \ + gui/updatewindow.h \ + gui/userpalette.cpp \ + gui/userpalette.h \ + gui/viewport.cpp \ + gui/viewport.h \ + gui/whoisonline.cpp \ + gui/whoisonline.h \ + gui/windowmenu.cpp \ + gui/windowmenu.h \ + gui/worldselectdialog.cpp \ + gui/worldselectdialog.h \ + net/adminhandler.h \ + net/charhandler.cpp \ + net/charhandler.h \ + net/chathandler.h \ + net/download.cpp \ + net/download.h \ + net/gamehandler.h \ + net/generalhandler.h \ + net/guildhandler.h \ + net/inventoryhandler.h \ + net/logindata.h \ + net/loginhandler.h \ + net/messagehandler.h \ + net/messagein.cpp \ + net/messagein.h \ + net/messageout.cpp \ + net/messageout.h \ + net/net.cpp \ + net/net.h \ + net/npchandler.h \ + net/partyhandler.h \ + net/playerhandler.h \ + net/serverinfo.h \ + net/specialhandler.h \ + net/tradehandler.h \ + net/worldinfo.h \ + net/packetcounters.cpp \ + net/packetcounters.h \ + resources/action.cpp \ + resources/action.h \ + resources/ambientlayer.cpp \ + resources/ambientlayer.h \ + resources/ambientoverlay.cpp \ + resources/ambientoverlay.h \ + resources/animation.cpp \ + resources/animation.h \ + resources/beinginfo.cpp \ + resources/beinginfo.h \ + resources/colordb.cpp \ + resources/colordb.h \ + resources/dye.cpp \ + resources/dye.h \ + resources/emotedb.cpp \ + resources/emotedb.h \ + resources/image.cpp \ + resources/image.h \ + resources/imageloader.cpp \ + resources/imageloader.h \ + resources/imageset.h \ + resources/imageset.cpp \ + resources/imagewriter.cpp \ + resources/imagewriter.h \ + resources/itemdb.cpp \ + resources/itemdb.h \ + resources/iteminfo.h \ + resources/iteminfo.cpp \ + resources/mapreader.cpp \ + resources/mapreader.h \ + resources/monsterdb.cpp \ + resources/monsterdb.h \ + resources/music.cpp \ + resources/music.h \ + resources/npcdb.cpp \ + resources/npcdb.h \ + resources/resource.cpp \ + resources/resource.h \ + resources/resourcemanager.cpp \ + resources/resourcemanager.h \ + resources/soundeffect.h \ + resources/soundeffect.cpp \ + resources/specialdb.cpp \ + resources/specialdb.h \ + resources/spritedef.h \ + resources/spritedef.cpp \ + resources/wallpaper.cpp \ + resources/wallpaper.h \ + utils/base64.cpp \ + utils/base64.h \ + utils/copynpaste.cpp \ + utils/copynpaste.h \ + utils/dtor.h \ + utils/gettext.h \ + utils/mathutils.h \ + utils/mkdir.cpp \ + utils/mkdir.h \ + utils/sha256.cpp \ + utils/sha256.h \ + utils/specialfolder.cpp \ + utils/specialfolder.h \ + utils/stringutils.cpp \ + utils/stringutils.h \ + utils/mutex.h \ + utils/xml.cpp \ + utils/xml.h \ + actor.cpp \ + actor.h \ + actorsprite.cpp \ + actorsprite.h \ + actorspritelistener.h \ + actorspritemanager.cpp \ + actorspritemanager.h \ + animatedsprite.cpp \ + animatedsprite.h \ + animationparticle.cpp \ + animationparticle.h \ + avatar.cpp \ + avatar.h \ + being.cpp \ + being.h \ + spellmanager.cpp \ + spellmanager.h \ + chatlog.cpp \ + chatlog.h \ + client.cpp \ + client.h \ + channel.cpp \ + channel.h \ + channelmanager.cpp \ + channelmanager.h \ + commandhandler.cpp \ + commandhandler.h \ + compoundsprite.cpp \ + compoundsprite.h \ + configlistener.h \ + configuration.cpp \ + configuration.h \ + defaults.cpp \ + defaults.h \ + effectmanager.cpp \ + effectmanager.h \ + emoteshortcut.cpp \ + emoteshortcut.h \ + equipment.h \ + event.cpp \ + event.h \ + flooritem.cpp \ + flooritem.h \ + game.cpp \ + game.h \ + graphics.cpp \ + graphics.h \ + guichanfwd.h \ + guild.cpp \ + guild.h \ + imageparticle.cpp \ + imageparticle.h \ + imagesprite.cpp \ + imagesprite.h \ + inventory.cpp \ + inventory.h \ + item.cpp \ + item.h \ + itemshortcut.cpp \ + itemshortcut.h \ + dropshortcut.cpp \ + dropshortcut.h \ + spellshortcut.cpp \ + spellshortcut.h \ + textcommand.cpp \ + textcommand.h \ + joystick.cpp \ + joystick.h \ + keyboardconfig.cpp \ + keyboardconfig.h \ + listener.cpp \ + listener.h \ + localplayer.cpp \ + localplayer.h \ + log.cpp \ + log.h \ + main.cpp \ + main.h \ + map.cpp\ + map.h \ + opengl1graphics.cpp\ + opengl1graphics.h \ + openglgraphics.cpp\ + openglgraphics.h \ + particle.cpp \ + particle.h \ + particlecontainer.cpp \ + particlecontainer.h \ + particleemitter.cpp \ + particleemitter.h \ + particleemitterprop.h \ + party.cpp \ + party.h \ + playerinfo.cpp \ + playerinfo.h \ + playerrelations.cpp \ + playerrelations.h \ + position.cpp \ + position.h \ + properties.h \ + rotationalparticle.cpp \ + rotationalparticle.h \ + shopitem.cpp \ + shopitem.h \ + simpleanimation.cpp \ + simpleanimation.h \ + sound.cpp \ + sound.h \ + sprite.h \ + statuseffect.cpp \ + statuseffect.h \ + text.cpp \ + text.h \ + textmanager.cpp \ + textmanager.h \ + textparticle.cpp \ + textparticle.h \ + textrenderer.h \ + tileset.h \ + units.cpp \ + units.h \ + variabledata.h \ + vector.cpp \ + vector.h \ + winver.h + +manaplus_SOURCES += \ + net/manaserv/attributes.cpp \ + net/manaserv/attributes.h \ + net/manaserv/adminhandler.cpp \ + net/manaserv/adminhandler.h \ + net/manaserv/beinghandler.cpp \ + net/manaserv/beinghandler.h \ + net/manaserv/buysellhandler.cpp \ + net/manaserv/buysellhandler.h \ + net/manaserv/charhandler.cpp \ + net/manaserv/charhandler.h \ + net/manaserv/chathandler.cpp \ + net/manaserv/chathandler.h \ + net/manaserv/connection.cpp \ + net/manaserv/connection.h \ + net/manaserv/defines.h \ + net/manaserv/effecthandler.cpp \ + net/manaserv/effecthandler.h \ + net/manaserv/gamehandler.cpp \ + net/manaserv/gamehandler.h \ + net/manaserv/generalhandler.cpp \ + net/manaserv/generalhandler.h \ + net/manaserv/guildhandler.cpp \ + net/manaserv/guildhandler.h \ + net/manaserv/internal.cpp \ + net/manaserv/internal.h \ + net/manaserv/inventoryhandler.cpp \ + net/manaserv/inventoryhandler.h \ + net/manaserv/itemhandler.h \ + net/manaserv/itemhandler.cpp \ + net/manaserv/loginhandler.cpp \ + net/manaserv/loginhandler.h \ + net/manaserv/messagehandler.cpp \ + net/manaserv/messagehandler.h \ + net/manaserv/messagein.cpp \ + net/manaserv/messagein.h \ + net/manaserv/messageout.cpp \ + net/manaserv/messageout.h \ + net/manaserv/network.cpp \ + net/manaserv/network.h \ + net/manaserv/npchandler.cpp \ + net/manaserv/npchandler.h \ + net/manaserv/partyhandler.cpp \ + net/manaserv/partyhandler.h \ + net/manaserv/playerhandler.cpp \ + net/manaserv/playerhandler.h \ + net/manaserv/protocol.h \ + net/manaserv/specialhandler.cpp \ + net/manaserv/specialhandler.h \ + net/manaserv/tradehandler.cpp \ + net/manaserv/tradehandler.h + +manaplus_SOURCES += \ + net/tmwa/gui/guildtab.cpp \ + net/tmwa/gui/guildtab.h \ + net/tmwa/gui/partytab.cpp \ + net/tmwa/gui/partytab.h \ + net/tmwa/adminhandler.cpp \ + net/tmwa/adminhandler.h \ + net/tmwa/beinghandler.cpp \ + net/tmwa/beinghandler.h \ + net/tmwa/buysellhandler.cpp \ + net/tmwa/buysellhandler.h \ + net/tmwa/charserverhandler.cpp \ + net/tmwa/charserverhandler.h \ + net/tmwa/chathandler.cpp \ + net/tmwa/chathandler.h \ + net/tmwa/gamehandler.cpp \ + net/tmwa/gamehandler.h \ + net/tmwa/generalhandler.cpp \ + net/tmwa/generalhandler.h \ + net/tmwa/guildhandler.cpp \ + net/tmwa/guildhandler.h \ + net/tmwa/inventoryhandler.cpp \ + net/tmwa/inventoryhandler.h \ + net/tmwa/itemhandler.cpp \ + net/tmwa/itemhandler.h \ + net/tmwa/loginhandler.cpp \ + net/tmwa/loginhandler.h \ + net/tmwa/messagehandler.cpp \ + net/tmwa/messagehandler.h \ + net/tmwa/messagein.cpp \ + net/tmwa/messagein.h \ + net/tmwa/messageout.cpp \ + net/tmwa/messageout.h \ + net/tmwa/network.cpp \ + net/tmwa/network.h \ + net/tmwa/npchandler.cpp \ + net/tmwa/npchandler.h \ + net/tmwa/partyhandler.cpp \ + net/tmwa/partyhandler.h \ + net/tmwa/playerhandler.cpp \ + net/tmwa/playerhandler.h \ + net/tmwa/protocol.h \ + net/tmwa/specialhandler.cpp \ + net/tmwa/specialhandler.h \ + net/tmwa/token.h \ + net/tmwa/tradehandler.cpp \ + net/tmwa/tradehandler.h + +manaplus_SOURCES += \ + mumblemanager.cpp \ + mumblemanager.h + +EXTRA_DIST = CMakeLists.txt \ + winver.h.in + +# set the include path found by configure +INCLUDES = $(all_includes) diff --git a/src/SDLMain.h b/src/SDLMain.h new file mode 100644 index 000000000..4683df57a --- /dev/null +++ b/src/SDLMain.h @@ -0,0 +1,11 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import + +@interface SDLMain : NSObject +@end diff --git a/src/SDLMain.m b/src/SDLMain.m new file mode 100644 index 000000000..d7e9273bc --- /dev/null +++ b/src/SDLMain.m @@ -0,0 +1,385 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs + */ + +#import "SDL.h" +#import "SDLMain.h" +#import /* for MAXPATHLEN */ +#import + +/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum + { + UInt32 lo; + UInt32 hi; + } CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface SDLApplication : NSApplication +@end + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) { + assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } + +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } + [ aMenu sizeToFit ]; +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [SDLApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [SDLApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } + else + { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + [SDLApplication poseAsClass:[NSApplication class]]; + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} diff --git a/src/SDL_gfxBlitFunc.h b/src/SDL_gfxBlitFunc.h new file mode 100644 index 000000000..f233aa617 --- /dev/null +++ b/src/SDL_gfxBlitFunc.h @@ -0,0 +1,128 @@ +/* + + SDL_gfxBlitFunc: custom blitters (part of SDL_gfx library) + + LGPL (c) A. Schiffler + +*/ + +#ifndef _SDL_gfxBlitFunc_h +#define _SDL_gfxBlitFunc_h + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +/* -------- Prototypes */ + +#ifdef WIN32 +# ifdef DLL_EXPORT +# define SDL_GFXBLITFUNC_SCOPE __declspec(dllexport) +# else +# ifdef LIBSDL_GFX_DLL_IMPORT +# define SDL_GFXBLITFUNC_SCOPE __declspec(dllimport) +# endif +# endif +#endif +#ifndef SDL_GFXBLITFUNC_SCOPE +# define SDL_GFXBLITFUNC_SCOPE extern +#endif + + +SDL_GFXBLITFUNC_SCOPE int SDL_gfxBlitRGBA(SDL_Surface * src, SDL_Rect * srcrect, SDL_Surface * dst, SDL_Rect * dstrect); + +SDL_GFXBLITFUNC_SCOPE int SDL_gfxSetAlpha(SDL_Surface * src, Uint8 a); + + +/* -------- Macros */ + +/* Define SDL macros locally as a substitute for a #include "SDL_blit.h", */ + +/* which doesn't work since the include file doesn't get installed. */ + +/* The structure passed to the low level blit functions */ + typedef struct { + Uint8 *s_pixels; + int s_width; + int s_height; + int s_skip; + Uint8 *d_pixels; + int d_width; + int d_height; + int d_skip; + void *aux_data; + SDL_PixelFormat *src; + Uint8 *table; + SDL_PixelFormat *dst; + } SDL_gfxBlitInfo; + +#define GFX_RGBA_FROM_PIXEL(pixel, fmt, r, g, b, a) \ +{ \ + r = ((pixel&fmt->Rmask)>>fmt->Rshift)<Rloss; \ + g = ((pixel&fmt->Gmask)>>fmt->Gshift)<Gloss; \ + b = ((pixel&fmt->Bmask)>>fmt->Bshift)<Bloss; \ + a = ((pixel&fmt->Amask)>>fmt->Ashift)<Aloss; \ +} + +#define GFX_DISEMBLE_RGBA(buf, bpp, fmt, pixel, r, g, b, a) \ +do { \ + pixel = *((Uint32 *)(buf)); \ + GFX_RGBA_FROM_PIXEL(pixel, fmt, r, g, b, a); \ + pixel &= ~fmt->Amask; \ +} while(0) + +#define GFX_PIXEL_FROM_RGBA(pixel, fmt, r, g, b, a) \ +{ \ + pixel = ((r>>fmt->Rloss)<Rshift)| \ + ((g>>fmt->Gloss)<Gshift)| \ + ((b>>fmt->Bloss)<Bshift)| \ + ((a<Aloss)<Ashift); \ +} + +#define GFX_ASSEMBLE_RGBA(buf, bpp, fmt, r, g, b, a) \ +{ \ + Uint32 pixel; \ + \ + GFX_PIXEL_FROM_RGBA(pixel, fmt, r, g, b, a); \ + *((Uint32 *)(buf)) = pixel; \ +} + +/* Blend the RGB values of two pixels based on a source alpha value */ +#define GFX_ALPHA_BLEND(sR, sG, sB, A, dR, dG, dB) \ +do { \ + dR = (((sR-dR)*(A))/255)+dR; \ + dG = (((sG-dG)*(A))/255)+dG; \ + dB = (((sB-dB)*(A))/255)+dB; \ +} while(0) + +/* This is a very useful loop for optimizing blitters */ + +/* 4-times unrolled loop */ +#define GFX_DUFFS_LOOP4(pixel_copy_increment, width) \ +{ int n = (width+3)/4; \ + switch (width & 3) { \ + case 0: do { pixel_copy_increment; \ + case 3: pixel_copy_increment; \ + case 2: pixel_copy_increment; \ + case 1: pixel_copy_increment; \ + } while ( --n > 0 ); \ + } \ +} + +//deleted + +/* --- */ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif /* _SDL_gfxBlitFunc_h */ diff --git a/src/actor.cpp b/src/actor.cpp new file mode 100644 index 000000000..b7b039755 --- /dev/null +++ b/src/actor.cpp @@ -0,0 +1,64 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "actor.h" + +#include "map.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +Actor::Actor(): + mMap(0) +{} + +Actor::~Actor() +{ + setMap(0); +} + +void Actor::setMap(Map *map) +{ + // Remove Actor from potential previous map + if (mMap) + mMap->removeActor(mMapActor); + + mMap = map; + + // Add Actor to potential new map + if (mMap) + mMapActor = mMap->addActor(this); +} + +int Actor::getTileX() const +{ + if (!mMap || !mMap->getTileWidth()) + return 0; + + return getPixelX() / mMap->getTileWidth(); +} + +int Actor::getTileY() const +{ + if (!mMap || !mMap->getTileHeight()) + return 0; + + return getPixelY() / mMap->getTileHeight(); +} diff --git a/src/actor.h b/src/actor.h new file mode 100644 index 000000000..4e1ede128 --- /dev/null +++ b/src/actor.h @@ -0,0 +1,128 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ACTOR_H +#define ACTOR_H + +#include "vector.h" + +#include + +class Actor; +class Graphics; +class Image; +class Map; + +typedef std::list Actors; + +class Actor +{ +public: + Actor(); + + virtual ~Actor(); + + /** + * Draws the Actor to the given graphics context. + * + * Note: this function could be simplified if the graphics context + * would support setting a translation offset. It already does this + * partly with the clipping rectangle support. + */ + virtual bool draw(Graphics *graphics, int offsetX, int offsetY) const = 0; + + /** + * Returns the horizontal size of the actors graphical representation + * in pixels or 0 when it is undefined. + */ + virtual int getWidth() const + { return 0; } + + /** + * Returns the vertical size of the actors graphical representation + * in pixels or 0 when it is undefined. + */ + virtual int getHeight() const + { return 0; } + + /** + * Returns the pixel position of this actor. + */ + const Vector &getPosition() const + { return mPos; } + + /** + * Sets the pixel position of this actor. + */ + virtual void setPosition(const Vector &pos) + { mPos = pos; } + + /** + * Returns the pixels X coordinate of the actor. + */ + int getPixelX() const + { return static_cast(mPos.x); } + + /** + * Returns the pixel Y coordinate of the actor. + */ + virtual int getPixelY() const + { return static_cast(mPos.y); } + + /** + * Returns the x coordinate in tiles of the actor. + */ + virtual int getTileX() const; + + /** + * Returns the y coordinate in tiles of the actor. + */ + virtual int getTileY() const; + + /** + * Returns the number of Image layers used to draw the actor. + */ + virtual int getNumberOfLayers() const + { return 0; } + + /** + * Returns the current alpha value used to draw the actor. + */ + virtual float getAlpha() const = 0; + + /** + * Sets the alpha value used to draw the actor. + */ + virtual void setAlpha(float alpha) = 0; + + void setMap(Map *map); + + Map* getMap() const + { return mMap; } + +protected: + Map *mMap; + Vector mPos; /**< Position in pixels relative to map. */ + +private: + Actors::iterator mMapActor; +}; + +#endif // ACTOR_H diff --git a/src/actorsprite.cpp b/src/actorsprite.cpp new file mode 100644 index 000000000..8b29b91b6 --- /dev/null +++ b/src/actorsprite.cpp @@ -0,0 +1,491 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "actorsprite.h" +#include "actorspritelistener.h" + +#include "client.h" +#include "effectmanager.h" +#include "imagesprite.h" +#include "localplayer.h" +#include "log.h" +#include "simpleanimation.h" +#include "sound.h" +#include "statuseffect.h" + +#include "gui/theme.h" + +#include "net/net.h" + +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#define EFFECTS_FILE "effects.xml" + +ImageSet *ActorSprite::targetCursorImages[2][NUM_TC]; +SimpleAnimation *ActorSprite::targetCursor[2][NUM_TC]; +bool ActorSprite::loaded = false; + +ActorSprite::ActorSprite(int id): + mId(id), + mStunMode(0), + mStatusParticleEffects(&mStunParticleEffects, false), + mChildParticleEffects(&mStatusParticleEffects, false), + mMustResetParticles(false), + mUsedTargetCursor(0) +{ +} + +ActorSprite::~ActorSprite() +{ + setMap(0); + + mUsedTargetCursor = 0; + + if (player_node && player_node->getTarget() == this) + player_node->setTarget(0); + + // Notify listeners of the destruction. + for (ActorSpriteListenerIterator iter = mActorSpriteListeners.begin(), + end = mActorSpriteListeners.end(); iter != end; ++iter) + { + (*iter)->actorSpriteDestroyed(*this); + } +} + +bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const +{ + // TODO: Eventually, we probably should fix all sprite offsets so that + // these translations aren't necessary anymore. The sprites know + // best where their base point should be. + const int px = getPixelX() + offsetX - 16; + // Temporary fix to the Y offset. + const int py = getPixelY() + offsetY - + ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32); + + if (mUsedTargetCursor) + { + mUsedTargetCursor->reset(); + mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK); + mUsedTargetCursor->draw(graphics, px + getTargetOffsetX(), + py + getTargetOffsetY()); + } + + return drawSpriteAt(graphics, px, py); +} + +bool ActorSprite::drawSpriteAt(Graphics *graphics, int x, int y) const +{ + return CompoundSprite::draw(graphics, x, y); +} + +void ActorSprite::logic() +{ + // Update sprite animations + update(tick_time * MILLISECONDS_IN_A_TICK); + + // Restart status/particle effects, if needed + if (mMustResetParticles) + { + mMustResetParticles = false; + for (std::set::iterator it = mStatusEffects.begin(); + it != mStatusEffects.end(); it++) + { + const StatusEffect *effect + = StatusEffect::getStatusEffect(*it, true); + if (effect && effect->particleEffectIsPersistent()) + updateStatusEffect(*it, true); + } + } + + // Update particle effects + mChildParticleEffects.moveTo(mPos.x, mPos.y); +} + +void ActorSprite::actorLogic() +{ +} + +void ActorSprite::setMap(Map* map) +{ + Actor::setMap(map); + + // Clear particle effect list because child particles became invalid + mChildParticleEffects.clear(); + mMustResetParticles = true; // Reset status particles on next redraw +} + +void ActorSprite::controlParticle(Particle *particle) +{ + mChildParticleEffects.addLocally(particle); +} + +void ActorSprite::setTargetType(TargetCursorType type) +{ + if (type == TCT_NONE) + untarget(); + else + mUsedTargetCursor = targetCursor[type][getTargetCursorSize()]; +} + +struct EffectDescription +{ + std::string mGFXEffect; + std::string mSFXEffect; +}; + +static EffectDescription *default_effect = 0; +static std::map effects; +static bool effects_initialized = false; + +static EffectDescription *getEffectDescription(xmlNodePtr node, int *id) +{ + EffectDescription *ed = new EffectDescription; + + *id = atoi(XML::getProperty(node, "id", "-1").c_str()); + ed->mSFXEffect = XML::getProperty(node, "audio", ""); + ed->mGFXEffect = XML::getProperty(node, "particle", ""); + + return ed; +} + +static EffectDescription *getEffectDescription(int effectId) +{ + if (!effects_initialized) + { + XML::Document doc(EFFECTS_FILE); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "being-effects")) + { + logger->log1("Error loading being effects file: " + EFFECTS_FILE); + return NULL; + } + + for_each_xml_child_node(node, root) + { + int id; + + if (xmlStrEqual(node->name, BAD_CAST "effect")) + { + EffectDescription *EffectDescription = + getEffectDescription(node, &id); + effects[id] = EffectDescription; + } + else if (xmlStrEqual(node->name, BAD_CAST "default")) + { + EffectDescription *effectDescription = + getEffectDescription(node, &id); + + delete default_effect; + + default_effect = effectDescription; + } + } + + effects_initialized = true; + } // done initializing + + EffectDescription *ed = effects[effectId]; + + return ed ? ed : default_effect; +} + +void ActorSprite::setStatusEffect(int index, bool active) +{ + const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end(); + + if (active != wasActive) + { + updateStatusEffect(index, active); + if (active) + mStatusEffects.insert(index); + else + mStatusEffects.erase(index); + } +} + +void ActorSprite::setStatusEffectBlock(int offset, Uint16 newEffects) +{ + for (int i = 0; i < STATUS_EFFECTS; i++) + { + int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i); + + if (index != -1) + setStatusEffect(index, (newEffects & (1 << i)) > 0); + } +} + +void ActorSprite::internalTriggerEffect(int effectId, bool sfx, bool gfx) +{ + if (player_node) + { + logger->log("Special effect #%d on %s", effectId, + getId() == player_node->getId() ? "self" : "other"); + } + + EffectDescription *ed = getEffectDescription(effectId); + + if (!ed) + { + logger->log1("Unknown special effect and no default recorded"); + return; + } + + if (gfx && !ed->mGFXEffect.empty() && particleEngine) + { + Particle *selfFX; + + selfFX = particleEngine->addEffect(ed->mGFXEffect, 0, 0); + controlParticle(selfFX); + } + + if (sfx && !ed->mSFXEffect.empty()) + sound.playSfx(ed->mSFXEffect); +} + +void ActorSprite::updateStunMode(int oldMode, int newMode) +{ + handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1); + handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1); +} + +void ActorSprite::updateStatusEffect(int index, bool newStatus) +{ + handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index); +} + +void ActorSprite::handleStatusEffect(StatusEffect *effect, int effectId) +{ + if (!effect) + return; + + // TODO: Find out how this is meant to be used + // (SpriteAction != Being::Action) + //SpriteAction action = effect->getAction(); + //if (action != ACTION_INVALID) + // setAction(action); + + Particle *particle = effect->getParticle(); + + if (effectId >= 0) + { + mStatusParticleEffects.setLocally(effectId, particle); + } + else + { + mStunParticleEffects.clearLocally(); + if (particle) + mStunParticleEffects.addLocally(particle); + } +} + +void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display, + bool forceDisplay) +{ + clear(); + + SpriteRefs it, it_end; + + for (it = display.sprites.begin(), it_end = display.sprites.end(); + it != it_end; it++) + { + std::string file = "graphics/sprites/" + (*it)->sprite; + int variant = (*it)->variant; + addSprite(AnimatedSprite::load(file, variant)); + } + + // Ensure that something is shown, if desired + if (size() == 0 && forceDisplay) + { + if (display.image.empty()) + { + addSprite(AnimatedSprite::load("graphics/sprites/error.xml")); + } + else + { + ResourceManager *resman = ResourceManager::getInstance(); + std::string imagePath = "graphics/items/" + display.image; + Image *img = resman->getImage(imagePath); + + if (!img) + img = Theme::getImageFromTheme("unknown-item.png"); + + addSprite(new ImageSprite(img)); + } + } + + mChildParticleEffects.clear(); + + //setup particle effects + if (Particle::enabled && particleEngine) + { + std::list::const_iterator it, it_end; + for (it = display.particles.begin(), it_end = display.particles.end(); + it != it_end; it++) + { + Particle *p = particleEngine->addEffect(*it, 0, 0); + controlParticle(p); + } + } + + mMustResetParticles = true; +} + +void ActorSprite::load() +{ + if (loaded) + unload(); + + initTargetCursor(); + + loaded = true; +} + +void ActorSprite::unload() +{ + if (!loaded) + return; + + cleanupTargetCursors(); + loaded = false; +} + +void ActorSprite::addActorSpriteListener(ActorSpriteListener *listener) +{ + mActorSpriteListeners.push_front(listener); +} + +void ActorSprite::removeActorSpriteListener(ActorSpriteListener *listener) +{ + mActorSpriteListeners.remove(listener); +} + +static const char *cursorType(int type) +{ + switch (type) + { + case ActorSprite::TCT_IN_RANGE: + return "in-range"; + default: + case ActorSprite::TCT_NORMAL: + return "normal"; +// default: +// assert(false); + } +} + +static const char *cursorSize(int size) +{ + switch (size) + { + case ActorSprite::TC_LARGE: + return "l"; + case ActorSprite::TC_MEDIUM: + return "m"; + default: + case ActorSprite::TC_SMALL: + return "s"; +// default: +// assert(false); + } +} + +void ActorSprite::initTargetCursor() +{ + static std::string targetCursor = "graphics/target-cursor-%s-%s.png"; + static int targetWidths[NUM_TC] = {44, 62, 82}; + static int targetHeights[NUM_TC] = {35, 44, 60}; + + // Load target cursors + for (int size = TC_SMALL; size < NUM_TC; size++) + { + for (int type = TCT_NORMAL; type < NUM_TCT; type++) + { + loadTargetCursor(strprintf(targetCursor.c_str(), cursorType(type), + cursorSize(size)), targetWidths[size], + targetHeights[size], type, size); + } + } +} + +void ActorSprite::cleanupTargetCursors() +{ + for (int size = TC_SMALL; size < NUM_TC; size++) + { + for (int type = TCT_NORMAL; type < NUM_TCT; type++) + { + if (targetCursor[type][size]) + { + delete targetCursor[type][size]; + targetCursor[type][size] = 0; + } + if (targetCursorImages[type][size]) + { + targetCursorImages[type][size]->decRef(); + targetCursorImages[type][size] = 0; + } + } + } +} + +void ActorSprite::loadTargetCursor(const std::string &filename, + int width, int height, int type, int size) +{ + if (size < TC_SMALL || size >= NUM_TC) + return; + +// assert(size > -1); +// assert(size < 3); + + ResourceManager *resman = ResourceManager::getInstance(); + ImageSet *currentImageSet = resman->getImageSet(filename, width, height); + + if (!currentImageSet) + { + logger->log("Error loading target cursor: %s", filename.c_str()); + return; + } + + Animation *anim = new Animation; + + for (unsigned int i = 0; i < currentImageSet->size(); ++i) + { +// anim->addFrame(currentImageSet->get(i), 0, + anim->addFrame(currentImageSet->get(i), 75, + (16 - (currentImageSet->getWidth() / 2)), + (16 - (currentImageSet->getHeight() / 2))); + } + + SimpleAnimation *currentCursor = new SimpleAnimation(anim); + + if (targetCursor[type][size]) + { + delete targetCursor[type][size]; + targetCursor[type][size] = 0; + if (targetCursorImages[type][size]) + targetCursorImages[type][size]->decRef(); + } + + targetCursorImages[type][size] = currentImageSet; + targetCursor[type][size] = currentCursor; +} diff --git a/src/actorsprite.h b/src/actorsprite.h new file mode 100644 index 000000000..9d648355e --- /dev/null +++ b/src/actorsprite.h @@ -0,0 +1,255 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ACTORSPRITE_H +#define ACTORSPRITE_H + +#include "actor.h" +#include "compoundsprite.h" +#include "map.h" +#include "particlecontainer.h" + +#include + +#include +#include + +class SimpleAnimation; +class StatusEffect; +class ActorSpriteListener; + +class ActorSprite : public CompoundSprite, public Actor +{ +public: + enum Type + { + UNKNOWN = 0, + PLAYER, + NPC, + MONSTER, + FLOOR_ITEM, + PORTAL + }; + + enum TargetCursorSize + { + TC_SMALL = 0, + TC_MEDIUM, + TC_LARGE, + NUM_TC + }; + + enum TargetCursorType + { + TCT_NONE = -1, + TCT_NORMAL = 0, + TCT_IN_RANGE, + NUM_TCT + }; + + ActorSprite(int id); + + ~ActorSprite(); + + int getId() const + { return mId; } + + void setId(int id) + { mId = id; } + + /** + * Returns the type of the ActorSprite. + */ + virtual Type getType() const + { return UNKNOWN; } + + virtual bool draw(Graphics *graphics, int offsetX, int offsetY) const; + + virtual bool drawSpriteAt(Graphics *graphics, int x, int y) const; + + virtual void logic(); + + static void actorLogic(); + + void setMap(Map* map); + + /** + * Gets the way the object blocks pathfinding for other objects + */ + virtual Map::BlockType getBlockType() const + { return Map::BLOCKTYPE_NONE; } + + /** + * Take control of a particle. + */ + void controlParticle(Particle *particle); + + /** + * Returns the required size of a target cursor for this being. + */ + virtual TargetCursorSize getTargetCursorSize() const + { return TC_MEDIUM; } + + virtual int getTargetOffsetX() const + { return 0; } + + virtual int getTargetOffsetY() const + { return 0; } + + /** + * Sets the target animation for this actor. + */ + void setTargetType(TargetCursorType type); + + /** + * Untargets the actor. + */ + void untarget() { mUsedTargetCursor = NULL; } + + /** + * Triggers a visual effect, such as `level up'. Only draws the visual + * effect, does not play sound effects. + * + * \param effectId ID of the effect to trigger + */ + virtual void triggerEffect(int effectId) + { internalTriggerEffect(effectId, false, true); } + + /** + * Sets the actor's stun mode. If zero, the being is `normal', otherwise it + * is `stunned' in some fashion. + */ + void setStunMode(Uint16 stunMode) + { + if (mStunMode != stunMode) + updateStunMode(mStunMode, stunMode); + mStunMode = stunMode; + } + + void setStatusEffect(int index, bool active); + + /** + * A status effect block is a 16 bit mask of status effects. We assign each + * such flag a block ID of offset + bitnr. + * + * These are NOT the same as the status effect indices. + */ + void setStatusEffectBlock(int offset, Uint16 flags); + + virtual void setAlpha(float alpha) + { CompoundSprite::setAlpha(alpha); } + + virtual float getAlpha() const + { return CompoundSprite::getAlpha(); } + + virtual int getWidth() const + { return CompoundSprite::getWidth(); } + + virtual int getHeight() const + { return CompoundSprite::getHeight(); } + + static void load(); + + static void unload(); + + /** + * Add an ActorSprite listener. + */ + void addActorSpriteListener(ActorSpriteListener *listener); + + /** + * Remove an ActorSprite listener. + */ + void removeActorSpriteListener(ActorSpriteListener *listener); + +protected: + /** + * Trigger visual effect, with components + * + * \param effectId ID of the effect to trigger + * \param sfx Whether to trigger sound effects + * \param gfx Whether to trigger graphical effects + */ + void internalTriggerEffect(int effectId, bool sfx, bool gfx); + + /** + * Notify self that the stun mode has been updated. Invoked by + * setStunMode if something changed. + */ + virtual void updateStunMode(int oldMode, int newMode); + + /** + * Notify self that a status effect has flipped. + * The new flag is passed. + */ + virtual void updateStatusEffect(int index, bool newStatus); + + /** + * Handle an update to a status or stun effect + * + * \param The StatusEffect to effect + * \param effectId -1 for stun, otherwise the effect index + */ + virtual void handleStatusEffect(StatusEffect *effect, int effectId); + + void setupSpriteDisplay(const SpriteDisplay &display, + bool forceDisplay = true); + + int mId; + Uint16 mStunMode; /**< Stun mode; zero if not stunned */ + std::set mStatusEffects; /**< set of active status effects */ + + ParticleList mStunParticleEffects; + ParticleVector mStatusParticleEffects; + ParticleList mChildParticleEffects; + +private: + /** Reset particle status effects on next redraw? */ + bool mMustResetParticles; + + /** Load the target cursors into memory */ + static void initTargetCursor(); + + /** Remove the target cursors from memory */ + static void cleanupTargetCursors(); + + /** + * Helper function for loading target cursors + */ + static void loadTargetCursor(const std::string &filename, + int width, int height, int type, int size); + + /** Images of the target cursor. */ + static ImageSet *targetCursorImages[NUM_TCT][NUM_TC]; + + /** Animated target cursors. */ + static SimpleAnimation *targetCursor[NUM_TCT][NUM_TC]; + + static bool loaded; + + /** Target cursor being used */ + SimpleAnimation *mUsedTargetCursor; + + typedef std::list ActorSpriteListeners; + typedef ActorSpriteListeners::iterator ActorSpriteListenerIterator; + ActorSpriteListeners mActorSpriteListeners; +}; + +#endif // ACTORSPRITE_H diff --git a/src/actorspritelistener.h b/src/actorspritelistener.h new file mode 100644 index 000000000..994494f4f --- /dev/null +++ b/src/actorspritelistener.h @@ -0,0 +1,42 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ACTORSPRITELISTENER_H +#define ACTORSPRITELISTENER_H + +class ActorSprite; + +class ActorSpriteListener +{ + public: + /** + * Destructor. + */ + virtual ~ActorSpriteListener() {} + + /** + * Called when the ActorSprite has been destroyed. The listener will + * have to be registered first. + * @param actorSprite the ActorSprite being destroyed. + */ + virtual void actorSpriteDestroyed(const ActorSprite &actorSprite) = 0; +}; + +#endif // ACTORSPRITELISTENER_H diff --git a/src/actorspritemanager.cpp b/src/actorspritemanager.cpp new file mode 100644 index 000000000..01eba1866 --- /dev/null +++ b/src/actorspritemanager.cpp @@ -0,0 +1,1081 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "actorspritemanager.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" +#include "playerinfo.h" +#include "playerrelations.h" + +#include "gui/widgets/chattab.h" +#include "gui/killstats.h" +#include "gui/chat.h" +#include "gui/skilldialog.h" +#include "gui/viewport.h" + +#include "utils/dtor.h" +#include "utils/stringutils.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include +#include +#include + +#define for_actors ActorSpritesConstIterator it, it_end; \ +for (it = mActors.begin(), it_end = mActors.end() ; it != it_end; it++) + +class FindBeingFunctor +{ + public: + bool operator() (ActorSprite *actor) + { + if (!actor || actor->getType() == ActorSprite::FLOOR_ITEM + || actor->getType() == ActorSprite::PORTAL) + { + return false; + } + Being* b = static_cast(actor); + + unsigned other_y = y + ((b->getType() + == ActorSprite::NPC) ? 1 : 0); + const Vector &pos = b->getPosition(); + return (static_cast(pos.x) / 32 == x && + (static_cast(pos.y) / 32 == y + || static_cast(pos.y) / 32 == other_y) && + b->isAlive() && (type == ActorSprite::UNKNOWN + || b->getType() == type)); + } + + Uint16 x, y; + ActorSprite::Type type; +} beingFinder; + +class FindBeingEqualFunctor +{ + public: + bool operator() (Being *being) + { + if (!being || !findBeing) + return false; + return being->getId() == findBeing->getId(); + } + + Being *findBeing; +} beingEqualFinder; + +class SortBeingFunctor +{ + public: + bool operator() (Being* being1, Being* being2) + { + if (!being1 || !being2) + return false; + + if (being1->getDistance() != being2->getDistance()) + return being1->getDistance() < being2->getDistance(); + + int d1, d2; + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + const Vector &pos1 = being1->getPosition(); + d1 = abs(((int) pos1.x) - x) + abs(((int) pos1.y) - y); + const Vector &pos2 = being2->getPosition(); + d2 = abs(((int) pos2.x) - x) + abs(((int) pos2.y) - y); + } + else + { + d1 = abs(being1->getTileX() - x) + abs(being1->getTileY() - y); + d2 = abs(being2->getTileX() - x) + abs(being2->getTileY() - y); + } + + if (d1 != d2) + return d1 < d2; + return (being1->getName() < being2->getName()); + } + int x, y; + +} beingSorter; + +ActorSpriteManager::ActorSpriteManager() : + mMap(0) +{ + mSpellHeal1 = serverConfig.getValue("spellHeal1", "#lum"); + mSpellHeal2 = serverConfig.getValue("spellHeal2", "#inma"); + mSpellItenplz = serverConfig.getValue("spellItenplz", "#itenplz"); + mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers"); + mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable"); + mCyclePlayers = config.getBoolValue("cyclePlayers"); + mCycleMonsters = config.getBoolValue("cycleMonsters"); + + config.addListener("targetDeadPlayers", this); + config.addListener("targetOnlyReachable", this); + config.addListener("cyclePlayers", this); + config.addListener("cycleMonsters", this); +} + +ActorSpriteManager::~ActorSpriteManager() +{ + config.removeListener("targetDeadPlayers", this); + config.removeListener("targetOnlyReachable", this); + config.removeListener("cyclePlayers", this); + config.removeListener("cycleMonsters", this); + clear(); +} + +void ActorSpriteManager::setMap(Map *map) +{ + mMap = map; + + if (player_node) + player_node->setMap(map); +} + +void ActorSpriteManager::setPlayer(LocalPlayer *player) +{ + player_node = player; + mActors.insert(player); +} + +Being *ActorSpriteManager::createBeing(int id, ActorSprite::Type type, + Uint16 subtype) +{ +/* + for_actors + { + if ((*it)->getId() == id && (*it)->getType() == type) + { + if ((*it) && (*it)->getType() == Being::PLAYER) + static_cast(*it)->addToCache(); + if (player_node) + { + if (player_node->getTarget() == *it) + player_node->setTarget(0); + if (player_node->getPickUpTarget() == *it) + player_node->unSetPickUpTarget(); + } + if (viewport) + viewport->clearHover(*it); + mActors.erase(*it); + delete *it; + break; + } + } +*/ + Being *being = new Being(id, type, subtype, mMap); + + mActors.insert(being); + return being; +} + +FloorItem *ActorSpriteManager::createItem(int id, int itemId, + int x, int y, int amount) +{ + FloorItem *floorItem = new FloorItem(id, itemId, x, y, mMap, amount); + + mActors.insert(floorItem); + return floorItem; +} + +void ActorSpriteManager::destroy(ActorSprite *actor) +{ + if (!actor || actor == player_node) + return; + + mDeleteActors.insert(actor); +} + +void ActorSpriteManager::erase(ActorSprite *actor) +{ + if (!actor || actor == player_node) + return; + + mActors.erase(actor); +} + +void ActorSpriteManager::undelete(ActorSprite *actor) +{ + if (!actor || actor == player_node) + return; + + ActorSpritesConstIterator it, it_end; + + for (it = mDeleteActors.begin(), it_end = mDeleteActors.end(); + it != it_end; ++it) + { + ActorSprite *actor = *it; + if (*it == actor) + { + mDeleteActors.erase(*it); + return; + } + } +} + +Being *ActorSpriteManager::findBeing(int id) const +{ + for_actors + { + ActorSprite *actor = *it; + if (actor->getId() == id && + actor->getType() != ActorSprite::FLOOR_ITEM) + { + return static_cast(actor); + } + } + + return NULL; +} + +Being *ActorSpriteManager::findBeing(int x, int y, + ActorSprite::Type type) const +{ + beingFinder.x = static_cast(x); + beingFinder.y = static_cast(y); + beingFinder.type = type; + + ActorSpritesConstIterator it = find_if(mActors.begin(), mActors.end(), + beingFinder); + + return (it == mActors.end()) ? NULL : static_cast(*it); +} + +Being *ActorSpriteManager::findBeingByPixel(int x, int y, + bool allPlayers) const +{ + if (!mMap) + return NULL; + + bool targetDead = mTargetDeadPlayers; + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + + int xtol = 16; + int uptol = 32; + + if ((being->isAlive() + || (targetDead && being->getType() == Being::PLAYER)) + && (allPlayers || being != player_node)) + { + + if ((being->getPixelX() - xtol <= x) && + (being->getPixelX() + xtol > x) && + (being->getPixelY() - uptol <= y) && + (being->getPixelY() > y)) + { + return being; + } + } + } + + return NULL; +} + +void ActorSpriteManager::findBeingsByPixel(std::list &beings, + int x, int y, bool allPlayers) const +{ + if (!mMap) + return; + + bool targetDead = mTargetDeadPlayers; + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + + int xtol = 16; + int uptol = 32; + + if ((being->isAlive() + || (targetDead && being->getType() == Being::PLAYER)) + && (allPlayers || being != player_node)) + { + + if ((being->getPixelX() - xtol <= x) && + (being->getPixelX() + xtol > x) && + (being->getPixelY() - uptol <= y) && + (being->getPixelY() > y)) + { + beings.push_back(being); + } + } + } +} + +Being *ActorSpriteManager::findPortalByTile(int x, int y) const +{ + if (!mMap) + return NULL; + + for_actors + { + if ((*it)->getType() != ActorSprite::PORTAL) + continue; + + Being *being = static_cast(*it); + + if (being->getTileX() == x && being->getTileY() == y) + return being; + } + + return NULL; +} + +FloorItem *ActorSpriteManager::findItem(int id) const +{ + for_actors + { + if ((*it)->getId() == id && + (*it)->getType() == ActorSprite::FLOOR_ITEM) + { + return static_cast(*it); + } + } + + return NULL; +} + +FloorItem *ActorSpriteManager::findItem(int x, int y) const +{ + for_actors + { + if ((*it)->getTileX() == x && (*it)->getTileY() == y && + (*it)->getType() == ActorSprite::FLOOR_ITEM) + { + return static_cast(*it); + } + } + + return NULL; +} + +bool ActorSpriteManager::pickUpAll(int x1, int y1, int x2, int y2, + bool serverBuggy) +{ + if (!player_node) + return false; + + bool finded(false); + if (!serverBuggy) + { + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + && ((*it)->getTileX() >= x1 && (*it)->getTileX() <= x2) + && ((*it)->getTileY() >= y1 && (*it)->getTileY() <= y2)) + { + if (player_node->pickUp(static_cast(*it))) + finded = true; + } + } + } + else if (Client::checkPackets(PACKET_PICKUP)) + { + FloorItem *item = 0; + unsigned cnt = 65535; + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + && ((*it)->getTileX() >= x1 && (*it)->getTileX() <= x2) + && ((*it)->getTileY() >= y1 && (*it)->getTileY() <= y2)) + { + FloorItem *tempItem = static_cast(*it); + if (tempItem->getPickupCount() < cnt) + { + item = tempItem; + cnt = item->getPickupCount(); + if (cnt == 0) + { + item->incrementPickup(); + player_node->pickUp(item); + return true; + } + } + } + } + if (item && player_node->pickUp(item)) + finded = true; + } + return finded; +} + +bool ActorSpriteManager::pickUpNearest(int x, int y, int maxdist) +{ + if (!player_node) + return false; + + maxdist = maxdist * maxdist; + FloorItem *closestItem = NULL; + int dist = 0; + + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM) + { + FloorItem *item = static_cast(*it); + + int d = (item->getTileX() - x) * (item->getTileX() - x) + + (item->getTileY() - y) * (item->getTileY() - y); + + if ((d < dist || !closestItem) && (!mTargetOnlyReachable + || player_node->isReachable(item->getTileX(), + item->getTileY()))) + { + dist = d; + closestItem = item; + } + } + } + if (closestItem && player_node && dist <= maxdist) + return player_node->pickUp(closestItem); + + return false; +} + +Being *ActorSpriteManager::findBeingByName(const std::string &name, + ActorSprite::Type type) const +{ + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + if (being->getName() == name && + (type == ActorSprite::UNKNOWN || type == being->getType())) + { + return being; + } + } + return NULL; +} + +Being *ActorSpriteManager::findNearestByName(const std::string &name, + Being::Type type) const +{ + if (!player_node) + return 0; + + int dist = 0; + Being* closestBeing = NULL; + int x, y; + + x = player_node->getTileX(); + y = player_node->getTileY(); + + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + + if (being && being->getName() == name && + (type == Being::UNKNOWN || type == being->getType())) + { + if (being->getType() == Being::PLAYER) + return being; + else + { + int d = (being->getTileX() - x) * (being->getTileX() - x) + + (being->getTileY() - y) * (being->getTileY() - y); + + if (validateBeing(0, being, type, 0, 50) + && (d < dist || closestBeing == NULL)) + { + dist = d; + closestBeing = being; + } + } + } + } + return closestBeing; +} + +const ActorSprites &ActorSpriteManager::getAll() const +{ + return mActors; +} + +void ActorSpriteManager::logic() +{ + for_actors + (*it)->logic(); + + if (mDeleteActors.empty()) + return; + + for (it = mDeleteActors.begin(), it_end = mDeleteActors.end(); + it != it_end; ++it) + { + if ((*it) && (*it)->getType() == Being::PLAYER) + static_cast(*it)->addToCache(); + if (player_node) + { + if (player_node->getTarget() == *it) + player_node->setTarget(0); + if (player_node->getPickUpTarget() == *it) + player_node->unSetPickUpTarget(); + } + if (viewport) + viewport->clearHover(*it); + } + + for (it = mDeleteActors.begin(), it_end = mDeleteActors.end(); + it != it_end; ++it) + { + mActors.erase(*it); + delete *it; + } + + mDeleteActors.clear(); +} + +void ActorSpriteManager::clear() +{ + if (player_node) + { + player_node->setTarget(0); + mActors.erase(player_node); + } + + for_actors + { + delete *it; + } + mActors.clear(); + mDeleteActors.clear(); + + if (player_node) + mActors.insert(player_node); +} + +Being *ActorSpriteManager::findNearestLivingBeing(int x, int y, + int maxTileDist, + ActorSprite::Type type, + Being *excluded) const +{ + //Being *closestBeing = 0; + //int dist = 0; + + const int maxDist = maxTileDist * 32; + + return findNearestLivingBeing(NULL, maxDist, type, x, y, excluded); + +/* + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM) + continue; + + Being *being = static_cast(*it); + const Vector &pos = being->getPosition(); + int d = abs(((int) pos.x) - x) + abs(((int) pos.y) - y); + + if ((being->getType() == type || type == ActorSprite::UNKNOWN) + && (d < dist || !closestBeing) // it is closer + && being->isAlive() // no dead beings + && being != excluded) + { + dist = d; + closestBeing = being; + } + } + + return (maxDist >= dist) ? closestBeing : 0; +*/ +} + +Being *ActorSpriteManager::findNearestLivingBeing(Being *aroundBeing, + int maxDist, + Being::Type type) const +{ + if (!aroundBeing) + return 0; + + int x = aroundBeing->getTileX(); + int y = aroundBeing->getTileY(); + + return findNearestLivingBeing(aroundBeing, maxDist, type, + x, y, aroundBeing); +} + +Being *ActorSpriteManager::findNearestLivingBeing(Being *aroundBeing, + int maxDist, + Being::Type type, + int x, int y, + Being *excluded) const +{ + if (!aroundBeing || !player_node) + return 0; + + Being *closestBeing = 0; + int dist = 0; + + maxDist = maxDist * maxDist; + + bool cycleSelect = (mCyclePlayers && type == Being::PLAYER) + || (mCycleMonsters && type == Being::MONSTER); + + if (cycleSelect) + { + std::vector sortedBeings; + + for (ActorSprites::const_iterator i = mActors.begin(), + i_end = mActors.end(); + i != i_end; ++i) + { + if ((*i)->getType() == ActorSprite::FLOOR_ITEM + || (*i)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*i); + + if (validateBeing(aroundBeing, being, type, 0, maxDist)) + { + if (being != excluded) + sortedBeings.push_back(being); + } + } + + // no selectable beings + if (sortedBeings.empty()) + return 0; + + beingSorter.x = x; + beingSorter.y = y; + sort(sortedBeings.begin(), sortedBeings.end(), beingSorter); + + if (player_node->getTarget() == NULL) + { + // if no selected being, return first nearest being + return sortedBeings.at(0); + } + + beingEqualFinder.findBeing = player_node->getTarget(); + std::vector::const_iterator i = find_if(sortedBeings.begin(), + sortedBeings.end(), beingEqualFinder); + + if (i == sortedBeings.end() || ++i == sortedBeings.end()) + { + // if no selected being in vector, return first nearest being + return sortedBeings.at(0); + } + + // we find next being after target + return *i; + } + else + { + for (ActorSprites::const_iterator i = mActors.begin(), + i_end = mActors.end(); + i != i_end; ++i) + { + if ((*i)->getType() == ActorSprite::FLOOR_ITEM + || (*i)->getType() == ActorSprite::PORTAL) + { + continue; + } + Being *being = static_cast(*i); + +// Being *being = (*i); + + bool valid = validateBeing(aroundBeing, being, type, excluded, 50); + int d = being->getDistance(); + if (being->getType() != Being::MONSTER + || !mTargetOnlyReachable) + { // if distance not calculated, use old distance + d = (being->getTileX() - x) * (being->getTileX() - x) + + (being->getTileY() - y) * (being->getTileY() - y); + } + +// logger->log("being name:" + being->getName()); +// logger->log("d:" + toString(d)); +// logger->log("valid:" + toString(valid)); + + if (valid && (d < dist || closestBeing == 0)) + { +// if ((being->getType() == type || type == Being::UNKNOWN) +// && (d < dist || closestBeing == NULL) // it is closer +// && (being->mAction != Being::DEAD // no dead beings +// || (config.getValue("targetDeadPlayers", false) && type == Being::PLAYER)) +// && being != aroundBeing) +// { + dist = d; + closestBeing = being; + } + } + return (maxDist >= dist) ? closestBeing : 0; + } +} + +bool ActorSpriteManager::validateBeing(Being *aroundBeing, Being* being, + Being::Type type, Being* excluded, + int maxCost) const +{ + return being && ((being->getType() == type + || type == Being::UNKNOWN) && (being->isAlive() + || (mTargetDeadPlayers && type == Being::PLAYER)) + && being != aroundBeing) && being != excluded + && (type != Being::MONSTER || !mTargetOnlyReachable + || player_node->isReachable(being, maxCost)); +} + +void ActorSpriteManager::healTarget(LocalPlayer* player_node) +{ + if (!player_node) + return; + + heal(player_node, player_node->getTarget()); +} + +void ActorSpriteManager::heal(LocalPlayer* player_node, Being* target) +{ + if (!player_node || !chatWindow || !player_node->isAlive() + || !Net::getPlayerHandler()->canUseMagic()) + { + return; + } + + if (target && player_node->getName() == target->getName()) + { + if (PlayerInfo::getAttribute(MP) >= 6 + && PlayerInfo::getAttribute(HP) + != PlayerInfo::getAttribute(MAX_HP)) + { + if (!Client::limitPackets(PACKET_CHAT)) + return; + chatWindow->localChatInput(mSpellHeal1); + } + } + else if (PlayerInfo::getStatEffective(340) < 2 + || PlayerInfo::getStatEffective(341) < 2) + { + if (PlayerInfo::getAttribute(MP) >= 6) + { + if (target && target->getType() != Being::MONSTER) + { + if (!Client::limitPackets(PACKET_CHAT)) + return; + chatWindow->localChatInput(mSpellHeal1 + " " + + target->getName()); + } + else if (PlayerInfo::getAttribute(HP) + != PlayerInfo::getAttribute(MAX_HP)) + { + if (!Client::limitPackets(PACKET_CHAT)) + return; + chatWindow->localChatInput(mSpellHeal1); + } + } + } + else + { + if (PlayerInfo::getAttribute(MP) >= 10 && target + && target->getType() != Being::MONSTER) + { + if (!Client::limitPackets(PACKET_CHAT)) + return; + chatWindow->localChatInput(mSpellHeal2 + " " + target->getName()); + } + else if ((!target || target->getType() == Being::MONSTER) + && PlayerInfo::getAttribute(MP) >= 6 + && PlayerInfo::getAttribute(HP) + != PlayerInfo::getAttribute(MAX_HP)) + { + if (!Client::limitPackets(PACKET_CHAT)) + return; + chatWindow->localChatInput(mSpellHeal1); + } + } +} + +void ActorSpriteManager::itenplz() +{ + if (!player_node || !chatWindow || !player_node->isAlive() + || !Net::getPlayerHandler()->canUseMagic()) + { + return; + } + + if (!Client::limitPackets(PACKET_CHAT)) + return; + + chatWindow->localChatInput(mSpellItenplz); +} + +bool ActorSpriteManager::hasActorSprite(ActorSprite *actor) const +{ + for_actors + { + if (actor == *it) + return true; + } + + return false; +} + +void ActorSpriteManager::addBlock(Uint32 id) +{ + bool alreadyBlocked(false); + for (int i = 0; i < (int)blockedBeings.size(); i++) + { + if (id == blockedBeings.at(i)) + { + alreadyBlocked = true; + break; + } + } + if (alreadyBlocked == false) + blockedBeings.push_back(id); +} + +void ActorSpriteManager::deleteBlock(Uint32 id) +{ + std::vector::iterator iter = blockedBeings.begin(); + while (iter != blockedBeings.end()) + { + if (*iter == id) + { + iter = blockedBeings.erase(iter); + break; + } + } +} + +bool ActorSpriteManager::isBlocked(Uint32 id) +{ + bool blocked(false); + for (int i = 0; i < (int)blockedBeings.size(); i++) + { + if (id == blockedBeings.at(i)) + { + blocked = true; + break; + } + } + return blocked; +} + +void ActorSpriteManager::printAllToChat() const +{ + printBeingsToChat(getAll(), "Visible on map"); +} + +void ActorSpriteManager::printBeingsToChat(ActorSprites beings, + std::string header) const +{ + if (!debugChatTab) + return; + + debugChatTab->chatLog("---------------------------------------"); + debugChatTab->chatLog(header); + std::set::iterator it; + for (it = beings.begin(); it != beings.end(); ++it) + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM) + continue; + + const Being *being = static_cast(*it); + + debugChatTab->chatLog(being->getName() + + " (" + toString(being->getTileX()) + "," + + toString(being->getTileY()) + ") " + + toString(being->getSubType()), BY_SERVER); + } + debugChatTab->chatLog("---------------------------------------"); +} + +void ActorSpriteManager::printBeingsToChat(std::vector beings, + std::string header) const +{ + if (!debugChatTab) + return; + + debugChatTab->chatLog("---------------------------------------"); + debugChatTab->chatLog(header); + + std::vector::iterator i; + for (i = beings.begin(); i != beings.end(); ++i) + { + const Being *being = *i; + + debugChatTab->chatLog(being->getName() + + " (" + toString(being->getTileX()) + "," + + toString(being->getTileY()) + ") " + + toString(being->getSubType()), BY_SERVER); + } + debugChatTab->chatLog("---------------------------------------"); +} + +void ActorSpriteManager::getPlayerNames(std::vector &names, + bool npcNames) +{ + names.clear(); + + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + if ((being->getType() == ActorSprite::PLAYER + || (being->getType() == ActorSprite::NPC && npcNames)) + && being->getName() != "") + { + names.push_back(being->getName()); + } + } +} + +void ActorSpriteManager::getMobNames(std::vector &names) +{ + names.clear(); + + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + if (being->getType() == ActorSprite::MONSTER && being->getName() != "") + names.push_back(being->getName()); + } +} + +void ActorSpriteManager::updatePlayerNames() +{ + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + if (being->getType() == ActorSprite::PLAYER && being->getName() != "") + being->updateName(); + } +} + +void ActorSpriteManager::updatePlayerColors() +{ + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + if (being->getType() == ActorSprite::PLAYER && being->getName() != "") + being->updateColors(); + } +} + +void ActorSpriteManager::updatePlayerGuild() +{ + for_actors + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM + || (*it)->getType() == ActorSprite::PORTAL) + { + continue; + } + + Being *being = static_cast(*it); + if (being->getType() == ActorSprite::PLAYER && being->getName() != "") + being->updateGuild(); + } +} + +void ActorSpriteManager::parseLevels(std::string levels) +{ + levels += ", "; + unsigned int f = 0; + unsigned long pos = 0; + const std::string brkEnd = "), "; + + pos = levels.find(brkEnd, f); + while (pos != std::string::npos) + { + std::string part = levels.substr(f, pos - f); + if (part.empty()) + break; + unsigned long bktPos = part.rfind("("); + if (bktPos != std::string::npos) + { + Being *being = findBeingByName(part.substr(0, bktPos), + Being::PLAYER); + if (being) + { + being->setLevel(atoi(part.substr(bktPos + 1).c_str())); + being->addToCache(); + } + } + f = static_cast(pos + brkEnd.length()); + pos = levels.find(brkEnd, f); + } + updatePlayerNames(); +} + +void ActorSpriteManager::optionChanged(const std::string &name) +{ + if (name == "targetDeadPlayers") + mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers"); + else if (name == "targetOnlyReachable") + mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable"); + else if (name == "cyclePlayers") + mCyclePlayers = config.getBoolValue("cyclePlayers"); + else if (name == "cycleMonsters") + mCycleMonsters = config.getBoolValue("cycleMonsters"); +} \ No newline at end of file diff --git a/src/actorspritemanager.h b/src/actorspritemanager.h new file mode 100644 index 000000000..e42bdf5bc --- /dev/null +++ b/src/actorspritemanager.h @@ -0,0 +1,253 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ACTORSPRITEMANAGER_H +#define ACTORSPRITEMANAGER_H + +#include "actorsprite.h" +#include "being.h" +#include "configlistener.h" +#include "flooritem.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class LocalPlayer; +class Map; + +typedef std::set ActorSprites; +typedef ActorSprites::iterator ActorSpritesIterator; +typedef ActorSprites::const_iterator ActorSpritesConstIterator; + +class ActorSpriteManager: public ConfigListener +{ + public: + ActorSpriteManager(); + + ~ActorSpriteManager(); + + /** + * Sets the map on which ActorSprites are created. + */ + void setMap(Map *map); + + /** + * Sets the current player. + */ + void setPlayer(LocalPlayer *player); + + /** + * Create a Being and add it to the list of ActorSprites. + */ + Being *createBeing(int id, ActorSprite::Type type, Uint16 subtype); + + /** + * Create a FloorItem and add it to the list of ActorSprites. + */ + FloorItem *createItem(int id, int itemId, int x, int y, int amount); + + /** + * Destroys the given ActorSprite at the end of + * ActorSpriteManager::logic. + */ + void destroy(ActorSprite *actor); + + void erase(ActorSprite *actor); + + void undelete(ActorSprite *actor); + + /** + * Returns a specific Being, by id; + */ + Being *findBeing(int id) const; + + /** + * Returns a being at specific coordinates. + */ + Being *findBeing(int x, int y, + ActorSprite::Type type = ActorSprite::UNKNOWN) const; + + /** + * Returns a being at the specific pixel. + */ + Being *findBeingByPixel(int x, int y, bool allPlayers = false) const; + + /** + * Returns a beings at the specific pixel. + */ + void findBeingsByPixel(std::list &beings, int x, int y, + bool allPlayers) const; + + /** + * Returns a portal at the specific tile. + */ + Being *findPortalByTile(int x, int y) const; + + /** + * Returns a specific FloorItem, by id. + */ + FloorItem *findItem(int id) const; + + /** + * Returns a FloorItem at specific coordinates. + */ + FloorItem *findItem(int x, int y) const; + + /** + * Returns a being nearest to specific coordinates. + * + * @param x X coordinate in pixels. + * @param y Y coordinate in pixels. + * @param maxTileDist Maximal distance in tiles. If minimal distance is + * larger, no being is returned. + * @param type The type of being to look for. + */ + Being *findNearestLivingBeing(int x, int y, int maxTileDist, + ActorSprite::Type type = Being::UNKNOWN, + Being *excluded = 0) const; + + /** + * Returns a being nearest to another being. + * + * @param aroundBeing The being to search around. + * @param maxTileDist Maximal distance in tiles. If minimal distance is + * larger, no being is returned. + * @param type The type of being to look for. + */ + Being *findNearestLivingBeing(Being *aroundBeing, int maxTileDist, + ActorSprite::Type type = Being::UNKNOWN + ) const; + + /** + * Finds a being by name and (optionally) by type. + */ + Being *findBeingByName(const std::string &name, + ActorSprite::Type type = Being::UNKNOWN) const; + + /** + * Finds a nearest being by name and (optionally) by type. + */ + Being *findNearestByName(const std::string &name, + Being::Type type = Being::UNKNOWN) const; + + /** + * Heal all players in distance. + * + * \param maxdist maximal distance. If minimal distance is larger, + * no being is returned + */ +// void HealAllTargets(Being *aroundBeing, int maxdist, +// Being::Type type) const; + + void healTarget(LocalPlayer* player_node); + + void heal(LocalPlayer* player_node, Being* target); + + void itenplz(); + + /** + * Returns the whole list of beings. + */ + const ActorSprites &getAll() const; + + /** + * Returns true if the given ActorSprite is in the manager's list, + * false otherwise. + * + * \param actor the ActorSprite to search for + */ + bool hasActorSprite(ActorSprite *actor) const; + + /** + * Performs ActorSprite logic and deletes ActorSprite scheduled to be + * deleted. + */ + void logic(); + + /** + * Destroys all ActorSprites except the local player + */ + void clear(); + + std::vector blockedBeings; + + void addBlock(Uint32 id); + + void deleteBlock(Uint32 id); + + bool isBlocked(Uint32 id); + + void printAllToChat() const; + + void printBeingsToChat(ActorSprites beings, std::string header) const; + + void printBeingsToChat(std::vector beings, + std::string header) const; + + void getPlayerNames(std::vector &names, + bool npcNames); + + void getMobNames(std::vector &names); + + void updatePlayerNames(); + + void updatePlayerColors(); + + void updatePlayerGuild(); + + void parseLevels(std::string levels); + + bool pickUpAll(int x1, int y1, int x2, int y2, + bool serverBuggy = false); + + bool pickUpNearest(int x, int y, int maxdist); + + void optionChanged(const std::string &name); + + protected: + bool validateBeing(Being *aroundBeing, Being* being, + Being::Type type, Being* excluded = 0, + int maxCost = 20) const; + + Being *findNearestLivingBeing(Being *aroundBeing, int maxdist, + Being::Type type, int x, int y, + Being *excluded = 0) const; + + ActorSprites mActors; + ActorSprites mDeleteActors; + Map *mMap; + std::string mSpellHeal1; + std::string mSpellHeal2; + std::string mSpellItenplz; + bool mTargetDeadPlayers; + bool mTargetOnlyReachable; + bool mCyclePlayers; + bool mCycleMonsters; +}; + +extern ActorSpriteManager *actorSpriteManager; + +#endif // ACTORSPRITEMANAGER_H diff --git a/src/animatedsprite.cpp b/src/animatedsprite.cpp new file mode 100644 index 000000000..327e9958e --- /dev/null +++ b/src/animatedsprite.cpp @@ -0,0 +1,241 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "animatedsprite.h" + +#include "graphics.h" +#include "log.h" + +#include "resources/action.h" +#include "resources/animation.h" +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/xml.h" + +#include + +AnimatedSprite::AnimatedSprite(SpriteDef *sprite): + mDirection(DIRECTION_DOWN), + mLastTime(0), + mFrameIndex(0), + mFrameTime(0), + mSprite(sprite), + mAction(0), + mAnimation(0), + mFrame(0) +{ +// assert(mSprite); + + mAlpha = 1.0f; + + // Take possession of the sprite + if (mSprite) + mSprite->incRef(); + + // Play the stand animation by default + play(SpriteAction::STAND); +} + +AnimatedSprite *AnimatedSprite::load(const std::string &filename, int variant) +{ + ResourceManager *resman = ResourceManager::getInstance(); + SpriteDef *s = resman->getSprite(filename, variant); + if (!s) + return 0; + AnimatedSprite *as = new AnimatedSprite(s); + s->decRef(); + return as; +} + +AnimatedSprite::~AnimatedSprite() +{ + if (mSprite) + mSprite->decRef(); +} + +bool AnimatedSprite::reset() +{ + bool ret = mFrameIndex !=0 || mFrameTime != 0 || mLastTime != 0; + + mFrameIndex = 0; + mFrameTime = 0; + mLastTime = 0; + + return ret; +} + +bool AnimatedSprite::play(std::string spriteAction) +{ + if (!mSprite) + return false; + + Action *action = mSprite->getAction(spriteAction); + if (!action) + return false; + + mAction = action; + Animation *animation = mAction->getAnimation(mDirection); + + if (animation && animation != mAnimation && animation->getLength() > 0) + { + mAnimation = animation; + mFrame = mAnimation->getFrame(0); + + reset(); + + return true; + } + + return false; +} + +bool AnimatedSprite::update(int time) +{ + // Avoid freaking out at first frame or when tick_time overflows + if (time < mLastTime || mLastTime == 0) + mLastTime = time; + + // If not enough time has passed yet, do nothing + if (time <= mLastTime || !mAnimation) + return false; + + unsigned int dt = time - mLastTime; + mLastTime = time; + + Animation *animation = mAnimation; + Frame *frame = mFrame; + + if (!updateCurrentAnimation(dt)) + { + // Animation finished, reset to default + play(SpriteAction::STAND); + } + + // Make sure something actually changed + return animation != mAnimation || frame != mFrame; +} + +bool AnimatedSprite::updateCurrentAnimation(unsigned int time) +{ + if (!mFrame || !mAnimation || Animation::isTerminator(*mFrame)) + return false; + + mFrameTime += time; + + while (mFrameTime > (unsigned)mFrame->delay && mFrame->delay > 0) + { + mFrameTime -= (unsigned)mFrame->delay; + mFrameIndex++; + + if (mFrameIndex == mAnimation->getLength()) + mFrameIndex = 0; + + mFrame = mAnimation->getFrame(mFrameIndex); + + if (Animation::isTerminator(*mFrame)) + { + mAnimation = 0; + mFrame = 0; + return false; + } + } + + return true; +} + +bool AnimatedSprite::draw(Graphics *graphics, int posX, int posY) const +{ + if (!mFrame || !mFrame->image) + return false; + + if (mFrame->image->getAlpha() != mAlpha) + mFrame->image->setAlpha(mAlpha); + + return graphics->drawImage(mFrame->image, + posX + mFrame->offsetX, + posY + mFrame->offsetY); +} + +bool AnimatedSprite::setDirection(SpriteDirection direction) +{ + if (mDirection != direction) + { + mDirection = direction; + + if (!mAction) + return false; + + Animation *animation = mAction->getAnimation(mDirection); + + if (animation && animation != mAnimation && animation->getLength() > 0) + { + mAnimation = animation; + mFrame = mAnimation->getFrame(0); + reset(); + } + + return true; + } + + return false; +} + +unsigned int AnimatedSprite::getCurrentFrame() const +{ + return mFrameIndex; +} + +unsigned int AnimatedSprite::getFrameCount() const +{ + if (mAnimation) + return mAnimation->getLength(); + else + return 0; +} + +int AnimatedSprite::getWidth() const +{ + if (mFrame) + return mFrame->image ? mFrame->image->getWidth() : 0; + else + return 0; +} + +int AnimatedSprite::getHeight() const +{ + if (mFrame) + return mFrame->image ? mFrame->image->getHeight() : 0; + else + return 0; +} + +std::string AnimatedSprite::getIdPath() +{ + if (!mSprite) + return ""; + return mSprite->getIdPath(); +} + +const Image* AnimatedSprite::getImage() const +{ + return mFrame ? mFrame->image : 0; +} diff --git a/src/animatedsprite.h b/src/animatedsprite.h new file mode 100644 index 000000000..4ba5a9503 --- /dev/null +++ b/src/animatedsprite.h @@ -0,0 +1,97 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ANIMATEDSPRITE_H +#define ANIMATEDSPRITE_H + +#include "sprite.h" + +#include +#include + +class Animation; +struct Frame; + +/** + * Animates a sprite by adding playback state. + */ +class AnimatedSprite : public Sprite +{ + public: + /** + * Constructor. + * @param sprite the sprite to animate + */ + AnimatedSprite(SpriteDef *sprite); + + /** + * An helper function, which will request the sprite to animate + * from the resource manager. + * + * @param filename the file of the sprite to animate + * @param variant the sprite variant + */ + static AnimatedSprite *load(const std::string &filename, + int variant = 0); + + virtual ~AnimatedSprite(); + + bool reset(); + + bool play(std::string action); + + bool update(int time); + + bool draw(Graphics* graphics, int posX, int posY) const; + + int getWidth() const; + + int getHeight() const; + + const Image* getImage() const; + + bool setDirection(SpriteDirection direction); + + int getNumberOfLayers() + { return 1; } + + std::string getIdPath(); + + unsigned int getCurrentFrame() const; + + unsigned int getFrameCount() const; + + private: + bool updateCurrentAnimation(unsigned int dt); + + SpriteDirection mDirection; /**< The sprite direction. */ + int mLastTime; /**< The last time update was called. */ + + unsigned int mFrameIndex; /**< The index of the current frame. */ + unsigned int mFrameTime; /**< The time since start of frame. */ + + SpriteDef *mSprite; /**< The sprite definition. */ + Action *mAction; /**< The currently active action. */ + Animation *mAnimation; /**< The currently active animation. */ + Frame *mFrame; /**< The currently active frame. */ +}; + +#endif diff --git a/src/animationparticle.cpp b/src/animationparticle.cpp new file mode 100644 index 000000000..d2bc9a5a9 --- /dev/null +++ b/src/animationparticle.cpp @@ -0,0 +1,54 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "animationparticle.h" + +#include "graphics.h" +#include "simpleanimation.h" + +AnimationParticle::AnimationParticle(Map *map, Animation *animation): + ImageParticle(map, 0), + mAnimation(new SimpleAnimation(animation)) +{ +} + +AnimationParticle::AnimationParticle(Map *map, xmlNodePtr animationNode): + ImageParticle(map, 0), + mAnimation(new SimpleAnimation(animationNode)) +{ +} + +AnimationParticle::~AnimationParticle() +{ + delete mAnimation; + mAnimation = 0; + mImage = 0; +} + +bool AnimationParticle::update() +{ + if (mAnimation) + { + mAnimation->update(10); // particle engine is updated every 10ms + mImage = mAnimation->getCurrentImage(); + } + return Particle::update(); +} diff --git a/src/animationparticle.h b/src/animationparticle.h new file mode 100644 index 000000000..0837fec64 --- /dev/null +++ b/src/animationparticle.h @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ANIMATION_PARTICLE_H +#define ANIMATION_PARTICLE_H + +#include "imageparticle.h" + +#include + +class Animation; +class Map; +class SimpleAnimation; + +class AnimationParticle : public ImageParticle +{ + public: + AnimationParticle(Map *map, Animation *animation); + + AnimationParticle(Map *map, xmlNodePtr animationNode); + + ~AnimationParticle(); + + virtual bool update(); + + private: + SimpleAnimation *mAnimation; /**< Used animation for this particle */ +}; + +#endif diff --git a/src/avatar.cpp b/src/avatar.cpp new file mode 100644 index 000000000..ea3f1fe87 --- /dev/null +++ b/src/avatar.cpp @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2008 The Mana World Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "avatar.h" + +#include "localplayer.h" + +#include + +Avatar::Avatar(const std::string &name): + mId(0), + mCharId(0), + mName(name), + mOriginalName(name), + mHp(0), mMaxHp(0), + mDamageHp(0), + mLevel(1), + mOnline(false), + mDisplayBold(false), + mMap(""), + mX(-1), + mY(-1), + mType(AVATAR_PLAYER), + mExp(0), + mGender(GENDER_UNSPECIFIED), + mRace(-1), + mIp("") +{ +} + +std::string Avatar::getComplexName() const +{ + if (mName == mOriginalName || mOriginalName.empty()) + return mName; + else + return mName + "(" + mOriginalName + ")"; +} + +std::string Avatar::getAdditionString() const +{ + if (!getIp().empty()) + return " - " + getIp(); + else + return ""; +} \ No newline at end of file diff --git a/src/avatar.h b/src/avatar.h new file mode 100644 index 000000000..59c0d3d51 --- /dev/null +++ b/src/avatar.h @@ -0,0 +1,189 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef AVATAR_H +#define AVATAR_H + +#include + +enum AvatarType +{ + AVATAR_PLAYER = 0 +// AVATAR_PORTAL +}; + +class Avatar +{ +public: + Avatar(const std::string &name = ""); + + /** + * Returns the avatar's name. + */ + std::string getName() const + { return mName; } + + /** + * Set the avatar's name. + */ + void setName(const std::string &name) + { mName = name; } + + /** + * Returns the avatar's original name. + */ + std::string getOriginalName() const + { return mOriginalName; } + + std::string getComplexName() const; + + virtual std::string getAdditionString() const; + + /** + * Set the avatar's original name. + */ + void setOriginalName(const std::string &name) + { mOriginalName = name; } + + /** + * Returns the avatar's online status. + */ + bool getOnline() const + { return mOnline; } + + /** + * Set the avatar's online status. + */ + void setOnline(bool online) + { mOnline = online; } + + int getHp() const + { return mHp; } + + void setHp(int hp) + { mHp = hp; } + + int getMaxHp() const + { return mMaxHp; } + + void setMaxHp(int maxHp) + { mMaxHp = maxHp; } + + int getDamageHp() const + { return mDamageHp; } + + void setDamageHp(int damageHp) + { mDamageHp = damageHp; } + + bool getDisplayBold() const + { return mDisplayBold; } + + void setDisplayBold(bool displayBold) + { mDisplayBold = displayBold; } + + int getLevel() const + { return mLevel; } + + void setLevel(int level) + { mLevel = level; } + + std::string getMap() const + { return mMap; } + + void setMap(std::string map) + { mMap = map; } + + int getX() const + { return mX; } + + void setX(int x) + { mX = x; } + + int getY() const + { return mY; } + + void setY(int y) + { mY = y; } + + int getType() const + { return mType; } + + void setType(int n) + { mType = n; } + + int getExp() const + { return mExp; } + + void setExp(int n) + { mExp = n; } + + int getID() const + { return mId; } + + void setID(int id) + { mId = id; } + + int getCharId() const + { return mCharId; } + + void setCharId(int id) + { mCharId = id; } + + int getGender() const + { return mGender; } + + void setGender(int g) + { mGender = g; } + + int getRace() const + { return mRace; } + + void setRace(int r) + { mRace = r; } + + const std::string &getIp() const + { return mIp; } + + void setIp(std::string ip) + { mIp = ip; } + +protected: + int mId; + int mCharId; + std::string mName; + std::string mOriginalName; + int mHp; + int mMaxHp; + int mDamageHp; + int mLevel; + bool mOnline; + bool mDisplayBold; + std::string mMap; + int mX; + int mY; + int mType; + int mExp; + int mGender; + int mRace; + std::string mIp; +}; + +#endif // AVATAR_H diff --git a/src/being.cpp b/src/being.cpp new file mode 100644 index 000000000..08893c2f4 --- /dev/null +++ b/src/being.cpp @@ -0,0 +1,2009 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "being.h" + +#include "actorspritemanager.h" +#include "animatedsprite.h" +#include "client.h" +#include "configuration.h" +#include "effectmanager.h" +#include "graphics.h" +#include "guild.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" +#include "particle.h" +#include "party.h" +#include "playerrelations.h" +#include "simpleanimation.h" +#include "sound.h" +#include "sprite.h" +#include "text.h" +#include "statuseffect.h" + +#include "gui/buy.h" +#include "gui/buysell.h" +#include "gui/gui.h" +#include "gui/npcdialog.h" +#include "gui/npcpostdialog.h" +#include "gui/sell.h" +#include "gui/socialwindow.h" +#include "gui/speechbubble.h" +#include "gui/theme.h" +#include "gui/truetypefont.h" +#include "gui/userpalette.h" + +#include "net/charhandler.h" +#include "net/gamehandler.h" +#include "net/net.h" +#include "net/npchandler.h" +#include "net/playerhandler.h" + +#include "resources/beinginfo.h" +#include "resources/colordb.h" +#include "resources/emotedb.h" +#include "resources/image.h" +#include "resources/itemdb.h" +#include "resources/iteminfo.h" +#include "resources/monsterdb.h" +#include "resources/npcdb.h" +#include "resources/resourcemanager.h" + +#include "gui/widgets/chattab.h" + +#include "utils/dtor.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include +#include + +#define CACHE_SIZE 50 +#define HAIR_FILE "hair.xml" + +static const int DEFAULT_BEING_WIDTH = 32; +static const int DEFAULT_BEING_HEIGHT = 32; + +class BeingCacheEntry +{ + public: + BeingCacheEntry(int id): + mId(id), + mName(""), + mPartyName(""), + mLevel(0), + mPvpRank(0), + mTime(0) + { + } + + int getId() const + { return mId; } + + /** + * Returns the name of the being. + */ + const std::string &getName() const + { return mName; } + + /** + * Sets the name for the being. + * + * @param name The name that should appear. + */ + void setName(const std::string &name) + { mName = name; } + + /** + * Following are set from the server (mainly for players) + */ + void setPartyName(const std::string &name) + { mPartyName = name; } + + const std::string &getPartyName() const + { return mPartyName; } + + void setLevel(int n) + { mLevel = n; } + + int getLevel() const + { return mLevel; } + + void setTime(int n) + { mTime = n; } + + int getTime() const + { return mTime; } + + unsigned getPvpRank() const + { return mPvpRank; } + + void setPvpRank(int r) + { mPvpRank = r; } + + std::string getIp() const + { return mIp; } + + void setIp(std::string ip) + { mIp = ip; } + + protected: + int mId; /**< Unique sprite id */ + std::string mName; /**< Name of character */ + std::string mPartyName; + int mLevel; + unsigned int mPvpRank; + int mTime; + std::string mIp; +}; + + +int Being::mNumberOfHairstyles = 1; + +int Being::mUpdateConfigTime = 0; +unsigned int Being::mConfLineLim = 0; +int Being::mSpeechType = 0; +bool Being::mHighlightMapPortals = false; +bool Being::mHighlightMonsterAttackRange = false; +bool Being::mLowTraffic = true; +bool Being::mDrawHotKeys = true; +bool Being::mShowBattleEvents = false; +bool Being::mShowMobHP = false; + +std::list beingInfoCache; + + +// TODO: mWalkTime used by eAthena only +Being::Being(int id, Type type, Uint16 subtype, Map *map): + ActorSprite(id), + mInfo(BeingInfo::Unknown), + mActionTime(0), + mEmotion(0), mEmotionTime(0), + mSpeechTime(0), + mAttackType(1), + mAttackSpeed(350), + mAction(STAND), + mSubType(0xFFFF), + mDirection(DOWN), + mSpriteDirection(DIRECTION_DOWN), + mDispName(0), + mShowName(false), + mEquippedWeapon(NULL), + mText(0), + mLevel(0), + mGender(GENDER_UNSPECIFIED), + mParty(0), + mIsGM(false), + mType(type), + mX(0), mY(0), + mDamageTaken(0), + mHP(0), mMaxHP(0), + mDistance(0), + mIsReachable(REACH_UNKNOWN), + mErased(false), + mEnemy(false), + mIp(""), + mPvpRank(0) +{ + mSpriteRemap = new int[20]; + + for (int f = 0; f < 20; f ++) + mSpriteRemap[f] = f; + + setMap(map); + setSubtype(subtype); + + mSpeechBubble = new SpeechBubble; + + mWalkSpeed = Net::getPlayerHandler()->getDefaultWalkSpeed(); + + if (getType() == PLAYER) + mShowName = config.getBoolValue("visiblenames"); + + config.addListener("visiblenames", this); + + if (getType() == NPC) + setShowName(true); + else + setShowName(mShowName); + + updateColors(); + resetCounters(); +} + +Being::~Being() +{ + config.removeListener("visiblenames", this); + + delete[] mSpriteRemap; + mSpriteRemap = 0; + + delete mSpeechBubble; + mSpeechBubble = 0; + delete mDispName; + mDispName = 0; + delete mText; + mText = 0; +} + +void Being::setSubtype(Uint16 subtype) +{ + if (!mInfo) + return; + + if (subtype == mSubType) + return; + + mSubType = subtype; + + if (getType() == MONSTER) + { + mInfo = MonsterDB::get(mSubType); + if (mInfo) + { + setName(mInfo->getName()); + setupSpriteDisplay(mInfo->getDisplay()); + } + } + else if (getType() == NPC) + { + mInfo = NPCDB::get(mSubType); + if (mInfo) + setupSpriteDisplay(mInfo->getDisplay(), false); + } + else if (getType() == PLAYER) + { + int id = -100 - subtype; + + // Prevent showing errors when sprite doesn't exist + if (!ItemDB::exists(id)) + id = -100; + + setSprite(Net::getCharHandler()->baseSprite(), id); + } +} + +ActorSprite::TargetCursorSize Being::getTargetCursorSize() const +{ + if (!mInfo) + return ActorSprite::TC_SMALL; + + return mInfo->getTargetCursorSize(); +} + +int Being::getTargetOffsetX() const +{ + if (!mInfo) + return 0; + + return mInfo->getTargetOffsetX(); +} + +int Being::getTargetOffsetY() const +{ + if (!mInfo) + return 0; + + return mInfo->getTargetOffsetY(); +} + +unsigned char Being::getWalkMask() const +{ + if (!mInfo) + return 0; + + return mInfo->getWalkMask(); +} + +Map::BlockType Being::getBlockType() const +{ + if (!mInfo) + return Map::BLOCKTYPE_NONE; + + return mInfo->getBlockType(); +} + +void Being::setPosition(const Vector &pos) +{ + Actor::setPosition(pos); + + updateCoords(); + + if (mText) + { + mText->adviseXY((int)pos.x, + (int)pos.y - getHeight() - mText->getHeight() - 6); + } +} + +void Being::setDestination(int dstX, int dstY) +{ + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + if (mMap) + setPath(mMap->findPath(mX, mY, dstX, dstY, getWalkMask())); + return; + } + + // Manaserv's part: + + // We can't calculate anything without a map anyway. + if (!mMap) + return; + + // Don't handle flawed destinations from server... + if (dstX == 0 || dstY == 0) + return; + + // If the destination is unwalkable, don't bother trying to get there + if (!mMap->getWalk(dstX / 32, dstY / 32)) + return; + + Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(), + dstX, dstY); + Path thisPath = mMap->findPixelPath(static_cast(mPos.x), + static_cast(mPos.y), dest.x, dest.y, + static_cast(getCollisionRadius()), + static_cast(getWalkMask())); + + if (thisPath.empty()) + { + // If there is no path but the destination is on the same walkable tile, + // we accept it. + if ((int)mPos.x / 32 == dest.x / 32 + && (int)mPos.y / 32 == dest.y / 32) + { + mDest.x = static_cast(dest.x); + mDest.y = static_cast(dest.y); + } + setPath(Path()); + return; + } + + // The destination is valid, so we set it. + mDest.x = static_cast(dest.x); + mDest.y = static_cast(dest.y); + + setPath(thisPath); +} + +void Being::clearPath() +{ + mPath.clear(); +} + +void Being::setPath(const Path &path) +{ + mPath = path; + + if ((Net::getNetworkType() == ServerInfo::TMWATHENA) && + mAction != MOVE && mAction != DEAD) + { + nextTile(); + mActionTime = tick_time; + } +} + +void Being::setSpeech(const std::string &text, int time) +{ + if (!userPalette) + return; + + // Remove colors + mSpeech = removeColors(text); + + // Trim whitespace + trim(mSpeech); + + unsigned int lineLim = mConfLineLim; + if (lineLim > 0 && mSpeech.length() > lineLim) + mSpeech = mSpeech.substr(0, lineLim); + + trim(mSpeech); + if (mSpeech.length() < 1) + return; + + // Check for links + std::string::size_type start = mSpeech.find('['); + std::string::size_type end = mSpeech.find(']', start); + + while (start != std::string::npos && end != std::string::npos) + { + // Catch multiple embeds and ignore them so it doesn't crash the client. + while ((mSpeech.find('[', start + 1) != std::string::npos) && + (mSpeech.find('[', start + 1) < end)) + { + start = mSpeech.find('[', start + 1); + } + + std::string::size_type position = mSpeech.find('|'); + if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@') + { + mSpeech.erase(end, 1); + mSpeech.erase(start, (position - start) + 1); + } + position = mSpeech.find('@'); + + while (position != std::string::npos) + { + mSpeech.erase(position, 2); + position = mSpeech.find('@'); + } + + start = mSpeech.find('[', start + 1); + end = mSpeech.find(']', start); + } + + if (!mSpeech.empty()) + mSpeechTime = time <= SPEECH_MAX_TIME ? time : SPEECH_MAX_TIME; + + const int speech = mSpeechType; + if (speech == TEXT_OVERHEAD && userPalette) + { + delete mText; + + mText = new Text(mSpeech, + getPixelX(), getPixelY() - getHeight(), + gcn::Graphics::CENTER, + &userPalette->getColor(UserPalette::PARTICLE), + true); + } +} + +void Being::takeDamage(Being *attacker, int amount, AttackType type) +{ + if (!userPalette || !attacker) + return; + + gcn::Font *font = 0; + std::string damage = amount ? toString(amount) : type == FLEE ? + "dodge" : "miss"; + const gcn::Color *color; + + if (gui) + font = gui->getInfoParticleFont(); + + // Selecting the right color + if (type == CRITICAL || type == FLEE) + { + if (attacker == player_node) + { + color = &userPalette->getColor( + UserPalette::HIT_LOCAL_PLAYER_CRITICAL); + } + else + { + color = &userPalette->getColor(UserPalette::HIT_CRITICAL); + } + } + else if (!amount) + { + if (attacker == player_node) + { + // This is intended to be the wrong direction to visually + // differentiate between hits and misses + color = &userPalette->getColor(UserPalette::HIT_LOCAL_PLAYER_MISS); + } + else + { + color = &userPalette->getColor(UserPalette::MISS); + } + } + else if (getType() == MONSTER) + { + if (attacker == player_node) + { + color = &userPalette->getColor( + UserPalette::HIT_LOCAL_PLAYER_MONSTER); + } + else + { + color = &userPalette->getColor( + UserPalette::HIT_PLAYER_MONSTER); + } + } + else if (getType() == PLAYER && attacker != player_node + && this == player_node) + { + // here player was attacked by other player. mark him as enemy. + color = &userPalette->getColor(UserPalette::HIT_PLAYER_PLAYER); + attacker->setEnemy(true); + attacker->updateColors(); + } + else + { + color = &userPalette->getColor(UserPalette::HIT_MONSTER_PLAYER); + } + + if (chatWindow && mShowBattleEvents) + { + if (this == player_node) + { + if (attacker->getType() == PLAYER || amount) + { + chatWindow->battleChatLog(attacker->getName() + " : Hit you -" + + toString(amount), BY_OTHER); + } + } + else if (attacker == player_node && amount) + { + chatWindow->battleChatLog(attacker->getName() + " : You hit " + + getName() + " -" + toString(amount), BY_PLAYER); + } + } + if (font && particleEngine) + { + // Show damage number + particleEngine->addTextSplashEffect(damage, + getPixelX(), getPixelY() - 16, + color, font, true); + } + + if (amount > 0) + { + if (player_node && player_node == this) + player_node->setLastHitFrom(attacker->getName()); + + mDamageTaken += amount; + if (mInfo) + { + sound.playSfx(mInfo->getSound(SOUND_EVENT_HURT)); + if (!mInfo->isStaticMaxHP()) + { + if (!mHP && mInfo->getMaxHP() < mDamageTaken) + mInfo->setMaxHP(mDamageTaken); + } + } + if (mHP && isAlive()) + { + mHP -= amount; + if (mHP < 0) + mHP = 0; + } + + if (getType() == MONSTER) + updateName(); + else if (getType() == PLAYER && socialWindow && getName() != "") + socialWindow->updateAvatar(getName()); + + if (effectManager) + { + if (type != CRITICAL) + effectManager->trigger(26, this); + else + effectManager->trigger(28, this); + } + } +} + +void Being::handleAttack(Being *victim, int damage, + AttackType type _UNUSED_) +{ + if (!victim || !mInfo) + return; + + if (this != player_node) + setAction(Being::ATTACK, 1); + + if (getType() == PLAYER && mEquippedWeapon) + fireMissile(victim, mEquippedWeapon->getMissileParticle()); + else if (mInfo->getAttack(mAttackType)) + fireMissile(victim, mInfo->getAttack(mAttackType)->missileParticle); + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + reset(); + mActionTime = tick_time; + } + + sound.playSfx(mInfo->getSound((damage > 0) ? + SOUND_EVENT_HIT : SOUND_EVENT_MISS)); +} + +void Being::setName(const std::string &name) +{ + if (getType() == NPC) + { + mName = name.substr(0, name.find('#', 0)); + showName(); + } + else + { + mName = name; + + if (getType() == PLAYER && getShowName()) + showName(); + } +} + +void Being::setShowName(bool doShowName) +{ + if (mShowName == doShowName) + return; + + mShowName = doShowName; + + if (doShowName) + { + showName(); + } + else + { + delete mDispName; + mDispName = 0; + } +} + +void Being::setGuildName(const std::string &name) +{ + mGuildName = name; +} + + +void Being::setGuildPos(const std::string &pos _UNUSED_) +{ +// logger->log("Got guild position \"%s\" for being %s(%i)", pos.c_str(), mName.c_str(), mId); +} + +void Being::addGuild(Guild *guild) +{ + if (!guild) + return; + + mGuilds[guild->getId()] = guild; +// guild->addMember(mId, 0, mName); + + if (this == player_node && socialWindow) + socialWindow->addTab(guild); +} + +void Being::removeGuild(int id) +{ + if (this == player_node && socialWindow) + socialWindow->removeTab(mGuilds[id]); + + if (mGuilds[id]) + mGuilds[id]->removeMember(getName()); + mGuilds.erase(id); +} + +Guild *Being::getGuild(const std::string &guildName) const +{ + std::map::const_iterator itr, itr_end = mGuilds.end(); + for (itr = mGuilds.begin(); itr != itr_end; ++itr) + { + Guild *guild = itr->second; + if (guild && guild->getName() == guildName) + return guild; + } + + return 0; +} + +Guild *Being::getGuild(int id) const +{ + std::map::const_iterator itr; + itr = mGuilds.find(id); + if (itr != mGuilds.end()) + return itr->second; + + return 0; +} + +Guild *Being::getGuild() const +{ + std::map::const_iterator itr; + itr = mGuilds.begin(); + if (itr != mGuilds.end()) + return itr->second; + + return 0; +} + +void Being::clearGuilds() +{ + std::map::const_iterator itr, itr_end = mGuilds.end(); + for (itr = mGuilds.begin(); itr != itr_end; ++itr) + { + Guild *guild = itr->second; + + if (guild) + { + if (this == player_node && socialWindow) + socialWindow->removeTab(guild); + + guild->removeMember(mId); + } + } + + mGuilds.clear(); +} + +void Being::setParty(Party *party) +{ + if (party == mParty) + return; + + Party *old = mParty; + mParty = party; + + if (old) + old->removeMember(mId); + + if (party) + party->addMember(mId, mName); + + updateColors(); + + if (this == player_node && socialWindow) + { + if (old) + socialWindow->removeTab(old); + + if (party) + socialWindow->addTab(party); + } +} + +void Being::updateGuild() +{ + if (!player_node) + return; + + Guild *guild = player_node->getGuild(); + if (!guild) + { + clearGuilds(); + updateColors(); + return; + } + if (guild->getMember(getName())) + setGuild(guild); + updateColors(); +} + +void Being::setGuild(Guild *guild) +{ + if (guild == getGuild()) + return; + + Guild *old = getGuild(); + clearGuilds(); + addGuild(guild); + + if (old) + old->removeMember(mName); + +// if (guild) +// guild->addMember(mId, mName); + + updateColors(); + + if (this == player_node && socialWindow) + { + if (old) + socialWindow->removeTab(old); + + if (guild) + socialWindow->addTab(guild); + } +} + +void Being::fireMissile(Being *victim, const std::string &particle) +{ + if (!victim || particle.empty() || !particleEngine) + return; + + Particle *target = particleEngine->createChild(); + + if (!target) + return; + + Particle *missile = target->addEffect(particle, getPixelX(), getPixelY()); + + if (missile) + { + target->moveBy(Vector(0.0f, 0.0f, 32.0f)); + target->setLifetime(1000); + victim->controlParticle(target); + + missile->setDestination(target, 7, 0); + missile->setDieDistance(8); + missile->setLifetime(900); + } + +} + +void Being::setAction(Action action, int attackType _UNUSED_) +{ + std::string currentAction = SpriteAction::INVALID; + + switch (action) + { + case MOVE: + currentAction = SpriteAction::MOVE; + // Note: When adding a run action, + // Differentiate walk and run with action name, + // while using only the ACTION_MOVE. + break; + case SIT: + currentAction = SpriteAction::SIT; + break; + case ATTACK: + if (mEquippedWeapon) + { + currentAction = mEquippedWeapon->getAttackAction(); + reset(); + } + else + { + mAttackType = attackType; + if (!mInfo || !mInfo->getAttack(attackType)) + break; + + currentAction = mInfo->getAttack(attackType)->action; + reset(); + + if (Net::getNetworkType() == ServerInfo::MANASERV + && mInfo->getAttack(attackType)) + { + //attack particle effect + std::string particleEffect = mInfo->getAttack(attackType) + ->particleEffect; + if (!particleEffect.empty() && Particle::enabled) + { + int rotation = 0; + switch (mSpriteDirection) + { + case DIRECTION_DOWN: rotation = 0; break; + case DIRECTION_LEFT: rotation = 90; break; + case DIRECTION_UP: rotation = 180; break; + case DIRECTION_RIGHT: rotation = 270; break; + default: break; + } + ; + if (particleEngine) + { + Particle *p = particleEngine->addEffect( + particleEffect, 0, 0, rotation); + if (p) + controlParticle(p); + } + } + } + } + + break; + case HURT: + //currentAction = SpriteAction::HURT;// Buggy: makes the player stop + // attacking and unable to attack + // again until he moves. + // TODO: fix this! + break; + case DEAD: + currentAction = SpriteAction::DEAD; + if (mInfo) + sound.playSfx(mInfo->getSound(SOUND_EVENT_DIE)); + break; + case STAND: + currentAction = SpriteAction::STAND; + break; + default: + logger->log("Being::setAction unknown action: " + + toString(static_cast(action))); + break; + } + + if (currentAction != SpriteAction::INVALID) + { + play(currentAction); + mAction = action; + } + + if (currentAction != SpriteAction::MOVE) + mActionTime = tick_time; +} + +void Being::setDirection(Uint8 direction) +{ + if (mDirection == direction) + return; + + mDirection = direction; + + // if the direction does not change much, keep the common component + int mFaceDirection = mDirection & direction; + if (!mFaceDirection) + mFaceDirection = direction; + + SpriteDirection dir; + if (mFaceDirection & UP) + dir = DIRECTION_UP; + else if (mFaceDirection & DOWN) + dir = DIRECTION_DOWN; + else if (mFaceDirection & RIGHT) + dir = DIRECTION_RIGHT; + else + dir = DIRECTION_LEFT; + mSpriteDirection = dir; + + CompoundSprite::setDirection(dir); +} + +/** TODO: Used by eAthena only */ +void Being::nextTile() +{ + if (mPath.empty()) + { + setAction(STAND); + return; + } + + Position pos = mPath.front(); + mPath.pop_front(); + + int dir = 0; + if (pos.x > mX) + dir |= RIGHT; + else if (pos.x < mX) + dir |= LEFT; + if (pos.y > mY) + dir |= DOWN; + else if (pos.y < mY) + dir |= UP; + + setDirection(static_cast(dir)); + + if (!mMap->getWalk(pos.x, pos.y, getWalkMask())) + { + setAction(STAND); + return; + } + + mX = pos.x; + mY = pos.y; + setAction(MOVE); + mActionTime += (int)(mWalkSpeed.x / 10); +} + +int Being::getCollisionRadius() const +{ + // FIXME: Get this from XML file + return 16; +} + +void Being::logic() +{ + // Reduce the time that speech is still displayed + if (mSpeechTime > 0) + mSpeechTime--; + + // Remove text and speechbubbles if speech boxes aren't being used + if (mSpeechTime == 0 && mText) + { + delete mText; + mText = 0; + } + + int frameCount = static_cast(getFrameCount()); + if ((Net::getNetworkType() == ServerInfo::MANASERV) && (mAction != DEAD)) + { + const Vector dest = (mPath.empty()) ? + mDest : Vector(static_cast(mPath.front().x), + static_cast(mPath.front().y)); + + // This is a hack that stops NPCs from running off the map... + if (mDest.x <= 0 && mDest.y <= 0) + return; + + // The Vector representing the difference between current position + // and the next destination path node. + Vector dir = dest - mPos; + + const float nominalLength = dir.length(); + + // When we've not reached our destination, move to it. + if (nominalLength > 0.0f && !mWalkSpeed.isNull()) + { + // The deplacement of a point along a vector is calculated + // using the Unit Vector (â) multiplied by the point speed. + // â = a / ||a|| (||a|| is the a length.) + // Then, diff = (dir/||dir||) * speed. + const Vector normalizedDir = dir.normalized(); + Vector diff(normalizedDir.x * mWalkSpeed.x, + normalizedDir.y * mWalkSpeed.y); + + // Test if we don't miss the destination by a move too far: + if (diff.length() > nominalLength) + { + setPosition(mPos + dir); + + // Also, if the destination is reached, try to get the next + // path point, if existing. + if (!mPath.empty()) + mPath.pop_front(); + } + // Otherwise, go to it using the nominal speed. + else + { + setPosition(mPos + diff); + } + + if (mAction != MOVE) + setAction(MOVE); + + // Update the player sprite direction. + // N.B.: We only change this if the distance is more than one pixel. + if (nominalLength > 1.0f) + { + int direction = 0; + const float dx = std::abs(dir.x); + float dy = std::abs(dir.y); + + // When not using mouse for the player, we slightly prefer + // UP and DOWN position, especially when walking diagonally. + if (player_node && this == player_node && + !player_node->isPathSetByMouse()) + { + dy = dy + 2; + } + + if (dx > dy) + direction |= (dir.x > 0) ? RIGHT : LEFT; + else + direction |= (dir.y > 0) ? DOWN : UP; + + setDirection(static_cast(direction)); + } + } + else if (!mPath.empty()) + { + // If the current path node has been reached, + // remove it and go to the next one. + mPath.pop_front(); + } + else if (mAction == MOVE) + { + setAction(STAND); + } + } + else if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + + switch (mAction) + { + case STAND: + case SIT: + case DEAD: + case HURT: + default: + break; + + case MOVE: + { + if (getWalkSpeed().x + && (int) ((get_elapsed_time(mActionTime) * frameCount) + / getWalkSpeed().x) >= frameCount) + { + nextTile(); + } + break; + } + + case ATTACK: + { + std::string particleEffect = ""; + + if (!mActionTime) + break; + + int curFrame = 0; + if (mAttackSpeed) + { + curFrame = (get_elapsed_time(mActionTime) * frameCount) + / mAttackSpeed; + } + + //attack particle effect + if (mEquippedWeapon) + { + particleEffect = mEquippedWeapon->getParticleEffect(); + + if (!particleEffect.empty() && + findSameSubstring(particleEffect, + paths.getStringValue("particles")).empty()) + { + particleEffect = paths.getStringValue("particles") + + particleEffect; + } + } + else if (mInfo && mInfo->getAttack(mAttackType)) + { + particleEffect = mInfo->getAttack(mAttackType) + ->particleEffect; + } + + if (particleEngine && !particleEffect.empty() + && Particle::enabled && curFrame == 1) + { + int rotation = 0; + + switch (mDirection) + { + case DOWN: rotation = 0; break; + case LEFT: rotation = 90; break; + case UP: rotation = 180; break; + case RIGHT: rotation = 270; break; + default: break; + } + Particle *p = particleEngine->addEffect(particleEffect, + 0, 0, rotation); + controlParticle(p); + } + + if (curFrame >= frameCount) + nextTile(); + + break; + } + } + + // Update pixel coordinates + setPosition(static_cast(mX * 32 + 16 + getXOffset()), + static_cast(mY * 32 + 32 + getYOffset())); + } + + if (mEmotion != 0) + { + mEmotionTime--; + if (mEmotionTime == 0) + mEmotion = 0; + } + + ActorSprite::logic(); + +// int frameCount = static_cast(getFrameCount()); + if (frameCount < 10) + frameCount = 10; + + if (!isAlive() && getWalkSpeed().x + && Net::getGameHandler()->removeDeadBeings() + && (int) ((get_elapsed_time(mActionTime) + / getWalkSpeed().x) >= static_cast(frameCount))) + { + if (getType() != PLAYER && actorSpriteManager) + actorSpriteManager->destroy(this); + } +} + +void Being::drawEmotion(Graphics *graphics, int offsetX, int offsetY) +{ + if (!mEmotion) + return; + + const int px = getPixelX() - offsetX - 16; + const int py = getPixelY() - offsetY - 64 - 32; + const int emotionIndex = mEmotion - 1; + + if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast()) + EmoteDB::getAnimation(emotionIndex)->draw(graphics, px, py); +} + +void Being::drawSpeech(int offsetX, int offsetY) +{ + if (!mSpeechBubble) + return; + + const int px = getPixelX() - offsetX; + const int py = getPixelY() - offsetY; + const int speech = mSpeechType; + + // Draw speech above this being + if (mSpeechTime == 0) + { + if (mSpeechBubble->isVisible()) + mSpeechBubble->setVisible(false); + } + else if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE || + speech == NO_NAME_IN_BUBBLE)) + { + const bool showName = (speech == NAME_IN_BUBBLE); + + delete mText; + mText = 0; + + mSpeechBubble->setCaption(showName ? mName : "", mTextColor); + + mSpeechBubble->setText(mSpeech, showName); + mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2), + py - getHeight() + - (mSpeechBubble->getHeight())); + mSpeechBubble->setVisible(true); + } + else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD) + { + mSpeechBubble->setVisible(false); + + if (!mText && userPalette) + { + mText = new Text(mSpeech, + getPixelX(), getPixelY() - getHeight(), + gcn::Graphics::CENTER, + &userPalette->getColor(UserPalette::PARTICLE), + true); + } + } + else if (speech == NO_SPEECH) + { + mSpeechBubble->setVisible(false); + + delete mText; + mText = 0; + } +} + +/** TODO: eAthena only */ +int Being::getOffset(char pos, char neg) const +{ + // Check whether we're walking in the requested direction + if (mAction != MOVE || !(mDirection & (pos | neg))) + return 0; + + int offset = 0; + + if (mMap) + { + offset = (pos == LEFT && neg == RIGHT) ? + (int)((static_cast(get_elapsed_time(mActionTime)) + * static_cast(mMap->getTileWidth())) + / static_cast(mWalkSpeed.x)) : + (int)((static_cast(get_elapsed_time(mActionTime)) + * static_cast(mMap->getTileHeight())) + / static_cast(mWalkSpeed.y)); + } + + // We calculate the offset _from_ the _target_ location + offset -= 32; + if (offset > 0) + offset = 0; + + // Going into negative direction? Invert the offset. + if (mDirection & pos) + offset = -offset; + + return offset; +} + +int Being::getWidth() const +{ + return std::max(CompoundSprite::getWidth(), DEFAULT_BEING_WIDTH); +} + +int Being::getHeight() const +{ + return std::max(CompoundSprite::getHeight(), DEFAULT_BEING_HEIGHT); +} + +void Being::updateCoords() +{ + if (!mDispName) + return; + + // Monster names show above the sprite instead of below it + if (getType() == MONSTER) + { + mDispName->adviseXY(getPixelX(), + getPixelY() - getHeight() - mDispName->getHeight()); + } + else + { + mDispName->adviseXY(getPixelX(), getPixelY()); + } +} + +void Being::optionChanged(const std::string &value) +{ + if (getType() == PLAYER && value == "visiblenames") + setShowName(config.getBoolValue("visiblenames")); +} + +void Being::flashName(int time) +{ + if (mDispName) + mDispName->flash(time); +} + +void Being::showName() +{ + delete mDispName; + mDispName = 0; + std::string mDisplayName(mName); + + if (getType() != MONSTER + && (config.getBoolValue("showgender") + || config.getBoolValue("showlevel"))) + { + mDisplayName += " "; + if (config.getBoolValue("showlevel") && getLevel() != 0) + mDisplayName += toString(getLevel()); + + if (config.getBoolValue("showgender")) + { + if (getGender() == GENDER_FEMALE) + mDisplayName += "\u2640"; + else if (getGender() == GENDER_MALE) + mDisplayName += "\u2642"; + } + } + + if (getType() == MONSTER) + { + if (config.getBoolValue("showMonstersTakedDamage")) + mDisplayName += ", " + toString(getDamageTaken()); + } + + gcn::Font *font = 0; + if (player_node && player_node->getTarget() == this + && getType() != MONSTER) + { + font = boldFont; + } + + mDispName = new FlashText(mDisplayName, getPixelX(), getPixelY(), + gcn::Graphics::CENTER, mNameColor, font); + + updateCoords(); +} + +void Being::updateColors() +{ + if (userPalette) + { + if (getType() == MONSTER) + { + mNameColor = &userPalette->getColor(UserPalette::MONSTER); + mTextColor = &userPalette->getColor(UserPalette::MONSTER); + } + else if (getType() == NPC) + { + mNameColor = &userPalette->getColor(UserPalette::NPC); + mTextColor = &userPalette->getColor(UserPalette::NPC); + } + else if (this == player_node) + { + mNameColor = &userPalette->getColor(UserPalette::SELF); + mTextColor = &Theme::getThemeColor(Theme::PLAYER); + } + else + { + mTextColor = &userPalette->getColor(Theme::PLAYER); + + mErased = false; + + if (mIsGM) + { + mTextColor = &userPalette->getColor(UserPalette::GM); + mNameColor = &userPalette->getColor(UserPalette::GM); + } + else if (mEnemy) + { + mNameColor = &userPalette->getColor(UserPalette::MONSTER); + } + else if (mParty && mParty == player_node->getParty()) + { + mNameColor = &userPalette->getColor(UserPalette::PARTY); + } + else if (getGuild() && getGuild() == player_node->getGuild()) + { + mNameColor = &userPalette->getColor(UserPalette::GUILD); + } + else if (player_relations.getRelation(mName) == + PlayerRelation::FRIEND) + { + mNameColor = &userPalette->getColor(UserPalette::FRIEND); + } + else if (player_relations.getRelation(mName) == + PlayerRelation::DISREGARDED) + { + mNameColor = &userPalette->getColor(UserPalette::DISREGARDED); + } + else if (player_relations.getRelation(mName) == + PlayerRelation::IGNORED) + { + mNameColor = &userPalette->getColor(UserPalette::IGNORED); + } + else if (player_relations.getRelation(mName) == + PlayerRelation::ERASED) + { + mNameColor = &userPalette->getColor(UserPalette::ERASED); + mErased = true; + } + else + { + mNameColor = &userPalette->getColor(UserPalette::PC); + } + } + + if (mDispName) + mDispName->setColor(mNameColor); + } +} + +void Being::setSprite(unsigned int slot, int id, const std::string &color, + bool isWeapon) +{ + if (slot >= Net::getCharHandler()->maxSprite()) + return; + + if (slot >= size()) + ensureSize(slot + 1); + + if (slot >= mSpriteIDs.size()) + mSpriteIDs.resize(slot + 1, 0); + + if (slot >= mSpriteColors.size()) + mSpriteColors.resize(slot + 1, ""); + + // id = 0 means unequip + if (id == 0) + { + removeSprite(slot); + + if (isWeapon) + mEquippedWeapon = NULL; + } + else + { + std::string filename = ItemDB::get(id).getSprite(mGender); + AnimatedSprite *equipmentSprite = NULL; + + if (!filename.empty()) + { + if (!color.empty()) + filename += "|" + color; + + equipmentSprite = AnimatedSprite::load( + paths.getStringValue("sprites") + filename); + } + + if (equipmentSprite) + equipmentSprite->setDirection(getSpriteDirection()); + + CompoundSprite::setSprite(slot, equipmentSprite); + + if (isWeapon) + mEquippedWeapon = &ItemDB::get(id); + + setAction(mAction); + } + + mSpriteIDs[slot] = id; + mSpriteColors[slot] = color; + recalcSpritesOrder(); +} + +void Being::setSpriteID(unsigned int slot, int id) +{ + setSprite(slot, id, mSpriteColors[slot]); +} + +void Being::setSpriteColor(unsigned int slot, const std::string &color) +{ + setSprite(slot, mSpriteIDs[slot], color); +} + +int Being::getNumberOfLayers() const +{ + return CompoundSprite::getNumberOfLayers(); +} + +void Being::load() +{ + // Hairstyles are encoded as negative numbers. Count how far negative + // we can go. + int hairstyles = 1; + + while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE) != + paths.getStringValue("spriteErrorFile")) + { + hairstyles++; + } + + mNumberOfHairstyles = hairstyles; +} + +void Being::updateName() +{ + if (mShowName) + showName(); +} + +void Being::reReadConfig() +{ + if (mUpdateConfigTime + 1 < cur_time) + { + mHighlightMapPortals = config.getBoolValue("highlightMapPortals"); + mConfLineLim = config.getIntValue("chatMaxCharLimit"); + mSpeechType = config.getIntValue("speech"); + mHighlightMonsterAttackRange = + config.getBoolValue("highlightMonsterAttackRange"); + mLowTraffic = config.getBoolValue("lowTraffic"); + mDrawHotKeys = config.getBoolValue("drawHotKeys"); + mShowBattleEvents = config.getBoolValue("showBattleEvents"); + mShowMobHP = config.getBoolValue("showMobHP"); + + mUpdateConfigTime = cur_time; + } +} + +bool Being::updateFromCache() +{ + BeingCacheEntry *entry = Being::getCacheEntry(getId()); + if (entry && !entry->getName().empty() + && entry->getTime() + 120 < cur_time) + { + setName(entry->getName()); + setPartyName(entry->getPartyName()); + setLevel(entry->getLevel()); + setPvpRank(entry->getPvpRank()); + setIp(entry->getIp()); + if (getType() == PLAYER) + updateColors(); + return true; + } + return false; +} + +void Being::addToCache() +{ + BeingCacheEntry *entry = Being::getCacheEntry(getId()); + if (!entry) + { + entry = new BeingCacheEntry(getId()); + beingInfoCache.push_front(entry); + + if (beingInfoCache.size() >= CACHE_SIZE) + beingInfoCache.pop_back(); + } + entry->setName(getName()); + entry->setLevel(getLevel()); + entry->setPartyName(getPartyName()); + entry->setTime(cur_time); + entry->setPvpRank(getPvpRank()); + entry->setIp(getIp()); +} + +BeingCacheEntry* Being::getCacheEntry(int id) +{ + for (std::list::iterator i = beingInfoCache.begin(); + i != beingInfoCache.end(); ++i) + { + if (id == (*i)->getId()) + { + // Raise priority: move it to front + if ((*i)->getTime() + 120 < cur_time) + { + beingInfoCache.splice(beingInfoCache.begin(), + beingInfoCache, i); + } + return *i; + } + } + return 0; +} + + +void Being::setGender(Gender gender) +{ + if (gender != mGender) + { + mGender = gender; + + // Reload all subsprites + for (unsigned int i = 0; i < mSpriteIDs.size(); i++) + { + if (mSpriteIDs.at(i) != 0) + setSprite(i, mSpriteIDs.at(i), mSpriteColors.at(i)); + } + + updateName(); + } +} + +void Being::setGM(bool gm) +{ + mIsGM = gm; + + updateColors(); +} + +bool Being::canTalk() +{ + return mType == NPC; +} + +void Being::talkTo() +{ + if (!Client::limitPackets(PACKET_NPC_TALK)) + return; + + Net::getNpcHandler()->talk(mId); +} + +bool Being::isTalking() +{ + return NpcDialog::isActive() || BuyDialog::isActive() || + SellDialog::isActive() || BuySellDialog::isActive() || + NpcPostDialog::isActive(); +} + +bool Being::draw(Graphics *graphics, int offsetX, int offsetY) const +{ + bool res = true; + if (!mErased) + res = ActorSprite::draw(graphics, offsetX, offsetY); + + return res; +} + +void Being::drawSprites(Graphics* graphics, int posX, int posY) const +{ +// CompoundSprite::drawSprites(graphics, posX, posY); + for (int f = 0; f < getNumberOfLayers(); f ++) + { + Sprite *sprite = getSprite(mSpriteRemap[f]); + if (sprite) + { + sprite->setAlpha(mAlpha); + sprite->draw(graphics, posX, posY); + } + } +} + +void Being::drawSpritesSDL(Graphics* graphics, int posX, int posY) const +{ +// CompoundSprite::drawSprites(graphics, posX, posY); + +// logger->log("getNumberOfLayers: %d", getNumberOfLayers()); + + for (unsigned f = 0; f < size(); f ++) + { + Sprite *sprite = getSprite(mSpriteRemap[f]); + if (sprite) + sprite->draw(graphics, posX, posY); + } +} + +bool Being::drawSpriteAt(Graphics *graphics, int x, int y) const +{ + bool res = true; + + if (!mErased) + res = ActorSprite::drawSpriteAt(graphics, x, y); + + if (mHighlightMapPortals && mMap && mSubType == 45 && !mMap->getHasWarps()) + { + graphics->setColor(userPalette-> + getColorWithAlpha(UserPalette::PORTAL_HIGHLIGHT)); + + graphics->fillRectangle(gcn::Rectangle( + x, y, 32, 32)); + +/* + int num = socialWindow->getPortalIndex(getTileX(), getTileY()); + if (num >= 0) + { + std::string str = outfitWindow->keyName(num); + if (str.length() > 4) + str = str.substr(0, 4); + gcn::Font *font = gui->getFont(); + graphics->setColor(userPalette->getColor(UserPalette::BEING)); + font->drawString(graphics, str, x, y); + } +*/ + if (mDrawHotKeys && !mName.empty()) + { + gcn::Font *font = gui->getFont(); + if (font) + { + graphics->setColor(userPalette->getColor(UserPalette::BEING)); + font->drawString(graphics, mName, x, y); + } + } + } + + if (mHighlightMonsterAttackRange && getType() == ActorSprite::MONSTER + && isAlive()) + { + const int attackRange = 32; + + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::MONSTER_ATTACK_RANGE)); + + graphics->fillRectangle(gcn::Rectangle( + x - attackRange, y - attackRange, + 2 * attackRange + 32, 2 * attackRange + 32)); + } + + if (mShowMobHP && player_node && player_node->getTarget() == this + && getType() == MONSTER) + { + // show hp bar here + drawHpBar(graphics, x - 50 + 16, y + 32 - 6, 2 * 50, 4); + } + return res; +} + +void Being::drawHpBar(Graphics *graphics, int x, int y, + int width, int height) const +{ + if (!mInfo) + return; + + int maxHP = mMaxHP; + + if (!maxHP) + maxHP = mInfo->getMaxHP(); + + if (maxHP <= 0) + return; + + if (!mHP && maxHP < mHP) + return; + + float p; + + if (mHP) + { + p = static_cast(maxHP) / static_cast(mHP); + } + else if (maxHP != mDamageTaken) + { + p = static_cast(maxHP) + / static_cast(maxHP - mDamageTaken); + } + else + { + p = 1; + } + + if (p <= 0 || p > width) + return; + + int dx = width / p; + + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::MONSTER_HP)); + + graphics->fillRectangle(gcn::Rectangle( + x, y, dx, height)); + + if (width - dx <= 0) + return; + + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::MONSTER_HP2)); + + graphics->fillRectangle(gcn::Rectangle( + x + dx, y, width - dx, height)); +} + +void Being::setHP(int hp) +{ + mHP = hp; + if (mMaxHP < mHP) + mMaxHP = mHP; +} + +void Being::setMaxHP(int hp) +{ + mMaxHP = hp; + if (mMaxHP < mHP) + mMaxHP = mHP; +} + +void Being::resetCounters() +{ + mMoveTime = 0; + mAttackTime = 0; + mTalkTime = 0; + mOtherTime = 0; + mTestTime = cur_time; +} + +void Being::recalcSpritesOrder() +{ +// logger->log("recalcSpritesOrder"); + unsigned sz = size(); + if (sz < 1) + return; + + std::vector slotRemap; + std::map itemSlotRemap; + +// logger->log("preparation start"); + std::vector::iterator it; + for (unsigned slot = 0; slot < sz; slot ++) + { + slotRemap.push_back(slot); + + int id = mSpriteIDs[slot]; + if (!id) + continue; + + const ItemInfo &info = ItemDB::get(id); + if (info.getDrawBefore() > 0) + { + int id2 = mSpriteIDs[info.getDrawBefore()]; + std::map::iterator orderIt = itemSlotRemap.find(id2); + if (orderIt != itemSlotRemap.end()) + { +// logger->log("found duplicate (before)"); + const ItemInfo &info2 = ItemDB::get(id2); + if (info.getDrawPriority() < info2.getDrawPriority()) + { +// logger->log("old more priority"); + continue; + } + else + { +// logger->log("new more priority"); + itemSlotRemap.erase(id2); + } + } + + itemSlotRemap[id] = -info.getDrawBefore(); +// logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]); + } + else if (info.getDrawAfter() > 0) + { + int id2 = mSpriteIDs[info.getDrawAfter()]; + std::map::iterator orderIt = itemSlotRemap.find(id2); + if (orderIt != itemSlotRemap.end()) + { +// logger->log("found duplicate (after)"); + const ItemInfo &info2 = ItemDB::get(id2); + if (info.getDrawPriority() < info2.getDrawPriority()) + { +// logger->log("old more priority"); + continue; + } + else + { +// logger->log("new more priority"); + itemSlotRemap.erase(id2); + } + } + + itemSlotRemap[id] = info.getDrawAfter(); +// logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]); + } + } +// logger->log("preparation end"); + + int lastRemap = 0; + int cnt = 0; + + while (cnt < 15 && lastRemap >= 0) + { + int lastRemap = -1; + cnt ++; +// logger->log("iteration"); + + for (unsigned slot0 = 0; slot0 < sz; slot0 ++) + { + int slot = searchSlotValue(slotRemap, slot0); + int val = slotRemap.at(slot); + int id = mSpriteIDs[val]; + int idx = -1; + int idx1 = -1; +// logger->log("item %d, id=%d", slot, id); + int reorder = 0; + std::map::iterator orderIt = itemSlotRemap.find(id); + if (orderIt != itemSlotRemap.end()) + reorder = orderIt->second; + + if (reorder < 0) + { +// logger->log("move item %d before %d", slot, -reorder); + searchSlotValueItr(it, idx, slotRemap, -reorder); + if (it == slotRemap.end()) + return; + searchSlotValueItr(it, idx1, slotRemap, val); + if (it == slotRemap.end()) + return; + lastRemap = idx1; + if (idx1 + 1 != idx) + { + slotRemap.erase(it); + searchSlotValueItr(it, idx, slotRemap, -reorder); + slotRemap.insert(it, val); + } + } + else if (reorder > 0) + { +// logger->log("move item %d after %d", slot, reorder); + searchSlotValueItr(it, idx, slotRemap, reorder); + searchSlotValueItr(it, idx1, slotRemap, val); + if (it == slotRemap.end()) + return; + lastRemap = idx1; + if (idx1 != idx + 1) + { + slotRemap.erase(it); + searchSlotValueItr(it, idx, slotRemap, reorder); + if (it != slotRemap.end()) + { + it ++; + if (it != slotRemap.end()) + slotRemap.insert(it, val); + else + slotRemap.push_back(val); + } + else + { + slotRemap.push_back(val); + } + } + } + } + } + +// logger->log("after remap"); + for (unsigned slot = 0; slot < sz; slot ++) + { + mSpriteRemap[slot] = slotRemap[slot]; +// logger->log("slot %d = %d", slot, mSpriteRemap[slot]); + } +} + +int Being::searchSlotValue(std::vector &slotRemap, int val) +{ + for (unsigned slot = 0; slot < size(); slot ++) + { + if (slotRemap[slot] == val) + return slot; + } + return getNumberOfLayers() - 1; +} + +void Being::searchSlotValueItr(std::vector::iterator &it, int &idx, + std::vector &slotRemap, int val) +{ +// logger->log("searching %d", val); + it = slotRemap.begin(); + idx = 0; + while(it != slotRemap.end()) + { +// logger->log("testing %d", *it); + if (*it == val) + { +// logger->log("found at %d", idx); + return; + } + it ++; + idx ++; + } +// logger->log("not found"); + idx = -1; + return; +} diff --git a/src/being.h b/src/being.h new file mode 100644 index 000000000..09466f5af --- /dev/null +++ b/src/being.h @@ -0,0 +1,770 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BEING_H +#define BEING_H + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +#include "actorsprite.h" +#include "configlistener.h" +#include "map.h" +#include "particlecontainer.h" +#include "position.h" +#include "vector.h" + +#include + +#include + +#include +#include +#include +#include + +#define FIRST_IGNORE_EMOTE 14 +#define STATUS_EFFECTS 32 + +#define SPEECH_TIME 500 +#define SPEECH_MAX_TIME 1000 + +class BeingCacheEntry; +class BeingInfo; +class FlashText; +class Guild; +class ItemInfo; +class Item; +class Particle; +class Party; +class Position; +class SpeechBubble; +class Text; + +extern volatile int cur_time; + +enum Gender +{ + GENDER_MALE = 0, + GENDER_FEMALE = 1, + GENDER_UNSPECIFIED = 2 +}; + +class Being : public ActorSprite, public ConfigListener +{ + public: + /** + * Action the being is currently performing + * WARNING: Has to be in sync with the same enum in the Being class + * of the server! + */ + enum Action + { + STAND = 0, + MOVE, + ATTACK, + SIT, + DEAD, + HURT + }; + + enum Speech + { + NO_SPEECH = 0, + TEXT_OVERHEAD, + NO_NAME_IN_BUBBLE, + NAME_IN_BUBBLE + }; + + enum AttackType + { + HIT = 0x00, + CRITICAL = 0x0a, + MULTI = 0x08, + REFLECT = 0x04, + FLEE = 0x0b + }; + + enum Reachable + { + REACH_UNKNOWN = 0, + REACH_YES = 1, + REACH_NO = 2 + }; + + /** + * Directions, to be used as bitmask values + */ + enum BeingDirection + { + DOWN = 1, + LEFT = 2, + UP = 4, + RIGHT = 8 + }; + + /** + * Constructor. + * + * @param id a unique being id + * @param subtype partly determines the type of the being + * @param map the map the being is on + */ + Being(int id, Type type, Uint16 subtype, Map *map); + + virtual ~Being(); + + Type getType() const + { return mType; } + + /** + * Removes all path nodes from this being. + */ + void clearPath(); + + /** + * Returns the time spent in the current action. + */ + int getActionTime() const + { return mActionTime; } + + /** + * Set the current action time. + * @see Ea::BeingHandler that set it to tick time. + */ + void setActionTime(int actionTime) + { mActionTime = actionTime; } + + /** + * Makes this being take the next tile of its path. + * TODO: Used by eAthena only? + */ + virtual void nextTile(); + + /** + * Get the current X pixel offset. + * TODO: Used by eAthena only? + */ + int getXOffset() const + { return getOffset(LEFT, RIGHT); } + + /** + * Get the current Y pixel offset. + * TODO: Used by eAthena only? + */ + int getYOffset() const + { return getOffset(UP, DOWN); } + + /** + * Creates a path for the being from current position to ex and ey + */ + void setDestination(int ex, int ey); + + /** + * Returns the destination for this being. + */ + const Vector &getDestination() const + { return mDest; } + + /** + * Returns the tile x coord + */ + int getTileX() const + { return mX; } + + /** + * Returns the tile y coord + */ + int getTileY() const + { return mY; } + + /** + * Sets the tile x and y coord + */ + void setTileCoords(int x, int y) + { mX = x; mY = y; } + + /** + * Puts a "speech balloon" above this being for the specified amount + * of time. + * + * @param text The text that should appear. + * @param time The amount of time the text should stay in milliseconds. + */ + void setSpeech(const std::string &text, int time = 500); + + /** + * Puts a damage bubble above this being. + * + * @param attacker the attacking being + * @param damage the amount of damage recieved (0 means miss) + * @param type the attack type + */ + void takeDamage(Being *attacker, int damage, AttackType type); + + /** + * Handles an attack of another being by this being. + * + * @param victim the victim being + * @param damage the amount of damage dealt (0 means miss) + * @param type the attack type + */ + virtual void handleAttack(Being *victim, int damage, AttackType type); + + /** + * Returns the name of the being. + */ + const std::string &getName() const + { return mName; } + + /** + * Sets the name for the being. + * + * @param name The name that should appear. + */ + void setName(const std::string &name); + + bool getShowName() const + { return mShowName; } + + void setShowName(bool doShowName); + + /** + * Sets the name of the party the being is in. Shown in BeingPopup. + */ + void setPartyName(const std::string &name) + { mPartyName = name; } + + const std::string &getPartyName() const + { return mPartyName; } + + const std::string &getGuildName() const + { return mGuildName; } + + /** + * Sets the name of the primary guild the being is in. Shown in + * BeingPopup (eventually). + */ + void setGuildName(const std::string &name); + + void setGuildPos(const std::string &pos); + + /** + * Adds a guild to the being. + */ + void addGuild(Guild *guild); + + /** + * Removers a guild from the being. + */ + void removeGuild(int id); + + /** + * Returns a pointer to the specified guild that the being is in. + */ + Guild *getGuild(const std::string &guildName) const; + + /** + * Returns a pointer to the specified guild that the being is in. + */ + Guild *getGuild(int id) const; + + /** + * Returns a pointer to the specified guild that the being is in. + */ + Guild *getGuild() const; + + /** + * Returns all guilds the being is in. + */ + const std::map &getGuilds() const + { return mGuilds; } + + /** + * Removes all guilds the being is in. + */ + void clearGuilds(); + + /** + * Get number of guilds the being belongs to. + */ + short getNumberOfGuilds() const + { return static_cast(mGuilds.size()); } + + bool isInParty() const + { return mParty != NULL; } + + void setParty(Party *party); + + void setGuild(Guild *guild); + + void updateGuild(); + + Party *getParty() const + { return mParty; } + + int getSpritesCount() + { return static_cast(size()); } + + /** + * Sets visible equipments for this being. + */ + void setSprite(unsigned int slot, int id, + const std::string &color = "", bool isWeapon = false); + + void setSpriteID(unsigned int slot, int id); + + void setSpriteColor(unsigned int slot, const std::string &color = ""); + + /** + * Get the number of hairstyles implemented + */ + static int getNumOfHairstyles() + { return mNumberOfHairstyles; } + + /** + * Get the number of layers used to draw the being + */ + int getNumberOfLayers() const; + + /** + * Performs being logic. + */ + virtual void logic(); + + /** + * Draws the speech text above the being. + */ + void drawSpeech(int offsetX, int offsetY); + + /** + * Draws the emotion picture above the being. + */ + void drawEmotion(Graphics *graphics, int offsetX, int offsetY); + + Uint16 getSubType() const + { return mSubType; } + + /** + * Set Being's subtype (mostly for view for monsters and NPCs) + */ + void setSubtype(Uint16 subtype); + + const BeingInfo *getInfo() const + { return mInfo; } + + TargetCursorSize getTargetCursorSize() const; + + int getTargetOffsetX() const; + + int getTargetOffsetY() const; + + /** + * Gets the way the object is blocked by other objects. + */ + unsigned char getWalkMask() const; + + /** + * Gets the way the monster blocks pathfinding for other objects + */ + Map::BlockType getBlockType() const; + + /** + * Sets the walk speed. + * in pixels per second for eAthena, + * in tiles per second for Manaserv. + */ + void setWalkSpeed(Vector speed) + { mWalkSpeed = speed; } + + /** + * Gets the walk speed. + * in pixels per second for eAthena, + * in tiles per second for Manaserv (0.1 precision). + */ + Vector getWalkSpeed() const + { return mWalkSpeed; } + + /** + * Sets the attack speed. + * @todo In what unit? + */ + void setAttackSpeed(int speed) + { mAttackSpeed = speed; } + + /** + * Gets the attack speed. + * @todo In what unit? + */ + int getAttackSpeed() const + { return mAttackSpeed; } + + /** + * Sets the current action. + */ + virtual void setAction(Action action, int attackType = 0); + + /** + * Get the being's action currently performed. + */ + Action getCurrentAction() const + { return mAction; } + + /** + * Returns whether this being is still alive. + */ + bool isAlive() const + { return mAction != DEAD; } + + /** + * Returns the current direction. + */ + Uint8 getDirection() const + { return mDirection; } + + /** + * Sets the current direction. + */ + virtual void setDirection(Uint8 direction); + + /** + * Returns the direction the being is facing. + */ + SpriteDirection getSpriteDirection() const + { return SpriteDirection(mSpriteDirection); } + + void setPosition(const Vector &pos); + + /** + * Overloaded method provided for convenience. + * + * @see setPosition(const Vector &pos) + */ + inline void setPosition(float x, float y, float z = 0.0f) + { setPosition(Vector(x, y, z)); } + + /** + * Returns the horizontal size of the current base sprite of the being. + */ + virtual int getWidth() const; + + /** + * Returns the vertical size of the current base sprite of the being. + */ + virtual int getHeight() const; + + /** + * Returns the being's pixel radius used to detect collisions. + */ + virtual int getCollisionRadius() const; + + /** + * Shoots a missile particle from this being, to target being + */ + void fireMissile(Being *target, const std::string &particle); + + /** + * Returns the path this being is following. An empty path is returned + * when this being isn't following any path currently. + */ + const Path &getPath() const + { return mPath; } + + int getDistance() + { return mDistance; } + + void setDistance(int n) + { mDistance = n; } + + /** + * Set the Emoticon type and time displayed above + * the being. + */ + void setEmote(Uint8 emotion, int emote_time) + { + mEmotion = emotion; + mEmotionTime = emote_time; + } + + /** + * Get the current Emoticon type displayed above + * the being. + */ + int getEmotion() const + { return mEmotion; } + + virtual void drawSprites(Graphics* graphics, int posX, int posY) const; + + virtual void drawSpritesSDL(Graphics* graphics, + int posX, int posY) const; + + void drawHpBar(Graphics *graphics, int x, int y, + int width, int height) const; + + static void load(); + + virtual void optionChanged(const std::string &value); + + void flashName(int time); + + int getDamageTaken() const + { return mDamageTaken; } + + void setDamageTaken(int damage) + { mDamageTaken = damage; } + + void updateName(); + + void setLevel(int n) + { mLevel = n; } + + virtual int getLevel() const + { return mLevel; } + + void setIsReachable(int n) + { mIsReachable = n; } + + int isReachable() + { return mIsReachable; } + + static void reReadConfig(); + + static BeingCacheEntry* getCacheEntry(int id); + + void addToCache(); + + bool updateFromCache(); + + /** + * Sets the gender of this being. + */ + virtual void setGender(Gender gender); + + Gender getGender() const + { return mGender; } + + /** + * Whether or not this player is a GM. + */ + bool isGM() const + { return mIsGM; } + + /** + * Triggers whether or not to show the name as a GM name. + */ + void setGM(bool gm); + + bool canTalk(); + + void talkTo(); + + static bool isTalking(); + + bool draw(Graphics *graphics, int offsetX, int offsetY) const; + + bool drawSpriteAt(Graphics *graphics, int x, int y) const; + + void setMoveTime() + { mMoveTime = cur_time; } + + void setAttackTime() + { mAttackTime = cur_time; } + + void setTalkTime() + { mTalkTime = cur_time; } + + void setTestTime() + { mTestTime = cur_time; } + + void setOtherTime() + { mOtherTime = cur_time; } + + unsigned int getMoveTime() const + { return mMoveTime; } + + unsigned int getAttackTime() const + { return mAttackTime; } + + unsigned int getTalkTime() const + { return mTalkTime; } + + unsigned int getTestTime() const + { return mTestTime; } + + unsigned int getOtherTime() const + { return mOtherTime; } + + void resetCounters(); + + virtual void updateColors(); + + void setEnemy(bool n) + { mEnemy = n; } + + const std::string &getIp() const + { return mIp; } + + void setIp(std::string ip) + { mIp = ip; } + + unsigned int getPvpRank() const + { return mPvpRank; } + + void setPvpRank(unsigned int rank) + { mPvpRank = rank; } + + void setHP(int n); + + void setMaxHP(int hp); + + int getHP() + { return mHP; } + + protected: + /** + * Sets the new path for this being. + */ + void setPath(const Path &path); + + /** + * Updates name's location. + */ + virtual void updateCoords(); + + void recalcSpritesOrder(); + + void showName(); + + BeingInfo *mInfo; + + int mActionTime; /**< Time spent in current action */ + + int mEmotion; /**< Currently showing emotion */ + int mEmotionTime; /**< Time until emotion disappears */ + /** Time until the last speech sentence disappears */ + int mSpeechTime; + + int mAttackType; + int mAttackSpeed; /**< Attack speed */ + + Action mAction; /**< Action the being is performing */ + Uint16 mSubType; /**< Subtype (graphical view, basically) */ + + Uint8 mDirection; /**< Facing direction */ + Uint8 mSpriteDirection; /**< Facing direction */ + std::string mName; /**< Name of character */ + std::string mPartyName; + std::string mGuildName; + + /** + * Holds a text object when the being displays it's name, 0 otherwise + */ + FlashText *mDispName; + const gcn::Color *mNameColor; + bool mShowName; + + /** Engine-related infos about weapon. */ + const ItemInfo *mEquippedWeapon; + + static int mNumberOfHairstyles; /** Number of hair styles in use */ + + Path mPath; + std::string mSpeech; + Text *mText; + const gcn::Color *mTextColor; + + int mLevel; + Vector mDest; /**< destination coordinates. */ + + std::vector mSpriteIDs; + std::vector mSpriteColors; + Gender mGender; + + // Character guild information + std::map mGuilds; + Party *mParty; + + bool mIsGM; + + private: + + /** + * Calculates the offset in the given directions. + * If walking in direction 'neg' the value is negated. + * TODO: Used by eAthena only? + */ + int getOffset(char pos, char neg) const; + + int searchSlotValue(std::vector &slotRemap, int val); + + void searchSlotValueItr(std::vector::iterator &it, int &idx, + std::vector &slotRemap, int val); + + const Type mType; + + /** Speech Bubble components */ + SpeechBubble *mSpeechBubble; + + /** + * Walk speed for x and y movement values. + * In pixels per second for eAthena, + * In pixels per ticks for Manaserv. + * @see MILLISECONDS_IN_A_TICK + */ + Vector mWalkSpeed; + + int mX, mY; /**< Position in tile */ + + int mDamageTaken; + int mHP; + int mMaxHP; + int mDistance; + int mIsReachable; /**< 0 - unknown, 1 - reachable, 2 - not reachable*/ + + static int mUpdateConfigTime; + static unsigned int mConfLineLim; + static int mSpeechType; + static bool mHighlightMapPortals; + static bool mHighlightMonsterAttackRange; + static bool mLowTraffic; + static bool mDrawHotKeys; + static bool mShowBattleEvents; + static bool mShowMobHP; +// std::string mDisplayName; + + unsigned int mMoveTime; + unsigned int mAttackTime; + unsigned int mTalkTime; + unsigned int mTestTime; + unsigned int mOtherTime; + bool mErased; + bool mEnemy; + std::string mIp; + unsigned int mPvpRank; + int *mSpriteRemap; +}; + +extern std::list beingInfoCache; + +#endif diff --git a/src/channel.cpp b/src/channel.cpp new file mode 100644 index 000000000..92c9b163d --- /dev/null +++ b/src/channel.cpp @@ -0,0 +1,40 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "channel.h" + +#include "gui/widgets/channeltab.h" + +Channel::Channel(short id, + const std::string &name, + const std::string &announcement) : + mId(id), + mName(name), + mAnnouncement(announcement), + mTab(new ChannelTab(this)) +{ +} + +Channel::~Channel() +{ + delete mTab; + mTab = 0; +} diff --git a/src/channel.h b/src/channel.h new file mode 100644 index 000000000..3e489802a --- /dev/null +++ b/src/channel.h @@ -0,0 +1,89 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHANNEL_H +#define CHANNEL_H + +#include + +class ChannelTab; + +class Channel +{ + public: + /** + * Constructor. + * + * @param id the id associated with the channel. + * @param name the name of the channel. + * @param announcement a welcome message. + */ + Channel(short id, + const std::string &name, + const std::string &announcement = std::string()); + + ~Channel(); + + /** + * Get the id associated witht his channel. + */ + int getId() const { return mId; } + + /** + * Get this channel's name. + */ + const std::string &getName() const + { return mName; } + + /** + * Get the announcement message for this channel. + */ + const std::string &getAnnouncement() const + { return mAnnouncement; } + + /** + * Sets the name of the channel. + */ + void setName(const std::string &channelName) + { mName = channelName; } + + /** + * Sets the announcement string of the channel. + */ + void setAnnouncement(const std::string &channelAnnouncement) + { mAnnouncement = channelAnnouncement; } + + ChannelTab *getTab() + { return mTab; } + + protected: + friend class ChannelTab; + void setTab(ChannelTab *tab) + { mTab = tab; } + + private: + unsigned short mId; + std::string mName; + std::string mAnnouncement; + ChannelTab *mTab; +}; + +#endif // CHANNEL_H diff --git a/src/channelmanager.cpp b/src/channelmanager.cpp new file mode 100644 index 000000000..5bdea4637 --- /dev/null +++ b/src/channelmanager.cpp @@ -0,0 +1,91 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "channelmanager.h" + +#include "channel.h" + +#include "utils/dtor.h" + +ChannelManager::ChannelManager() +{ +} + +ChannelManager::~ChannelManager() +{ + delete_all(mChannels); + mChannels.clear(); +} + +Channel *ChannelManager::findById(int id) const +{ + Channel *channel = 0; + for (std::list::const_iterator itr = mChannels.begin(), + end = mChannels.end(); + itr != end; + itr++) + { + Channel *c = (*itr); + if (!c) + continue; + if (c->getId() == id) + { + channel = c; + break; + } + } + return channel; +} + +Channel *ChannelManager::findByName(const std::string &name) const +{ + Channel *channel = 0; + if (!name.empty()) + { + for (std::list::const_iterator itr = mChannels.begin(), + end = mChannels.end(); + itr != end; + itr++) + { + Channel *c = (*itr); + if (!c) + continue; + if (c->getName() == name) + { + channel = c; + break; + } + } + } + return channel; +} + +void ChannelManager::addChannel(Channel *channel) +{ + mChannels.push_back(channel); +} + +void ChannelManager::removeChannel(Channel *channel) +{ + mChannels.remove(channel); + delete channel; + channel = 0; +} diff --git a/src/channelmanager.h b/src/channelmanager.h new file mode 100644 index 000000000..aeb66a9ab --- /dev/null +++ b/src/channelmanager.h @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHANNELMANAGER_H +#define CHANNELMANAGER_H + +#include +#include + +class Channel; + +class ChannelManager +{ +public: + ChannelManager(); + ~ChannelManager(); + + Channel *findById(int id) const; + Channel *findByName(const std::string &name) const; + + void addChannel(Channel *channel); + void removeChannel(Channel *channel); + +private: + std::list mChannels; +}; + +extern ChannelManager *channelManager; + +#endif diff --git a/src/chatlog.cpp b/src/chatlog.cpp new file mode 100644 index 000000000..73acab880 --- /dev/null +++ b/src/chatlog.cpp @@ -0,0 +1,201 @@ +/* + * The Mana World + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "chatlog.h" + +#include +#include +#include + +#include +#include +#include + +#ifdef WIN32 +#include +#elif defined __APPLE__ +#include +#endif + +#include "log.h" +#include "configuration.h" + +#include "utils/stringutils.h" + +ChatLogger::ChatLogger() +{ +} + +ChatLogger::~ChatLogger() +{ + if (mLogFile.is_open()) + mLogFile.close(); +} + +void ChatLogger::setLogFile(const std::string &logFilename) +{ + if (mLogFile.is_open()) + mLogFile.close(); + + mLogFile.open(logFilename.c_str(), std::ios_base::app); + + if (!mLogFile.is_open()) + { + std::cout << "Warning: error while opening " << logFilename << + " for writing.\n"; + } +} + +void ChatLogger::setLogDir(const std::string &logDir) +{ + mLogDir = logDir; + + if (mLogFile.is_open()) + mLogFile.close(); + + DIR *dir = opendir(mLogDir.c_str()); + if (!dir) + makeDir(mLogDir); + else + closedir(dir); +} + +void ChatLogger::log(std::string str) +{ + std::string dateStr = getDateString(); + if (!mLogFile.is_open() || dateStr != mLogDate) + { + mLogDate = dateStr; + setLogFile(strprintf("%s/%s/#General_%s.log", mLogDir.c_str(), + mServerName.c_str(), dateStr.c_str())); + } + + str = removeColors(str); + writeTo(mLogFile, str); +} + +void ChatLogger::log(std::string name, std::string str) +{ + std::ofstream logFile; + logFile.open(strprintf("%s/%s/%s_%s.log", mLogDir.c_str(), + mServerName.c_str(), secureName(name).c_str(), + getDateString().c_str()).c_str(), std::ios_base::app); + + if (!logFile.is_open()) + return; + + str = removeColors(str); + writeTo(logFile, str); + + if (logFile.is_open()) + logFile.close(); +} + +std::string ChatLogger::getDateString() const +{ + std::string date; + + time_t rawtime; + struct tm *timeinfo; + char buffer [81]; + + time (&rawtime); + timeinfo = localtime(&rawtime); + + strftime(buffer, 79, "%y-%m-%d", timeinfo); + date = buffer; + return date; +} + +std::string ChatLogger::secureName(std::string &name) const +{ + for (unsigned int f = 0; f < name.length(); f ++) + { + if (name[f] < '0' && name[f] > '9' && name[f] < 'a' && name[f] > 'z' + && name[f] < 'A' && name[f] > 'Z' + && name[f] != '-' && name[f] != '+' && name[f] != '=' + && name[f] != '.' && name[f] != ',' && name[f] != ')' + && name[f] != '(' && name[f] != '[' && name[f] != ']') + { + name[f] = '_'; + } + } + return name; +} + +void ChatLogger::writeTo(std::ofstream &file, const std::string &str) const +{ + file << str << std::endl; +} + +void ChatLogger::setServerName(const std::string &serverName) +{ + mServerName = serverName; + if (mServerName == "") + mServerName = config.getStringValue("MostUsedServerName0"); + + if (mLogFile.is_open()) + mLogFile.close(); + + secureName(mServerName); + if (mLogDir != "") + { + DIR *dir = opendir((mLogDir + "/" + mServerName).c_str()); + if (!dir) + makeDir(mLogDir + "/" + mServerName); + else + closedir(dir); + } +} + +void ChatLogger::makeDir(const std::string &dir) +{ +#ifdef WIN32 + mkdir(dir.c_str()); +#else + mkdir(dir.c_str(), 0750); +#endif +} + +void ChatLogger::loadLast(std::string name, std::list &list, + unsigned n) +{ + std::ifstream logFile; + + logFile.open(strprintf("%s/%s/%s_%s.log", mLogDir.c_str(), + mServerName.c_str(), secureName(name).c_str(), + getDateString().c_str()).c_str(), std::ios::in); + + if (!logFile.is_open()) + return; + + char line[710]; + while (logFile.getline(line, 700)) + { + list.push_back(line); + if (list.size() > n) + list.pop_front(); + } + + if (logFile.is_open()) + logFile.close(); +} diff --git a/src/chatlog.h b/src/chatlog.h new file mode 100644 index 000000000..d4cd4e73f --- /dev/null +++ b/src/chatlog.h @@ -0,0 +1,78 @@ +/* + * The Mana World + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CHATLOG_H +#define _CHATLOG_H + +#include +#include + +class ChatLogger +{ + public: + /** + * Constructor. + */ + ChatLogger(); + + /** + * Destructor, closes log file. + */ + ~ChatLogger(); + + void setLogDir(const std::string &logDir); + + /** + * Enters a message in the log. The message will be timestamped. + */ + void log(std::string str); + + void log(std::string name, std::string str); + + void loadLast(std::string name, std::list &list, + unsigned n); + + std::string getDateString() const; + + std::string secureName(std::string &str) const; + + void setServerName(const std::string &serverName); + + private: + /** + * Sets the file to log to and opens it + */ + void setLogFile(const std::string &logFilename); + + void writeTo(std::ofstream &file, const std::string &str) const; + + void makeDir(const std::string &dir); + + std::ofstream mLogFile; + std::string mLogDir; + std::string mServerName; + std::string mLogDate; +}; + +extern ChatLogger *chatLogger; + +#endif diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 000000000..0039ce025 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,1986 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "client.h" +#include "main.h" + +#include "chatlog.h" +#include "configuration.h" +#include "emoteshortcut.h" +#include "event.h" +#include "game.h" +#include "itemshortcut.h" +#include "dropshortcut.h" +#include "keyboardconfig.h" +#ifdef USE_OPENGL +#include "openglgraphics.h" +#include "opengl1graphics.h" +#endif +#include "playerrelations.h" +#include "sound.h" +#include "statuseffect.h" +#include "units.h" + +#include "gui/changeemaildialog.h" +#include "gui/changepassworddialog.h" +#include "gui/charselectdialog.h" +#include "gui/connectiondialog.h" +#include "gui/gui.h" +#include "gui/login.h" +#include "gui/okdialog.h" +#include "gui/quitdialog.h" +#include "gui/register.h" +#include "gui/sdlinput.h" +#include "gui/serverdialog.h" +#include "gui/setup.h" +#include "gui/theme.h" +#include "gui/unregisterdialog.h" +#include "gui/updatewindow.h" +#include "gui/userpalette.h" +#include "gui/worldselectdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/desktop.h" + +#include "net/charhandler.h" +#include "net/gamehandler.h" +#include "net/generalhandler.h" +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" +#include "net/packetcounters.h" +#include "net/worldinfo.h" + +#include "resources/colordb.h" +#include "resources/emotedb.h" +#include "resources/image.h" +#include "resources/itemdb.h" +#include "resources/monsterdb.h" +#include "resources/specialdb.h" +#include "resources/npcdb.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/mkdir.h" +#include "utils/stringutils.h" + +#ifdef __APPLE__ +#include +#endif + +#include +#include + +#ifdef WIN32 +#include +#include "utils/specialfolder.h" +#else +#include +#endif + +#include +#include + +#include +#include + +#include "mumblemanager.h" + +/** + * Tells the max tick value, + * setting it back to zero (and start again). + */ +static const int MAX_TICK_VALUE = 10000; + +static const int defaultSfxVolume = 100; +static const int defaultMusicVolume = 60; + +// TODO: Get rid fo these globals +std::string errorMessage; +ErrorListener errorListener; +LoginData loginData; + +Configuration config; /**< XML file configuration reader */ +Configuration serverConfig; /**< XML file server configuration reader */ +Configuration branding; /**< XML branding information reader */ +Configuration paths; /**< XML default paths information reader */ +Logger *logger; /**< Log object */ +ChatLogger *chatLogger; /**< Chat log object */ +KeyboardConfig keyboard; +UserPalette *userPalette; +Graphics *graphics; + +Sound sound; + +Uint32 nextTick(Uint32 interval, void *param _UNUSED_); +Uint32 nextSecond(Uint32 interval, void *param _UNUSED_); + +void ErrorListener::action(const gcn::ActionEvent &) +{ + Client::setState(STATE_CHOOSE_SERVER); +} + +volatile int tick_time; /**< Tick counter */ +volatile int fps = 0; /**< Frames counted in the last second */ +volatile int frame_count = 0; /**< Counts the frames during one second */ +volatile int cur_time; +volatile bool runCounters; +bool isSafeMode = false; + +/** + * Advances game logic counter. + * Called every 10 milliseconds by SDL_AddTimer() + * @see MILLISECONDS_IN_A_TICK value + */ +Uint32 nextTick(Uint32 interval, void *param _UNUSED_) +{ + tick_time++; + if (tick_time == MAX_TICK_VALUE) + tick_time = 0; + return interval; +} + +/** + * Updates fps. + * Called every seconds by SDL_AddTimer() + */ +Uint32 nextSecond(Uint32 interval, void *param _UNUSED_) +{ + fps = frame_count; + frame_count = 0; + + return interval; +} + +/** + * @return the elapsed time in milliseconds + * between two tick values. + */ +int get_elapsed_time(int start_time) +{ + if (start_time <= tick_time) + { + return (tick_time - start_time) * MILLISECONDS_IN_A_TICK; + } + else + { + return (tick_time + (MAX_TICK_VALUE - start_time)) + * MILLISECONDS_IN_A_TICK; + } +} + + +// This anonymous namespace hides whatever is inside from other modules. +namespace +{ + +class AccountListener : public gcn::ActionListener +{ + public: + void action(const gcn::ActionEvent &) + { + Client::setState(STATE_CHAR_SELECT); + } +} accountListener; + +class LoginListener : public gcn::ActionListener +{ + public: + void action(const gcn::ActionEvent &) + { + Client::setState(STATE_LOGIN); + } +} loginListener; + +} // anonymous namespace + + +Client *Client::mInstance = 0; + +Client::Client(const Options &options): + mOptions(options), + mServerConfigDir(""), + mRootDir(""), + mCurrentDialog(0), + mQuitDialog(0), + mDesktop(0), + mSetupButton(0), + mState(STATE_CHOOSE_SERVER), + mOldState(STATE_START), + mIcon(0), + mLogicCounterId(0), + mSecondsCounterId(0), + mLimitFps(false), + mConfigAutoSaved(false), + mIsMinimized(false), + mGuiAlpha(1.0f) +{ + assert(!mInstance); + mInstance = this; + + logger = new Logger; + + // Load branding information + if (!options.brandingPath.empty()) + branding.init(options.brandingPath); + branding.setDefaultValues(getBrandingDefaults()); + + initRootDir(); + initHomeDir(); + + // Configure logger + if (!options.logFileName.empty()) + logger->setLogFile(options.logFileName); + else + logger->setLogFile(mLocalDataDir + std::string("/manaplus.log")); + + initConfiguration(); + logger->setDebugLog(config.getBoolValue("debugLog")); + + storeSafeParameters(); + + chatLogger = new ChatLogger; + if (options.chatLogDir == "") + chatLogger->setLogDir(mLocalDataDir + std::string("/logs/")); + else + chatLogger->setLogDir(options.chatLogDir); + + logger->setLogToStandardOut(config.getBoolValue("logToStandardOut")); + + // Log the mana version + logger->log("ManaPlus %s", FULL_VERSION); + logger->log("Start configPath: " + config.getConfigPath()); + + initScreenshotDir(); + + // Initialize SDL + logger->log1("Initializing SDL..."); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) + { + logger->error(strprintf("Could not initialize SDL: %s", + SDL_GetError())); + } + atexit(SDL_Quit); + + initPacketLimiter(); + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + + SDL_WM_SetCaption(branding.getValue("appName", "ManaPlus").c_str(), NULL); + + ResourceManager *resman = ResourceManager::getInstance(); + + if (!resman->setWriteDir(mLocalDataDir)) + { + logger->error(strprintf("%s couldn't be set as home directory! " + "Exiting.", mLocalDataDir.c_str())); + } + +#if defined USE_OPENGL + Image::SDLSetEnableAlphaCache(config.getBoolValue("alphaCache") + && !config.getIntValue("opengl")); + Image::setEnableAlpha(config.getFloatValue("guialpha") != 1.0f + || config.getIntValue("opengl")); +#else + Image::SDLSetEnableAlphaCache(config.getBoolValue("alphaCache")); + Image::setEnableAlpha(config.getFloatValue("guialpha") != 1.0f); +#endif + +#if defined __APPLE__ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); + char path[PATH_MAX]; + if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, + PATH_MAX)) + { + fprintf(stderr, "Can't find Resources directory\n"); + } + CFRelease(resourcesURL); + //possible crash + strncat(path, "/data", PATH_MAX - 1); + resman->addToSearchPath(path, false); + mPackageDir = path; +#else + resman->addToSearchPath(PKG_DATADIR "data", false); + mPackageDir = PKG_DATADIR "data"; +#endif + + resman->addToSearchPath("data", false); + + // Add branding/data to PhysFS search path + if (!options.brandingPath.empty()) + { + std::string path = options.brandingPath; + + // Strip blah.mana from the path +#ifdef WIN32 + int loc1 = path.find_last_of('/'); + int loc2 = path.find_last_of('\\'); + int loc = static_cast(std::max(loc1, loc2)); +#else + int loc = static_cast(path.find_last_of('/')); +#endif + if (loc > 0) + resman->addToSearchPath(path.substr(0, loc + 1) + "data", false); + } + + // Add the main data directories to our PhysicsFS search path + if (!options.dataPath.empty()) + resman->addToSearchPath(options.dataPath, false); + + // Add the local data directory to PhysicsFS search path + resman->addToSearchPath(mLocalDataDir, false); + + //resman->selectSkin(); + +#ifdef WIN32 + static SDL_SysWMinfo pInfo; + SDL_GetWMInfo(&pInfo); + HICON icon = LoadIcon(GetModuleHandle(NULL), "A"); + if (icon) + SetClassLong(pInfo.window, GCL_HICON, (LONG) icon); +#else + mIcon = IMG_Load(resman->getPath( + branding.getValue("appIcon", "icons/manaplus.png")).c_str()); + if (mIcon) + { + SDL_SetAlpha(mIcon, SDL_SRCALPHA, SDL_ALPHA_OPAQUE); + SDL_WM_SetIcon(mIcon, NULL); + } +#endif + +#ifdef USE_OPENGL + int useOpenGL = 0; + if (!mOptions.noOpenGL) + useOpenGL = config.getIntValue("opengl"); + + // Setup image loading for the right image format + Image::setLoadAsOpenGL(useOpenGL); + + // Create the graphics context + switch(useOpenGL) + { + case 0: + graphics = new Graphics; + break; + case 1: + default: + graphics = new OpenGLGraphics; + break; + case 2: + graphics = new OpenGL1Graphics; + break; + }; + +#else + // Create the graphics context + graphics = new Graphics; +#endif + + runCounters = config.getBoolValue("packetcounters"); + + const int width = config.getIntValue("screenwidth"); + const int height = config.getIntValue("screenheight"); + const int bpp = 0; + const bool fullscreen = config.getBoolValue("screen"); + const bool hwaccel = config.getBoolValue("hwaccel"); + + // Try to set the desired video mode + if (!graphics->setVideoMode(width, height, bpp, fullscreen, hwaccel)) + { + logger->error(strprintf("Couldn't set %dx%dx%d video mode: %s", + width, height, bpp, SDL_GetError())); + } + + // Initialize for drawing + graphics->_beginDraw(); + + Theme::selectSkin(); +// Theme::prepareThemePath(); + + // Initialize the item and emote shortcuts. + for (int f = 0; f < SHORTCUT_TABS; f ++) + itemShortcut[f] = new ItemShortcut(f); + + emoteShortcut = new EmoteShortcut; + + // Initialize the drop shortcuts. + dropShortcut = new DropShortcut; + + gui = new Gui(graphics); + + // Initialize sound engine + try + { + if (config.getBoolValue("sound")) + sound.init(); + + sound.setSfxVolume(config.getIntValue("sfxVolume")); + sound.setMusicVolume(config.getIntValue("musicVolume")); + } + catch (const char *err) + { + mState = STATE_ERROR; + errorMessage = err; + logger->log("Warning: %s", err); + } + + // Initialize keyboard + keyboard.init(); + + // Initialise player relations + player_relations.init(); + + userPalette = new UserPalette; + setupWindow = new Setup; + + sound.playMusic(branding.getValue("loginMusic", "Magick - Real.ogg")); + + // Initialize default server + mCurrentServer.hostname = options.serverName; + mCurrentServer.port = options.serverPort; + + loginData.username = options.username; + loginData.password = options.password; + loginData.remember = serverConfig.getValue("remember", 0); + loginData.registerLogin = false; + + if (mCurrentServer.hostname.empty()) + { + mCurrentServer.hostname = + branding.getValue("defaultServer", "").c_str(); + } + + if (mCurrentServer.port == 0) + { + mCurrentServer.port = (short) branding.getValue("defaultPort", + DEFAULT_PORT); + mCurrentServer.type = ServerInfo::parseType( + branding.getValue("defaultServerType", "tmwathena")); + } + + if (chatLogger) + chatLogger->setServerName(mCurrentServer.hostname); + + if (loginData.username.empty() && loginData.remember) + loginData.username = serverConfig.getValue("username", ""); + + if (mState != STATE_ERROR) + mState = STATE_CHOOSE_SERVER; + + // Initialize logic and seconds counters + tick_time = 0; + mLogicCounterId = SDL_AddTimer(MILLISECONDS_IN_A_TICK, nextTick, NULL); + mSecondsCounterId = SDL_AddTimer(1000, nextSecond, NULL); + + const int fpsLimit = (int) config.getIntValue("fpslimit"); + mLimitFps = fpsLimit > 0; + + // Initialize frame limiting + mFpsManager.framecount = 0; + mFpsManager.rateticks = 0; + mFpsManager.lastticks = 0; + mFpsManager.rate = 0; + + SDL_initFramerate(&mFpsManager); + logger->log("mFpsManager.framecount: " + toString(mFpsManager.framecount)); + logger->log("mFpsManager.rateticks: " + toString(mFpsManager.rateticks)); + logger->log("mFpsManager.lastticks: " + toString(mFpsManager.lastticks)); + logger->log("mFpsManager.rate: " + toString(mFpsManager.rate)); + setFramerate(fpsLimit); + config.addListener("fpslimit", this); + config.addListener("guialpha", this); + setGuiAlpha(config.getFloatValue("guialpha")); + + optionChanged("fpslimit"); + + // Initialize PlayerInfo + PlayerInfo::init(); +} + +Client::~Client() +{ + logger->log1("Quitting1"); + config.removeListener("fpslimit", this); + config.removeListener("guialpha", this); + + SDL_RemoveTimer(mLogicCounterId); + SDL_RemoveTimer(mSecondsCounterId); + + // Unload XML databases + ColorDB::unload(); + EmoteDB::unload(); + ItemDB::unload(); + MonsterDB::unload(); + NPCDB::unload(); + StatusEffect::unload(); + + // Before config.write() since it writes the shortcuts to the config + for (int f = 0; f < SHORTCUT_TABS; f ++) + { + delete itemShortcut[f]; + itemShortcut[f] = 0; + } + delete emoteShortcut; + emoteShortcut = 0; + delete dropShortcut; + dropShortcut = 0; + + player_relations.store(); + + logger->log1("Quitting2"); + + delete gui; + gui = 0; + + logger->log1("Quitting3"); + + delete graphics; + graphics = 0; + + logger->log1("Quitting4"); + + // Shutdown libxml + xmlCleanupParser(); + + logger->log1("Quitting5"); + + // Shutdown sound + sound.close(); + + logger->log1("Quitting6"); + + ResourceManager::deleteInstance(); + + logger->log1("Quitting8"); + + SDL_FreeSurface(mIcon); + + logger->log1("Quitting9"); + + delete userPalette; + userPalette = 0; + + logger->log1("Quitting10"); + + config.write(); + serverConfig.write(); + + logger->log1("Quitting11"); + + delete chatLogger; + chatLogger = 0; + + delete logger; + logger = 0; + + mInstance = 0; +} + +int Client::exec() +{ + int lastTickTime = tick_time; + + if (!mumbleManager) + mumbleManager = new MumbleManager(); + + Game *game = 0; + SDL_Event event; + + while (mState != STATE_EXIT) + { +// bool handledEvents = false; + + if (game) + { + // Let the game handle the events while it is active + game->handleInput(); + } + else + { + // Handle SDL events + while (SDL_PollEvent(&event)) + { +// handledEvents = true; + + switch (event.type) + { + case SDL_QUIT: + mState = STATE_EXIT; + break; + + case SDL_KEYDOWN: + default: + break; + } + + guiInput->pushInput(event); + if (player_node && mumbleManager) + { + mumbleManager->setPos(player_node->getTileX(), + player_node->getTileY(), player_node->getDirection()); + } + } + } + + if (Net::getGeneralHandler()) + Net::getGeneralHandler()->flushNetwork(); + + while (get_elapsed_time(lastTickTime) > 0) + { + gui->logic(); + if (game) + game->logic(); + + ++lastTickTime; + } + + // This is done because at some point tick_time will wrap. + lastTickTime = tick_time; + + // Update the screen when application is active, delay otherwise. + if (SDL_GetAppState() & SDL_APPACTIVE) + { + frame_count++; + gui->draw(); + graphics->updateScreen(); +// logger->log("active"); + } + else + { +// logger->log("inactive"); + SDL_Delay(10); + } + + if (mLimitFps) + SDL_framerateDelay(&mFpsManager); + + // TODO: Add connect timeouts + if (mState == STATE_CONNECT_GAME && + Net::getGameHandler()->isConnected()) + { + Net::getLoginHandler()->disconnect(); + } + else if (mState == STATE_CONNECT_SERVER && + mOldState == STATE_CHOOSE_SERVER) + { + mServerName = mCurrentServer.hostname; + initServerConfig(mCurrentServer.hostname); + if (mOptions.username.empty()) + loginData.username = serverConfig.getValue("username", ""); + else + loginData.username = mOptions.username; + + loginData.remember = serverConfig.getValue("remember", 0); + + Net::connectToServer(mCurrentServer); + + if (mumbleManager) + mumbleManager->setServer(mCurrentServer.hostname); + + if (!mConfigAutoSaved) + { + mConfigAutoSaved = true; + config.write(); + } + } + else if (mState == STATE_CONNECT_SERVER && + mOldState != STATE_CHOOSE_SERVER && + Net::getLoginHandler()->isConnected()) + { + mState = STATE_LOGIN; + } + else if (mState == STATE_WORLD_SELECT && mOldState == STATE_UPDATE) + { + if (Net::getLoginHandler()->getWorlds().size() < 2) + mState = STATE_LOGIN; + } + else if (mOldState == STATE_START || + (mOldState == STATE_GAME && mState != STATE_GAME)) + { + gcn::Container *top = static_cast(gui->getTop()); + + mDesktop = new Desktop; + top->add(mDesktop); + mSetupButton = new Button(_("Setup"), "Setup", this); + mSetupButton->setPosition(top->getWidth() + - mSetupButton->getWidth() - 3, 3); + top->add(mSetupButton); + + int screenWidth = config.getIntValue("screenwidth"); + int screenHeight = config.getIntValue("screenheight"); + + mDesktop->setSize(screenWidth, screenHeight); + } + + if (mState == STATE_SWITCH_LOGIN && mOldState == STATE_GAME) + Net::getGameHandler()->disconnect(); + + if (mState != mOldState) + { + { + Mana::Event event(EVENT_STATECHANGE); + event.setInt("oldState", mOldState); + event.setInt("newState", mState); + Mana::Event::trigger(CHANNEL_CLIENT, event); + } + + if (mOldState == STATE_GAME) + { + delete game; + game = 0; + } + + mOldState = mState; + + // Get rid of the dialog of the previous state + delete mCurrentDialog; + mCurrentDialog = 0; + // State has changed, while the quitDialog was active, it might + // not be correct anymore + if (mQuitDialog) + { + mQuitDialog->scheduleDelete(); + mQuitDialog = 0; + } + + switch (mState) + { + case STATE_CHOOSE_SERVER: + logger->log1("State: CHOOSE SERVER"); + + // Allow changing this using a server choice dialog + // We show the dialog box only if the command-line + // options weren't set. + if (mOptions.serverName.empty() && mOptions.serverPort == 0 + && !branding.getValue("onlineServerList", "a").empty()) + { + // Don't allow an alpha opacity + // lower than the default value + Theme::instance()->setMinimumOpacity(0.8f); + + mCurrentDialog = new ServerDialog(&mCurrentServer, + mConfigDir); + } + else + { + mState = STATE_CONNECT_SERVER; + + // Reset options so that cancelling or connect + // timeout will show the server dialog. + mOptions.serverName.clear(); + mOptions.serverPort = 0; + } + break; + + case STATE_CONNECT_SERVER: + logger->log1("State: CONNECT SERVER"); + mCurrentDialog = new ConnectionDialog( + _("Connecting to server"), STATE_SWITCH_SERVER); + break; + + case STATE_LOGIN: + logger->log1("State: LOGIN"); + // Don't allow an alpha opacity + // lower than the default value + Theme::instance()->setMinimumOpacity(0.8f); + + loginData.updateType + = serverConfig.getValue("updateType", 1); + + if (mOptions.username.empty() + || mOptions.password.empty()) + { + mCurrentDialog = new LoginDialog(&loginData, + mCurrentServer.hostname, &mOptions.updateHost); + } + else + { + mState = STATE_LOGIN_ATTEMPT; + // Clear the password so that when login fails, the + // dialog will show up next time. + mOptions.password.clear(); + } + break; + + case STATE_LOGIN_ATTEMPT: + logger->log1("State: LOGIN ATTEMPT"); + accountLogin(&loginData); + mCurrentDialog = new ConnectionDialog( + _("Logging in"), STATE_SWITCH_SERVER); + break; + + case STATE_WORLD_SELECT: + logger->log1("State: WORLD SELECT"); + { + Worlds worlds = Net::getLoginHandler()->getWorlds(); + + if (worlds.empty()) + { + // Trust that the netcode knows what it's doing + mState = STATE_UPDATE; + } + else if (worlds.size() == 1) + { + Net::getLoginHandler()->chooseServer(0); + mState = STATE_UPDATE; + } + else + { + mCurrentDialog = new WorldSelectDialog(worlds); + if (mOptions.chooseDefault) + { + static_cast(mCurrentDialog) + ->action(gcn::ActionEvent(0, "ok")); + } + } + } + break; + + case STATE_WORLD_SELECT_ATTEMPT: + logger->log1("State: WORLD SELECT ATTEMPT"); + mCurrentDialog = new ConnectionDialog( + _("Entering game world"), STATE_WORLD_SELECT); + break; + + case STATE_UPDATE: + // Determine which source to use for the update host + if (!mOptions.updateHost.empty()) + mUpdateHost = mOptions.updateHost; + else + mUpdateHost = loginData.updateHost; + initUpdatesDir(); + + if (mOptions.skipUpdate) + { + mState = STATE_LOAD_DATA; + } + else if (loginData.updateType & LoginData::Upd_Skip) + { + UpdaterWindow::loadLocalUpdates(mLocalDataDir + "/" + + mUpdatesDir); + mState = STATE_LOAD_DATA; + } + else + { + logger->log1("State: UPDATE"); + mCurrentDialog = new UpdaterWindow(mUpdateHost, + mLocalDataDir + "/" + mUpdatesDir, + mOptions.dataPath.empty(), + loginData.updateType); + } + break; + + case STATE_LOAD_DATA: + { + logger->log1("State: LOAD DATA"); + + ResourceManager *resman = ResourceManager::getInstance(); + + // If another data path has been set, + // we don't load any other files... + if (mOptions.dataPath.empty()) + { + // Add customdata directory + resman->searchAndAddArchives( + "customdata/", + "zip", + false); + } + + if (!mOptions.skipUpdate) + { + resman->searchAndAddArchives( + mUpdatesDir + "/local/", + "zip", + false); + + resman->addToSearchPath(mLocalDataDir + "/" + + mUpdatesDir + "/local/", false); + } + + // Read default paths file 'data/paths.xml' + paths.init("paths.xml", true); + paths.setDefaultValues(getPathsDefaults()); + + Mana::Event event(EVENT_STATECHANGE); + event.setInt("newState", STATE_LOAD_DATA); + event.setInt("oldState", mOldState); + Mana::Event::trigger(CHANNEL_CLIENT, event); + + // Load XML databases + ColorDB::load(); + ItemDB::load(); + Being::load(); // Hairstyles + MonsterDB::load(); + SpecialDB::load(); + NPCDB::load(); + EmoteDB::load(); + StatusEffect::load(); + Units::loadUnits(); + + ActorSprite::load(); + + if (mDesktop) + mDesktop->reloadWallpaper(); + + mState = STATE_GET_CHARACTERS; + break; + } + case STATE_GET_CHARACTERS: + logger->log1("State: GET CHARACTERS"); + Net::getCharHandler()->requestCharacters(); + mCurrentDialog = new ConnectionDialog( + _("Requesting characters"), + STATE_SWITCH_SERVER); + break; + + case STATE_CHAR_SELECT: + logger->log1("State: CHAR SELECT"); + // Don't allow an alpha opacity + // lower than the default value + Theme::instance()->setMinimumOpacity(0.8f); + + mCurrentDialog = new CharSelectDialog(&loginData); + + if (!(static_cast(mCurrentDialog)) + ->selectByName(mOptions.character, + CharSelectDialog::Choose)) + { + (static_cast(mCurrentDialog)) + ->selectByName( + serverConfig.getValue("lastCharacter", ""), + mOptions.chooseDefault ? + CharSelectDialog::Choose : + CharSelectDialog::Focus); + } + + break; + + case STATE_CONNECT_GAME: + logger->log1("State: CONNECT GAME"); + + Net::getGameHandler()->connect(); + mCurrentDialog = new ConnectionDialog( + _("Connecting to the game server"), + Net::getNetworkType() == ServerInfo::TMWATHENA ? + STATE_CHOOSE_SERVER : STATE_SWITCH_CHARACTER); + break; + + case STATE_CHANGE_MAP: + logger->log1("State: CHANGE_MAP"); + + Net::getGameHandler()->connect(); + mCurrentDialog = new ConnectionDialog( + _("Changing game servers"), + STATE_SWITCH_CHARACTER); + break; + + case STATE_GAME: + if (player_node) + { + logger->log("Memorizing selected character %s", + player_node->getName().c_str()); + config.setValue("lastCharacter", + player_node->getName()); + if (mumbleManager) + mumbleManager->setPlayer(player_node->getName()); + } + + // Fade out logon-music here too to give the desired effect + // of "flowing" into the game. + sound.fadeOutMusic(1000); + + // Allow any alpha opacity + Theme::instance()->setMinimumOpacity(-1.0f); + + delete mSetupButton; + mSetupButton = 0; + delete mDesktop; + mDesktop = 0; + + mCurrentDialog = NULL; + + logger->log1("State: GAME"); + game = new Game; + break; + + case STATE_LOGIN_ERROR: + logger->log1("State: LOGIN ERROR"); + mCurrentDialog = new OkDialog(_("Error"), errorMessage); + mCurrentDialog->addActionListener(&loginListener); + mCurrentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_ACCOUNTCHANGE_ERROR: + logger->log1("State: ACCOUNT CHANGE ERROR"); + mCurrentDialog = new OkDialog(_("Error"), errorMessage); + mCurrentDialog->addActionListener(&accountListener); + mCurrentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_REGISTER_PREP: + logger->log1("State: REGISTER_PREP"); + Net::getLoginHandler()->getRegistrationDetails(); + mCurrentDialog = new ConnectionDialog( + _("Requesting registration details"), STATE_LOGIN); + break; + + case STATE_REGISTER: + logger->log1("State: REGISTER"); + mCurrentDialog = new RegisterDialog(&loginData); + break; + + case STATE_REGISTER_ATTEMPT: + logger->log("Username is %s", loginData.username.c_str()); + Net::getLoginHandler()->registerAccount(&loginData); + break; + + case STATE_CHANGEPASSWORD: + logger->log1("State: CHANGE PASSWORD"); + mCurrentDialog = new ChangePasswordDialog(&loginData); + break; + + case STATE_CHANGEPASSWORD_ATTEMPT: + logger->log1("State: CHANGE PASSWORD ATTEMPT"); + Net::getLoginHandler()->changePassword(loginData.username, + loginData.password, + loginData.newPassword); + break; + + case STATE_CHANGEPASSWORD_SUCCESS: + logger->log1("State: CHANGE PASSWORD SUCCESS"); + mCurrentDialog = new OkDialog(_("Password Change"), + _("Password changed successfully!")); + mCurrentDialog->addActionListener(&accountListener); + mCurrentDialog = NULL; // OkDialog deletes itself + loginData.password = loginData.newPassword; + loginData.newPassword = ""; + break; + + case STATE_CHANGEEMAIL: + logger->log1("State: CHANGE EMAIL"); + mCurrentDialog = new ChangeEmailDialog(&loginData); + break; + + case STATE_CHANGEEMAIL_ATTEMPT: + logger->log1("State: CHANGE EMAIL ATTEMPT"); + Net::getLoginHandler()->changeEmail(loginData.email); + break; + + case STATE_CHANGEEMAIL_SUCCESS: + logger->log1("State: CHANGE EMAIL SUCCESS"); + mCurrentDialog = new OkDialog(_("Email Change"), + _("Email changed successfully!")); + mCurrentDialog->addActionListener(&accountListener); + mCurrentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_UNREGISTER: + logger->log1("State: UNREGISTER"); + mCurrentDialog = new UnRegisterDialog(&loginData); + break; + + case STATE_UNREGISTER_ATTEMPT: + logger->log1("State: UNREGISTER ATTEMPT"); + Net::getLoginHandler()->unregisterAccount( + loginData.username, loginData.password); + break; + + case STATE_UNREGISTER_SUCCESS: + logger->log1("State: UNREGISTER SUCCESS"); + Net::getLoginHandler()->disconnect(); + + mCurrentDialog = new OkDialog(_("Unregister Successful"), + _("Farewell, come back any time...")); + loginData.clear(); + //The errorlistener sets the state to STATE_CHOOSE_SERVER + mCurrentDialog->addActionListener(&errorListener); + mCurrentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_SWITCH_SERVER: + logger->log1("State: SWITCH SERVER"); + + Net::getLoginHandler()->disconnect(); + Net::getGameHandler()->disconnect(); + + mState = STATE_CHOOSE_SERVER; + break; + + case STATE_SWITCH_LOGIN: + logger->log1("State: SWITCH LOGIN"); + + Net::getLoginHandler()->logout(); + + mState = STATE_LOGIN; + break; + + case STATE_SWITCH_CHARACTER: + logger->log1("State: SWITCH CHARACTER"); + + // Done with game + Net::getGameHandler()->disconnect(); + + mState = STATE_GET_CHARACTERS; + break; + + case STATE_LOGOUT_ATTEMPT: + logger->log1("State: LOGOUT ATTEMPT"); + // TODO + break; + + case STATE_WAIT: + logger->log1("State: WAIT"); + break; + + case STATE_EXIT: + logger->log1("State: EXIT"); + Net::unload(); + break; + + case STATE_FORCE_QUIT: + logger->log1("State: FORCE QUIT"); + if (Net::getGeneralHandler()) + Net::getGeneralHandler()->unload(); + mState = STATE_EXIT; + break; + + case STATE_ERROR: + logger->log1("State: ERROR"); + logger->log("Error: %s\n", errorMessage.c_str()); + mCurrentDialog = new OkDialog(_("Error"), errorMessage); + mCurrentDialog->addActionListener(&errorListener); + mCurrentDialog = NULL; // OkDialog deletes itself + Net::getGameHandler()->disconnect(); + break; + + case STATE_AUTORECONNECT_SERVER: + //++++++ + break; + + default: + mState = STATE_FORCE_QUIT; + break; + } + } + } + + return 0; +} + +void Client::optionChanged(const std::string &name) +{ + if (name == "fpslimit") + { + const int fpsLimit = config.getIntValue("fpslimit"); + mLimitFps = fpsLimit > 0; + setFramerate(fpsLimit); + } + else if (name == "guialpha") + { + setGuiAlpha(config.getFloatValue("guialpha")); + } +} + +void Client::action(const gcn::ActionEvent &event) +{ + Window *window = 0; + + if (event.getId() == "Setup") + window = setupWindow; + + if (window) + { + window->setVisible(!window->isVisible()); + if (window->isVisible()) + window->requestMoveToTop(); + } +} + +void Client::initRootDir() +{ + mRootDir = PHYSFS_getBaseDir(); + std::string portableName = mRootDir + "portable.xml"; + struct stat statbuf; + + if (!stat(portableName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + std::string dir; + Configuration portable; + portable.init(portableName); + + logger->log("Portable file: %s", portableName.c_str()); + + if (mOptions.localDataDir.empty()) + { + dir = portable.getValue("dataDir", ""); + if (!dir.empty()) + { + mOptions.localDataDir = mRootDir + dir; + logger->log("Portable data dir: %s", + mOptions.localDataDir.c_str()); + } + } + + if (mOptions.configDir.empty()) + { + dir = portable.getValue("configDir", ""); + if (!dir.empty()) + { + mOptions.configDir = mRootDir + dir; + logger->log("Portable config dir: %s", + mOptions.configDir.c_str()); + } + } + + if (mOptions.screenshotDir.empty()) + { + dir = portable.getValue("screenshotDir", ""); + if (!dir.empty()) + { + mOptions.screenshotDir = mRootDir + dir; + logger->log("Portable screenshot dir: %s", + mOptions.screenshotDir.c_str()); + } + } + } +} + +/** + * Initializes the home directory. On UNIX and FreeBSD, ~/.mana is used. On + * Windows and other systems we use the current working directory. + */ +void Client::initHomeDir() +{ + mLocalDataDir = mOptions.localDataDir; + + if (mLocalDataDir.empty()) + { +#ifdef __APPLE__ + // Use Application Directory instead of .mana + mLocalDataDir = std::string(PHYSFS_getUserDir()) + + "/Library/Application Support/" + + branding.getValue("appName", "Mana"); +#elif defined WIN32 + mLocalDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA); + if (mLocalDataDir.empty()) + mLocalDataDir = std::string(PHYSFS_getUserDir()); + mLocalDataDir += "/Mana"; +#else + mLocalDataDir = std::string(PHYSFS_getUserDir()) + + ".local/share/mana"; +#endif + } + + if (mkdir_r(mLocalDataDir.c_str())) + { + logger->error(strprintf(_("%s doesn't exist and can't be created! " + "Exiting."), mLocalDataDir.c_str())); + } + + mConfigDir = mOptions.configDir; + + if (mConfigDir.empty()) + { +#ifdef __APPLE__ + mConfigDir = mLocalDataDir + "/" + + branding.getValue("appShort", "mana"); +#elif defined WIN32 + mConfigDir = getSpecialFolderLocation(CSIDL_APPDATA); + if (mConfigDir.empty()) + mConfigDir = mLocalDataDir; + else + mConfigDir += "/mana/" + branding.getValue("appShort", "Mana"); +#else + mConfigDir = std::string(PHYSFS_getUserDir()) + + "/.config/mana/" + branding.getValue("appShort", "mana"); +#endif + logger->log("Generating config dir: " + mConfigDir); + } + + if (mkdir_r(mConfigDir.c_str())) + { + logger->error(strprintf(_("%s doesn't exist and can't be created! " + "Exiting."), mConfigDir.c_str())); + } + + struct stat statbuf; + std::string newConfigFile = mConfigDir + "/config.xml"; + if (stat(newConfigFile.c_str(), &statbuf)) + { + std::string oldConfigFile = std::string(PHYSFS_getUserDir()) + + "/.mana/config.xml"; + if (mRootDir.empty() && !stat(oldConfigFile.c_str(), &statbuf) + && S_ISREG(statbuf.st_mode)) + { + std::ifstream oldConfig; + std::ofstream newConfig; + logger->log1("Copying old TMW settings."); + + oldConfig.open(oldConfigFile.c_str(), std::ios::binary); + newConfig.open(newConfigFile.c_str(), std::ios::binary); + + if (!oldConfig.is_open() || !newConfig.is_open()) + { + logger->log1("Unable to copy old settings."); + } + else + { + newConfig << oldConfig.rdbuf(); + newConfig.close(); + oldConfig.close(); + } + } + } +} + +/** + * Initializes the home directory. On UNIX and FreeBSD, ~/.mana is used. On + * Windows and other systems we use the current working directory. + */ +void Client::initServerConfig(std::string serverName) +{ + mServerConfigDir = mConfigDir + "/" + serverName; + + if (mkdir_r(mServerConfigDir.c_str())) + { + logger->error(strprintf(_("%s doesn't exist and can't be created! " + "Exiting."), mServerConfigDir.c_str())); + } + FILE *configFile = 0; + std::string configPath; + + configPath = mServerConfigDir + "/config.xml"; + configFile = fopen(configPath.c_str(), "r"); + if (!configFile) + { + configFile = fopen(configPath.c_str(), "wt"); + logger->log("Creating new server config: " + configPath); + } + if (configFile) + { + fclose(configFile); + serverConfig.init(configPath); + logger->log("serverConfigPath: " + configPath); + } + initPacketLimiter(); + initTradeFilter(); + player_relations.init(); + + // Initialize the item and emote shortcuts. + for (int f = 0; f < SHORTCUT_TABS; f ++) + { + delete itemShortcut[f]; + itemShortcut[f] = new ItemShortcut(f); + } + delete emoteShortcut; + emoteShortcut = new EmoteShortcut; + + // Initialize the drop shortcuts. + delete dropShortcut; + dropShortcut = new DropShortcut; +} + +/** + * Initialize configuration. + */ +void Client::initConfiguration() +{ + // Fill configuration with defaults + config.setValue("hwaccel", false); +#if (defined __APPLE__ || defined WIN32) && defined USE_OPENGL + config.setValue("opengl", 1); +#else + config.setValue("opengl", 0); +#endif + config.setValue("screen", false); + config.setValue("sound", true); + config.setValue("guialpha", 0.8f); + config.setValue("remember", true); + config.setValue("sfxVolume", 100); + config.setValue("musicVolume", 60); + config.setValue("fpslimit", 60); + std::string defaultUpdateHost = branding.getValue("defaultUpdateHost", ""); + config.setValue("updatehost", defaultUpdateHost); + config.setValue("customcursor", true); + config.setValue("useScreenshotDirectorySuffix", true); + config.setValue("ChatLogLength", 128); + + // Checking if the configuration file exists... otherwise create it with + // default options. + FILE *configFile = 0; + std::string configPath; +// bool oldConfig = false; +// int emptySize = config.getSize(); + + configPath = mConfigDir + "/config.xml"; + + configFile = fopen(configPath.c_str(), "r"); + + // If we can't read it, it doesn't exist ! + if (!configFile) + { + // We reopen the file in write mode and we create it + configFile = fopen(configPath.c_str(), "wt"); + logger->log1("Creating new config"); +// oldConfig = false; + } + if (!configFile) + { + logger->log("Can't create %s. Using defaults.", configPath.c_str()); + } + else + { + fclose(configFile); + config.init(configPath); + config.setDefaultValues(getConfigDefaults()); + logger->log("configPath: " + configPath); + } +} + +/** + * Parse the update host and determine the updates directory + * Then verify that the directory exists (creating if needed). + */ +void Client::initUpdatesDir() +{ + std::stringstream updates; + + // If updatesHost is currently empty, fill it from config file + if (mUpdateHost.empty()) + mUpdateHost = config.getStringValue("updatehost"); + + // Don't go out of range int he next check + if (mUpdateHost.length() < 2) + return; + + // Remove any trailing slash at the end of the update host + if (!mUpdateHost.empty() && mUpdateHost.at(mUpdateHost.size() - 1) == '/') + mUpdateHost.resize(mUpdateHost.size() - 1); + + // Parse out any "http://" or "ftp://", and set the updates directory + size_t pos; + pos = mUpdateHost.find("://"); + if (pos != mUpdateHost.npos) + { + if (pos + 3 < mUpdateHost.length() && !mUpdateHost.empty()) + { + updates << "updates/" << mUpdateHost.substr(pos + 3); + mUpdatesDir = updates.str(); + } + else + { + logger->log("Error: Invalid update host: %s", mUpdateHost.c_str()); + errorMessage = strprintf(_("Invalid update host: %s"), + mUpdateHost.c_str()); + mState = STATE_ERROR; + } + } + else + { + logger->log1("Warning: no protocol was specified for the update host"); + updates << "updates/" << mUpdateHost; + mUpdatesDir = updates.str(); + } + + ResourceManager *resman = ResourceManager::getInstance(); + + // Verify that the updates directory exists. Create if necessary. + if (!resman->isDirectory("/" + mUpdatesDir)) + { + if (!resman->mkdir("/" + mUpdatesDir)) + { +#if defined WIN32 + std::string newDir = mLocalDataDir + "\\" + mUpdatesDir; + std::string::size_type loc = newDir.find("/", 0); + + while (loc != std::string::npos) + { + newDir.replace(loc, 1, "\\"); + loc = newDir.find("/", loc); + } + + if (!CreateDirectory(newDir.c_str(), 0) && + GetLastError() != ERROR_ALREADY_EXISTS) + { + logger->log("Error: %s can't be made, but doesn't exist!", + newDir.c_str()); + errorMessage = _("Error creating updates directory!"); + mState = STATE_ERROR; + } +#else + logger->log("Error: %s/%s can't be made, but doesn't exist!", + mLocalDataDir.c_str(), mUpdatesDir.c_str()); + errorMessage = _("Error creating updates directory!"); + mState = STATE_ERROR; +#endif + } + } + std::string updateLocal = "/" + mUpdatesDir + "/local"; + std::string updateFix = "/" + mUpdatesDir + "/fix"; + if (!resman->isDirectory(updateLocal)) + resman->mkdir(updateLocal); + if (!resman->isDirectory(updateFix)) + resman->mkdir(updateFix); +} + +void Client::initScreenshotDir() +{ + if (!mOptions.screenshotDir.empty()) + { + mScreenshotDir = mOptions.screenshotDir; + if (mkdir_r(mScreenshotDir.c_str())) + { + logger->log(strprintf( + _("Error: %s doesn't exist and can't be created! " + "Exiting."), mScreenshotDir.c_str())); + } + } + else if (mScreenshotDir.empty()) + { + std::string configScreenshotDir = + config.getStringValue("screenshotDirectory"); + if (!configScreenshotDir.empty()) + { + mScreenshotDir = configScreenshotDir; + } + else + { +#ifdef WIN32 + mScreenshotDir = getSpecialFolderLocation(CSIDL_MYPICTURES); + if (mScreenshotDir.empty()) + mScreenshotDir = getSpecialFolderLocation(CSIDL_DESKTOP); +#else + mScreenshotDir = std::string(PHYSFS_getUserDir()) + "Desktop"; +#endif + } + //config.setValue("screenshotDirectory", mScreenshotDir); + logger->log("screenshotDirectory: " + mScreenshotDir); + + if (config.getBoolValue("useScreenshotDirectorySuffix")) + { + std::string configScreenshotSuffix = + branding.getValue("appShort", "Mana"); + + if (!configScreenshotSuffix.empty()) + { + mScreenshotDir += "/" + configScreenshotSuffix; +// config.setValue("screenshotDirectorySuffix", +// configScreenshotSuffix); + } + } + } +} + +void Client::accountLogin(LoginData *loginData) +{ + logger->log("Username is %s", loginData->username.c_str()); + + // Send login infos + if (loginData->registerLogin) + Net::getLoginHandler()->registerAccount(loginData); + else + Net::getLoginHandler()->loginAccount(loginData); + + // Clear the password, avoids auto login when returning to login + loginData->password = ""; + + // TODO This is not the best place to save the config, but at least better + // than the login gui window + if (loginData->remember) + serverConfig.setValue("username", loginData->username); + serverConfig.setValue("remember", loginData->remember); +} + +bool Client::copyFile(std::string &configPath, std::string &oldConfigPath) +{ + FILE *configFile = 0; + + configFile = fopen(oldConfigPath.c_str(), "r"); + + if (configFile != NULL) + { + fclose(configFile); + + std::ifstream ifs(oldConfigPath.c_str(), std::ios::binary); + std::ofstream ofs(configPath.c_str(), std::ios::binary); + ofs << ifs.rdbuf(); + ifs.close(); + ofs.close(); + return true; + } + return false; +} + +bool Client::createConfig(std::string &configPath) +{ + std::string oldHomeDir; +#ifdef __APPLE__ + // Use Application Directory instead of .mana + oldHomeDir = std::string(PHYSFS_getUserDir()) + + "/Library/Application Support/" + + branding.getValue("appName", "Mana"); +#else + oldHomeDir = std::string(PHYSFS_getUserDir()) + + "/." + branding.getValue("appShort", "mana"); +#endif + + oldHomeDir += "/config.xml"; + + logger->log("Restore config from: " + configPath); + return copyFile(configPath, oldHomeDir); +} + +void Client::storeSafeParameters() +{ + bool tmpHwaccel; + int tmpOpengl; + int tmpFpslimit; + int tmpAltFpslimit; + bool tmpSound; + int width; + int height; + std::string font; + std::string boldFont; + std::string particleFont; + std::string helpFont; + bool showBackground; + bool enableMumble; + + isSafeMode = config.getBoolValue("safemode"); + if (isSafeMode) + logger->log1("Run in safe mode"); + + tmpHwaccel = config.getBoolValue("hwaccel"); + +#if defined USE_OPENGL + tmpOpengl = config.getIntValue("opengl"); +#else + tmpOpengl = 0; +#endif + tmpFpslimit = config.getIntValue("fpslimit"); + tmpAltFpslimit = config.getIntValue("altfpslimit"); + tmpSound = config.getBoolValue("sound"); + + width = config.getIntValue("screenwidth"); + height = config.getIntValue("screenheight"); + + font = config.getStringValue("font"); + boldFont = config.getStringValue("boldFont"); + particleFont = config.getStringValue("particleFont"); + helpFont = config.getStringValue("helpFont"); + + showBackground = config.getBoolValue("showBackground"); + enableMumble = config.getBoolValue("enableMumble"); + + config.setValue("hwaccel", false); + config.setValue("opengl", 0); + config.setValue("fpslimit", 0); + config.setValue("altfpslimit", 0); + config.setValue("sound", false); + config.setValue("safemode", true); + config.setValue("screenwidth", 640); + config.setValue("screenheight", 480); + config.setValue("font", "fonts/dejavusans.ttf"); + config.setValue("boldFont", "fonts/dejavusans-bold.ttf"); + config.setValue("particleFont", "fonts/dejavusans.ttf"); + config.setValue("helpFont", "fonts/dejavusansmono.ttf"); + config.setValue("showBackground", false); + config.setValue("enableMumble", false); + + config.write(); + + if (mOptions.safeMode) + { + isSafeMode = true; + return; + } + + config.setValue("hwaccel", tmpHwaccel); + config.setValue("opengl", tmpOpengl); + config.setValue("fpslimit", tmpFpslimit); + config.setValue("altfpslimit", tmpAltFpslimit); + config.setValue("sound", tmpSound); + config.setValue("safemode", false); + config.setValue("screenwidth", width); + config.setValue("screenheight", height); + config.setValue("font", font); + config.setValue("boldFont", boldFont); + config.setValue("particleFont", particleFont); + config.setValue("helpFont", helpFont); + config.setValue("showBackground", showBackground); + config.setValue("enableMumble", enableMumble); +} + +void Client::initTradeFilter() +{ + std::string tradeListName = + Client::getServerConfigDirectory() + "/tradefilter.txt"; + + std::ofstream tradeFile; + struct stat statbuf; + + if (stat(tradeListName.c_str(), &statbuf) || !S_ISREG(statbuf.st_mode)) + { + tradeFile.open(tradeListName.c_str(), std::ios::out); + tradeFile << ": sell" << std::endl; + tradeFile << ": buy" << std::endl; + tradeFile << ": trade" << std::endl; + tradeFile << "i sell" << std::endl; + tradeFile << "i buy" << std::endl; + tradeFile << "i trade" << std::endl; + tradeFile << "i trading" << std::endl; + tradeFile << "i am buy" << std::endl; + tradeFile << "i am sell" << std::endl; + tradeFile << "i am trade" << std::endl; + tradeFile << "i am trading" << std::endl; + tradeFile << "i'm buy" << std::endl; + tradeFile << "i'm sell" << std::endl; + tradeFile << "i'm trade" << std::endl; + tradeFile << "i'm trading" << std::endl; + tradeFile.close(); + } +} + +void Client::initPacketLimiter() +{ + //here i setting packet limits. but current server is broken, + // and this limits may not help. + + mPacketLimits[PACKET_CHAT].timeLimit = 10 + 5; + mPacketLimits[PACKET_CHAT].lastTime = 0; + mPacketLimits[PACKET_CHAT].cntLimit = 1; + mPacketLimits[PACKET_CHAT].cnt = 0; + + //10 + mPacketLimits[PACKET_PICKUP].timeLimit = 10 + 5; + mPacketLimits[PACKET_PICKUP].lastTime = 0; + mPacketLimits[PACKET_PICKUP].cntLimit = 1; + mPacketLimits[PACKET_PICKUP].cnt = 0; + + //10 5 + mPacketLimits[PACKET_DROP].timeLimit = 5; + mPacketLimits[PACKET_DROP].lastTime = 0; + mPacketLimits[PACKET_DROP].cntLimit = 1; + mPacketLimits[PACKET_DROP].cnt = 0; + + //100 + mPacketLimits[PACKET_NPC_NEXT].timeLimit = 0; + mPacketLimits[PACKET_NPC_NEXT].lastTime = 0; + mPacketLimits[PACKET_NPC_NEXT].cntLimit = 1; + mPacketLimits[PACKET_NPC_NEXT].cnt = 0; + + mPacketLimits[PACKET_NPC_INPUT].timeLimit = 100; + mPacketLimits[PACKET_NPC_INPUT].lastTime = 0; + mPacketLimits[PACKET_NPC_INPUT].cntLimit = 1; + mPacketLimits[PACKET_NPC_INPUT].cnt = 0; + + //50 + mPacketLimits[PACKET_NPC_TALK].timeLimit = 60; + mPacketLimits[PACKET_NPC_TALK].lastTime = 0; + mPacketLimits[PACKET_NPC_TALK].cntLimit = 1; + mPacketLimits[PACKET_NPC_TALK].cnt = 0; + + //10 + mPacketLimits[PACKET_EMOTE].timeLimit = 10 + 5; + mPacketLimits[PACKET_EMOTE].lastTime = 0; + mPacketLimits[PACKET_EMOTE].cntLimit = 1; + mPacketLimits[PACKET_EMOTE].cnt = 0; + + //100 + mPacketLimits[PACKET_SIT].timeLimit = 100; + mPacketLimits[PACKET_SIT].lastTime = 0; + mPacketLimits[PACKET_SIT].cntLimit = 1; + mPacketLimits[PACKET_SIT].cnt = 0; + + mPacketLimits[PACKET_DIRECTION].timeLimit = 50; + mPacketLimits[PACKET_DIRECTION].lastTime = 0; + mPacketLimits[PACKET_DIRECTION].cntLimit = 1; + mPacketLimits[PACKET_DIRECTION].cnt = 0; + + //2+ + mPacketLimits[PACKET_ATTACK].timeLimit = 2 + 10; + mPacketLimits[PACKET_ATTACK].lastTime = 0; + mPacketLimits[PACKET_ATTACK].cntLimit = 1; + mPacketLimits[PACKET_ATTACK].cnt = 0; + + + if (!mServerConfigDir.empty()) + { + std::string packetLimitsName = + Client::getServerConfigDirectory() + "/packetlimiter.txt"; + + std::ifstream inPacketFile; + struct stat statbuf; + + if (stat(packetLimitsName.c_str(), &statbuf) + || !S_ISREG(statbuf.st_mode)) + { + // wtiting new file + writePacketLimits(packetLimitsName); + } + else + { // reading existent file + inPacketFile.open(packetLimitsName.c_str(), std::ios::in); + char line[101]; + + if (!inPacketFile.getline(line, 100)) + return; + + int ver = atoi(line); + + for (int f = 0; f < PACKET_SIZE; f ++) + { + if (!inPacketFile.getline(line, 100)) + break; + + if (!(ver == 1 && (f == PACKET_DROP || f == PACKET_NPC_NEXT))) + mPacketLimits[f].timeLimit = atoi(line); + } + inPacketFile.close(); + if (ver == 1) + writePacketLimits(packetLimitsName); + } + } +} + +void Client::writePacketLimits(std::string packetLimitsName) +{ + std::ofstream outPacketFile; + outPacketFile.open(packetLimitsName.c_str(), std::ios::out); + outPacketFile << "2" << std::endl; + for (int f = 0; f < PACKET_SIZE; f ++) + { + outPacketFile << toString(mPacketLimits[f].timeLimit) + << std::endl; + } + + outPacketFile.close(); +} + +bool Client::checkPackets(int type) +{ + if (type > PACKET_SIZE) + return false; + + if (!serverConfig.getValueBool("enableBuggyServers", true)) + return true; + + int timeLimit = instance()->mPacketLimits[type].timeLimit; + + if (!timeLimit) + return true; + + int time = tick_time; + int lastTime = instance()->mPacketLimits[type].lastTime; + int cnt = instance()->mPacketLimits[type].cnt; + int cntLimit = instance()->mPacketLimits[type].cntLimit; + + if (lastTime > tick_time) + { +// instance()->mPacketLimits[type].lastTime = time; +// instance()->mPacketLimits[type].cnt = 0; + + return true; + } + else if (lastTime + timeLimit > time) + { + if (cnt >= cntLimit) + { + return false; + } + else + { +// instance()->mPacketLimits[type].cnt ++; + return true; + } + } +// instance()->mPacketLimits[type].lastTime = time; +// instance()->mPacketLimits[type].cnt = 1; + return true; +} + +bool Client::limitPackets(int type) +{ + if (type > PACKET_SIZE) + return false; + + if (!serverConfig.getValueBool("enableBuggyServers", true)) + return true; + + int timeLimit = instance()->mPacketLimits[type].timeLimit; + + if (!timeLimit) + return true; + + int time = tick_time; + int lastTime = instance()->mPacketLimits[type].lastTime; + int cnt = instance()->mPacketLimits[type].cnt; + int cntLimit = instance()->mPacketLimits[type].cntLimit; + + if (lastTime > tick_time) + { + instance()->mPacketLimits[type].lastTime = time; + instance()->mPacketLimits[type].cnt = 0; + + return true; + } + else if (lastTime + timeLimit > time) + { + if (cnt >= cntLimit) + { + return false; + } + else + { + instance()->mPacketLimits[type].cnt ++; + return true; + } + } + instance()->mPacketLimits[type].lastTime = time; + instance()->mPacketLimits[type].cnt = 1; + return true; +} + +const std::string Client::getServerConfigDirectory() +{ + return instance()->mServerConfigDir; +} + +void Client::setGuiAlpha(float n) +{ + instance()->mGuiAlpha = n; +} + +float Client::getGuiAlpha() +{ + return instance()->mGuiAlpha; +} + +void Client::setFramerate(int fpsLimit) +{ + if (!fpsLimit || !instance()->mLimitFps) + return; + + SDL_setFramerate(&instance()->mFpsManager, fpsLimit); +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 000000000..c853411d3 --- /dev/null +++ b/src/client.h @@ -0,0 +1,317 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "configlistener.h" + +#include "net/serverinfo.h" + +#include + +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Button; +class Desktop; +class LoginData; +class Window; +class QuitDialog; + +/** + * Set the milliseconds value of a tick time. + */ +static const int MILLISECONDS_IN_A_TICK = 10; + +//manaserv uses 9601 +//static const short DEFAULT_PORT = 9601; +static const short DEFAULT_PORT = 6901; + +extern volatile int fps; +extern volatile int tick_time; +extern volatile int cur_time; +extern bool isSafeMode; + +class ErrorListener : public gcn::ActionListener +{ + public: + void action(const gcn::ActionEvent &event); +}; + +extern std::string errorMessage; +extern ErrorListener errorListener; +extern LoginData loginData; + +/** + * Returns elapsed time. (Warning: supposes the delay is always < 100 seconds) + */ +int get_elapsed_time(int start_time); + +/** + * All client states. + */ +enum State +{ + STATE_ERROR = -1, + STATE_START = 0, + STATE_CHOOSE_SERVER, + STATE_CONNECT_SERVER, + STATE_LOGIN, + STATE_LOGIN_ATTEMPT, + STATE_WORLD_SELECT, // 5 + STATE_WORLD_SELECT_ATTEMPT, + STATE_UPDATE, + STATE_LOAD_DATA, + STATE_GET_CHARACTERS, + STATE_CHAR_SELECT, // 10 + STATE_CONNECT_GAME, + STATE_GAME, + STATE_CHANGE_MAP, // Switch map-server/gameserver + STATE_LOGIN_ERROR, + STATE_ACCOUNTCHANGE_ERROR, // 15 + STATE_REGISTER_PREP, + STATE_REGISTER, + STATE_REGISTER_ATTEMPT, + STATE_CHANGEPASSWORD, + STATE_CHANGEPASSWORD_ATTEMPT, // 20 + STATE_CHANGEPASSWORD_SUCCESS, + STATE_CHANGEEMAIL, + STATE_CHANGEEMAIL_ATTEMPT, + STATE_CHANGEEMAIL_SUCCESS, + STATE_UNREGISTER, // 25 + STATE_UNREGISTER_ATTEMPT, + STATE_UNREGISTER_SUCCESS, + STATE_SWITCH_SERVER, + STATE_SWITCH_LOGIN, + STATE_SWITCH_CHARACTER, // 30 + STATE_LOGOUT_ATTEMPT, + STATE_WAIT, + STATE_EXIT, + STATE_FORCE_QUIT, + STATE_AUTORECONNECT_SERVER = 1000 +}; + +enum PacketTypes +{ + PACKET_CHAT = 0, + PACKET_PICKUP = 1, + PACKET_DROP = 2, + PACKET_NPC_NEXT = 3, + PACKET_NPC_TALK = 4, + PACKET_NPC_INPUT = 5, + PACKET_EMOTE = 6, + PACKET_SIT = 7, + PACKET_DIRECTION = 8, + PACKET_ATTACK = 9, + PACKET_SIZE +}; + +struct PacketLimit +{ + int lastTime; + int timeLimit; + int cnt; + int cntLimit; +}; + +/** + * The core part of the client. This class initializes all subsystems, runs + * the event loop, and shuts everything down again. + */ +class Client : public ConfigListener, public gcn::ActionListener +{ +public: + /** + * A structure holding the values of various options that can be passed + * from the command line. + */ + struct Options + { + Options(): + printHelp(false), + printVersion(false), + skipUpdate(false), + chooseDefault(false), + noOpenGL(false), + safeMode(false), + serverPort(0) + {} + + bool printHelp; + bool printVersion; + bool skipUpdate; + bool chooseDefault; + bool noOpenGL; + std::string username; + std::string password; + std::string character; + std::string brandingPath; + std::string updateHost; + std::string dataPath; + std::string homeDir; + std::string logFileName; + std::string chatLogDir; + std::string configDir; + std::string localDataDir; + std::string screenshotDir; + bool safeMode; + + std::string serverName; + short serverPort; + }; + + Client(const Options &options); + ~Client(); + + /** + * Provides access to the client instance. + */ + static Client *instance() + { return mInstance; } + + int exec(); + + static void setState(State state) + { instance()->mState = state; } + + static State getState() + { return instance()->mState; } + + static const std::string &getPackageDirectory() + { return instance()->mPackageDir; } + + static const std::string &getConfigDirectory() + { return instance()->mConfigDir; } + + static const std::string &getLocalDataDirectory() + { return instance()->mLocalDataDir; } + + static const std::string &getScreenshotDirectory() + { return instance()->mScreenshotDir; } + + static const std::string getServerConfigDirectory(); + + static bool getIsMinimized() + { return instance()->mIsMinimized; } + + static void setIsMinimized(bool n) + { instance()->mIsMinimized = n; } + + static bool getInputFocused() + { return instance()->mInputFocused; } + + static void setInputFocused(bool n) + { instance()->mInputFocused = n; } + + static bool getMouseFocused() + { return instance()->mMouseFocused; } + + static void setMouseFocused(bool n) + { instance()->mMouseFocused = n; } + + static std::string getUpdatesDir() + { return instance()->mUpdatesDir; } + + static std::string getServerName() + { return instance()->mServerName; } + + static void setGuiAlpha(float n); + + static float getGuiAlpha(); + + static void setFramerate(int fpsLimit); + void optionChanged(const std::string &name); + void action(const gcn::ActionEvent &event); + void initTradeFilter(); + + void initPacketLimiter(); + + void writePacketLimits(std::string packetLimitsName); + + static bool limitPackets(int type); + + static bool checkPackets(int type); + + PacketLimit mPacketLimits[PACKET_SIZE + 1]; + +private: + void initRootDir(); + void initHomeDir(); + void initConfiguration(); + void initUpdatesDir(); + void initScreenshotDir(); + void initServerConfig(std::string serverName); + + bool copyFile(std::string &configPath, std::string &oldConfigPath); + bool createConfig(std::string &configPath); + + void accountLogin(LoginData *loginData); + + void storeSafeParameters(); + + static Client *mInstance; + + Options mOptions; + + std::string mPackageDir; + std::string mConfigDir; + std::string mLocalDataDir; + std::string mUpdateHost; + std::string mUpdatesDir; + std::string mScreenshotDir; + std::string mServerConfigDir; + std::string mRootDir; + std::string mServerName; + + ServerInfo mCurrentServer; + + Window *mCurrentDialog; + QuitDialog *mQuitDialog; + Desktop *mDesktop; + Button *mSetupButton; + + State mState; + State mOldState; + + SDL_Surface *mIcon; + + SDL_TimerID mLogicCounterId; + SDL_TimerID mSecondsCounterId; + + bool mLimitFps; + bool mConfigAutoSaved; + bool mIsMinimized; + bool mInputFocused; + bool mMouseFocused; + float mGuiAlpha; + FPSmanager mFpsManager; +}; + +#endif // CLIENT_H diff --git a/src/commandhandler.cpp b/src/commandhandler.cpp new file mode 100644 index 000000000..727374f6b --- /dev/null +++ b/src/commandhandler.cpp @@ -0,0 +1,1124 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "commandhandler.h" + +#include "actorspritemanager.h" +#include "channelmanager.h" +#include "channel.h" +#include "game.h" +#include "localplayer.h" +#include "log.h" +#include "main.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/outfitwindow.h" +#include "gui/shopwindow.h" +#include "gui/trade.h" +#include "gui/truetypefont.h" + +#include "gui/widgets/channeltab.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/whispertab.h" + +#include "net/adminhandler.h" +#include "net/beinghandler.h" +#include "net/chathandler.h" +#include "net/gamehandler.h" +#include "net/guildhandler.h" +#include "net/net.h" +#include "net/partyhandler.h" +#include "net/tradehandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern std::string tradePartnerName; + +CommandHandler::CommandHandler() +{ +} + +void CommandHandler::handleCommands(const std::string &command, ChatTab *tab) +{ + // here need add splitting commands + handleCommand(command, tab); +} + +void CommandHandler::handleCommand(const std::string &command, ChatTab *tab) +{ + std::string::size_type pos = command.find(' '); + std::string type(command, 0, pos); + std::string args(command, pos == std::string::npos + ? command.size() : pos + 1); + + if (command == "closeall") + { + handleCloseAll(args, tab); + } + else if (type == "ignoreall") + { + handleIgnoreAll(args, tab); + } + else if (type == "help") // Do help before tabs so they can't override it + { + handleHelp(args, tab); + } + else if (type == "announce") + { + handleAnnounce(args, tab); + } + else if (type == "where") + { + handleWhere(args, tab); + } + else if (type == "who") + { + handleWho(args, tab); + } + else if (type == "msg" || type == "whisper" || type == "w") + { + handleMsg(args, tab); + } + else if (type == "query" || type == "q") + { + handleQuery(args, tab); + } + else if (type == "ignore") + { + handleIgnore(args, tab); + } + else if (type == "unignore") + { + handleUnignore(args, tab); + } + else if (type == "friend" || type == "befriend") + { + handleFriend(args, tab); + } + else if (type == "disregard") + { + handleDisregard(args, tab); + } + else if (type == "neutral") + { + handleNeutral(args, tab); + } + else if (type == "erase") + { + handleErase(args, tab); + } + else if (type == "join") + { + handleJoin(args, tab); + } + else if (type == "list") + { + handleListChannels(args, tab); + } + else if (type == "clear") + { + handleClear(args, tab); + } + else if (type == "createparty") + { + handleCreateParty(args, tab); + } + else if (type == "createguild") + { + handleCreateGuild(args, tab); + } + else if (type == "party") + { + handleParty(args, tab); + } + else if (type == "me") + { + handleMe(args, tab); + } + else if (type == "toggle") + { + handleToggle(args, tab); + } + else if (type == "present") + { + handlePresent(args, tab); + } + else if (type == "quit") + { + handleQuit(args, tab); + } + else if (type == "all") + { + handleShowAll(args, tab); + } + else if (type == "move") + { + handleMove(args, tab); + } + else if (type == "target") + { + handleTarget(args, tab); + } + else if (type == "outfit") + { + handleOutfit(args, tab); + } + else if (type == "emote") + { + handleEmote(args, tab); + } + else if (type == "away") + { + handleAway(args, tab); + } + else if (type == "follow") + { + handleFollow(args, tab); + } + else if (type == "heal") + { + handleHeal(args, tab); + } + else if (type == "navigate") + { + handleNavigate(args, tab); + } + else if (type == "imitation") + { + handleImitation(args, tab); + } + else if (type == "mail") + { + handleMail(args, tab); + } + else if (type == "trade") + { + handleTrade(args, tab); + } + else if (type == "priceload") + { + handlePriceLoad(args, tab); + } + else if (type == "pricesave") + { + handlePriceSave(args, tab); + } + else if (type == "cacheinfo") + { + handleCacheInfo(args, tab); + } + else if (type == "disconnect") + { + handleDisconnect(args, tab); + } + else if (type == "undress") + { + handleUndress(args, tab); + } + else if (type == "attack") + { + handleAttack(args, tab); + } + else if (type == "dirs") + { + handleDirs(args, tab); + } + else if (type == "info") + { + handleInfo(args, tab); + } + else if (type == "wait") + { + handleWait(args, tab); + } + else if (tab->handleCommand(type, args)) + { + // Nothing to do + } + else if (type == "hack") + { + handleHack(args, tab); + } + else + { + tab->chatLog(_("Unknown command.")); + } +} + +char CommandHandler::parseBoolean(const std::string &value) +{ + std::string opt = value.substr(0, 1); + + if (opt == "1" || + opt == "y" || opt == "Y" || + opt == "t" || opt == "T") + return 1; + else if (opt == "0" || + opt == "n" || opt == "N" || + opt == "f" || opt == "F") + return 0; + else + return -1; +} + +void CommandHandler::handleAnnounce(const std::string &args, + ChatTab *tab _UNUSED_) +{ + Net::getAdminHandler()->announce(args); +} + +void CommandHandler::handleHelp(const std::string &args, ChatTab *tab) +{ + if (args == "") + { + tab->chatLog(_("-- Help --")); + tab->chatLog(_("/help > Display this help")); + + tab->chatLog(_("/where > Display map name")); + tab->chatLog(_("/who > Display number of online users")); + tab->chatLog(_("/me > Tell something about yourself")); + + tab->chatLog(_("/clear > Clears this window")); + + tab->chatLog(_("/msg > Send a private message to a user")); + tab->chatLog(_("/whisper > Alias of msg")); + tab->chatLog(_("/w > Alias of msg")); + tab->chatLog(_("/query > Makes a tab for private messages " + "with another user")); + tab->chatLog(_("/q > Alias of query")); + + tab->chatLog(_("/away > Tell the other whispering players " + "you're away from keyboard.")); + + tab->chatLog(_("/ignore > ignore a player")); + tab->chatLog(_("/unignore > stop ignoring a player")); + tab->chatLog(_("/ignoreall > Ignore all opened whisper tabs")); + tab->chatLog(_("/erase > Erase a player")); + tab->chatLog(_("/befriend > Be friend a player")); + tab->chatLog(_("/desregard > Disregard a player")); + tab->chatLog(_("/neutral > Neutral a player")); + + tab->chatLog(_("/list > Display all public channels")); + tab->chatLog(_("/join > Join or create a channel")); + + tab->chatLog(_("/createparty > Create a new party")); + tab->chatLog(_("/createguild > Create a new guild")); + tab->chatLog(_("/party > Invite a user to party")); + + tab->chatLog(_("/toggle > Determine whether " + "toggles the chat log")); + tab->chatLog(_("/present > Get list of players present " + "(sent to chat log, if logging)")); + + tab->chatLog(_("/announce > Global announcement (GM only)")); + + tab->chatLog(_("/closeall > Close all opened whisper tabs")); + + tab->chatLog(_("/all > Show all visible beings in debug tab")); + + tab->chatLog(_("/move > Move to given position")); + tab->chatLog(_("/navigate > Draw path to given position")); + tab->chatLog(_("/target > Set target to being")); + tab->chatLog(_("/outfit > Wear outfit by index")); + tab->chatLog(_("/emote > Show emote by index")); + tab->chatLog(_("/follow > Follow player")); + tab->chatLog(_("/imitation > Imitate player")); + tab->chatLog(_("/heal > Heal player")); + tab->chatLog(_("/mail > Send offline message to player")); + + tab->showHelp(); // Allow the tab to show it's help + + tab->chatLog(_("For more information, type /help .")); + } + else if (args == "help") // Do this before tabs so they can't change it + { + tab->chatLog(_("Command: /help")); + tab->chatLog(_("This command displays a list " + "of all commands available.")); + tab->chatLog(_("Command: /help ")); + tab->chatLog(_("This command displays help on .")); + } + else if (tab->handleCommand("help", args)) + { + // Nothing to do + } + else if (args == "announce") + { + tab->chatLog(_("Command: /announce ")); + tab->chatLog(_("*** only available to a GM ***")); + tab->chatLog(_("This command sends the message to " + "all players currently online.")); + } + else if (args == "clear") + { + tab->chatLog(_("Command: /clear")); + tab->chatLog(_("This command clears the chat log of previous chat.")); + } + else if (args == "ignore") + { + tab->chatLog(_("Command: /ignore ")); + tab->chatLog(_("This command ignores the given player regardless of " + "current relations.")); + } + else if (args == "join") + { + tab->chatLog(_("Command: /join ")); + tab->chatLog(_("This command makes you enter .")); + tab->chatLog(_("If doesn't exist, it's created.")); + } + else if (args == "list") + { + tab->chatLog(_("Command: /list")); + tab->chatLog(_("This command shows a list of all channels.")); + } + else if (args == "me") + { + tab->chatLog(_("Command: /me ")); + tab->chatLog(_("This command tell others you are (doing) .")); + } + else if (args == "msg" || args == "whisper" || args == "w") + { + tab->chatLog(_("Command: /msg ")); + tab->chatLog(_("Command: /whisper ")); + tab->chatLog(_("Command: /w ")); + tab->chatLog(_("This command sends the text to .")); + tab->chatLog(_("If the has spaces in it, enclose it in " + "double quotes (\").")); + } + else if (args == "query" || args == "q") + { + tab->chatLog(_("Command: /query ")); + tab->chatLog(_("Command: /q ")); + tab->chatLog(_("This command tries to make a tab for whispers between" + "you and .")); + } + else if (args == "away") + { + tab->chatLog(_("Command: /away ")); + tab->chatLog(_("This command tells " + "you're away from keyboard with the given reason.")); + tab->chatLog(_("Command: /away")); + tab->chatLog(_("This command clears the away status and message.")); + } + else if (args == "createparty") + { + tab->chatLog(_("Command: /createparty ")); + tab->chatLog(_("This command creates a new party called .")); + } + else if (args == "createguild") + { + tab->chatLog(_("Command: /createguild ")); + tab->chatLog(_("This command creates a new guild called .")); + } + else if (args == "party") + { + tab->chatLog(_("Command: /party ")); + tab->chatLog(_("This command invites to party with you.")); + tab->chatLog(_("If the has spaces in it, enclose it in " + "double quotes (\").")); + } + else if (args == "present") + { + tab->chatLog(_("Command: /present")); + tab->chatLog(_("This command gets a list of players within hearing " + "and sends it to chat log.")); + } + else if (args == "toggle") + { + tab->chatLog(_("Command: /toggle ")); + tab->chatLog(_("This command sets whether the return key should " + "toggle the chat log, or whether the chat log turns off " + "automatically.")); + tab->chatLog(_(" can be one of \"1\", \"yes\", \"true\" to " + "turn the toggle on, or \"0\", \"no\", \"false\" to turn the " + "toggle off.")); + tab->chatLog(_("Command: /toggle")); + tab->chatLog(_("This command displays the return toggle status.")); + } + else if (args == "unignore") + { + tab->chatLog(_("Command: /unignore ")); + tab->chatLog(_("This command stops ignoring the given player if they " + "are being ignored")); + } + else if (args == "where") + { + tab->chatLog(_("Command: /where")); + tab->chatLog(_("This command displays the name of the current map.")); + } + else if (args == "who") + { + tab->chatLog(_("Command: /who")); + tab->chatLog(_("This command displays the number of players currently " + "online.")); + } + else + { + tab->chatLog(_("Unknown command.")); + tab->chatLog(_("Type /help for a list of commands.")); + } +} + +void CommandHandler::handleWhere(const std::string &args _UNUSED_, + ChatTab *tab) +{ + std::ostringstream where; + where << Game::instance()->getCurrentMapName() << ", coordinates: " + << ((player_node->getPixelX() - 16) / 32) << ", " + << ((player_node->getPixelY() - 32) / 32); + + tab->chatLog(where.str(), BY_SERVER); +} + +void CommandHandler::handleWho(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + Net::getChatHandler()->who(); +} + +void CommandHandler::handleMsg(const std::string &args, ChatTab *tab) +{ + std::string recvnick = ""; + std::string msg = ""; + + if (args.substr(0, 1) == "\"") + { + const std::string::size_type pos = args.find('"', 1); + if (pos != std::string::npos) + { + recvnick = args.substr(1, pos - 1); + if (pos + 2 < args.length()) + msg = args.substr(pos + 2, args.length()); + } + } + else + { + const std::string::size_type pos = args.find(" "); + if (pos != std::string::npos) + { + recvnick = args.substr(0, pos); + if (pos + 1 < args.length()) + msg = args.substr(pos + 1, args.length()); + } + else + { + recvnick = std::string(args); + msg = ""; + } + } + + trim(msg); + + if (msg.length() > 0) + { + std::string playerName = player_node->getName(); + std::string tempNick = recvnick; + + toLower(playerName); + toLower(tempNick); + + if (tempNick.compare(playerName) == 0 || args.empty()) + return; + + chatWindow->whisper(recvnick, msg, BY_PLAYER); + } + else + tab->chatLog(_("Cannot send empty whispers!"), BY_SERVER); +} + +void CommandHandler::handleQuery(const std::string &args, ChatTab *tab) +{ + if (chatWindow && chatWindow->addWhisperTab(args, true)) + return; + + tab->chatLog(strprintf(_("Cannot create a whisper tab for nick \"%s\"! " + "It either already exists, or is you."), + args.c_str()), BY_SERVER); +} + +void CommandHandler::handleClear(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (chatWindow) + chatWindow->clearTab(); +} + +void CommandHandler::handleJoin(const std::string &args, ChatTab *tab) +{ + if (!tab) + return; + + std::string::size_type pos = args.find(' '); + std::string name(args, 0, pos); + std::string password(args, pos + 1); + tab->chatLog(strprintf(_("Requesting to join channel %s."), name.c_str())); + Net::getChatHandler()->enterChannel(name, password); +} + +void CommandHandler::handleListChannels(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + Net::getChatHandler()->channelList(); +} + +void CommandHandler::handleCreateParty(const std::string &args, ChatTab *tab) +{ + if (!tab) + return; + + if (args.empty()) + tab->chatLog(_("Party name is missing."), BY_SERVER); + else + Net::getPartyHandler()->create(args); +} + +void CommandHandler::handleCreateGuild(const std::string &args, ChatTab *tab) +{ + if (!tab) + return; + + if (args.empty()) + tab->chatLog(_("Guild name is missing."), BY_SERVER); + else + Net::getGuildHandler()->create(args); +} + +void CommandHandler::handleParty(const std::string &args, ChatTab *tab) +{ + if (!tab) + return; + + if (args != "") + Net::getPartyHandler()->invite(args); + else + tab->chatLog(_("Please specify a name."), BY_SERVER); +} + +void CommandHandler::handleMe(const std::string &args, ChatTab *tab) +{ + if (!tab) + { + Net::getChatHandler()->me(args); + return; + } + + const std::string str = strprintf("*%s*", args.c_str()); + switch (tab->getType()) + { + case ChatTab::TAB_PARTY: + { + Net::getPartyHandler()->chat(str); + break; + } + case ChatTab::TAB_GUILD: + { + if (!player_node) + return; + const Guild *guild = player_node->getGuild(); + if (guild) + Net::getGuildHandler()->chat(guild->getId(), str); + break; + } + default: + Net::getChatHandler()->me(args); + break; + } +} + +void CommandHandler::handleToggle(const std::string &args, ChatTab *tab) +{ + if (args.empty()) + { + if (chatWindow && tab) + { + tab->chatLog(chatWindow->getReturnTogglesChat() ? + _("Return toggles chat.") : _("Message closes chat.")); + } + return; + } + + char opt = parseBoolean(args); + + switch (opt) + { + case 1: + if (tab) + tab->chatLog(_("Return now toggles chat.")); + if (chatWindow) + chatWindow->setReturnTogglesChat(true); + return; + case 0: + if (tab) + tab->chatLog(_("Message now closes chat.")); + if (chatWindow) + chatWindow->setReturnTogglesChat(false); + return; + case -1: + if (tab) + tab->chatLog(strprintf(BOOLEAN_OPTIONS, "toggle")); + return; + default: + return; + } +} + +void CommandHandler::handlePresent(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (chatWindow) + chatWindow->doPresent(); +} + +void CommandHandler::handleIgnore(const std::string &args, + ChatTab *tab _UNUSED_) +{ + changeRelation(args, PlayerRelation::IGNORED, "ignored", tab); +} + +void CommandHandler::handleFriend(const std::string &args, ChatTab *tab) +{ + changeRelation(args, PlayerRelation::FRIEND, _("friend"), tab); +} + +void CommandHandler::handleDisregard(const std::string &args, ChatTab *tab) +{ + changeRelation(args, PlayerRelation::DISREGARDED, _("disregarded"), tab); +} + +void CommandHandler::handleNeutral(const std::string &args, ChatTab *tab) +{ + changeRelation(args, PlayerRelation::NEUTRAL, _("neutral"), tab); +} + +void CommandHandler::changeRelation(const std::string &args, + PlayerRelation::Relation relation, + const std::string &relationText, + ChatTab *tab) +{ + if (args.empty()) + { + if (tab) + tab->chatLog(_("Please specify a name."), BY_SERVER); + return; + } + + if (player_relations.getRelation(args) == relation) + { + if (tab) + { + tab->chatLog(strprintf(_("Player already %s!"), + relationText.c_str()), BY_SERVER); + } + return; + } + else + { + player_relations.setRelation(args, relation); + } + + if (player_relations.getRelation(args) == relation) + { + if (tab) + { + tab->chatLog(strprintf(_("Player successfully %s!"), + relationText.c_str()), BY_SERVER); + } + } + else + { + if (tab) + { + tab->chatLog(strprintf(_("Player could not be %s!"), + relationText.c_str()), BY_SERVER); + } + } +} + +void CommandHandler::handleUnignore(const std::string &args, ChatTab *tab) +{ + if (args.empty()) + { + if (tab) + tab->chatLog(_("Please specify a name."), BY_SERVER); + return; + } + + if (player_relations.getRelation(args) == PlayerRelation::IGNORED) + { + player_relations.removePlayer(args); + } + else + { + if (tab) + tab->chatLog(_("Player wasn't ignored!"), BY_SERVER); + return; + } + + if (tab) + { + if (player_relations.getRelation(args) != PlayerRelation::IGNORED) + tab->chatLog(_("Player no longer ignored!"), BY_SERVER); + else + tab->chatLog(_("Player could not be unignored!"), BY_SERVER); + } +} + + +void CommandHandler::handleErase(const std::string &args, ChatTab *tab) +{ + if (args.empty()) + { + if (tab) + tab->chatLog(_("Please specify a name."), BY_SERVER); + return; + } + + if (player_relations.getRelation(args) == PlayerRelation::ERASED) + { + if (tab) + tab->chatLog(_("Player already erased!"), BY_SERVER); + return; + } + else + { + player_relations.setRelation(args, PlayerRelation::ERASED); + } + + if (tab) + { + if (player_relations.getRelation(args) == PlayerRelation::ERASED) + tab->chatLog(_("Player successfully erased!"), BY_SERVER); + else + tab->chatLog(_("Player could not be erased!"), BY_SERVER); + } +} + +void CommandHandler::handleQuit(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ +// quit(); +} + +void CommandHandler::handleShowAll(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (actorSpriteManager) + actorSpriteManager->printAllToChat(); +} + +void CommandHandler::handleMove(const std::string &args, ChatTab *tab _UNUSED_) +{ + int x = 0; + int y = 0; + + if (player_node && parse2Int(args, &x, &y)) + player_node->moveTo(x, y); +} + +void CommandHandler::handleNavigate(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (!player_node) + return; + + int x = 0; + int y = 0; + + if (parse2Int(args, &x, &y)) + player_node->navigateTo(x, y); + else + player_node->naviageClean(); +} + +bool CommandHandler::parse2Int(const std::string &args, int *x, int *y) +{ + bool isValid = false; + const std::string::size_type pos = args.find(" "); + if (pos != std::string::npos) + { + if (pos + 1 < args.length()) + { + *x = atoi(args.substr(0, pos).c_str()); + *y = atoi(args.substr(pos + 1, args.length()).c_str()); + isValid = true; + } + } + return isValid; +} + +void CommandHandler::handleTarget(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (!actorSpriteManager || !player_node) + return; + + Being* target = actorSpriteManager->findNearestByName(args); + if (target) + player_node->setTarget(target); +} + +void CommandHandler::handleCloseAll(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (chatWindow) + chatWindow->removeAllWhispers(); +} + +void CommandHandler::handleIgnoreAll(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (chatWindow) + chatWindow->ignoreAllWhispers(); +} + +void CommandHandler::handleOutfit(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (outfitWindow) + { + if (!args.empty()) + { + const std::string op = args.substr(0, 1); + if (op == "n") + outfitWindow->wearNextOutfit(true); + else if (op == "p") + outfitWindow->wearPreviousOutfit(true); + else + outfitWindow->wearOutfit(atoi(args.c_str()) - 1, false, true); + } + else + { + outfitWindow->wearOutfit(atoi(args.c_str()) - 1, false, true); + } + } +} + +void CommandHandler::handleEmote(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (player_node) + player_node->emote(static_cast(atoi(args.c_str()))); +} + +void CommandHandler::handleAway(const std::string &args, ChatTab *tab _UNUSED_) +{ + if (player_node) + player_node->setAway(args); +} + +void CommandHandler::handleFollow(const std::string &args, ChatTab *tab) +{ + if (!player_node) + return; + + if (!args.empty()) + { + player_node->setFollow(args); + } + else if (tab) + { + if (tab->getType() == ChatTab::TAB_WHISPER) + { + WhisperTab *wTab = static_cast(tab); + if (wTab) + player_node->setFollow(wTab->getNick()); + } + } +} + +void CommandHandler::handleImitation(const std::string &args, ChatTab *tab) +{ + if (!player_node) + return; + + if (!args.empty()) + { + player_node->setImitate(args); + } + else if (tab && tab->getType() == ChatTab::TAB_WHISPER) + { + WhisperTab *wTab = static_cast(tab); + if (wTab) + player_node->setImitate(wTab->getNick()); + } + else + { + player_node->setImitate(""); + } +} + +void CommandHandler::handleHeal(const std::string &args, ChatTab *tab _UNUSED_) +{ + if (!actorSpriteManager) + return; + + if (!args.empty()) + { + Being *being = actorSpriteManager->findBeingByName( + args, Being::PLAYER); + if (being) + actorSpriteManager->heal(player_node, being); + } + else + { + actorSpriteManager->heal(player_node, player_node); + } +} + +void CommandHandler::handleHack(const std::string &args, ChatTab *tab _UNUSED_) +{ + Net::getChatHandler()->sendRaw(args); +} + +void CommandHandler::handleMail(const std::string &args, ChatTab *tab _UNUSED_) +{ + Net::getChatHandler()->privateMessage("AuctionBot", "!mail " + args); +} + +void CommandHandler::handlePriceLoad(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (shopWindow) + shopWindow->loadList(); +} + +void CommandHandler::handlePriceSave(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (shopWindow) + shopWindow->saveList(); +} + +void CommandHandler::handleDisconnect(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + Net::getGameHandler()->disconnect2(); +} + +void CommandHandler::handleUndress(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (!actorSpriteManager) + return; + + Being* target = actorSpriteManager->findNearestByName(args); + if (target) + Net::getBeingHandler()->undress(target); +} + +void CommandHandler::handleAttack(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (!player_node || !actorSpriteManager) + return; + + Being* target = actorSpriteManager->findNearestByName(args); + if (target) + player_node->setTarget(target); + player_node->attack2(player_node->getTarget(), true); +} + +void CommandHandler::handleTrade(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (!actorSpriteManager) + return; + + Being *being = actorSpriteManager->findBeingByName(args, Being::PLAYER); + if (being) + { + Net::getTradeHandler()->request(being); + tradePartnerName = being->getName(); + if (tradeWindow) + tradeWindow->clear(); + } +} + +void CommandHandler::handleDirs(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (!player_node || !debugChatTab) + return; + + debugChatTab->chatLog("config directory: " + + Client::getConfigDirectory()); + debugChatTab->chatLog("logs directory: " + + Client::getLocalDataDirectory()); + debugChatTab->chatLog("screenshots directory: " + + Client::getScreenshotDirectory()); +} + +void CommandHandler::handleInfo(const std::string &args _UNUSED_, + ChatTab *tab) +{ + switch (tab->getType()) + { + case ChatTab::TAB_GUILD: + { + if (!player_node) + return; + const Guild *guild = player_node->getGuild(); + if (guild) + Net::getGuildHandler()->info(guild->getId()); + break; + } + default: + break; + } +} + +void CommandHandler::handleWait(const std::string &args, + ChatTab *tab _UNUSED_) +{ + if (player_node) + player_node->waitFor(args); +} + +void CommandHandler::handleCacheInfo(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_) +{ + if (!chatWindow || !debugChatTab) + return; + + TrueTypeFont *font = dynamic_cast(chatWindow->getFont()); + if (!font) + return; + + std::list *cache = font->getCache(); + if (!cache) + return; + + debugChatTab->chatLog("font cache size"); + std::string str; + for (int f = 0; f < 256; f ++) + { + if (!cache[f].empty()) + str += strprintf("%d: %u, ", f, (unsigned int)cache[f].size()); + } + debugChatTab->chatLog(str); +#ifdef DEBUG_FONT_COUNTERS + debugChatTab->chatLog(""); + debugChatTab->chatLog("Created: " + toString(font->getCreateCounter())); + debugChatTab->chatLog("Deleted: " + toString(font->getDeleteCounter())); +#endif +} diff --git a/src/commandhandler.h b/src/commandhandler.h new file mode 100644 index 000000000..5a4d9f220 --- /dev/null +++ b/src/commandhandler.h @@ -0,0 +1,281 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COMMANDHANDLER_H +#define COMMANDHANDLER_H + +#include "playerrelations.h" + +#include + +class ChatTab; + +extern ChatTab *localChatTab; + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +#define BOOLEAN_OPTIONS _("Options to /%s are \"yes\", \"no\", \"true\", "\ +"\"false\", \"1\", \"0\".") + +/** + * A class to parse and handle user commands + */ +class CommandHandler +{ + public: + /** + * Constructor + */ + CommandHandler(); + + /** + * Destructor + */ + ~CommandHandler() {} + + /** + * Parse and handle the given command. + */ + void handleCommand(const std::string &command, + ChatTab *tab = localChatTab); + + void handleCommands(const std::string &command, + ChatTab *tab = localChatTab); + + static char parseBoolean(const std::string &value); + + protected: + friend class ChatTab; + friend class WhisperTab; + + /** + * Handle an announce command. + */ + void handleAnnounce(const std::string &args, ChatTab *tab); + + /** + * Handle a help command. + */ + void handleHelp(const std::string &args, ChatTab *tab); + + /** + * Handle a where command. + */ + void handleWhere(const std::string &args, ChatTab *tab); + + /** + * Handle a who command. + */ + void handleWho(const std::string &args, ChatTab *tab); + + /** + * Handle a msg command. + */ + void handleMsg(const std::string &args, ChatTab *tab); + + /** + * Handle a msg tab request. + */ + void handleQuery(const std::string &args, ChatTab *tab); + + /** + * Handle a join command. + */ + void handleJoin(const std::string &args, ChatTab *tab); + + /** + * Handle a listchannels command. + */ + void handleListChannels(const std::string &args, ChatTab *tab); + + /** + * Handle a clear command. + */ + void handleClear(const std::string &args, ChatTab *tab); + + /** + * Handle a createparty command. + */ + void handleCreateParty(const std::string &args, ChatTab *tab); + + /** + * Handle a createguild command. + */ + void handleCreateGuild(const std::string &args, ChatTab *tab); + + /** + * Handle a party command. + */ + void handleParty(const std::string &args, ChatTab *tab); + + /** + * Handle a me command. + */ + void handleMe(const std::string &args, ChatTab *tab); + + /** + * Handle a toggle command. + */ + void handleToggle(const std::string &args, ChatTab *tab); + + /** + * Handle a present command. + */ + void handlePresent(const std::string &args, ChatTab *tab); + + /** + * Handle an ignore command. + */ + void handleIgnore(const std::string &args, ChatTab *tab); + + /** + * Handle an unignore command. + */ + void handleUnignore(const std::string &args, ChatTab *tab); + + /** + * Handle an friend command. + */ + void handleFriend(const std::string &args, ChatTab *tab); + + /** + * Handle an disregard command. + */ + void handleDisregard(const std::string &args, ChatTab *tab); + + /** + * Handle an neutral command. + */ + void handleNeutral(const std::string &args, ChatTab *tab); + + /** + * Handle an erase command. + */ + void handleErase(const std::string &args, ChatTab *tab); + + /** + * Change relation. + */ + void changeRelation(const std::string &args, + PlayerRelation::Relation relation, + const std::string &relationText, ChatTab *tab); + + /** + * Handle a quit command. + */ + void handleQuit(const std::string &args, ChatTab *tab); + + /** + * Handle show all command. + */ + void handleShowAll(const std::string &args, ChatTab *tab); + + /** + * Handle move command. + */ + void handleMove(const std::string &args, ChatTab *tab); + + /** + * Handle target command. + */ + void handleTarget(const std::string &args, ChatTab *tab); + + /** + * Handle closeall command. + */ + void handleCloseAll(const std::string &args, ChatTab *tab); + + /** + * Handle ignoreall command. + */ + void handleIgnoreAll(const std::string &args, ChatTab *tab); + + /** + * Handle outfit command. + */ + void handleOutfit(const std::string &args, ChatTab *tab); + + /** + * Handle emote command. + */ + void handleEmote(const std::string &args, ChatTab *tab); + + /** + * Handle away command. + */ + void handleAway(const std::string &args, ChatTab *tab); + + /** + * Handle follow command. + */ + void handleFollow(const std::string &args, ChatTab *tab); + + /** + * Handle imitation command. + */ + void handleImitation(const std::string &args, ChatTab *tab); + + /** + * Handle heal command. + */ + void handleHeal(const std::string &args, ChatTab *tab); + + /** + * Handle navigate command. + */ + void handleNavigate(const std::string &args, ChatTab *tab); + + void handleMail(const std::string &args, ChatTab *tab _UNUSED_); + + void handleHack(const std::string &args, ChatTab *tab); + + void handlePriceLoad(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_); + + void handlePriceSave(const std::string &args _UNUSED_, + ChatTab *tab _UNUSED_); + + void handleTrade(const std::string &args, ChatTab *tab _UNUSED_); + + void handleDisconnect(const std::string &args, ChatTab *tab _UNUSED_); + + void handleUndress(const std::string &args, ChatTab *tab _UNUSED_); + + void handleAttack(const std::string &args, ChatTab *tab _UNUSED_); + + void handleDirs(const std::string &args, ChatTab *tab); + + void handleInfo(const std::string &args, ChatTab *tab); + + void handleWait(const std::string &args, ChatTab *tab); + + void handleCacheInfo(const std::string &args, ChatTab *tab _UNUSED_); + + bool parse2Int(const std::string &args, int *x, int *y); +}; + +extern CommandHandler *commandHandler; + +#endif // COMMANDHANDLER_H diff --git a/src/compoundsprite.cpp b/src/compoundsprite.cpp new file mode 100644 index 000000000..9bbc328ac --- /dev/null +++ b/src/compoundsprite.cpp @@ -0,0 +1,408 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "compoundsprite.h" + +#include "game.h" +#include "graphics.h" +#ifdef USE_OPENGL +#include "openglgraphics.h" +#include "opengl1graphics.h" +#endif +#include "map.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include + +#define BUFFER_WIDTH 100 +#define BUFFER_HEIGHT 100 + +CompoundSprite::CompoundSprite(): + mImage(0), + mAlphaImage(0), + mOffsetX(0), mOffsetY(0), + mNeedsRedraw(false) +{ + mAlpha = 1.0f; +} + +CompoundSprite::~CompoundSprite() +{ + SpriteIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + delete (*it); + + clear(); + + delete mImage; + mImage = 0; + delete mAlphaImage; + mAlphaImage = 0; +} + +bool CompoundSprite::reset() +{ + bool ret = false; + + SpriteIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + ret |= (*it)->reset(); + } + + mNeedsRedraw |= ret; + return ret; +} + +bool CompoundSprite::play(std::string action) +{ + bool ret = false; + + SpriteIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + ret |= (*it)->play(action); + } + + mNeedsRedraw |= ret; + return ret; +} + +bool CompoundSprite::update(int time) +{ + bool ret = false; + + SpriteIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + ret |= (*it)->update(time); + } + + mNeedsRedraw |= ret; + return ret; +} + +bool CompoundSprite::draw(Graphics* graphics, int posX, int posY) const +{ + if (mNeedsRedraw) + redraw(); + + if (mAlpha == 1.0f && mImage) + { + return graphics->drawImage(mImage, posX + mOffsetX, posY + mOffsetY); + } + else if (mAlpha && mAlphaImage) + { + mAlphaImage->setAlpha(mAlpha); + + return graphics->drawImage(mAlphaImage, + posX + mOffsetX, posY + mOffsetY); + } + else + { + drawSprites(graphics, posX, posY); + } + + return false; +} + +void CompoundSprite::drawSprites(Graphics* graphics, int posX, int posY) const +{ + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + { + (*it)->setAlpha(mAlpha); + (*it)->draw(graphics, posX, posY); + } + } +} + +void CompoundSprite::drawSpritesSDL(Graphics* graphics, + int posX, int posY) const +{ + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + (*it)->draw(graphics, posX, posY); + } +} + +int CompoundSprite::getWidth() const +{ + Sprite *base = NULL; + + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if ((base = (*it))) + break; + } + + if (base) + return base->getWidth(); + + return 0; +} + +int CompoundSprite::getHeight() const +{ + Sprite *base = NULL; + + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if ((base = (*it))) + break; + } + + if (base) + return base->getHeight(); + + return 0; +} + +const Image* CompoundSprite::getImage() const +{ + return mImage; +} + +bool CompoundSprite::setDirection(SpriteDirection direction) +{ + bool ret = false; + + SpriteIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + ret |= (*it)->setDirection(direction); + } + + mNeedsRedraw |= ret; + return ret; +} + +int CompoundSprite::getNumberOfLayers() const +{ + if (mImage || mAlphaImage) + return 1; + else + return size(); +} + +unsigned int CompoundSprite::getCurrentFrame() const +{ + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + return (*it)->getCurrentFrame(); + } + + return 0; +} + +unsigned int CompoundSprite::getFrameCount() const +{ + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + return (*it)->getFrameCount(); + } + + return 0; +} + +void CompoundSprite::addSprite(Sprite* sprite) +{ + push_back(sprite); + mNeedsRedraw = true; +} + +void CompoundSprite::setSprite(int layer, Sprite* sprite) +{ + // Skip if it won't change anything + if (at(layer) == sprite) + return; + + if (at(layer)) + delete at(layer); + at(layer) = sprite; + mNeedsRedraw = true; +} + +void CompoundSprite::removeSprite(int layer) +{ + // Skip if it won't change anything + if (at(layer) == NULL) + return; + + delete at(layer); + at(layer) = 0; + mNeedsRedraw = true; +} + +void CompoundSprite::clear() +{ + // Skip if it won't change anything + if (empty()) + return; + + std::vector::clear(); + mNeedsRedraw = true; +} + +void CompoundSprite::ensureSize(size_t layerCount) +{ + // Skip if it won't change anything + if (size() >= layerCount) + return; + + resize(layerCount, NULL); + mNeedsRedraw = true; +} + +/** + * Returns the curent frame in the current animation of the given layer. + */ +unsigned int CompoundSprite::getCurrentFrame(unsigned int layer) +{ + if (layer >= size()) + return 0; + + Sprite *s = getSprite(layer); + if (s) + return s->getCurrentFrame(); + + return 0; +} + +/** + * Returns the frame count in the current animation of the given layer. + */ +unsigned int CompoundSprite::getFrameCount(unsigned int layer) +{ + if (layer >= size()) + return 0; + + Sprite *s = getSprite(layer); + if (s) + return s->getFrameCount(); + + return 0; +} + +void CompoundSprite::redraw() const +{ +#ifdef USE_OPENGL + // TODO OpenGL support + if (Image::mUseOpenGL) + { + mNeedsRedraw = false; + return; + } +#endif + + if (size() <= 1) + return; + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + int rmask = 0xff000000; + int gmask = 0x00ff0000; + int bmask = 0x0000ff00; + int amask = 0x000000ff; +#else + int rmask = 0x000000ff; + int gmask = 0x0000ff00; + int bmask = 0x00ff0000; + int amask = 0xff000000; +#endif + + SDL_Surface *surface = SDL_CreateRGBSurface(SDL_HWSURFACE, + BUFFER_WIDTH, BUFFER_HEIGHT, 32, rmask, gmask, bmask, amask); + + if (!surface) + return; + + Graphics *graphics = new Graphics(); + graphics->setBlitMode(Graphics::BLIT_GFX); + graphics->setTarget(surface); + graphics->_beginDraw(); + + int tileX = 32 / 2; + int tileY = 32; + + Game *game = Game::instance(); + if (game) + { + Map *map = game->getCurrentMap(); + if (map) + { + tileX = map->getTileWidth() / 2; + tileY = map->getTileWidth(); + } + } + + int posX = BUFFER_WIDTH / 2 - tileX; + int posY = BUFFER_HEIGHT - tileY; + + mOffsetX = tileX - BUFFER_WIDTH / 2; + mOffsetY = tileY - BUFFER_HEIGHT; + + drawSpritesSDL(graphics, posX, posY); + +/* + SpriteConstIterator it, it_end; + for (it = begin(), it_end = end(); it != it_end; it++) + { + if (*it) + (*it)->draw(graphics, posX, posY); + } +*/ + + delete graphics; + graphics = 0; + + SDL_Surface *surfaceA = SDL_CreateRGBSurface(SDL_HWSURFACE, + BUFFER_WIDTH, BUFFER_HEIGHT, 32, rmask, gmask, bmask, amask); + + SDL_SetAlpha(surface, 0, SDL_ALPHA_OPAQUE); + SDL_BlitSurface(surface, NULL, surfaceA, NULL); + + delete mImage; + delete mAlphaImage; + + mImage = Image::load(surface); + SDL_FreeSurface(surface); + + mAlphaImage = Image::load(surfaceA); + SDL_FreeSurface(surfaceA); + + mNeedsRedraw = false; +} diff --git a/src/compoundsprite.h b/src/compoundsprite.h new file mode 100644 index 000000000..0e688c907 --- /dev/null +++ b/src/compoundsprite.h @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COMPOUNDSPRITE_H +#define COMPOUNDSPRITE_H + +#include "sprite.h" + +#include + +class Image; + +class CompoundSprite : public Sprite, private std::vector +{ +public: + typedef CompoundSprite::iterator SpriteIterator; + typedef CompoundSprite::const_iterator SpriteConstIterator; + + CompoundSprite(); + + ~CompoundSprite(); + + virtual bool reset(); + + virtual bool play(std::string action); + + virtual bool update(int time); + + virtual bool draw(Graphics* graphics, int posX, int posY) const; + + /** + * Gets the width in pixels of the first sprite in the list. + */ + virtual int getWidth() const; + + /** + * Gets the height in pixels of the first sprite in the list. + */ + virtual int getHeight() const; + + virtual const Image* getImage() const; + + virtual bool setDirection(SpriteDirection direction); + + int getNumberOfLayers() const; + + unsigned int getCurrentFrame() const; + + unsigned int getFrameCount() const; + + size_t size() const + { return std::vector::size(); } + + void addSprite(Sprite* sprite); + + void setSprite(int layer, Sprite* sprite); + + Sprite *getSprite(int layer) const + { return at(layer); } + + void removeSprite(int layer); + + void clear(); + + void ensureSize(size_t layerCount); + + virtual void drawSprites(Graphics* graphics, + int posX, int posY) const; + + virtual void drawSpritesSDL(Graphics* graphics, + int posX, int posY) const; + + /** + * Returns the curent frame in the current animation of the given layer. + */ + virtual unsigned int getCurrentFrame(unsigned int layer); + + /** + * Returns the frame count in the current animation of the given layer. + */ + virtual unsigned int getFrameCount(unsigned int layer); + +private: + + void redraw() const; + + mutable Image *mImage; + mutable Image *mAlphaImage; + + mutable int mOffsetX, mOffsetY; + + mutable bool mNeedsRedraw; +}; + +#endif // COMPOUNDSPRITE_H diff --git a/src/configlistener.h b/src/configlistener.h new file mode 100644 index 000000000..923b31151 --- /dev/null +++ b/src/configlistener.h @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONFIGLISTENER_H +#define CONFIGLISTENER_H + +#include + +/** + * The listener interface for receiving notifications about changes to + * configuration options. + * + * \ingroup CORE + */ +class ConfigListener +{ + public: + /** + * Destructor. + */ + virtual ~ConfigListener() {} + + /** + * Called when an option changed. The config listener will have to be + * registered to the option name first. + */ + virtual void optionChanged(const std::string &name) = 0; +}; + +#endif diff --git a/src/configuration.cpp b/src/configuration.cpp new file mode 100644 index 000000000..1e79d1e54 --- /dev/null +++ b/src/configuration.cpp @@ -0,0 +1,438 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "configuration.h" + +#include "configlistener.h" +#include "log.h" + +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include + +#ifdef DEBUG_CONFIG +#define GETLOG() if (logger) {logger->log("config get: " + key); } +#else +#define GETLOG() +#endif + +void ConfigurationObject::setValue(const std::string &key, + const std::string &value) +{ + mOptions[key] = value; +} + +void ConfigurationObject::deleteKey(const std::string &key) +{ + mOptions.erase(key); +} + +void Configuration::setValue(const std::string &key, const std::string &value) +{ + ConfigurationObject::setValue(key, value); + + // Notify listeners + ListenerMapIterator list = mListenerMap.find(key); + if (list != mListenerMap.end()) + { + Listeners listeners = list->second; + for (ListenerIterator i = listeners.begin(); i != listeners.end(); i++) + (*i)->optionChanged(key); + } +} + +std::string ConfigurationObject::getValue(const std::string &key, + const std::string &deflt) const +{ + GETLOG(); + Options::const_iterator iter = mOptions.find(key); + return ((iter != mOptions.end()) ? iter->second : deflt); +} + +int ConfigurationObject::getValue(const std::string &key, int deflt) const +{ + GETLOG(); + Options::const_iterator iter = mOptions.find(key); + return (iter != mOptions.end()) ? atoi(iter->second.c_str()) : deflt; +} + +int ConfigurationObject::getValueInt(const std::string &key, int deflt) const +{ + GETLOG(); + Options::const_iterator iter = mOptions.find(key); + return (iter != mOptions.end()) ? atoi(iter->second.c_str()) : deflt; +} + +bool ConfigurationObject::getValueBool(const std::string &key, + bool deflt) const +{ + GETLOG(); + Options::const_iterator iter = mOptions.find(key); + if (iter != mOptions.end()) + return atoi(iter->second.c_str()) != 0 ? true : false; + else + return deflt; +} + +unsigned ConfigurationObject::getValue(const std::string &key, + unsigned deflt) const +{ + GETLOG(); + Options::const_iterator iter = mOptions.find(key); + return (iter != mOptions.end()) ? static_cast( + atol(iter->second.c_str())) : deflt; +} + +double ConfigurationObject::getValue(const std::string &key, + double deflt) const +{ + GETLOG(); + Options::const_iterator iter = mOptions.find(key); + return (iter != mOptions.end()) ? atof(iter->second.c_str()) : deflt; +} + +void ConfigurationObject::deleteList(const std::string &name) +{ + for (ConfigurationList::const_iterator + it = mContainerOptions[name].begin(); + it != mContainerOptions[name].end(); it++) + { + delete *it; + } + + mContainerOptions[name].clear(); +} + +void ConfigurationObject::clear() +{ + for (std::map::const_iterator + it = mContainerOptions.begin(); + it != mContainerOptions.end(); it++) + { + deleteList(it->first); + } + mOptions.clear(); +} + +ConfigurationObject::~ConfigurationObject() +{ + clear(); +} + +void Configuration::cleanDefaults() +{ + if (mDefaultsData) + { + for (DefaultsData::const_iterator iter = mDefaultsData->begin(); + iter != mDefaultsData->end(); iter++) + { + delete(iter->second); + } + mDefaultsData->clear(); + delete mDefaultsData; + mDefaultsData = 0; + } +} + +Configuration::~Configuration() +{ + cleanDefaults(); +} + +void Configuration::setDefaultValues(DefaultsData *defaultsData) +{ + cleanDefaults(); + mDefaultsData = defaultsData; +} + +int Configuration::getIntValue(const std::string &key) const +{ + GETLOG(); + int defaultValue = 0; + Options::const_iterator iter = mOptions.find(key); + if (iter == mOptions.end()) + { + if (mDefaultsData) + { + DefaultsData::const_iterator itdef = mDefaultsData->find(key); + + if (itdef != mDefaultsData->end() && itdef->second + && itdef->second->getType() == Mana::VariableData::DATA_INT) + { + defaultValue = ((Mana::IntData*)itdef->second)->getData(); + } + else + { + logger->log("%s: No integer value in registry for key %s", + mConfigPath.c_str(), key.c_str()); + } + } + } + else + { + defaultValue = atoi(iter->second.c_str()); + } + return defaultValue; +} + +std::string Configuration::getStringValue(const std::string &key) const +{ + GETLOG(); + std::string defaultValue = ""; + Options::const_iterator iter = mOptions.find(key); + if (iter == mOptions.end()) + { + if (mDefaultsData) + { + DefaultsData::const_iterator itdef = mDefaultsData->find(key); + + if (itdef != mDefaultsData->end() && itdef->second + && itdef->second->getType() == Mana::VariableData::DATA_STRING) + { + defaultValue = ((Mana::StringData*)itdef->second)->getData(); + } + else + { + logger->log("%s: No string value in registry for key %s", + mConfigPath.c_str(), key.c_str()); + } + } + } + else + { + defaultValue = iter->second; + } + return defaultValue; +} + + +float Configuration::getFloatValue(const std::string &key) const +{ + GETLOG(); + float defaultValue = 0.0f; + Options::const_iterator iter = mOptions.find(key); + if (iter == mOptions.end()) + { + if (mDefaultsData) + { + DefaultsData::const_iterator itdef = mDefaultsData->find(key); + + if (itdef != mDefaultsData->end() && itdef->second + && itdef->second->getType() == Mana::VariableData::DATA_FLOAT) + { + defaultValue = static_cast( + ((Mana::FloatData*)itdef->second)->getData()); + } + else + { + logger->log("%s: No float value in registry for key %s", + mConfigPath.c_str(), key.c_str()); + } + } + } + else + { + defaultValue = atof(iter->second.c_str()); + } + return defaultValue; +} + +bool Configuration::getBoolValue(const std::string &key) const +{ + GETLOG(); + bool defaultValue = false; + Options::const_iterator iter = mOptions.find(key); + if (iter == mOptions.end()) + { + if (mDefaultsData) + { + DefaultsData::const_iterator itdef = mDefaultsData->find(key); + + if (itdef != mDefaultsData->end() && itdef->second + && itdef->second->getType() == Mana::VariableData::DATA_BOOL) + { + defaultValue = ((Mana::BoolData*)itdef->second)->getData(); + } + else + { + logger->log("%s: No boolean value in registry for key %s", + mConfigPath.c_str(), key.c_str()); + } + } + } + else + { + defaultValue = getBoolFromString(iter->second); + } + + return defaultValue; +} + +void ConfigurationObject::initFromXML(xmlNodePtr parent_node) +{ + clear(); + + for_each_xml_child_node(node, parent_node) + { + if (xmlStrEqual(node->name, BAD_CAST "list")) + { + // list option handling + + std::string name = XML::getProperty(node, "name", std::string()); + + for_each_xml_child_node(subnode, node) + { + if (xmlStrEqual(subnode->name, BAD_CAST name.c_str()) + && subnode->type == XML_ELEMENT_NODE) + { + ConfigurationObject *cobj = new ConfigurationObject; + + cobj->initFromXML(subnode); // recurse + + mContainerOptions[name].push_back(cobj); + } + } + + } + else if (xmlStrEqual(node->name, BAD_CAST "option")) + { + // single option handling + + std::string name = XML::getProperty(node, "name", std::string()); + std::string value = XML::getProperty(node, "value", std::string()); + + if (!name.empty()) + mOptions[name] = value; + } // otherwise ignore + } +} + +void Configuration::init(const std::string &filename, bool useResManager) +{ + mDefaultsData = 0; + XML::Document doc(filename, useResManager); + + if (useResManager) + mConfigPath = "PhysFS://" + filename; + else + mConfigPath = filename; + + if (!doc.rootNode()) + { + logger->log("Couldn't open configuration file: %s", filename.c_str()); + return; + } + + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "configuration")) + { + logger->log("Warning: No configuration file (%s)", filename.c_str()); + return; + } + + initFromXML(rootNode); +} + +void ConfigurationObject::writeToXML(xmlTextWriterPtr writer) +{ + for (Options::const_iterator i = mOptions.begin(), i_end = mOptions.end(); + i != i_end; ++i) + { + xmlTextWriterStartElement(writer, BAD_CAST "option"); + xmlTextWriterWriteAttribute(writer, + BAD_CAST "name", BAD_CAST i->first.c_str()); + + xmlTextWriterWriteAttribute(writer, + BAD_CAST "value", BAD_CAST i->second.c_str()); + xmlTextWriterEndElement(writer); + } + + for (std::map::const_iterator + it = mContainerOptions.begin(); + it != mContainerOptions.end(); it++) + { + const char *name = it->first.c_str(); + + xmlTextWriterStartElement(writer, BAD_CAST "list"); + xmlTextWriterWriteAttribute(writer, BAD_CAST "name", BAD_CAST name); + + // recurse on all elements + for (ConfigurationList::const_iterator elt_it = it->second.begin(); + elt_it != it->second.end(); elt_it++) + { + xmlTextWriterStartElement(writer, BAD_CAST name); + (*elt_it)->writeToXML(writer); + xmlTextWriterEndElement(writer); + } + + xmlTextWriterEndElement(writer); + } +} + +void Configuration::write() +{ + // Do not attempt to write to file that cannot be opened for writing + FILE *testFile = fopen(mConfigPath.c_str(), "w"); + if (!testFile) + { + logger->log("Configuration::write() couldn't open %s for writing", + mConfigPath.c_str()); + return; + } + else + { + fclose(testFile); + } + + xmlTextWriterPtr writer = xmlNewTextWriterFilename(mConfigPath.c_str(), 0); + + if (!writer) + { + logger->log1("Configuration::write() error while creating writer"); + return; + } + + logger->log1("Configuration::write() writing configuration..."); + + xmlTextWriterSetIndent(writer, 1); + xmlTextWriterStartDocument(writer, NULL, NULL, NULL); +// xmlTextWriterStartDocument(writer, NULL, "utf8", NULL); + xmlTextWriterStartElement(writer, BAD_CAST "configuration"); + + writeToXML(writer); + + xmlTextWriterEndDocument(writer); + xmlFreeTextWriter(writer); +} + +void Configuration::addListener( + const std::string &key, ConfigListener *listener) +{ + mListenerMap[key].push_front(listener); +} + +void Configuration::removeListener( + const std::string &key, ConfigListener *listener) +{ + mListenerMap[key].remove(listener); +} \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h new file mode 100644 index 000000000..d8999500a --- /dev/null +++ b/src/configuration.h @@ -0,0 +1,302 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include "utils/stringutils.h" +#include "defaults.h" + +#include + +#include +#include +#include +#include + +class ConfigListener; +class ConfigurationObject; + +/** + * Configuration list manager interface; responsible for + * serializing/deserializing configuration choices in containers. + * + * \param T Type of the container elements to serialise + * \param CONT Type of the container we (de)serialise + */ +template +class ConfigurationListManager +{ + public: + /** + * Writes a value into a configuration object + * + * \param value The value to write out + * \param obj The configuation object to write to + * \return obj, or otherwise NULL to indicate that this option should + * be skipped + */ + virtual ConfigurationObject + *writeConfigItem(T value, ConfigurationObject *obj) = 0; + + /** + * Reads a value from a configuration object + * + * \param obj The configuration object to read from + * \param container The container to insert the object to + */ + virtual CONT readConfigItem(ConfigurationObject *obj, + CONT container) = 0; + + virtual ~ConfigurationListManager() {} +}; + +/** + * Configuration object, mapping values to names and possibly containing + * lists of further configuration objects + * + * \ingroup CORE + */ +class ConfigurationObject +{ + friend class Configuration; + + public: + virtual ~ConfigurationObject(); + + /** + * Sets an option using a string value. + * + * \param key Option identifier. + * \param value Value. + */ + virtual void setValue(const std::string &key, + const std::string &value); + + void deleteKey(const std::string &key); + + /** + * Gets a value as string. + * + * \param key Option identifier. + * \param deflt Default option if not there or error. + */ + std::string getValue(const std::string &key, + const std::string &deflt) const; + + int getValue(const std::string &key, int deflt) const; + + int getValueInt(const std::string &key, int deflt) const; + + bool getValueBool(const std::string &key, bool deflt) const; + + unsigned getValue(const std::string &key, unsigned deflt) const; + + double getValue(const std::string &key, double deflt) const; + + /** + * Re-sets all data in the configuration + */ + virtual void clear(); + + /** + * Serialises a container into a list of configuration options + * + * \param IT Iterator type over CONT + * \param T Elements that IT iterates over + * \param CONT The associated container type + * + * \param name Name of the list the elements should be stored under + * \param begin Iterator start + * \param end Iterator end + * \param manager An object capable of serialising T items + */ + template + void setList(const std::string &name, IT begin, IT end, + ConfigurationListManager *manager) + { + if (!manager) + return; + + ConfigurationObject *nextobj = new ConfigurationObject; + deleteList(name); + ConfigurationList *list = &(mContainerOptions[name]); + + for (IT it = begin; it != end; it++) + { + ConfigurationObject *wrobj + = manager->writeConfigItem(*it, nextobj); + if (wrobj) + { // wrote something + assert (wrobj == nextobj); + nextobj = new ConfigurationObject; + list->push_back(wrobj); + } + else + { + nextobj->clear(); // you never know... + } + } + + delete nextobj; + nextobj = 0; + } + + /** + * Serialises a container into a list of configuration options + * + * \param IT Iterator type over CONT + * \param T Elements that IT iterates over + * \param CONT The associated container type + * + * \param name Name of the list the elements should be read from under + * \param empty Initial (empty) container to write to + * \param manager An object capable of deserialising items into CONT + */ + template + CONT getList(const std::string &name, CONT empty, + ConfigurationListManager *manager) + { + ConfigurationList *list = &(mContainerOptions[name]); + CONT container = empty; + + for (ConfigurationList::const_iterator it = list->begin(); + it != list->end(); it++) + { + container = manager->readConfigItem(*it, container); + } + + return container; + } + + protected: + virtual void initFromXML(xmlNodePtr node); + virtual void writeToXML(xmlTextWriterPtr writer); + + void deleteList(const std::string &name); + + typedef std::map Options; + Options mOptions; + + typedef std::list ConfigurationList; + std::map mContainerOptions; +}; + +/** + * Configuration handler for reading (and writing). + * + * \ingroup CORE + */ +class Configuration : public ConfigurationObject +{ + public: + ~Configuration(); + + /** + * Reads config file and parse all options into memory. + * + * @param filename path to config file + * @param useResManager Make use of the resource manager. + */ + void init(const std::string &filename, bool useResManager = false); + + /** + * Set the default values for each keys. + * + * @param defaultsData data used as defaults. + */ + void setDefaultValues(DefaultsData *defaultsData); + + /** + * Writes the current settings back to the config file. + */ + void write(); + + /** + * Adds a listener to the listen list of the specified config option. + */ + void addListener(const std::string &key, ConfigListener *listener); + + /** + * Removes a listener from the listen list of the specified config + * option. + */ + void removeListener(const std::string &key, ConfigListener *listener); + + void setValue(const std::string &key, const std::string &value); + + inline void setValue(const std::string &key, const char *value) + { setValue(key, std::string(value)); } + + inline void setValue(const std::string &key, float value) + { setValue(key, toString(value)); } + + inline void setValue(const std::string &key, double value) + { setValue(key, toString(value)); } + + inline void setValue(const std::string &key, int value) + { setValue(key, toString(value)); } + + inline void setValueInt(const std::string &key, int value) + { setValue(key, toString(value)); } + + inline void setValue(const std::string &key, unsigned value) + { setValue(key, toString(value)); } + + inline void setValue(const std::string &key, bool value) + { setValue(key, value ? "1" : "0"); } + + const std::string getConfigPath() const + { return mConfigPath; } + + /** + * returns a value corresponding to the given key. + * The default value returned in based on fallbacks registry. + * @see defaults.h + */ + int getIntValue(const std::string &key) const; + float getFloatValue(const std::string &key) const; + std::string getStringValue(const std::string &key) const; + bool getBoolValue(const std::string &key) const; + + private: + /** + * Clean up the default values member. + */ + void cleanDefaults(); + + typedef std::list Listeners; + typedef Listeners::iterator ListenerIterator; + typedef std::map ListenerMap; + typedef ListenerMap::iterator ListenerMapIterator; + ListenerMap mListenerMap; + + std::string mConfigPath; /**< Location of config file */ + DefaultsData *mDefaultsData; /**< Defaults of value + for a given key */ +}; + +extern Configuration branding; +extern Configuration config; +extern Configuration serverConfig; +extern Configuration paths; + +#endif diff --git a/src/defaults.cpp b/src/defaults.cpp new file mode 100644 index 000000000..0628aa72c --- /dev/null +++ b/src/defaults.cpp @@ -0,0 +1,253 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "defaults.h" + +#include "utils/stringutils.h" + +#include "being.h" +#include "graphics.h" +#include "client.h" + +#include + +using namespace Mana; + +VariableData* createData(int defData) +{ + return new IntData(defData); +} + +VariableData* createData(double defData) +{ + return new FloatData(defData); +} + +VariableData* createData(float defData) +{ + return new FloatData(defData); +} + +VariableData* createData(const std::string &defData) +{ + return new StringData(defData); +} + +VariableData* createData(const char* defData) +{ + return new StringData(defData); +} + +VariableData* createData(bool defData) +{ + return new BoolData(defData); +} + +#define AddDEF(defaultsData, key, value) \ + defaultsData->insert(std::pair \ + (key, createData(value))); + + +DefaultsData* getConfigDefaults() +{ + DefaultsData* configData = new DefaultsData; + // Init main config defaults + AddDEF(configData, "OverlayDetail", 2); + AddDEF(configData, "speechBubblecolor", "000000"); + AddDEF(configData, "speechBubbleAlpha", 1.0f); + AddDEF(configData, "MostUsedServerName0", "server.themanaworld.org"); + AddDEF(configData, "visiblenames", true); + AddDEF(configData, "speech", static_cast(Being::TEXT_OVERHEAD)); + AddDEF(configData, "showgender", true); + AddDEF(configData, "showlevel", false); + AddDEF(configData, "showMonstersTakedDamage", true); + AddDEF(configData, "highlightAttackRange", false); + AddDEF(configData, "highlightMapPortals", true); + AddDEF(configData, "highlightMonsterAttackRange", false); + AddDEF(configData, "chatMaxCharLimit", 300); + AddDEF(configData, "lowTraffic", true); + AddDEF(configData, "invertMoveDirection", 0); + AddDEF(configData, "crazyMoveType", 1); + AddDEF(configData, "attackWeaponType", 1); + AddDEF(configData, "quickDropCounter", 1); + AddDEF(configData, "pickUpType", 2); + AddDEF(configData, "magicAttackType", 0); + AddDEF(configData, "attackType", 1); + AddDEF(configData, "followMode", 0); + AddDEF(configData, "imitationMode", 0); + AddDEF(configData, "syncPlayerMove", false); + AddDEF(configData, "drawPath", false); + AddDEF(configData, "moveToTargetType", 0); + AddDEF(configData, "crazyMoveProgram", "mumrsonmdmlon"); + AddDEF(configData, "disableGameModifiers", false); + AddDEF(configData, "targetDeadPlayers", false); + AddDEF(configData, "afkMessage", "I am away from keyboard"); + AddDEF(configData, "particleMaxCount", 3000); + AddDEF(configData, "particleFastPhysics", 0); + AddDEF(configData, "particleEmitterSkip", 1); + AddDEF(configData, "particleeffects", true); + AddDEF(configData, "logToStandardOut", false); + AddDEF(configData, "opengl", 0); + AddDEF(configData, "screenwidth", defaultScreenWidth); + AddDEF(configData, "screenheight", defaultScreenHeight); + AddDEF(configData, "screen", false); + AddDEF(configData, "hwaccel", false); + AddDEF(configData, "sound", false); + AddDEF(configData, "sfxVolume", 100); + AddDEF(configData, "musicVolume", 60); + AddDEF(configData, "remember", false); + AddDEF(configData, "username", ""); + AddDEF(configData, "lastCharacter", ""); + AddDEF(configData, "fpslimit", 60); + AddDEF(configData, "altfpslimit", 5); + AddDEF(configData, "updatehost", ""); + AddDEF(configData, "screenshotDirectory", ""); + AddDEF(configData, "useScreenshotDirectorySuffix", true); + AddDEF(configData, "screenshotDirectorySuffix", ""); + AddDEF(configData, "EnableSync", false); + AddDEF(configData, "joystickEnabled", false); + AddDEF(configData, "upTolerance", 100); + AddDEF(configData, "downTolerance", 100); + AddDEF(configData, "leftTolerance", 100); + AddDEF(configData, "rightTolerance", 100); + AddDEF(configData, "logNpcInGui", true); + AddDEF(configData, "download-music", false); + AddDEF(configData, "guialpha", 0.8f); + AddDEF(configData, "ChatLogLength", 0); + AddDEF(configData, "enableChatLog", false); + AddDEF(configData, "whispertab", true); + AddDEF(configData, "customcursor", true); + AddDEF(configData, "showownname", true); + AddDEF(configData, "showpickupparticle", false); + AddDEF(configData, "showpickupchat", true); + AddDEF(configData, "fontSize", 11); + AddDEF(configData, "ReturnToggles", false); + AddDEF(configData, "ScrollLaziness", 16); + AddDEF(configData, "ScrollRadius", 0); + AddDEF(configData, "ScrollCenterOffsetX", 0); + AddDEF(configData, "ScrollCenterOffsetY", 0); + AddDEF(configData, "onlineServerList", ""); + AddDEF(configData, "theme", ""); + AddDEF(configData, "enableMumble", false); + AddDEF(configData, "playBattleSound", true); + AddDEF(configData, "playGuiSound", true); + AddDEF(configData, "playMusic", true); + AddDEF(configData, "packetcounters", true); + AddDEF(configData, "safemode", false); + AddDEF(configData, "font", "fonts/dejavusans.ttf"); + AddDEF(configData, "boldFont", "fonts/dejavusans-bold.ttf"); + AddDEF(configData, "particleFont", "fonts/dejavusans.ttf"); + AddDEF(configData, "helpFont", "fonts/dejavusansmono.ttf"); + AddDEF(configData, "showBackground", true); + AddDEF(configData, "enableTradeTab", true); + AddDEF(configData, "logToChat", false); + AddDEF(configData, "cyclePlayers", true); + AddDEF(configData, "cycleMonsters", false); + AddDEF(configData, "floorItemsHighlight", true); + AddDEF(configData, "enableBotCheker", true); + AddDEF(configData, "removeColors", true); + AddDEF(configData, "showMagicInDebug", false); + AddDEF(configData, "allowCommandsInChatTabs", true); + AddDEF(configData, "serverMsgInDebug", false); + AddDEF(configData, "hideShopMessages", true); + AddDEF(configData, "showChatHistory", true); + AddDEF(configData, "showChatColorsList", true); + AddDEF(configData, "chatMaxLinesLimit", 20); + AddDEF(configData, "chatColor", 0); + AddDEF(configData, "hideShield", true); + AddDEF(configData, "showJob", false); + AddDEF(configData, "updateOnlineList", true); + AddDEF(configData, "targetOnlyReachable", true); + AddDEF(configData, "errorsInDebug", true); + AddDEF(configData, "tradebot", true); + AddDEF(configData, "debugLog", false); + AddDEF(configData, "drawHotKeys", true); + AddDEF(configData, "serverAttack", true); + AddDEF(configData, "autofixPos", true); + AddDEF(configData, "alphaCache", false); + AddDEF(configData, "attackMoving", true); + AddDEF(configData, "quickStats", true); + AddDEF(configData, "warpParticle", false); + AddDEF(configData, "autoShop", false); + AddDEF(configData, "enableBattleTab", false); + AddDEF(configData, "showBattleEvents", false); + AddDEF(configData, "showMobHP", true); + return configData; +} + +DefaultsData* getBrandingDefaults() +{ + DefaultsData* brandingData = new DefaultsData; + // Init config defaults + AddDEF(brandingData, "wallpapersPath", ""); + AddDEF(brandingData, "wallpapersFile", ""); + AddDEF(brandingData, "appName", "Mana"); + AddDEF(brandingData, "appIcon", "icons/mana.png"); + AddDEF(brandingData, "loginMusic", "Magick - Real.ogg"); + AddDEF(brandingData, "defaultServer", ""); + AddDEF(brandingData, "defaultPort", DEFAULT_PORT); + AddDEF(brandingData, "defaultServerType", "tmwathena"); + AddDEF(brandingData, "onlineServerList", + "http://manasource.org/serverlist.xml"); + AddDEF(brandingData, "appShort", "mana"); + AddDEF(brandingData, "defaultUpdateHost", ""); + AddDEF(brandingData, "helpPath", ""); + AddDEF(brandingData, "onlineServerList", ""); + AddDEF(brandingData, "theme", ""); + AddDEF(brandingData, "font", "fonts/dejavusans.ttf"); + AddDEF(brandingData, "boldFont", "fonts/dejavusans-bold.ttf"); + AddDEF(brandingData, "particleFont", "fonts/dejavusans.ttf"); + AddDEF(brandingData, "helpFont", "fonts/dejavusansmono.ttf"); + + AddDEF(brandingData, "guiPath", "graphics/gui/"); + AddDEF(brandingData, "guiThemePath", "themes/"); + AddDEF(brandingData, "fontsPath", "fonts/"); + return brandingData; +} + +DefaultsData* getPathsDefaults() +{ + DefaultsData *pathsData = new DefaultsData; + // Init paths.xml defaults + AddDEF(pathsData, "itemIcons", "graphics/items/"); + AddDEF(pathsData, "unknownItemFile", "unknown-item.png"); + AddDEF(pathsData, "sprites", "graphics/sprites/"); + AddDEF(pathsData, "spriteErrorFile", "error.xml"); + + AddDEF(pathsData, "particles", "graphics/particles/"); + AddDEF(pathsData, "levelUpEffectFile", "levelup.particle.xml"); + AddDEF(pathsData, "portalEffectFile", "warparea.particle.xml"); + + AddDEF(pathsData, "minimaps", "graphics/minimaps/"); + AddDEF(pathsData, "maps", "maps/"); + + AddDEF(pathsData, "sfx", "sfx/"); + AddDEF(pathsData, "attackSfxFile", "fist-swish.ogg"); + AddDEF(pathsData, "music", "music/"); + + AddDEF(pathsData, "wallpapers", "graphics/images/"); + AddDEF(pathsData, "wallpaperFile", "login_wallpaper.png"); + + AddDEF(pathsData, "help", "help/"); + + return pathsData; +} + +#undef AddDEF diff --git a/src/defaults.h b/src/defaults.h new file mode 100644 index 000000000..9b45ff777 --- /dev/null +++ b/src/defaults.h @@ -0,0 +1,42 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DEFAULTS_H +#define DEFAULTS_H + +#include +#include +#include "variabledata.h" + +using namespace Mana; + +typedef std::map DefaultsData; + +VariableData* createData(int defData); +VariableData* createData(double defData); +VariableData* createData(float defData); +VariableData* createData(const std::string &defData); +VariableData* createData(const char* defData); +VariableData* createData(bool defData); +DefaultsData* getConfigDefaults(); +DefaultsData* getBrandingDefaults(); +DefaultsData* getPathsDefaults(); + +#endif diff --git a/src/dropshortcut.cpp b/src/dropshortcut.cpp new file mode 100644 index 000000000..763a7aadd --- /dev/null +++ b/src/dropshortcut.cpp @@ -0,0 +1,172 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "dropshortcut.h" + +#include "configuration.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "gui/chat.h" +#include "gui/widgets/chattab.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "utils/stringutils.h" + +DropShortcut *dropShortcut; + +DropShortcut::DropShortcut(): + mItemSelected(-1) +{ + for (int i = 0; i < DROP_SHORTCUT_ITEMS; i++) + mItems[i] = -1; + + load(); + mLastDropIndex = 0; +} + +DropShortcut::~DropShortcut() +{ +// save(); +} + +void DropShortcut::load(bool oldConfig) +{ + Configuration *cfg; + if (oldConfig) + cfg = &config; + else + cfg = &serverConfig; + + for (int i = 0; i < DROP_SHORTCUT_ITEMS; i++) + { + int itemId = (int) cfg->getValue("drop" + toString(i), -1); + + if (itemId != -1) + mItems[i] = itemId; + } +} + +void DropShortcut::save() +{ + for (int i = 0; i < DROP_SHORTCUT_ITEMS; i++) + { + const int itemId = mItems[i] ? mItems[i] : -1; + serverConfig.setValue("drop" + toString(i), itemId); + } +} + +void DropShortcut::dropFirst() +{ + if (!player_node) + return; + + if (!Client::limitPackets(PACKET_DROP)) + return; + + int itemId; + itemId = getItem(0); + + if (itemId > 0) + { + Item *item = PlayerInfo::getInventory()->findItem(itemId); + if (item && item->getQuantity()) + { + if (player_node->isServerBuggy()) + { + Net::getInventoryHandler()->dropItem(item, + player_node->getQuickDropCounter()); + } + else + { + for (int i = 0; i < player_node->getQuickDropCounter(); i++) + Net::getInventoryHandler()->dropItem(item, 1); + } + } + } +} + +void DropShortcut::dropItems(int cnt) +{ + if (!player_node) + return; + + int n = 0; + for (int f = 0; f < 9; f++) + { + for (int i = 0; i < player_node->getQuickDropCounter(); i++) + { + if (!Client::limitPackets(PACKET_DROP)) + return; + if (dropItem()) + n++; + } + if (n >= cnt) + break; + } +} + +bool DropShortcut::dropItem(int cnt) +{ + int itemId = 0; + while (mLastDropIndex < DROP_SHORTCUT_ITEMS && itemId < 1) + { + itemId = getItem(mLastDropIndex); + mLastDropIndex++; + } + if (itemId > 0) + { + Item *item = PlayerInfo::getInventory()->findItem(itemId); + if (item && item->getQuantity() > 0) + { + Net::getInventoryHandler()->dropItem(item, cnt); + return true; + } + } + if (mLastDropIndex >= DROP_SHORTCUT_ITEMS) + mLastDropIndex = 0; + + if (itemId < 1) + { + while (mLastDropIndex < DROP_SHORTCUT_ITEMS && itemId < 1) + { + itemId = getItem(mLastDropIndex); + mLastDropIndex++; + } + if (itemId > 0) + { + Item *item = PlayerInfo::getInventory()->findItem(itemId); + if (item && item->getQuantity() > 0) + { + Net::getInventoryHandler()->dropItem(item, cnt); + return true; + } + } + if (mLastDropIndex >= DROP_SHORTCUT_ITEMS) + mLastDropIndex = 0; + } + return false; +} diff --git a/src/dropshortcut.h b/src/dropshortcut.h new file mode 100644 index 000000000..5a020879e --- /dev/null +++ b/src/dropshortcut.h @@ -0,0 +1,155 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DROPSHORTCUT_H +#define DROPSHORTCUT_H + +#define DROP_SHORTCUT_ITEMS 16 + +class Item; + +/** + * The class which keeps track of the item shortcuts. + */ +class DropShortcut +{ + public: + /** + * Constructor. + */ + DropShortcut(); + + /** + * Destructor. + */ + ~DropShortcut(); + + /** + * Load the configuration information. + */ + void load(bool oldConfig = false); + + /** + * Save the configuration information. + */ + void save(); + + /** + * Returns the shortcut item ID specified by the index. + * + * @param index Index of the shortcut item. + */ + int getItem(int index) const + { return mItems[index]; } + + /** + * Returns the amount of shortcut items. + */ + int getItemCount() const + { return DROP_SHORTCUT_ITEMS; } + + /** + * Returns the item ID that is currently selected. + */ + int getItemSelected() const + { return mItemSelected; } + + /** + * Adds the selected item ID to the items specified by the index. + * + * @param index Index of the items. + */ + void setItem(int index) + { mItems[index] = mItemSelected; save(); } + + /** + * Adds an item to the items store specified by the index. + * + * @param index Index of the item. + * @param itemId ID of the item. + */ + void setItems(int index, int itemId) + { mItems[index] = itemId; save(); } + + /** + * Set the item that is selected. + * + * @param itemId The ID of the item that is to be assigned. + */ + void setItemSelected(int itemId) + { mItemSelected = itemId; } + + /** + * A flag to check if the item is selected. + */ + bool isItemSelected() const + { return mItemSelected > -1; } + + /** + * Remove a item from the shortcut. + */ + void removeItem(int index) + { mItems[index] = -1; save(); } + + /** + * Try to use the item specified by the index. + * + * @param index Index of the item shortcut. + */ + void useItem(int index); + + /** + * Equip a item from the shortcut. + */ + void equipItem(int index); + + /** + * UnEquip a item from the shortcut. + */ + void unequipItem(int index); + + /** + * Drop first item. + */ + void dropFirst(); + + /** + * Drop all items in cicle. + */ + void dropItems(int cnt = 1); + + private: + + /** + * Drop item in cicle. + */ + bool dropItem(int cnt = 1); + + int mItems[DROP_SHORTCUT_ITEMS]; /**< The items stored. */ + int mItemSelected; /**< The item held by cursor. */ + + int mLastDropIndex; +}; + +extern DropShortcut *dropShortcut; + +#endif diff --git a/src/effectmanager.cpp b/src/effectmanager.cpp new file mode 100644 index 000000000..e7722b4a9 --- /dev/null +++ b/src/effectmanager.cpp @@ -0,0 +1,107 @@ +/* + * An effects manager + * Copyright (C) 2008 Fate + * Copyright (C) 2008 Chuck Miller + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "being.h" +#include "effectmanager.h" +#include "log.h" +#include "particle.h" +#include "sound.h" + +#include "utils/xml.h" + +EffectManager::EffectManager() +{ + XML::Document doc("effects.xml"); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "being-effects")) + { + logger->log1("Error loading being effects file: effects.xml"); + return; + } + else + { + logger->log1("Effects are now loading"); + } + + for_each_xml_child_node(node, root) + { + if (xmlStrEqual(node->name, BAD_CAST "effect")) + { + EffectDescription ed; + ed.id = XML::getProperty(node, "id", -1); + ed.GFX = XML::getProperty(node, "particle", ""); + ed.SFX = XML::getProperty(node, "audio", ""); + mEffects.push_back(ed); + } + } +} + +EffectManager::~EffectManager() +{ +} + +bool EffectManager::trigger(int id, Being* being) +{ + if (!being || !particleEngine) + return false; + + bool rValue = false; + for (std::list::iterator i = mEffects.begin(); + i != mEffects.end(); ++i) + { + if ((*i).id == id) + { + rValue = true; + if (!(*i).GFX.empty()) + { + Particle *selfFX = particleEngine->addEffect((*i).GFX, 0, 0); + being->controlParticle(selfFX); + } + if (!(*i).SFX.empty()) + sound.playSfx((*i).SFX); + break; + } + } + return rValue; +} + +bool EffectManager::trigger(int id, int x, int y) +{ + if (!particleEngine) + return false; + + bool rValue = false; + for (std::list::iterator i = mEffects.begin(); + i != mEffects.end(); ++i) + { + if ((*i).id == id) + { + rValue = true; + if (!(*i).GFX.empty() && particleEngine) + particleEngine->addEffect((*i).GFX, x, y); + if (!(*i).SFX.empty()) + sound.playSfx((*i).SFX); + break; + } + } + return rValue; +} diff --git a/src/effectmanager.h b/src/effectmanager.h new file mode 100644 index 000000000..ef1e0f275 --- /dev/null +++ b/src/effectmanager.h @@ -0,0 +1,61 @@ +/* + * An effects manager + * Copyright (C) 2008 Fate + * Copyright (C) 2008 Chuck Miller + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EFFECT_MANAGER_H +#define EFFECT_MANAGER_H + +#include +#include + +class Being; + +class EffectManager +{ + public: + struct EffectDescription + { + int id; + std::string GFX; + std::string SFX; + }; + + EffectManager(); + ~EffectManager(); + + /** + * Triggers a effect with the id, at + * the specified being. + */ + bool trigger(int id, Being* being); + + /** + * Triggers a effect with the id, at + * the specified x and y coordinate. + */ + bool trigger(int id, int x, int y); + + private: + std::list mEffects; +}; + +extern EffectManager *effectManager; + +#endif // EFFECT_MANAGER_H diff --git a/src/emoteshortcut.cpp b/src/emoteshortcut.cpp new file mode 100644 index 000000000..59477fdf6 --- /dev/null +++ b/src/emoteshortcut.cpp @@ -0,0 +1,89 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "emoteshortcut.h" + +#include "configuration.h" +#include "localplayer.h" + +#include "utils/stringutils.h" + +#include "resources/emotedb.h" + +EmoteShortcut *emoteShortcut; + +EmoteShortcut::EmoteShortcut(): + mEmoteSelected(0) +{ +// load(); + for (int i = 0; i < SHORTCUT_EMOTES; i++) + mEmotes[i] = 0; +} + +EmoteShortcut::~EmoteShortcut() +{ + save(); +} + +void EmoteShortcut::load() +{ + for (int i = 0, j = 0; i <= EmoteDB::getLast() && j < SHORTCUT_EMOTES; i++) + { + const AnimatedSprite* sprite = EmoteDB::getAnimation(i, true); + if (sprite) + { + mEmotes[j] = i + 1; + j ++; + } + } + +/* + for (int i = 0; i < SHORTCUT_EMOTES; i++) + { + unsigned char emoteId = static_cast( + serverConfig.getValue("emoteshortcut" + toString(i), i + 1)); + mEmotes[i] = emoteId; +// mEmotes[i] = i + 1; + } +*/ +} + +void EmoteShortcut::save() +{ + for (int i = 0; i < SHORTCUT_EMOTES; i++) + { + unsigned char emoteId = mEmotes[i] ? mEmotes[i] + : static_cast(0); + serverConfig.setValue("emoteshortcut" + toString(i), + static_cast(emoteId)); + } +} + +void EmoteShortcut::useEmote(int index) +{ + if (!player_node) + return; + + if (index > 0 && index <= SHORTCUT_EMOTES) + { + if (mEmotes[index - 1] > 0) + player_node->emote(mEmotes[index - 1]); + } +} diff --git a/src/emoteshortcut.h b/src/emoteshortcut.h new file mode 100644 index 000000000..0e06bb633 --- /dev/null +++ b/src/emoteshortcut.h @@ -0,0 +1,125 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EMOTESHORTCUT_H +#define EMOTESHORTCUT_H + +#define SHORTCUT_EMOTES 42 + +/** + * The class which keeps track of the emote shortcuts. + */ +class EmoteShortcut +{ + public: + /** + * Constructor. + */ + EmoteShortcut(); + + /** + * Destructor. + */ + ~EmoteShortcut(); + + /** + * Load the configuration information. + */ + void load(); + + /** + * Returns the shortcut Emote ID specified by the index. + * + * @param index Index of the shortcut Emote. + */ + unsigned char getEmote(int index) const + { return mEmotes[index]; } + + /** + * Returns the amount of shortcut Emotes. + */ + unsigned int getEmoteCount() const + { return SHORTCUT_EMOTES; } + + /** + * Returns the emote ID that is currently selected. + */ + unsigned char getEmoteSelected() const + { return mEmoteSelected; } + + /** + * Adds the selected emote ID to the emotes specified by the index. + * + * @param index Index of the emotes. + */ + void setEmote(int index) + { mEmotes[index] = mEmoteSelected; } + + /** + * Adds a emoticon to the emotes store specified by the index. + * + * @param index Index of the emote. + * @param emoteId ID of the emote. + */ + void setEmotes(int index, unsigned char emoteId) + { mEmotes[index] = emoteId; } + + /** + * Set the Emote that is selected. + * + * @param emoteId The ID of the emote that is to be assigned. + */ + void setEmoteSelected(unsigned char emoteId) + { mEmoteSelected = emoteId; } + + /** + * A flag to check if the Emote is selected. + */ + bool isEmoteSelected() const + { return mEmoteSelected; } + + /** + * Remove a Emote from the shortcut. + */ + void removeEmote(int index) + { if (index >= 0 && index < SHORTCUT_EMOTES) mEmotes[index] = 0; } + + /** + * Try to use the Emote specified by the index. + * + * @param index Index of the emote shortcut. + */ + void useEmote(int index); + + private: + /** + * Save the configuration information. + */ + void save(); + + unsigned char mEmotes[SHORTCUT_EMOTES]; /**< The emote stored. */ + unsigned char mEmoteSelected; /**< The emote held + by cursor. */ + +}; + +extern EmoteShortcut *emoteShortcut; + +#endif diff --git a/src/enet/ChangeLog b/src/enet/ChangeLog new file mode 100644 index 000000000..45f14db6a --- /dev/null +++ b/src/enet/ChangeLog @@ -0,0 +1,86 @@ +ENet 1.3.0 (June 5, 2010): + +* enet_host_create() now requires the channel limit to be specified as +a parameter +* enet_host_connect() now accepts a data parameter which is supplied +to the receiving receiving host in the event data field for a connect event +* added an adaptive order-2 PPM range coder as a built-in compressor option +which can be set with enet_host_compress_with_range_coder() +* added support for packet compression configurable with a callback +* improved session number handling to not rely on the packet checksum +field, saving 4 bytes per packet unless the checksum option is used +* removed the dependence on the rand callback for session number handling + +Caveats: This version is not protocol compatible with the 1.2 series or +earlier. The enet_host_connect and enet_host_create API functions require +supplying additional parameters. + +ENet 1.2.2 (June 5, 2010): + +* checksum functionality is now enabled by setting a checksum callback +inside ENetHost instead of being a configure script option +* added totalSentData, totalSentPackets, totalReceivedData, and +totalReceivedPackets counters inside ENetHost for getting usage +statistics +* added enet_host_channel_limit() for limiting the maximum number of +channels allowed by connected peers +* now uses dispatch queues for event dispatch rather than potentially +unscalable array walking +* added no_memory callback that is called when a malloc attempt fails, +such that if no_memory returns rather than aborts (the default behavior), +then the error is propagated to the return value of the API calls +* now uses packed attribute for protocol structures on platforms with +strange alignment rules +* improved autoconf build system contributed by Nathan Brink allowing +for easier building as a shared library + +Caveats: If you were using the compile-time option that enabled checksums, +make sure to set the checksum callback inside ENetHost to enet_crc32 to +regain the old behavior. The ENetCallbacks structure has added new fields, +so make sure to clear the structure to zero before use if +using enet_initialize_with_callbacks(). + +ENet 1.2.1 (November 12, 2009): + +* fixed bug that could cause disconnect events to be dropped +* added thin wrapper around select() for portable usage +* added ENET_SOCKOPT_REUSEADDR socket option +* factored enet_socket_bind()/enet_socket_listen() out of enet_socket_create() +* added contributed Code::Blocks build file + +ENet 1.2 (February 12, 2008): + +* fixed bug in VERIFY_CONNECT acknowledgement that could cause connect +attempts to occasionally timeout +* fixed acknowledgements to check both the outgoing and sent queues +when removing acknowledged packets +* fixed accidental bit rot in the MSVC project file +* revised sequence number overflow handling to address some possible +disconnect bugs +* added enet_host_check_events() for getting only local queued events +* factored out socket option setting into enet_socket_set_option() so +that socket options are now set separately from enet_socket_create() + +Caveats: While this release is superficially protocol compatible with 1.1, +differences in the sequence number overflow handling can potentially cause +random disconnects. + +ENet 1.1 (June 6, 2007): + +* optional CRC32 just in case someone needs a stronger checksum than UDP +provides (--enable-crc32 configure option) +* the size of packet headers are half the size they used to be (so less +overhead when sending small packets) +* enet_peer_disconnect_later() that waits till all queued outgoing +packets get sent before issuing an actual disconnect +* freeCallback field in individual packets for notification of when a +packet is about to be freed +* ENET_PACKET_FLAG_NO_ALLOCATE for supplying pre-allocated data to a +packet (can be used in concert with freeCallback to support some custom +allocation schemes that the normal memory allocation callbacks would +normally not allow) +* enet_address_get_host_ip() for printing address numbers +* promoted the enet_socket_*() functions to be part of the API now +* a few stability/crash fixes + + diff --git a/src/enet/LICENSE b/src/enet/LICENSE new file mode 100644 index 000000000..df0f91e98 --- /dev/null +++ b/src/enet/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2002-2010 Lee Salzman + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights 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. diff --git a/src/enet/README b/src/enet/README new file mode 100644 index 000000000..7f7a0ad01 --- /dev/null +++ b/src/enet/README @@ -0,0 +1,15 @@ +Please visit the ENet homepage at http://enet.bespin.org for installation +and usage instructions. + +If you obtained this package from CVS, the quick description on how to build +is: + +# Generate the build system. + +autoreconf -vfi + +# Compile and install the library. + +./configure && make && make install + + diff --git a/src/enet/callbacks.c b/src/enet/callbacks.c new file mode 100644 index 000000000..f94128256 --- /dev/null +++ b/src/enet/callbacks.c @@ -0,0 +1,47 @@ +/** + @file callbacks.c + @brief ENet callback functions +*/ +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +static ENetCallbacks callbacks = { malloc, free, abort }; + +int +enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits) +{ + if (version < ENET_VERSION_CREATE (1, 3, 0)) + return -1; + + if (inits -> malloc != NULL || inits -> free != NULL) + { + if (inits -> malloc == NULL || inits -> free == NULL) + return -1; + + callbacks.malloc = inits -> malloc; + callbacks.free = inits -> free; + } + + if (inits -> no_memory != NULL) + callbacks.no_memory = inits -> no_memory; + + return enet_initialize (); +} + +void * +enet_malloc (size_t size) +{ + void * memory = callbacks.malloc (size); + + if (memory == NULL) + callbacks.no_memory (); + + return memory; +} + +void +enet_free (void * memory) +{ + callbacks.free (memory); +} + diff --git a/src/enet/callbacks.h b/src/enet/callbacks.h new file mode 100644 index 000000000..340a4a989 --- /dev/null +++ b/src/enet/callbacks.h @@ -0,0 +1,27 @@ +/** + @file callbacks.h + @brief ENet callbacks +*/ +#ifndef __ENET_CALLBACKS_H__ +#define __ENET_CALLBACKS_H__ + +#include + +typedef struct _ENetCallbacks +{ + void * (ENET_CALLBACK * malloc) (size_t size); + void (ENET_CALLBACK * free) (void * memory); + void (ENET_CALLBACK * no_memory) (void); +} ENetCallbacks; + +/** @defgroup callbacks ENet internal callbacks + @{ + @ingroup private +*/ +extern void * enet_malloc (size_t); +extern void enet_free (void *); + +/** @} */ + +#endif /* __ENET_CALLBACKS_H__ */ + diff --git a/src/enet/compress.c b/src/enet/compress.c new file mode 100644 index 000000000..784489a78 --- /dev/null +++ b/src/enet/compress.c @@ -0,0 +1,654 @@ +/** + @file compress.c + @brief An adaptive order-2 PPM range coder +*/ +#define ENET_BUILDING_LIB 1 +#include +#include "enet/enet.h" + +typedef struct _ENetSymbol +{ + /* binary indexed tree of symbols */ + enet_uint8 value; + enet_uint8 count; + enet_uint16 under; + enet_uint16 left, right; + + /* context defined by this symbol */ + enet_uint16 symbols; + enet_uint16 escapes; + enet_uint16 total; + enet_uint16 parent; +} ENetSymbol; + +/* adaptation constants tuned aggressively for small packet sizes rather than large file compression */ +enum +{ + ENET_RANGE_CODER_TOP = 1<<24, + ENET_RANGE_CODER_BOTTOM = 1<<16, + + ENET_CONTEXT_SYMBOL_DELTA = 3, + ENET_CONTEXT_SYMBOL_MINIMUM = 1, + ENET_CONTEXT_ESCAPE_MINIMUM = 1, + + ENET_SUBCONTEXT_ORDER = 2, + ENET_SUBCONTEXT_SYMBOL_DELTA = 2, + ENET_SUBCONTEXT_ESCAPE_DELTA = 5 +}; + +/* context exclusion roughly halves compression speed, so disable for now */ +#undef ENET_CONTEXT_EXCLUSION + +typedef struct _ENetRangeCoder +{ + /* only allocate enough symbols for reasonable MTUs, would need to be larger for large file compression */ + ENetSymbol symbols[4096]; +} ENetRangeCoder; + +void * +enet_range_coder_create (void) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) enet_malloc (sizeof (ENetRangeCoder)); + if (rangeCoder == NULL) + return NULL; + + return rangeCoder; +} + +void +enet_range_coder_destroy (void * context) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + if (rangeCoder == NULL) + return; + + enet_free (rangeCoder); +} + +#define ENET_SYMBOL_CREATE(symbol, value_, count_) \ +{ \ + symbol = & rangeCoder -> symbols [nextSymbol ++]; \ + symbol -> value = value_; \ + symbol -> count = count_; \ + symbol -> under = count_; \ + symbol -> left = 0; \ + symbol -> right = 0; \ + symbol -> symbols = 0; \ + symbol -> escapes = 0; \ + symbol -> total = 0; \ + symbol -> parent = 0; \ +} + +#define ENET_CONTEXT_CREATE(context, escapes_, minimum) \ +{ \ + ENET_SYMBOL_CREATE (context, 0, 0); \ + (context) -> escapes = escapes_; \ + (context) -> total = escapes_ + 256*minimum; \ + (context) -> symbols = 0; \ +} + +static enet_uint16 +enet_symbol_rescale (ENetSymbol * symbol) +{ + enet_uint16 total = 0; + for (;;) + { + symbol -> count -= symbol->count >> 1; + symbol -> under = symbol -> count; + if (symbol -> left) + symbol -> under += enet_symbol_rescale (symbol + symbol -> left); + total += symbol -> under; + if (! symbol -> right) break; + symbol += symbol -> right; + } + return total; +} + +#define ENET_CONTEXT_RESCALE(context, minimum) \ +{ \ + (context) -> total = (context) -> symbols ? enet_symbol_rescale ((context) + (context) -> symbols) : 0; \ + (context) -> escapes -= (context) -> escapes >> 1; \ + (context) -> total += (context) -> escapes + 256*minimum; \ +} + +#define ENET_RANGE_CODER_OUTPUT(value) \ +{ \ + if (outData >= outEnd) \ + return 0; \ + * outData ++ = value; \ +} + +#define ENET_RANGE_CODER_ENCODE(under, count, total) \ +{ \ + encodeRange /= (total); \ + encodeLow += (under) * encodeRange; \ + encodeRange *= (count); \ + for (;;) \ + { \ + if((encodeLow ^ (encodeLow + encodeRange)) >= ENET_RANGE_CODER_TOP) \ + { \ + if(encodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ + encodeRange = -encodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ + } \ + ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ + encodeRange <<= 8; \ + encodeLow <<= 8; \ + } \ +} + +#define ENET_RANGE_CODER_FLUSH \ +{ \ + while (encodeLow) \ + { \ + ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ + encodeLow <<= 8; \ + } \ +} + +#define ENET_RANGE_CODER_FREE_SYMBOLS \ +{ \ + if (nextSymbol >= sizeof (rangeCoder -> symbols) / sizeof (ENetSymbol) - ENET_SUBCONTEXT_ORDER ) \ + { \ + nextSymbol = 0; \ + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); \ + predicted = 0; \ + order = 0; \ + } \ +} + +#define ENET_CONTEXT_ENCODE(context, symbol_, value_, under_, count_, update, minimum) \ +{ \ + under_ = value*minimum; \ + count_ = minimum; \ + if (! (context) -> symbols) \ + { \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + (context) -> symbols = symbol_ - (context); \ + } \ + else \ + { \ + ENetSymbol * node = (context) + (context) -> symbols; \ + for (;;) \ + { \ + if (value_ < node -> value) \ + { \ + node -> under += update; \ + if (node -> left) { node += node -> left; continue; } \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> left = symbol_ - node; \ + } \ + else \ + if (value_ > node -> value) \ + { \ + under_ += node -> under; \ + if (node -> right) { node += node -> right; continue; } \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> right = symbol_ - node; \ + } \ + else \ + { \ + count_ += node -> count; \ + under_ += node -> under - node -> count; \ + node -> under += update; \ + node -> count += update; \ + symbol_ = node; \ + } \ + break; \ + } \ + } \ +} + +#ifdef ENET_CONTEXT_EXCLUSION +static const ENetSymbol emptyContext = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +#define ENET_CONTEXT_WALK(context, body) \ +{ \ + const ENetSymbol * node = (context) + (context) -> symbols; \ + const ENetSymbol * stack [256]; \ + size_t stackSize = 0; \ + while (node -> left) \ + { \ + stack [stackSize ++] = node; \ + node += node -> left; \ + } \ + for (;;) \ + { \ + body; \ + if (node -> right) \ + { \ + node += node -> right; \ + while (node -> left) \ + { \ + stack [stackSize ++] = node; \ + node += node -> left; \ + } \ + } \ + else \ + if (stackSize <= 0) \ + break; \ + else \ + node = stack [-- stackSize]; \ + } \ +} + +#define ENET_CONTEXT_ENCODE_EXCLUDE(context, value_, under, total, minimum) \ +ENET_CONTEXT_WALK(context, { \ + if (node -> value != value_) \ + { \ + enet_uint16 parentCount = rangeCoder -> symbols [node -> parent].count + minimum; \ + if (node -> value < value_) \ + under -= parentCount; \ + total -= parentCount; \ + } \ +}) +#endif + +size_t +enet_range_coder_compress (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; + const enet_uint8 * inData, * inEnd; + enet_uint32 encodeLow = 0, encodeRange = ~0; + ENetSymbol * root; + enet_uint16 predicted = 0; + size_t order = 0, nextSymbol = 0; + + if (rangeCoder == NULL || inBufferCount <= 0 || inLimit <= 0) + return 0; + + inData = (const enet_uint8 *) inBuffers -> data; + inEnd = & inData [inBuffers -> dataLength]; + inBuffers ++; + inBufferCount --; + + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); + + for (;;) + { + ENetSymbol * subcontext, * symbol; +#ifdef ENET_CONTEXT_EXCLUSION + const ENetSymbol * childContext = & emptyContext; +#endif + enet_uint8 value; + enet_uint16 count, under, * parent = & predicted, total; + if (inData >= inEnd) + { + if (inBufferCount <= 0) + break; + inData = (const enet_uint8 *) inBuffers -> data; + inEnd = & inData [inBuffers -> dataLength]; + inBuffers ++; + inBufferCount --; + } + value = * inData ++; + + for (subcontext = & rangeCoder -> symbols [predicted]; + subcontext != root; +#ifdef ENET_CONTEXT_EXCLUSION + childContext = subcontext, +#endif + subcontext = & rangeCoder -> symbols [subcontext -> parent]) + { + ENET_CONTEXT_ENCODE (subcontext, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + total = subcontext -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) + ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, 0); +#endif + if (count > 0) + { + ENET_RANGE_CODER_ENCODE (subcontext -> escapes + under, count, total); + } + else + { + if (subcontext -> escapes > 0 && subcontext -> escapes < total) + ENET_RANGE_CODER_ENCODE (0, subcontext -> escapes, total); + subcontext -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; + subcontext -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; + } + subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (subcontext, 0); + if (count > 0) goto nextInput; + } + + ENET_CONTEXT_ENCODE (root, symbol, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + total = root -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) + ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, ENET_CONTEXT_SYMBOL_MINIMUM); +#endif + ENET_RANGE_CODER_ENCODE (root -> escapes + under, count, total); + root -> total += ENET_CONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); + + nextInput: + if (order >= ENET_SUBCONTEXT_ORDER) + predicted = rangeCoder -> symbols [predicted].parent; + else + order ++; + ENET_RANGE_CODER_FREE_SYMBOLS; + } + + ENET_RANGE_CODER_FLUSH; + + return (size_t) (outData - outStart); +} + +#define ENET_RANGE_CODER_SEED \ +{ \ + if (inData < inEnd) decodeCode |= * inData ++ << 24; \ + if (inData < inEnd) decodeCode |= * inData ++ << 16; \ + if (inData < inEnd) decodeCode |= * inData ++ << 8; \ + if (inData < inEnd) decodeCode |= * inData ++; \ +} + +#define ENET_RANGE_CODER_READ(total) ((decodeCode - decodeLow) / (decodeRange /= (total))) + +#define ENET_RANGE_CODER_DECODE(under, count, total) \ +{ \ + decodeLow += (under) * decodeRange; \ + decodeRange *= (count); \ + for (;;) \ + { \ + if((decodeLow ^ (decodeLow + decodeRange)) >= ENET_RANGE_CODER_TOP) \ + { \ + if(decodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ + decodeRange = -decodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ + } \ + decodeCode <<= 8; \ + if (inData < inEnd) \ + decodeCode |= * inData ++; \ + decodeRange <<= 8; \ + decodeLow <<= 8; \ + } \ +} + +#define ENET_CONTEXT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, createRoot, visitNode, createRight, createLeft) \ +{ \ + under_ = 0; \ + count_ = minimum; \ + if (! (context) -> symbols) \ + { \ + createRoot; \ + } \ + else \ + { \ + ENetSymbol * node = (context) + (context) -> symbols; \ + for (;;) \ + { \ + enet_uint16 after = under_ + node -> under + (node -> value + 1)*minimum, before = node -> count + minimum; \ + visitNode; \ + if (code >= after) \ + { \ + under_ += node -> under; \ + if (node -> right) { node += node -> right; continue; } \ + createRight; \ + } \ + else \ + if (code < after - before) \ + { \ + node -> under += update; \ + if (node -> left) { node += node -> left; continue; } \ + createLeft; \ + } \ + else \ + { \ + value_ = node -> value; \ + count_ += node -> count; \ + under_ = after - before; \ + node -> under += update; \ + node -> count += update; \ + symbol_ = node; \ + } \ + break; \ + } \ + } \ +} + +#define ENET_CONTEXT_TRY_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ +ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, return 0, exclude (node -> value, after, before), return 0, return 0) + +#define ENET_CONTEXT_ROOT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ +ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, \ + { \ + value_ = code / minimum; \ + under_ = code - code%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + (context) -> symbols = symbol_ - (context); \ + }, \ + exclude (node -> value, after, before), \ + { \ + value_ = node->value + 1 + (code - after)/minimum; \ + under_ = code - (code - after)%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> right = symbol_ - node; \ + }, \ + { \ + value_ = node->value - 1 - (after - before - code - 1)/minimum; \ + under_ = code - (after - before - code - 1)%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> left = symbol_ - node; \ + }) \ + +#ifdef ENET_CONTEXT_EXCLUSION +typedef struct _ENetExclude +{ + enet_uint8 value; + enet_uint16 under; +} ENetExclude; + +#define ENET_CONTEXT_DECODE_EXCLUDE(context, total, minimum) \ +{ \ + enet_uint16 under = 0; \ + nextExclude = excludes; \ + ENET_CONTEXT_WALK (context, { \ + under += rangeCoder -> symbols [node -> parent].count + minimum; \ + nextExclude -> value = node -> value; \ + nextExclude -> under = under; \ + nextExclude ++; \ + }); \ + total -= under; \ +} + +#define ENET_CONTEXT_EXCLUDED(value_, after, before) \ +{ \ + size_t low = 0, high = nextExclude - excludes; \ + for(;;) \ + { \ + size_t mid = (low + high) >> 1; \ + const ENetExclude * exclude = & excludes [mid]; \ + if (value_ < exclude -> value) \ + { \ + if (low + 1 < high) \ + { \ + high = mid; \ + continue; \ + } \ + if (exclude > excludes) \ + after -= exclude [-1].under; \ + } \ + else \ + { \ + if (value_ > exclude -> value) \ + { \ + if (low + 1 < high) \ + { \ + low = mid; \ + continue; \ + } \ + } \ + else \ + before = 0; \ + after -= exclude -> under; \ + } \ + break; \ + } \ +} +#endif + +#define ENET_CONTEXT_NOT_EXCLUDED(value_, after, before) + +size_t +enet_range_coder_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; + const enet_uint8 * inEnd = & inData [inLimit]; + enet_uint32 decodeLow = 0, decodeCode = 0, decodeRange = ~0; + ENetSymbol * root; + enet_uint16 predicted = 0; + size_t order = 0, nextSymbol = 0; +#ifdef ENET_CONTEXT_EXCLUSION + ENetExclude excludes [256]; + ENetExclude * nextExclude = excludes; +#endif + + if (rangeCoder == NULL || inLimit <= 0) + return 0; + + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); + + ENET_RANGE_CODER_SEED; + + for (;;) + { + ENetSymbol * subcontext, * symbol, * patch; +#ifdef ENET_CONTEXT_EXCLUSION + const ENetSymbol * childContext = & emptyContext; +#endif + enet_uint8 value = 0; + enet_uint16 code, under, count, bottom, * parent = & predicted, total; + + for (subcontext = & rangeCoder -> symbols [predicted]; + subcontext != root; +#ifdef ENET_CONTEXT_EXCLUSION + childContext = subcontext, +#endif + subcontext = & rangeCoder -> symbols [subcontext -> parent]) + { + if (subcontext -> escapes <= 0) + continue; + total = subcontext -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, 0); +#endif + if (subcontext -> escapes >= total) + continue; + code = ENET_RANGE_CODER_READ (total); + if (code < subcontext -> escapes) + { + ENET_RANGE_CODER_DECODE (0, subcontext -> escapes, total); + continue; + } + code -= subcontext -> escapes; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + { + ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_EXCLUDED); + } + else +#endif + { + ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_NOT_EXCLUDED); + } + bottom = symbol - rangeCoder -> symbols; + ENET_RANGE_CODER_DECODE (subcontext -> escapes + under, count, total); + subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (subcontext, 0); + goto patchContexts; + } + + total = root -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, ENET_CONTEXT_SYMBOL_MINIMUM); +#endif + code = ENET_RANGE_CODER_READ (total); + if (code < root -> escapes) + { + ENET_RANGE_CODER_DECODE (0, root -> escapes, total); + break; + } + code -= root -> escapes; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + { + ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_EXCLUDED); + } + else +#endif + { + ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_NOT_EXCLUDED); + } + bottom = symbol - rangeCoder -> symbols; + ENET_RANGE_CODER_DECODE (root -> escapes + under, count, total); + root -> total += ENET_CONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); + + patchContexts: + for (patch = & rangeCoder -> symbols [predicted]; + patch != subcontext; + patch = & rangeCoder -> symbols [patch -> parent]) + { + ENET_CONTEXT_ENCODE (patch, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + if (count <= 0) + { + patch -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; + patch -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; + } + patch -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || patch -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (patch, 0); + } + * parent = bottom; + + ENET_RANGE_CODER_OUTPUT (value); + + if (order >= ENET_SUBCONTEXT_ORDER) + predicted = rangeCoder -> symbols [predicted].parent; + else + order ++; + ENET_RANGE_CODER_FREE_SYMBOLS; + } + + return (size_t) (outData - outStart); +} + +/** @defgroup host ENet host functions + @{ +*/ + +/** Sets the packet compressor the host should use to the default range coder. + @param host host to enable the range coder for + @returns 0 on success, < 0 on failure +*/ +int +enet_host_compress_with_range_coder (ENetHost * host) +{ + ENetCompressor compressor; + memset (& compressor, 0, sizeof (compressor)); + compressor.context = enet_range_coder_create(); + if (compressor.context == NULL) + return -1; + compressor.compress = enet_range_coder_compress; + compressor.decompress = enet_range_coder_decompress; + compressor.destroy = enet_range_coder_destroy; + enet_host_compress (host, & compressor); + return 0; +} + +/** @} */ + + diff --git a/src/enet/design.txt b/src/enet/design.txt new file mode 100644 index 000000000..9a88dba9f --- /dev/null +++ b/src/enet/design.txt @@ -0,0 +1,117 @@ +* Why ENet? + + ENet evolved specifically as a UDP networking layer for the multiplayer +first person shooter Cube. Cube necessitated low latency communcation with +data sent out very frequently, so TCP was an unsuitable choice due to its +high latency and stream orientation. UDP, however, lacks many sometimes +necessary features from TCP such as reliability, sequencing, unrestricted +packet sizes, and connection management. So UDP by itself was not suitable +as a network protocol either. No suitable freely available networking +libraries existed at the time of ENet's creation to fill this niche. + + UDP and TCP could have been used together in Cube to benefit somewhat +from both of their features, however, the resulting combinations of protocols +still leaves much to be desired. TCP lacks multiple streams of communication +without resorting to opening many sockets and complicates delineation of +packets due to its buffering behavior. UDP lacks sequencing, connection +management, management of bandwidth resources, and imposes limitations on +the size of packets. A significant investment is required to integrate these +two protocols, and the end result is worse off in features and performance +than the uniform protocol presented by ENet. + + ENet thus attempts to address these issues and provide a single, uniform +protocol layered over UDP to the developer with the best features of UDP and +TCP as well as some useful features neither provide, with a much cleaner +integration than any resulting from a mixture of UDP and TCP. + +* Connection management + + ENet provides a simple connection interface over which to communicate +with a foreign host. The liveness of the connection is actively monitored +by pinging the foreign host at frequent intervals, and also monitors the +network conditions from the local host to the foreign host such as the +mean round trip time and packet loss in this fashion. + +* Sequencing + + Rather than a single byte stream that complicates the delineation +of packets, ENet presents connections as multiple, properly sequenced packet +streams that simplify the transfer of various types of data. + + ENet provides sequencing for all packets by assigning to each sent +packet a sequence number that is incremented as packets are sent. ENet +guarentees that no packet with a higher sequence number will be delivered +before a packet with a lower sequence number, thus ensuring packets are +delivered exactly in the order they are sent. + + For unreliable packets, ENet will simply discard the lower sequence +number packet if a packet with a higher sequence number has already been +delivered. This allows the packets to be dispatched immediately as they +arrive, and reduce latency of unreliable packets to an absolute minimum. +For reliable packets, if a higher sequence number packet arrives, but the +preceding packets in the sequence have not yet arrived, ENet will stall +delivery of the higher sequence number packets until its predecessors +have arrived. + +* Channels + + Since ENet will stall delivery of reliable packets to ensure proper +sequencing, and consequently any packets of higher sequence number whether +reliable or unreliable, in the event the reliable packet's predecessors +have not yet arrived, this can introduce latency into the delivery of other +packets which may not need to be as strictly ordered with respect to the +packet that stalled their delivery. + + To combat this latency and reduce the ordering restrictions on packets, +ENet provides multiple channels of communication over a given connection. +Each channel is independently sequenced, and so the delivery status of +a packet in one channel will not stall the delivery of other packets +in another channel. + +* Reliability + + ENet provides optional reliability of packet delivery by ensuring the +foreign host acknowledges receipt of all reliable packets. ENet will attempt +to resend the packet up to a reasonable amount of times, if no acknowledgement +of the packet's receipt happens within a specified timeout. Retry timeouts +are progressive and become more lenient with every failed attempt to allow +for temporary turbulence in network conditions. + +* Fragmentation and reassembly + + ENet will send and deliver packets regardless of size. Large packets are +fragmented into many smaller packets of suitable size, and reassembled on +the foreign host to recover the original packet for delivery. The process +is entirely transparent to the developer. + +* Aggregation + + ENet aggregates all protocol commands, including acknowledgements and +packet transfer, into larger protocol packets to ensure the proper utilization +of the connection and to limit the opportunities for packet loss that might +otherwise result in further delivery latency. + +* Adaptability + + ENet provides an in-flight data window for reliable packets to ensure +connections are not overwhelmed by volumes of packets. It also provides a +static bandwidth allocation mechanism to ensure the total volume of packets +sent and received to a host don't exceed the host's capabilities. Further, +ENet also provides a dynamic throttle that responds to deviations from normal +network connections to rectify various types of network congestion by further +limiting the volume of packets sent. + +* Portability + + ENet works on Windows and any other Unix or Unix-like platform providing +a BSD sockets interface. The library has a small and stable code base that +can easily be extended to support other platforms and integrates easily. + +* Freedom + + ENet demands no royalties and doesn't carry a viral license that would +restrict you in how you might use it in your programs. ENet is licensed under +a short-and-sweet MIT-style license, which gives you the freedom to do anything +you want with it (well, almost anything). + + diff --git a/src/enet/enet.h b/src/enet/enet.h new file mode 100644 index 000000000..2f656d6cb --- /dev/null +++ b/src/enet/enet.h @@ -0,0 +1,540 @@ +/** + @file enet.h + @brief ENet public header file +*/ +#ifndef __ENET_ENET_H__ +#define __ENET_ENET_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#ifdef WIN32 +#include "enet/win32.h" +#else +#include "enet/unix.h" +#endif + +#include "enet/types.h" +#include "enet/protocol.h" +#include "enet/list.h" +#include "enet/callbacks.h" + +#define ENET_VERSION_MAJOR 1 +#define ENET_VERSION_MINOR 3 +#define ENET_VERSION_PATCH 0 +#define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch)) +#define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH) + +typedef enet_uint32 ENetVersion; + +typedef enum _ENetSocketType +{ + ENET_SOCKET_TYPE_STREAM = 1, + ENET_SOCKET_TYPE_DATAGRAM = 2 +} ENetSocketType; + +typedef enum _ENetSocketWait +{ + ENET_SOCKET_WAIT_NONE = 0, + ENET_SOCKET_WAIT_SEND = (1 << 0), + ENET_SOCKET_WAIT_RECEIVE = (1 << 1) +} ENetSocketWait; + +typedef enum _ENetSocketOption +{ + ENET_SOCKOPT_NONBLOCK = 1, + ENET_SOCKOPT_BROADCAST = 2, + ENET_SOCKOPT_RCVBUF = 3, + ENET_SOCKOPT_SNDBUF = 4, + ENET_SOCKOPT_REUSEADDR = 5 +} ENetSocketOption; + +enum +{ + ENET_HOST_ANY = 0, /**< specifies the default server host */ + ENET_HOST_BROADCAST = 0xFFFFFFFF, /**< specifies a subnet-wide broadcast */ + + ENET_PORT_ANY = 0 /**< specifies that a port should be automatically chosen */ +}; + +/** + * Portable internet address structure. + * + * The host must be specified in network byte-order, and the port must be in host + * byte-order. The constant ENET_HOST_ANY may be used to specify the default + * server host. The constant ENET_HOST_BROADCAST may be used to specify the + * broadcast address (255.255.255.255). This makes sense for enet_host_connect, + * but not for enet_host_create. Once a server responds to a broadcast, the + * address is updated from ENET_HOST_BROADCAST to the server's actual IP address. + */ +typedef struct _ENetAddress +{ + enet_uint32 host; + enet_uint16 port; +} ENetAddress; + +/** + * Packet flag bit constants. + * + * The host must be specified in network byte-order, and the port must be in + * host byte-order. The constant ENET_HOST_ANY may be used to specify the + * default server host. + + @sa ENetPacket +*/ +typedef enum _ENetPacketFlag +{ + /** packet must be received by the target peer and resend attempts should be + * made until the packet is delivered */ + ENET_PACKET_FLAG_RELIABLE = (1 << 0), + /** packet will not be sequenced with other packets + * not supported for reliable packets + */ + ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), + /** packet will not allocate data, and user must supply it instead */ + ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2) +} ENetPacketFlag; + +struct _ENetPacket; +typedef void (ENET_CALLBACK * ENetPacketFreeCallback) (struct _ENetPacket *); + +/** + * ENet packet structure. + * + * An ENet data packet that may be sent to or received from a peer. The shown + * fields should only be read and never modified. The data field contains the + * allocated data for the packet. The dataLength fields specifies the length + * of the allocated data. The flags field is either 0 (specifying no flags), + * or a bitwise-or of any combination of the following flags: + * + * ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer + * and resend attempts should be made until the packet is delivered + * + * ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets + * (not supported for reliable packets) + * + * ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead + + @sa ENetPacketFlag + */ +typedef struct _ENetPacket +{ + size_t referenceCount; /**< internal use only */ + enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */ + enet_uint8 * data; /**< allocated data for packet */ + size_t dataLength; /**< length of data */ + ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */ +} ENetPacket; + +typedef struct _ENetAcknowledgement +{ + ENetListNode acknowledgementList; + enet_uint32 sentTime; + ENetProtocol command; +} ENetAcknowledgement; + +typedef struct _ENetOutgoingCommand +{ + ENetListNode outgoingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + enet_uint32 sentTime; + enet_uint32 roundTripTimeout; + enet_uint32 roundTripTimeoutLimit; + enet_uint32 fragmentOffset; + enet_uint16 fragmentLength; + enet_uint16 sendAttempts; + ENetProtocol command; + ENetPacket * packet; +} ENetOutgoingCommand; + +typedef struct _ENetIncomingCommand +{ + ENetListNode incomingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + ENetProtocol command; + enet_uint32 fragmentCount; + enet_uint32 fragmentsRemaining; + enet_uint32 * fragments; + ENetPacket * packet; +} ENetIncomingCommand; + +typedef enum _ENetPeerState +{ + ENET_PEER_STATE_DISCONNECTED = 0, + ENET_PEER_STATE_CONNECTING = 1, + ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2, + ENET_PEER_STATE_CONNECTION_PENDING = 3, + ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4, + ENET_PEER_STATE_CONNECTED = 5, + ENET_PEER_STATE_DISCONNECT_LATER = 6, + ENET_PEER_STATE_DISCONNECTING = 7, + ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8, + ENET_PEER_STATE_ZOMBIE = 9 +} ENetPeerState; + +#ifndef ENET_BUFFER_MAXIMUM +#define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) +#endif + +enum +{ + ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, + ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, + ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, + ENET_HOST_DEFAULT_MTU = 1400, + + ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500, + ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, + ENET_PEER_PACKET_THROTTLE_SCALE = 32, + ENET_PEER_PACKET_THROTTLE_COUNTER = 7, + ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2, + ENET_PEER_PACKET_THROTTLE_DECELERATION = 2, + ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000, + ENET_PEER_PACKET_LOSS_SCALE = (1 << 16), + ENET_PEER_PACKET_LOSS_INTERVAL = 10000, + ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024, + ENET_PEER_TIMEOUT_LIMIT = 32, + ENET_PEER_TIMEOUT_MINIMUM = 5000, + ENET_PEER_TIMEOUT_MAXIMUM = 30000, + ENET_PEER_PING_INTERVAL = 500, + ENET_PEER_UNSEQUENCED_WINDOWS = 64, + ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024, + ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32, + ENET_PEER_RELIABLE_WINDOWS = 16, + ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000, + ENET_PEER_FREE_RELIABLE_WINDOWS = 8 +}; + +typedef struct _ENetChannel +{ + enet_uint16 outgoingReliableSequenceNumber; + enet_uint16 outgoingUnreliableSequenceNumber; + enet_uint16 usedReliableWindows; + enet_uint16 reliableWindows [ENET_PEER_RELIABLE_WINDOWS]; + enet_uint16 incomingReliableSequenceNumber; + ENetList incomingReliableCommands; + ENetList incomingUnreliableCommands; +} ENetChannel; + +/** + * An ENet peer which data packets may be sent or received from. + * + * No fields should be modified unless otherwise specified. + */ +typedef struct _ENetPeer +{ + ENetListNode dispatchList; + struct _ENetHost * host; + enet_uint16 outgoingPeerID; + enet_uint16 incomingPeerID; + enet_uint32 connectID; + enet_uint8 outgoingSessionID; + enet_uint8 incomingSessionID; + ENetAddress address; /**< Internet address of the peer */ + void * data; /**< Application private data, may be freely modified */ + ENetPeerState state; + ENetChannel * channels; + size_t channelCount; /**< Number of channels allocated for communication with peer */ + enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */ + enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */ + enet_uint32 incomingBandwidthThrottleEpoch; + enet_uint32 outgoingBandwidthThrottleEpoch; + enet_uint32 incomingDataTotal; + enet_uint32 outgoingDataTotal; + enet_uint32 lastSendTime; + enet_uint32 lastReceiveTime; + enet_uint32 nextTimeout; + enet_uint32 earliestTimeout; + enet_uint32 packetLossEpoch; + enet_uint32 packetsSent; + enet_uint32 packetsLost; + enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */ + enet_uint32 packetLossVariance; + enet_uint32 packetThrottle; + enet_uint32 packetThrottleLimit; + enet_uint32 packetThrottleCounter; + enet_uint32 packetThrottleEpoch; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 packetThrottleInterval; + enet_uint32 lastRoundTripTime; + enet_uint32 lowestRoundTripTime; + enet_uint32 lastRoundTripTimeVariance; + enet_uint32 highestRoundTripTimeVariance; + enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */ + enet_uint32 roundTripTimeVariance; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 reliableDataInTransit; + enet_uint16 outgoingReliableSequenceNumber; + ENetList acknowledgements; + ENetList sentReliableCommands; + ENetList sentUnreliableCommands; + ENetList outgoingReliableCommands; + ENetList outgoingUnreliableCommands; + ENetList dispatchedCommands; + int needsDispatch; + enet_uint16 incomingUnsequencedGroup; + enet_uint16 outgoingUnsequencedGroup; + enet_uint32 unsequencedWindow [ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; + enet_uint32 eventData; +} ENetPeer; + +/** An ENet packet compressor for compressing UDP packets before socket sends or receives. + */ +typedef struct _ENetCompressor +{ + /** Context data for the compressor. Must be non-NULL. */ + void * context; + /** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t (ENET_CALLBACK * compress) (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); + /** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t (ENET_CALLBACK * decompress) (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); + /** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */ + void (ENET_CALLBACK * destroy) (void * context); +} ENetCompressor; + +/** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */ +typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback) (const ENetBuffer * buffers, size_t bufferCount); + +/** An ENet host for communicating with peers. + * + * No fields should be modified unless otherwise stated. + + @sa enet_host_create() + @sa enet_host_destroy() + @sa enet_host_connect() + @sa enet_host_service() + @sa enet_host_flush() + @sa enet_host_broadcast() + @sa enet_host_compress() + @sa enet_host_compress_with_range_coder() + @sa enet_host_channel_limit() + @sa enet_host_bandwidth_limit() + @sa enet_host_bandwidth_throttle() + */ +typedef struct _ENetHost +{ + ENetSocket socket; + ENetAddress address; /**< Internet address of the host */ + enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */ + enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */ + enet_uint32 bandwidthThrottleEpoch; + enet_uint32 mtu; + enet_uint32 randomSeed; + int recalculateBandwidthLimits; + ENetPeer * peers; /**< array of peers allocated for this host */ + size_t peerCount; /**< number of peers allocated for this host */ + size_t channelLimit; /**< maximum number of channels allowed for connected peers */ + enet_uint32 serviceTime; + ENetList dispatchQueue; + int continueSending; + size_t packetSize; + enet_uint16 headerFlags; + ENetProtocol commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; + size_t commandCount; + ENetBuffer buffers [ENET_BUFFER_MAXIMUM]; + size_t bufferCount; + ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */ + ENetCompressor compressor; + enet_uint8 packetData [2][ENET_PROTOCOL_MAXIMUM_MTU]; + ENetAddress receivedAddress; + enet_uint8 * receivedData; + size_t receivedDataLength; + enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */ +} ENetHost; + +/** + * An ENet event type, as specified in @ref ENetEvent. + */ +typedef enum _ENetEventType +{ + /** no event occurred within the specified time limit */ + ENET_EVENT_TYPE_NONE = 0, + + /** a connection request initiated by enet_host_connect has completed. + * The peer field contains the peer which successfully connected. + */ + ENET_EVENT_TYPE_CONNECT = 1, + + /** a peer has disconnected. This event is generated on a successful + * completion of a disconnect initiated by enet_pper_disconnect, if + * a peer has timed out, or if a connection request intialized by + * enet_host_connect has timed out. The peer field contains the peer + * which disconnected. The data field contains user supplied data + * describing the disconnection, or 0, if none is available. + */ + ENET_EVENT_TYPE_DISCONNECT = 2, + + /** a packet has been received from a peer. The peer field specifies the + * peer which sent the packet. The channelID field specifies the channel + * number upon which the packet was received. The packet field contains + * the packet that was received; this packet must be destroyed with + * enet_packet_destroy after use. + */ + ENET_EVENT_TYPE_RECEIVE = 3 +} ENetEventType; + +/** + * An ENet event as returned by enet_host_service(). + + @sa enet_host_service + */ +typedef struct _ENetEvent +{ + ENetEventType type; /**< type of the event */ + ENetPeer * peer; /**< peer that generated a connect, disconnect or receive event */ + enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */ + enet_uint32 data; /**< data associated with the event, if appropriate */ + ENetPacket * packet; /**< packet associated with the event, if appropriate */ +} ENetEvent; + +/** @defgroup global ENet global functions + @{ +*/ + +/** + Initializes ENet globally. Must be called prior to using any functions in + ENet. + @returns 0 on success, < 0 on failure +*/ +ENET_API int enet_initialize (void); + +/** + Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored. + + @param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use + @param inits user-overriden callbacks where any NULL callbacks will use ENet's defaults + @returns 0 on success, < 0 on failure +*/ +ENET_API int enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits); + +/** + Shuts down ENet globally. Should be called when a program that has + initialized ENet exits. +*/ +ENET_API void enet_deinitialize (void); + +/** @} */ + +/** @defgroup private ENet private implementation functions */ + +/** + Returns the wall-time in milliseconds. Its initial value is unspecified + unless otherwise set. + */ +ENET_API enet_uint32 enet_time_get (void); +/** + Sets the current wall-time in milliseconds. + */ +ENET_API void enet_time_set (enet_uint32); + +/** @defgroup socket ENet socket functions + @{ +*/ +ENET_API ENetSocket enet_socket_create (ENetSocketType); +ENET_API int enet_socket_bind (ENetSocket, const ENetAddress *); +ENET_API int enet_socket_listen (ENetSocket, int); +ENET_API ENetSocket enet_socket_accept (ENetSocket, ENetAddress *); +ENET_API int enet_socket_connect (ENetSocket, const ENetAddress *); +ENET_API int enet_socket_send (ENetSocket, const ENetAddress *, const ENetBuffer *, size_t); +ENET_API int enet_socket_receive (ENetSocket, ENetAddress *, ENetBuffer *, size_t); +ENET_API int enet_socket_wait (ENetSocket, enet_uint32 *, enet_uint32); +ENET_API int enet_socket_set_option (ENetSocket, ENetSocketOption, int); +ENET_API void enet_socket_destroy (ENetSocket); +ENET_API int enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32); + +/** @} */ + +/** @defgroup Address ENet address functions + @{ +*/ +/** Attempts to resolve the host named by the parameter hostName and sets + the host field in the address parameter if successful. + @param address destination to store resolved address + @param hostName host name to lookup + @retval 0 on success + @retval < 0 on failure + @returns the address of the given hostName in address on success +*/ +ENET_API int enet_address_set_host (ENetAddress * address, const char * hostName); + +/** Gives the printable form of the ip address specified in the address parameter. + @param address address printed + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure +*/ +ENET_API int enet_address_get_host_ip (const ENetAddress * address, char * hostName, size_t nameLength); + +/** Attempts to do a reverse lookup of the host field in the address parameter. + @param address address used for reverse lookup + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure +*/ +ENET_API int enet_address_get_host (const ENetAddress * address, char * hostName, size_t nameLength); + +/** @} */ + +ENET_API ENetPacket * enet_packet_create (const void *, size_t, enet_uint32); +ENET_API void enet_packet_destroy (ENetPacket *); +ENET_API int enet_packet_resize (ENetPacket *, size_t); +extern enet_uint32 enet_crc32 (const ENetBuffer *, size_t); + +ENET_API ENetHost * enet_host_create (const ENetAddress *, size_t, size_t, enet_uint32, enet_uint32); +ENET_API void enet_host_destroy (ENetHost *); +ENET_API ENetPeer * enet_host_connect (ENetHost *, const ENetAddress *, size_t, enet_uint32); +ENET_API int enet_host_check_events (ENetHost *, ENetEvent *); +ENET_API int enet_host_service (ENetHost *, ENetEvent *, enet_uint32); +ENET_API void enet_host_flush (ENetHost *); +ENET_API void enet_host_broadcast (ENetHost *, enet_uint8, ENetPacket *); +ENET_API void enet_host_compress (ENetHost *, const ENetCompressor *); +ENET_API int enet_host_compress_with_range_coder (ENetHost * host); +ENET_API void enet_host_channel_limit (ENetHost *, size_t); +ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32); +extern void enet_host_bandwidth_throttle (ENetHost *); + +ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *); +ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID); +ENET_API void enet_peer_ping (ENetPeer *); +ENET_API void enet_peer_reset (ENetPeer *); +ENET_API void enet_peer_disconnect (ENetPeer *, enet_uint32); +ENET_API void enet_peer_disconnect_now (ENetPeer *, enet_uint32); +ENET_API void enet_peer_disconnect_later (ENetPeer *, enet_uint32); +ENET_API void enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); +extern int enet_peer_throttle (ENetPeer *, enet_uint32); +extern void enet_peer_reset_queues (ENetPeer *); +extern void enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *); +extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16); +extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32); +extern ENetAcknowledgement * enet_peer_queue_acknowledgement (ENetPeer *, const ENetProtocol *, enet_uint16); +extern void enet_peer_dispatch_incoming_unreliable_commands (ENetPeer *, ENetChannel *); +extern void enet_peer_dispatch_incoming_reliable_commands (ENetPeer *, ENetChannel *); + +ENET_API void * enet_range_coder_create (void); +ENET_API void enet_range_coder_destroy (void *); +ENET_API size_t enet_range_coder_compress (void *, const ENetBuffer *, size_t, size_t, enet_uint8 *, size_t); +ENET_API size_t enet_range_coder_decompress (void *, const enet_uint8 *, size_t, enet_uint8 *, size_t); + +extern size_t enet_protocol_command_size (enet_uint8); + +#ifdef __cplusplus +} +#endif + +#endif /* __ENET_ENET_H__ */ + diff --git a/src/enet/host.c b/src/enet/host.c new file mode 100644 index 000000000..8bb2433fe --- /dev/null +++ b/src/enet/host.c @@ -0,0 +1,479 @@ +/** + @file host.c + @brief ENet host management functions +*/ +#define ENET_BUILDING_LIB 1 +#include +#include +#include "enet/enet.h" + +/** @defgroup host ENet host functions + @{ +*/ + +/** Creates a host for communicating to peers. + + @param address the address at which other peers may connect to this host. If NULL, then no peers may connect to the host. + @param peerCount the maximum number of peers that should be allocated for the host. + @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT + @param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + @param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + + @returns the host on success and NULL on failure + + @remarks ENet will strategically drop packets on specific sides of a connection between hosts + to ensure the host's bandwidth is not overwhelmed. The bandwidth parameters also determine + the window size of a connection which limits the amount of reliable packets that may be in transit + at any given time. +*/ +ENetHost * +enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) +{ + ENetHost * host; + ENetPeer * currentPeer; + + if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID) + return NULL; + + host = (ENetHost *) enet_malloc (sizeof (ENetHost)); + if (host == NULL) + return NULL; + + host -> peers = (ENetPeer *) enet_malloc (peerCount * sizeof (ENetPeer)); + if (host -> peers == NULL) + { + enet_free (host); + + return NULL; + } + memset (host -> peers, 0, peerCount * sizeof (ENetPeer)); + + host -> socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM); + if (host -> socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind (host -> socket, address) < 0)) + { + if (host -> socket != ENET_SOCKET_NULL) + enet_socket_destroy (host -> socket); + + enet_free (host -> peers); + enet_free (host); + + return NULL; + } + + enet_socket_set_option (host -> socket, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_BROADCAST, 1); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE); + + if (address != NULL) + host -> address = * address; + + if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + else + if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + + host -> randomSeed = (enet_uint32) time(NULL) + (enet_uint32) (size_t) host; + host -> randomSeed = (host -> randomSeed << 16) | (host -> randomSeed >> 16); + host -> channelLimit = channelLimit; + host -> incomingBandwidth = incomingBandwidth; + host -> outgoingBandwidth = outgoingBandwidth; + host -> bandwidthThrottleEpoch = 0; + host -> recalculateBandwidthLimits = 0; + host -> mtu = ENET_HOST_DEFAULT_MTU; + host -> peerCount = peerCount; + host -> commandCount = 0; + host -> bufferCount = 0; + host -> checksum = NULL; + host -> receivedAddress.host = ENET_HOST_ANY; + host -> receivedAddress.port = 0; + host -> receivedData = NULL; + host -> receivedDataLength = 0; + + host -> totalSentData = 0; + host -> totalSentPackets = 0; + host -> totalReceivedData = 0; + host -> totalReceivedPackets = 0; + + host -> compressor.context = NULL; + host -> compressor.compress = NULL; + host -> compressor.decompress = NULL; + host -> compressor.destroy = NULL; + + enet_list_clear (& host -> dispatchQueue); + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + currentPeer -> host = host; + currentPeer -> incomingPeerID = currentPeer - host -> peers; + currentPeer -> outgoingSessionID = currentPeer -> incomingSessionID = 0xFF; + currentPeer -> data = NULL; + + enet_list_clear (& currentPeer -> acknowledgements); + enet_list_clear (& currentPeer -> sentReliableCommands); + enet_list_clear (& currentPeer -> sentUnreliableCommands); + enet_list_clear (& currentPeer -> outgoingReliableCommands); + enet_list_clear (& currentPeer -> outgoingUnreliableCommands); + enet_list_clear (& currentPeer -> dispatchedCommands); + + enet_peer_reset (currentPeer); + } + + return host; +} + +/** Destroys the host and all resources associated with it. + @param host pointer to the host to destroy +*/ +void +enet_host_destroy (ENetHost * host) +{ + ENetPeer * currentPeer; + + enet_socket_destroy (host -> socket); + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + enet_peer_reset (currentPeer); + } + + if (host -> compressor.context != NULL && host -> compressor.destroy) + (* host -> compressor.destroy) (host -> compressor.context); + + enet_free (host -> peers); + enet_free (host); +} + +/** Initiates a connection to a foreign host. + @param host host seeking the connection + @param address destination for the connection + @param channelCount number of channels to allocate + @param data user data supplied to the receiving host + @returns a peer representing the foreign host on success, NULL on failure + @remarks The peer returned will have not completed the connection until enet_host_service() + notifies of an ENET_EVENT_TYPE_CONNECT event for the peer. +*/ +ENetPeer * +enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelCount, enet_uint32 data) +{ + ENetPeer * currentPeer; + ENetChannel * channel; + ENetProtocol command; + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + else + if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED) + break; + } + + if (currentPeer >= & host -> peers [host -> peerCount]) + return NULL; + + currentPeer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel)); + if (currentPeer -> channels == NULL) + return NULL; + currentPeer -> channelCount = channelCount; + currentPeer -> state = ENET_PEER_STATE_CONNECTING; + currentPeer -> address = * address; + currentPeer -> connectID = ++ host -> randomSeed; + + if (host -> outgoingBandwidth == 0) + currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + currentPeer -> windowSize = (host -> outgoingBandwidth / + ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (currentPeer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + currentPeer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (currentPeer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + for (channel = currentPeer -> channels; + channel < & currentPeer -> channels [channelCount]; + ++ channel) + { + channel -> outgoingReliableSequenceNumber = 0; + channel -> outgoingUnreliableSequenceNumber = 0; + channel -> incomingReliableSequenceNumber = 0; + + enet_list_clear (& channel -> incomingReliableCommands); + enet_list_clear (& channel -> incomingUnreliableCommands); + + channel -> usedReliableWindows = 0; + memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows)); + } + + command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.connect.outgoingPeerID = ENET_HOST_TO_NET_16 (currentPeer -> incomingPeerID); + command.connect.incomingSessionID = currentPeer -> incomingSessionID; + command.connect.outgoingSessionID = currentPeer -> outgoingSessionID; + command.connect.mtu = ENET_HOST_TO_NET_32 (currentPeer -> mtu); + command.connect.windowSize = ENET_HOST_TO_NET_32 (currentPeer -> windowSize); + command.connect.channelCount = ENET_HOST_TO_NET_32 (channelCount); + command.connect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth); + command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth); + command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleInterval); + command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleAcceleration); + command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleDeceleration); + command.connect.connectID = currentPeer -> connectID; + command.connect.data = ENET_HOST_TO_NET_32 (data); + + enet_peer_queue_outgoing_command (currentPeer, & command, NULL, 0, 0); + + return currentPeer; +} + +/** Queues a packet to be sent to all peers associated with the host. + @param host host on which to broadcast the packet + @param channelID channel on which to broadcast + @param packet packet to broadcast +*/ +void +enet_host_broadcast (ENetHost * host, enet_uint8 channelID, ENetPacket * packet) +{ + ENetPeer * currentPeer; + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state != ENET_PEER_STATE_CONNECTED) + continue; + + enet_peer_send (currentPeer, channelID, packet); + } + + if (packet -> referenceCount == 0) + enet_packet_destroy (packet); +} + +/** Sets the packet compressor the host should use to compress and decompress packets. + @param host host to enable or disable compression for + @param compressor callbacks for for the packet compressor; if NULL, then compression is disabled +*/ +void +enet_host_compress (ENetHost * host, const ENetCompressor * compressor) +{ + if (host -> compressor.context != NULL && host -> compressor.destroy) + (* host -> compressor.destroy) (host -> compressor.context); + + if (compressor) + host -> compressor = * compressor; + else + host -> compressor.context = NULL; +} + +/** Limits the maximum allowed channels of future incoming connections. + @param host host to limit + @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT +*/ +void +enet_host_channel_limit (ENetHost * host, size_t channelLimit) +{ + if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + else + if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + + host -> channelLimit = channelLimit; +} + + +/** Adjusts the bandwidth limits of a host. + @param host host to adjust + @param incomingBandwidth new incoming bandwidth + @param outgoingBandwidth new outgoing bandwidth + @remarks the incoming and outgoing bandwidth parameters are identical in function to those + specified in enet_host_create(). +*/ +void +enet_host_bandwidth_limit (ENetHost * host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) +{ + host -> incomingBandwidth = incomingBandwidth; + host -> outgoingBandwidth = outgoingBandwidth; + host -> recalculateBandwidthLimits = 1; +} + +void +enet_host_bandwidth_throttle (ENetHost * host) +{ + enet_uint32 timeCurrent = enet_time_get (), + elapsedTime = timeCurrent - host -> bandwidthThrottleEpoch, + peersTotal = 0, + dataTotal = 0, + peersRemaining, + bandwidth, + throttle = 0, + bandwidthLimit = 0; + int needsAdjustment; + ENetPeer * peer; + ENetProtocol command; + + if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + return; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + continue; + + ++ peersTotal; + dataTotal += peer -> outgoingDataTotal; + } + + if (peersTotal == 0) + return; + + peersRemaining = peersTotal; + needsAdjustment = 1; + + if (host -> outgoingBandwidth == 0) + bandwidth = ~0; + else + bandwidth = (host -> outgoingBandwidth * elapsedTime) / 1000; + + while (peersRemaining > 0 && needsAdjustment != 0) + { + needsAdjustment = 0; + + if (dataTotal < bandwidth) + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + else + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + enet_uint32 peerBandwidth; + + if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer -> incomingBandwidth == 0 || + peer -> outgoingBandwidthThrottleEpoch == timeCurrent) + continue; + + peerBandwidth = (peer -> incomingBandwidth * elapsedTime) / 1000; + if ((throttle * peer -> outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth) + continue; + + peer -> packetThrottleLimit = (peerBandwidth * + ENET_PEER_PACKET_THROTTLE_SCALE) / peer -> outgoingDataTotal; + + if (peer -> packetThrottleLimit == 0) + peer -> packetThrottleLimit = 1; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + + peer -> outgoingBandwidthThrottleEpoch = timeCurrent; + + + needsAdjustment = 1; + -- peersRemaining; + bandwidth -= peerBandwidth; + dataTotal -= peerBandwidth; + } + } + + if (peersRemaining > 0) + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer -> outgoingBandwidthThrottleEpoch == timeCurrent) + continue; + + peer -> packetThrottleLimit = throttle; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + } + + if (host -> recalculateBandwidthLimits) + { + host -> recalculateBandwidthLimits = 0; + + peersRemaining = peersTotal; + bandwidth = host -> incomingBandwidth; + needsAdjustment = 1; + + if (bandwidth == 0) + bandwidthLimit = 0; + else + while (peersRemaining > 0 && needsAdjustment != 0) + { + needsAdjustment = 0; + bandwidthLimit = bandwidth / peersRemaining; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if ((peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer -> incomingBandwidthThrottleEpoch == timeCurrent) + continue; + + if (peer -> outgoingBandwidth > 0 && + peer -> outgoingBandwidth >= bandwidthLimit) + continue; + + peer -> incomingBandwidthThrottleEpoch = timeCurrent; + + needsAdjustment = 1; + -- peersRemaining; + bandwidth -= peer -> outgoingBandwidth; + } + } + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + continue; + + command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth); + + if (peer -> incomingBandwidthThrottleEpoch == timeCurrent) + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (peer -> outgoingBandwidth); + else + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32 (bandwidthLimit); + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); + } + } + + host -> bandwidthThrottleEpoch = timeCurrent; + + for (peer = host -> peers; + peer < & host -> peers [host -> peerCount]; + ++ peer) + { + peer -> incomingDataTotal = 0; + peer -> outgoingDataTotal = 0; + } +} + +/** @} */ diff --git a/src/enet/list.c b/src/enet/list.c new file mode 100644 index 000000000..8487200ff --- /dev/null +++ b/src/enet/list.c @@ -0,0 +1,75 @@ +/** + @file list.c + @brief ENet linked list functions +*/ +#define ENET_BUILDING_LIB 1 +#include "enet/list.h" + +/** + @defgroup list ENet linked list utility functions + @ingroup private + @{ +*/ +void +enet_list_clear (ENetList * list) +{ + list -> sentinel.next = & list -> sentinel; + list -> sentinel.previous = & list -> sentinel; +} + +ENetListIterator +enet_list_insert (ENetListIterator position, void * data) +{ + ENetListIterator result = (ENetListIterator) data; + + result -> previous = position -> previous; + result -> next = position; + + result -> previous -> next = result; + position -> previous = result; + + return result; +} + +void * +enet_list_remove (ENetListIterator position) +{ + position -> previous -> next = position -> next; + position -> next -> previous = position -> previous; + + return position; +} + +ENetListIterator +enet_list_move (ENetListIterator position, void * dataFirst, void * dataLast) +{ + ENetListIterator first = (ENetListIterator) dataFirst, + last = (ENetListIterator) dataLast; + + first -> previous -> next = last -> next; + last -> next -> previous = first -> previous; + + first -> previous = position -> previous; + last -> next = position; + + first -> previous -> next = first; + position -> previous = last; + + return first; +} + +size_t +enet_list_size (ENetList * list) +{ + size_t size = 0; + ENetListIterator position; + + for (position = enet_list_begin (list); + position != enet_list_end (list); + position = enet_list_next (position)) + ++ size; + + return size; +} + +/** @} */ diff --git a/src/enet/list.h b/src/enet/list.h new file mode 100644 index 000000000..d7b260084 --- /dev/null +++ b/src/enet/list.h @@ -0,0 +1,43 @@ +/** + @file list.h + @brief ENet list management +*/ +#ifndef __ENET_LIST_H__ +#define __ENET_LIST_H__ + +#include + +typedef struct _ENetListNode +{ + struct _ENetListNode * next; + struct _ENetListNode * previous; +} ENetListNode; + +typedef ENetListNode * ENetListIterator; + +typedef struct _ENetList +{ + ENetListNode sentinel; +} ENetList; + +extern void enet_list_clear (ENetList *); + +extern ENetListIterator enet_list_insert (ENetListIterator, void *); +extern void * enet_list_remove (ENetListIterator); +extern ENetListIterator enet_list_move (ENetListIterator, void *, void *); + +extern size_t enet_list_size (ENetList *); + +#define enet_list_begin(list) ((list) -> sentinel.next) +#define enet_list_end(list) (& (list) -> sentinel) + +#define enet_list_empty(list) (enet_list_begin (list) == enet_list_end (list)) + +#define enet_list_next(iterator) ((iterator) -> next) +#define enet_list_previous(iterator) ((iterator) -> previous) + +#define enet_list_front(list) ((void *) (list) -> sentinel.next) +#define enet_list_back(list) ((void *) (list) -> sentinel.previous) + +#endif /* __ENET_LIST_H__ */ + diff --git a/src/enet/packet.c b/src/enet/packet.c new file mode 100644 index 000000000..d6730dd33 --- /dev/null +++ b/src/enet/packet.c @@ -0,0 +1,159 @@ +/** + @file packet.c + @brief ENet packet management functions +*/ +#include +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** @defgroup Packet ENet packet functions + @{ +*/ + +/** Creates a packet that may be sent to a peer. + @param dataContents initial contents of the packet's data; the packet's data will remain uninitialized if dataContents is NULL. + @param dataLength size of the data allocated for this packet + @param flags flags for this packet as described for the ENetPacket structure. + @returns the packet on success, NULL on failure +*/ +ENetPacket * +enet_packet_create (const void * data, size_t dataLength, enet_uint32 flags) +{ + ENetPacket * packet = (ENetPacket *) enet_malloc (sizeof (ENetPacket)); + if (packet == NULL) + return NULL; + + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) + packet -> data = (enet_uint8 *) data; + else + { + packet -> data = (enet_uint8 *) enet_malloc (dataLength); + if (packet -> data == NULL) + { + enet_free (packet); + return NULL; + } + + if (data != NULL) + memcpy (packet -> data, data, dataLength); + } + + packet -> referenceCount = 0; + packet -> flags = flags; + packet -> dataLength = dataLength; + packet -> freeCallback = NULL; + + return packet; +} + +/** Destroys the packet and deallocates its data. + @param packet packet to be destroyed +*/ +void +enet_packet_destroy (ENetPacket * packet) +{ + if (packet -> freeCallback != NULL) + (* packet -> freeCallback) (packet); + if (! (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE)) + enet_free (packet -> data); + enet_free (packet); +} + +/** Attempts to resize the data in the packet to length specified in the + dataLength parameter + @param packet packet to resize + @param dataLength new size for the packet data + @returns 0 on success, < 0 on failure +*/ +int +enet_packet_resize (ENetPacket * packet, size_t dataLength) +{ + enet_uint8 * newData; + + if (dataLength <= packet -> dataLength + || (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE)) + { + packet -> dataLength = dataLength; + + return 0; + } + + newData = (enet_uint8 *) enet_malloc (dataLength); + if (newData == NULL) + return -1; + + memcpy (newData, packet -> data, packet -> dataLength); + enet_free (packet -> data); + + packet -> data = newData; + packet -> dataLength = dataLength; + + return 0; +} + +static int initializedCRC32 = 0; +static enet_uint32 crcTable [256]; + +static enet_uint32 +reflect_crc (int val, int bits) +{ + int result = 0, bit; + + for (bit = 0; bit < bits; bit ++) + { + if (val & 1) + result |= 1 << (bits - 1 - bit); + val >>= 1; + } + + return result; +} + +static void +initialize_crc32 () +{ + int byte; + + for (byte = 0; byte < 256; ++ byte) + { + enet_uint32 crc = reflect_crc (byte, 8) << 24; + int offset; + + for (offset = 0; offset < 8; ++ offset) + { + if (crc & 0x80000000) + crc = (crc << 1) ^ 0x04c11db7; + else + crc <<= 1; + } + + crcTable [byte] = reflect_crc (crc, 32); + } + + initializedCRC32 = 1; +} + +enet_uint32 +enet_crc32 (const ENetBuffer * buffers, size_t bufferCount) +{ + enet_uint32 crc = 0xFFFFFFFF; + + if (! initializedCRC32) initialize_crc32 (); + + while (bufferCount -- > 0) + { + const enet_uint8 * data = (const enet_uint8 *) buffers -> data, + * dataEnd = & data [buffers -> dataLength]; + + while (data < dataEnd) + { + crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++]; + } + + ++ buffers; + } + + return ENET_HOST_TO_NET_32 (~ crc); +} + +/** @} */ diff --git a/src/enet/peer.c b/src/enet/peer.c new file mode 100644 index 000000000..d778ce1d6 --- /dev/null +++ b/src/enet/peer.c @@ -0,0 +1,816 @@ +/** + @file peer.c + @brief ENet peer management functions +*/ +#include +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** @defgroup peer ENet peer functions + @{ +*/ + +/** Configures throttle parameter for a peer. + + Unreliable packets are dropped by ENet in response to the varying conditions + of the Internet connection to the peer. The throttle represents a probability + that an unreliable packet should not be dropped and thus sent by ENet to the peer. + The lowest mean round trip time from the sending of a reliable packet to the + receipt of its acknowledgement is measured over an amount of time specified by + the interval parameter in milliseconds. If a measured round trip time happens to + be significantly less than the mean round trip time measured over the interval, + then the throttle probability is increased to allow more traffic by an amount + specified in the acceleration parameter, which is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE + constant. If a measured round trip time happens to be significantly greater than + the mean round trip time measured over the interval, then the throttle probability + is decreased to limit traffic by an amount specified in the deceleration parameter, which + is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE constant. When the throttle has + a value of ENET_PEER_PACKET_THROTTLE_SCALE, on unreliable packets are dropped by + ENet, and so 100% of all unreliable packets will be sent. When the throttle has a + value of 0, all unreliable packets are dropped by ENet, and so 0% of all unreliable + packets will be sent. Intermediate values for the throttle represent intermediate + probabilities between 0% and 100% of unreliable packets being sent. The bandwidth + limits of the local and foreign hosts are taken into account to determine a + sensible limit for the throttle probability above which it should not raise even in + the best of conditions. + + @param peer peer to configure + @param interval interval, in milliseconds, over which to measure lowest mean RTT; the default value is ENET_PEER_PACKET_THROTTLE_INTERVAL. + @param acceleration rate at which to increase the throttle probability as mean RTT declines + @param deceleration rate at which to decrease the throttle probability as mean RTT increases +*/ +void +enet_peer_throttle_configure (ENetPeer * peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deceleration) +{ + ENetProtocol command; + + peer -> packetThrottleInterval = interval; + peer -> packetThrottleAcceleration = acceleration; + peer -> packetThrottleDeceleration = deceleration; + + command.header.command = ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + command.throttleConfigure.packetThrottleInterval = ENET_HOST_TO_NET_32 (interval); + command.throttleConfigure.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (acceleration); + command.throttleConfigure.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (deceleration); + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); +} + +int +enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt) +{ + if (peer -> lastRoundTripTime <= peer -> lastRoundTripTimeVariance) + { + peer -> packetThrottle = peer -> packetThrottleLimit; + } + else + if (rtt < peer -> lastRoundTripTime) + { + peer -> packetThrottle += peer -> packetThrottleAcceleration; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + + return 1; + } + else + if (rtt > peer -> lastRoundTripTime + 2 * peer -> lastRoundTripTimeVariance) + { + if (peer -> packetThrottle > peer -> packetThrottleDeceleration) + peer -> packetThrottle -= peer -> packetThrottleDeceleration; + else + peer -> packetThrottle = 0; + + return -1; + } + + return 0; +} + +/** Queues a packet to be sent. + @param peer destination for the packet + @param channelID channel on which to send + @param packet packet to send + @retval 0 on success + @retval < 0 on failure +*/ +int +enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet) +{ + ENetChannel * channel = & peer -> channels [channelID]; + ENetProtocol command; + size_t fragmentLength; + + if (peer -> state != ENET_PEER_STATE_CONNECTED || + channelID >= peer -> channelCount) + return -1; + + fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment); + + if (packet -> dataLength > fragmentLength) + { + enet_uint16 startSequenceNumber = ENET_HOST_TO_NET_16 (channel -> outgoingReliableSequenceNumber + 1); + enet_uint32 fragmentCount = ENET_HOST_TO_NET_32 ((packet -> dataLength + fragmentLength - 1) / fragmentLength), + fragmentNumber, + fragmentOffset; + ENetList fragments; + ENetOutgoingCommand * fragment; + + enet_list_clear (& fragments); + + for (fragmentNumber = 0, + fragmentOffset = 0; + fragmentOffset < packet -> dataLength; + ++ fragmentNumber, + fragmentOffset += fragmentLength) + { + if (packet -> dataLength - fragmentOffset < fragmentLength) + fragmentLength = packet -> dataLength - fragmentOffset; + + fragment = (ENetOutgoingCommand *) enet_malloc (sizeof (ENetOutgoingCommand)); + if (fragment == NULL) + { + while (! enet_list_empty (& fragments)) + { + fragment = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (& fragments)); + + enet_free (fragment); + } + + return -1; + } + + fragment -> fragmentOffset = fragmentOffset; + fragment -> fragmentLength = fragmentLength; + fragment -> packet = packet; + fragment -> command.header.command = ENET_PROTOCOL_COMMAND_SEND_FRAGMENT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + fragment -> command.header.channelID = channelID; + fragment -> command.sendFragment.startSequenceNumber = startSequenceNumber; + fragment -> command.sendFragment.dataLength = ENET_HOST_TO_NET_16 (fragmentLength); + fragment -> command.sendFragment.fragmentCount = fragmentCount; + fragment -> command.sendFragment.fragmentNumber = ENET_HOST_TO_NET_32 (fragmentNumber); + fragment -> command.sendFragment.totalLength = ENET_HOST_TO_NET_32 (packet -> dataLength); + fragment -> command.sendFragment.fragmentOffset = ENET_NET_TO_HOST_32 (fragmentOffset); + + enet_list_insert (enet_list_end (& fragments), fragment); + } + + packet -> referenceCount += fragmentNumber; + + while (! enet_list_empty (& fragments)) + { + fragment = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (& fragments)); + + enet_peer_setup_outgoing_command (peer, fragment); + } + + return 0; + } + + command.header.channelID = channelID; + + if (packet -> flags & ENET_PACKET_FLAG_RELIABLE) + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.sendReliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + else + if (packet -> flags & ENET_PACKET_FLAG_UNSEQUENCED) + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup + 1); + command.sendUnsequenced.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + else + if (channel -> outgoingUnreliableSequenceNumber >= 0xFFFF) + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.sendReliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + else + { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE; + command.sendUnreliable.unreliableSequenceNumber = ENET_HOST_TO_NET_16 (channel -> outgoingUnreliableSequenceNumber + 1); + command.sendUnreliable.dataLength = ENET_HOST_TO_NET_16 (packet -> dataLength); + } + + if (enet_peer_queue_outgoing_command (peer, & command, packet, 0, packet -> dataLength) == NULL) + return -1; + + return 0; +} + +/** Attempts to dequeue any incoming queued packet. + @param peer peer to dequeue packets from + @param channelID holds the channel ID of the channel the packet was received on success + @returns a pointer to the packet, or NULL if there are no available incoming queued packets +*/ +ENetPacket * +enet_peer_receive (ENetPeer * peer, enet_uint8 * channelID) +{ + ENetIncomingCommand * incomingCommand; + ENetPacket * packet; + + if (enet_list_empty (& peer -> dispatchedCommands)) + return NULL; + + incomingCommand = (ENetIncomingCommand *) enet_list_remove (enet_list_begin (& peer -> dispatchedCommands)); + + if (channelID != NULL) + * channelID = incomingCommand -> command.header.channelID; + + packet = incomingCommand -> packet; + + -- packet -> referenceCount; + + if (incomingCommand -> fragments != NULL) + enet_free (incomingCommand -> fragments); + + enet_free (incomingCommand); + + return packet; +} + +static void +enet_peer_reset_outgoing_commands (ENetList * queue) +{ + ENetOutgoingCommand * outgoingCommand; + + while (! enet_list_empty (queue)) + { + outgoingCommand = (ENetOutgoingCommand *) enet_list_remove (enet_list_begin (queue)); + + if (outgoingCommand -> packet != NULL) + { + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (outgoingCommand -> packet); + } + + enet_free (outgoingCommand); + } +} + +static void +enet_peer_reset_incoming_commands (ENetList * queue) +{ + ENetIncomingCommand * incomingCommand; + + while (! enet_list_empty (queue)) + { + incomingCommand = (ENetIncomingCommand *) enet_list_remove (enet_list_begin (queue)); + + if (incomingCommand -> packet != NULL) + { + -- incomingCommand -> packet -> referenceCount; + + if (incomingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (incomingCommand -> packet); + } + + if (incomingCommand -> fragments != NULL) + enet_free (incomingCommand -> fragments); + + enet_free (incomingCommand); + } +} + +void +enet_peer_reset_queues (ENetPeer * peer) +{ + ENetChannel * channel; + + if (peer -> needsDispatch) + { + enet_list_remove (& peer -> dispatchList); + + peer -> needsDispatch = 0; + } + + while (! enet_list_empty (& peer -> acknowledgements)) + enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements))); + + enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands); + enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands); + enet_peer_reset_outgoing_commands (& peer -> outgoingReliableCommands); + enet_peer_reset_outgoing_commands (& peer -> outgoingUnreliableCommands); + enet_peer_reset_incoming_commands (& peer -> dispatchedCommands); + + if (peer -> channels != NULL && peer -> channelCount > 0) + { + for (channel = peer -> channels; + channel < & peer -> channels [peer -> channelCount]; + ++ channel) + { + enet_peer_reset_incoming_commands (& channel -> incomingReliableCommands); + enet_peer_reset_incoming_commands (& channel -> incomingUnreliableCommands); + } + + enet_free (peer -> channels); + } + + peer -> channels = NULL; + peer -> channelCount = 0; +} + +/** Forcefully disconnects a peer. + @param peer peer to forcefully disconnect + @remarks The foreign host represented by the peer is not notified of the disconnection and will timeout + on its connection to the local host. +*/ +void +enet_peer_reset (ENetPeer * peer) +{ + peer -> outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID; + peer -> connectID = 0; + + peer -> state = ENET_PEER_STATE_DISCONNECTED; + + peer -> incomingBandwidth = 0; + peer -> outgoingBandwidth = 0; + peer -> incomingBandwidthThrottleEpoch = 0; + peer -> outgoingBandwidthThrottleEpoch = 0; + peer -> incomingDataTotal = 0; + peer -> outgoingDataTotal = 0; + peer -> lastSendTime = 0; + peer -> lastReceiveTime = 0; + peer -> nextTimeout = 0; + peer -> earliestTimeout = 0; + peer -> packetLossEpoch = 0; + peer -> packetsSent = 0; + peer -> packetsLost = 0; + peer -> packetLoss = 0; + peer -> packetLossVariance = 0; + peer -> packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE; + peer -> packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE; + peer -> packetThrottleCounter = 0; + peer -> packetThrottleEpoch = 0; + peer -> packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION; + peer -> packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION; + peer -> packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL; + peer -> lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer -> lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer -> lastRoundTripTimeVariance = 0; + peer -> highestRoundTripTimeVariance = 0; + peer -> roundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer -> roundTripTimeVariance = 0; + peer -> mtu = peer -> host -> mtu; + peer -> reliableDataInTransit = 0; + peer -> outgoingReliableSequenceNumber = 0; + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + peer -> incomingUnsequencedGroup = 0; + peer -> outgoingUnsequencedGroup = 0; + peer -> eventData = 0; + + memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow)); + + enet_peer_reset_queues (peer); +} + +/** Sends a ping request to a peer. + @param peer destination for the ping request + @remarks ping requests factor into the mean round trip time as designated by the + roundTripTime field in the ENetPeer structure. Enet automatically pings all connected + peers at regular intervals, however, this function may be called to ensure more + frequent ping requests. +*/ +void +enet_peer_ping (ENetPeer * peer) +{ + ENetProtocol command; + + if (peer -> state != ENET_PEER_STATE_CONNECTED) + return; + + command.header.command = ENET_PROTOCOL_COMMAND_PING | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); +} + +/** Force an immediate disconnection from a peer. + @param peer peer to disconnect + @param data data describing the disconnection + @remarks No ENET_EVENT_DISCONNECT event will be generated. The foreign peer is not + guarenteed to receive the disconnect notification, and is reset immediately upon + return from this function. +*/ +void +enet_peer_disconnect_now (ENetPeer * peer, enet_uint32 data) +{ + ENetProtocol command; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTED) + return; + + if (peer -> state != ENET_PEER_STATE_ZOMBIE && + peer -> state != ENET_PEER_STATE_DISCONNECTING) + { + enet_peer_reset_queues (peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32 (data); + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); + + enet_host_flush (peer -> host); + } + + enet_peer_reset (peer); +} + +/** Request a disconnection from a peer. + @param peer peer to request a disconnection + @param data data describing the disconnection + @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + once the disconnection is complete. +*/ +void +enet_peer_disconnect (ENetPeer * peer, enet_uint32 data) +{ + ENetProtocol command; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTING || + peer -> state == ENET_PEER_STATE_DISCONNECTED || + peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT || + peer -> state == ENET_PEER_STATE_ZOMBIE) + return; + + enet_peer_reset_queues (peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32 (data); + + if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + else + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + + enet_peer_queue_outgoing_command (peer, & command, NULL, 0, 0); + + if (peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + peer -> state = ENET_PEER_STATE_DISCONNECTING; + else + { + enet_host_flush (peer -> host); + enet_peer_reset (peer); + } +} + +/** Request a disconnection from a peer, but only after all queued outgoing packets are sent. + @param peer peer to request a disconnection + @param data data describing the disconnection + @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + once the disconnection is complete. +*/ +void +enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data) +{ + if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && + ! (enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands))) + { + peer -> state = ENET_PEER_STATE_DISCONNECT_LATER; + peer -> eventData = data; + } + else + enet_peer_disconnect (peer, data); +} + +ENetAcknowledgement * +enet_peer_queue_acknowledgement (ENetPeer * peer, const ENetProtocol * command, enet_uint16 sentTime) +{ + ENetAcknowledgement * acknowledgement; + + if (command -> header.channelID < peer -> channelCount) + { + ENetChannel * channel = & peer -> channels [command -> header.channelID]; + enet_uint16 reliableWindow = command -> header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE, + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (command -> header.reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1 && reliableWindow <= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS) + return NULL; + } + + acknowledgement = (ENetAcknowledgement *) enet_malloc (sizeof (ENetAcknowledgement)); + if (acknowledgement == NULL) + return NULL; + + peer -> outgoingDataTotal += sizeof (ENetProtocolAcknowledge); + + acknowledgement -> sentTime = sentTime; + acknowledgement -> command = * command; + + enet_list_insert (enet_list_end (& peer -> acknowledgements), acknowledgement); + + return acknowledgement; +} + +void +enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand) +{ + ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID]; + + peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength; + + if (outgoingCommand -> command.header.channelID == 0xFF) + { + ++ peer -> outgoingReliableSequenceNumber; + + outgoingCommand -> reliableSequenceNumber = peer -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + { + ++ channel -> outgoingReliableSequenceNumber; + channel -> outgoingUnreliableSequenceNumber = 0; + + outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) + { + ++ peer -> outgoingUnsequencedGroup; + + outgoingCommand -> reliableSequenceNumber = 0; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + { + ++ channel -> outgoingUnreliableSequenceNumber; + + outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber; + } + + outgoingCommand -> sendAttempts = 0; + outgoingCommand -> sentTime = 0; + outgoingCommand -> roundTripTimeout = 0; + outgoingCommand -> roundTripTimeoutLimit = 0; + outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber); + + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + enet_list_insert (enet_list_end (& peer -> outgoingReliableCommands), outgoingCommand); + else + enet_list_insert (enet_list_end (& peer -> outgoingUnreliableCommands), outgoingCommand); +} + +ENetOutgoingCommand * +enet_peer_queue_outgoing_command (ENetPeer * peer, const ENetProtocol * command, ENetPacket * packet, enet_uint32 offset, enet_uint16 length) +{ + ENetOutgoingCommand * outgoingCommand = (ENetOutgoingCommand *) enet_malloc (sizeof (ENetOutgoingCommand)); + if (outgoingCommand == NULL) + return NULL; + + outgoingCommand -> command = * command; + outgoingCommand -> fragmentOffset = offset; + outgoingCommand -> fragmentLength = length; + outgoingCommand -> packet = packet; + if (packet != NULL) + ++ packet -> referenceCount; + + enet_peer_setup_outgoing_command (peer, outgoingCommand); + + return outgoingCommand; +} + +void +enet_peer_dispatch_incoming_unreliable_commands (ENetPeer * peer, ENetChannel * channel) +{ + ENetListIterator currentCommand; + + for (currentCommand = enet_list_begin (& channel -> incomingUnreliableCommands); + currentCommand != enet_list_end (& channel -> incomingUnreliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE && + incomingCommand -> reliableSequenceNumber != channel -> incomingReliableSequenceNumber) + break; + } + + if (currentCommand == enet_list_begin (& channel -> incomingUnreliableCommands)) + return; + + enet_list_move (enet_list_end (& peer -> dispatchedCommands), enet_list_begin (& channel -> incomingUnreliableCommands), enet_list_previous (currentCommand)); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } +} + +void +enet_peer_dispatch_incoming_reliable_commands (ENetPeer * peer, ENetChannel * channel) +{ + ENetListIterator currentCommand; + + for (currentCommand = enet_list_begin (& channel -> incomingReliableCommands); + currentCommand != enet_list_end (& channel -> incomingReliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (incomingCommand -> fragmentsRemaining > 0 || + incomingCommand -> reliableSequenceNumber != (enet_uint16) (channel -> incomingReliableSequenceNumber + 1)) + break; + + channel -> incomingReliableSequenceNumber = incomingCommand -> reliableSequenceNumber; + + if (incomingCommand -> fragmentCount > 0) + channel -> incomingReliableSequenceNumber += incomingCommand -> fragmentCount - 1; + } + + if (currentCommand == enet_list_begin (& channel -> incomingReliableCommands)) + return; + + enet_list_move (enet_list_end (& peer -> dispatchedCommands), enet_list_begin (& channel -> incomingReliableCommands), enet_list_previous (currentCommand)); + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& peer -> host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } + + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); +} + +ENetIncomingCommand * +enet_peer_queue_incoming_command (ENetPeer * peer, const ENetProtocol * command, ENetPacket * packet, enet_uint32 fragmentCount) +{ + static ENetIncomingCommand dummyCommand; + + ENetChannel * channel = & peer -> channels [command -> header.channelID]; + enet_uint32 unreliableSequenceNumber = 0, reliableSequenceNumber; + enet_uint16 reliableWindow, currentWindow; + ENetIncomingCommand * incomingCommand; + ENetListIterator currentCommand; + + if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) + goto freePacket; + + if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) + { + reliableSequenceNumber = command -> header.reliableSequenceNumber; + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + goto freePacket; + } + + switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK) + { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (reliableSequenceNumber == channel -> incomingReliableSequenceNumber) + goto freePacket; + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingReliableCommands)); + currentCommand != enet_list_end (& channel -> incomingReliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber <= reliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber) + break; + + goto freePacket; + } + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + unreliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendUnreliable.unreliableSequenceNumber); + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingUnreliableCommands)); + currentCommand != enet_list_end (& channel -> incomingUnreliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + incomingCommand = (ENetIncomingCommand *) currentCommand; + + if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE) + continue; + + if (reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber < reliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber > reliableSequenceNumber) + continue; + + if (incomingCommand -> unreliableSequenceNumber <= unreliableSequenceNumber) + { + if (incomingCommand -> unreliableSequenceNumber < unreliableSequenceNumber) + break; + + goto freePacket; + } + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + currentCommand = enet_list_end (& channel -> incomingUnreliableCommands); + break; + + default: + goto freePacket; + } + + incomingCommand = (ENetIncomingCommand *) enet_malloc (sizeof (ENetIncomingCommand)); + if (incomingCommand == NULL) + goto notifyError; + + incomingCommand -> reliableSequenceNumber = command -> header.reliableSequenceNumber; + incomingCommand -> unreliableSequenceNumber = unreliableSequenceNumber & 0xFFFF; + incomingCommand -> command = * command; + incomingCommand -> fragmentCount = fragmentCount; + incomingCommand -> fragmentsRemaining = fragmentCount; + incomingCommand -> packet = packet; + incomingCommand -> fragments = NULL; + + if (fragmentCount > 0) + { + incomingCommand -> fragments = (enet_uint32 *) enet_malloc ((fragmentCount + 31) / 32 * sizeof (enet_uint32)); + if (incomingCommand -> fragments == NULL) + { + enet_free (incomingCommand); + + goto notifyError; + } + memset (incomingCommand -> fragments, 0, (fragmentCount + 31) / 32 * sizeof (enet_uint32)); + } + + if (packet != NULL) + ++ packet -> referenceCount; + + enet_list_insert (enet_list_next (currentCommand), incomingCommand); + + switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK) + { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + enet_peer_dispatch_incoming_reliable_commands (peer, channel); + break; + + default: + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); + break; + } + + return incomingCommand; + +freePacket: + if (fragmentCount > 0) + goto notifyError; + + if (packet != NULL && packet -> referenceCount == 0) + enet_packet_destroy (packet); + + return & dummyCommand; + +notifyError: + if (packet != NULL && packet -> referenceCount == 0) + enet_packet_destroy (packet); + + return NULL; +} + +/** @} */ diff --git a/src/enet/protocol.c b/src/enet/protocol.c new file mode 100644 index 000000000..8e26dfb24 --- /dev/null +++ b/src/enet/protocol.c @@ -0,0 +1,1671 @@ +/** + @file protocol.c + @brief ENet protocol functions +*/ +#include +#include +#define ENET_BUILDING_LIB 1 +#include "enet/utility.h" +#include "enet/time.h" +#include "enet/enet.h" + +static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] = +{ + 0, + sizeof (ENetProtocolAcknowledge), + sizeof (ENetProtocolConnect), + sizeof (ENetProtocolVerifyConnect), + sizeof (ENetProtocolDisconnect), + sizeof (ENetProtocolPing), + sizeof (ENetProtocolSendReliable), + sizeof (ENetProtocolSendUnreliable), + sizeof (ENetProtocolSendFragment), + sizeof (ENetProtocolSendUnsequenced), + sizeof (ENetProtocolBandwidthLimit), + sizeof (ENetProtocolThrottleConfigure), +}; + +size_t +enet_protocol_command_size (enet_uint8 commandNumber) +{ + return commandSizes [commandNumber & ENET_PROTOCOL_COMMAND_MASK]; +} + +static int +enet_protocol_dispatch_incoming_commands (ENetHost * host, ENetEvent * event) +{ + while (! enet_list_empty (& host -> dispatchQueue)) + { + ENetPeer * peer = (ENetPeer *) enet_list_remove (enet_list_begin (& host -> dispatchQueue)); + + peer -> needsDispatch = 0; + + switch (peer -> state) + { + case ENET_PEER_STATE_CONNECTION_PENDING: + case ENET_PEER_STATE_CONNECTION_SUCCEEDED: + peer -> state = ENET_PEER_STATE_CONNECTED; + + event -> type = ENET_EVENT_TYPE_CONNECT; + event -> peer = peer; + event -> data = peer -> eventData; + + return 1; + + case ENET_PEER_STATE_ZOMBIE: + host -> recalculateBandwidthLimits = 1; + + event -> type = ENET_EVENT_TYPE_DISCONNECT; + event -> peer = peer; + event -> data = peer -> eventData; + + enet_peer_reset (peer); + + return 1; + + case ENET_PEER_STATE_CONNECTED: + if (enet_list_empty (& peer -> dispatchedCommands)) + continue; + + event -> packet = enet_peer_receive (peer, & event -> channelID); + if (event -> packet == NULL) + continue; + + event -> type = ENET_EVENT_TYPE_RECEIVE; + event -> peer = peer; + + if (! enet_list_empty (& peer -> dispatchedCommands)) + { + peer -> needsDispatch = 1; + + enet_list_insert (enet_list_end (& host -> dispatchQueue), & peer -> dispatchList); + } + + return 1; + } + } + + return 0; +} + +static void +enet_protocol_dispatch_state (ENetHost * host, ENetPeer * peer, ENetPeerState state) +{ + peer -> state = state; + + if (! peer -> needsDispatch) + { + enet_list_insert (enet_list_end (& host -> dispatchQueue), & peer -> dispatchList); + + peer -> needsDispatch = 1; + } +} + +static void +enet_protocol_notify_connect (ENetHost * host, ENetPeer * peer, ENetEvent * event) +{ + host -> recalculateBandwidthLimits = 1; + + if (event != NULL) + { + peer -> state = ENET_PEER_STATE_CONNECTED; + + event -> type = ENET_EVENT_TYPE_CONNECT; + event -> peer = peer; + event -> data = peer -> eventData; + } + else + enet_protocol_dispatch_state (host, peer, peer -> state == ENET_PEER_STATE_CONNECTING ? ENET_PEER_STATE_CONNECTION_SUCCEEDED : ENET_PEER_STATE_CONNECTION_PENDING); +} + +static void +enet_protocol_notify_disconnect (ENetHost * host, ENetPeer * peer, ENetEvent * event) +{ + if (peer -> state >= ENET_PEER_STATE_CONNECTION_PENDING) + host -> recalculateBandwidthLimits = 1; + + if (peer -> state != ENET_PEER_STATE_CONNECTING && peer -> state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) + enet_peer_reset (peer); + else + if (event != NULL) + { + event -> type = ENET_EVENT_TYPE_DISCONNECT; + event -> peer = peer; + event -> data = 0; + + enet_peer_reset (peer); + } + else + { + peer -> eventData = 0; + + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + } +} + +static void +enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer) +{ + ENetOutgoingCommand * outgoingCommand; + + while (! enet_list_empty (& peer -> sentUnreliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands); + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (outgoingCommand -> packet); + } + + enet_free (outgoingCommand); + } +} + +static ENetProtocolCommand +enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) +{ + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + ENetProtocolCommand commandNumber; + + for (currentCommand = enet_list_begin (& peer -> sentReliableCommands); + currentCommand != enet_list_end (& peer -> sentReliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber && + outgoingCommand -> command.header.channelID == channelID) + break; + } + + if (currentCommand == enet_list_end (& peer -> sentReliableCommands)) + { + for (currentCommand = enet_list_begin (& peer -> outgoingReliableCommands); + currentCommand != enet_list_end (& peer -> outgoingReliableCommands); + currentCommand = enet_list_next (currentCommand)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (outgoingCommand -> sendAttempts < 1) return ENET_PROTOCOL_COMMAND_NONE; + + if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber && + outgoingCommand -> command.header.channelID == channelID) + break; + } + + if (currentCommand == enet_list_end (& peer -> outgoingReliableCommands)) + return ENET_PROTOCOL_COMMAND_NONE; + } + + if (channelID < peer -> channelCount) + { + ENetChannel * channel = & peer -> channels [channelID]; + enet_uint16 reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (channel -> reliableWindows [reliableWindow] > 0) + { + -- channel -> reliableWindows [reliableWindow]; + if (! channel -> reliableWindows [reliableWindow]) + channel -> usedReliableWindows &= ~ (1 << reliableWindow); + } + } + + commandNumber = (ENetProtocolCommand) (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK); + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; + + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (outgoingCommand -> packet); + } + + enet_free (outgoingCommand); + + if (enet_list_empty (& peer -> sentReliableCommands)) + return commandNumber; + + outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentReliableCommands); + + peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout; + + return commandNumber; +} + +static ENetPeer * +enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENetProtocol * command) +{ + enet_uint8 incomingSessionID, outgoingSessionID; + enet_uint32 mtu, windowSize; + ENetChannel * channel; + size_t channelCount; + ENetPeer * currentPeer; + ENetProtocol verifyCommand; + + channelCount = ENET_NET_TO_HOST_32 (command -> connect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || + channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + return NULL; + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state != ENET_PEER_STATE_DISCONNECTED && + currentPeer -> address.host == host -> receivedAddress.host && + currentPeer -> address.port == host -> receivedAddress.port && + currentPeer -> connectID == command -> connect.connectID) + return NULL; + } + + for (currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED) + break; + } + + if (currentPeer >= & host -> peers [host -> peerCount]) + return NULL; + + if (channelCount > host -> channelLimit) + channelCount = host -> channelLimit; + currentPeer -> channels = (ENetChannel *) enet_malloc (channelCount * sizeof (ENetChannel)); + if (currentPeer -> channels == NULL) + return NULL; + currentPeer -> channelCount = channelCount; + currentPeer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT; + currentPeer -> connectID = command -> connect.connectID; + currentPeer -> address = host -> receivedAddress; + currentPeer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID); + currentPeer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth); + currentPeer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth); + currentPeer -> packetThrottleInterval = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleInterval); + currentPeer -> packetThrottleAcceleration = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleAcceleration); + currentPeer -> packetThrottleDeceleration = ENET_NET_TO_HOST_32 (command -> connect.packetThrottleDeceleration); + currentPeer -> eventData = ENET_NET_TO_HOST_32 (command -> connect.data); + + incomingSessionID = command -> connect.incomingSessionID == 0xFF ? currentPeer -> outgoingSessionID : command -> connect.incomingSessionID; + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + if (incomingSessionID == currentPeer -> outgoingSessionID) + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + currentPeer -> outgoingSessionID = incomingSessionID; + + outgoingSessionID = command -> connect.outgoingSessionID == 0xFF ? currentPeer -> incomingSessionID : command -> connect.outgoingSessionID; + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + if (outgoingSessionID == currentPeer -> incomingSessionID) + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + currentPeer -> incomingSessionID = outgoingSessionID; + + for (channel = currentPeer -> channels; + channel < & currentPeer -> channels [channelCount]; + ++ channel) + { + channel -> outgoingReliableSequenceNumber = 0; + channel -> outgoingUnreliableSequenceNumber = 0; + channel -> incomingReliableSequenceNumber = 0; + + enet_list_clear (& channel -> incomingReliableCommands); + enet_list_clear (& channel -> incomingUnreliableCommands); + + channel -> usedReliableWindows = 0; + memset (channel -> reliableWindows, 0, sizeof (channel -> reliableWindows)); + } + + mtu = ENET_NET_TO_HOST_32 (command -> connect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) + mtu = ENET_PROTOCOL_MINIMUM_MTU; + else + if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + + currentPeer -> mtu = mtu; + + if (host -> outgoingBandwidth == 0 && + currentPeer -> incomingBandwidth == 0) + currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + if (host -> outgoingBandwidth == 0 || + currentPeer -> incomingBandwidth == 0) + currentPeer -> windowSize = (ENET_MAX (host -> outgoingBandwidth, currentPeer -> incomingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + currentPeer -> windowSize = (ENET_MIN (host -> outgoingBandwidth, currentPeer -> incomingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (currentPeer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + currentPeer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (currentPeer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + if (host -> incomingBandwidth == 0) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + windowSize = (host -> incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (windowSize > ENET_NET_TO_HOST_32 (command -> connect.windowSize)) + windowSize = ENET_NET_TO_HOST_32 (command -> connect.windowSize); + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + verifyCommand.header.channelID = 0xFF; + verifyCommand.verifyConnect.outgoingPeerID = ENET_HOST_TO_NET_16 (currentPeer -> incomingPeerID); + verifyCommand.verifyConnect.incomingSessionID = incomingSessionID; + verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID; + verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_16 (currentPeer -> mtu); + verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32 (windowSize); + verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32 (channelCount); + verifyCommand.verifyConnect.incomingBandwidth = ENET_HOST_TO_NET_32 (host -> incomingBandwidth); + verifyCommand.verifyConnect.outgoingBandwidth = ENET_HOST_TO_NET_32 (host -> outgoingBandwidth); + verifyCommand.verifyConnect.packetThrottleInterval = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleInterval); + verifyCommand.verifyConnect.packetThrottleAcceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleAcceleration); + verifyCommand.verifyConnect.packetThrottleDeceleration = ENET_HOST_TO_NET_32 (currentPeer -> packetThrottleDeceleration); + verifyCommand.verifyConnect.connectID = currentPeer -> connectID; + + enet_peer_queue_outgoing_command (currentPeer, & verifyCommand, NULL, 0, 0); + + return currentPeer; +} + +static int +enet_protocol_handle_send_reliable (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + ENetPacket * packet; + size_t dataLength; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16 (command -> sendReliable.dataLength); + * currentData += dataLength; + if (* currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + packet = enet_packet_create ((const enet_uint8 *) command + sizeof (ENetProtocolSendReliable), + dataLength, + ENET_PACKET_FLAG_RELIABLE); + if (packet == NULL || + enet_peer_queue_incoming_command (peer, command, packet, 0) == NULL) + return -1; + + return 0; +} + +static int +enet_protocol_handle_send_unsequenced (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + ENetPacket * packet; + enet_uint32 unsequencedGroup, index; + size_t dataLength; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16 (command -> sendUnsequenced.dataLength); + * currentData += dataLength; + if (* currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + unsequencedGroup = ENET_NET_TO_HOST_16 (command -> sendUnsequenced.unsequencedGroup); + index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE; + + if (unsequencedGroup < peer -> incomingUnsequencedGroup) + unsequencedGroup += 0x10000; + + if (unsequencedGroup >= (enet_uint32) peer -> incomingUnsequencedGroup + ENET_PEER_FREE_UNSEQUENCED_WINDOWS * ENET_PEER_UNSEQUENCED_WINDOW_SIZE) + return 0; + + unsequencedGroup &= 0xFFFF; + + if (unsequencedGroup - index != peer -> incomingUnsequencedGroup) + { + peer -> incomingUnsequencedGroup = unsequencedGroup - index; + + memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow)); + } + else + if (peer -> unsequencedWindow [index / 32] & (1 << (index % 32))) + return 0; + + packet = enet_packet_create ((const enet_uint8 *) command + sizeof (ENetProtocolSendUnsequenced), + dataLength, + ENET_PACKET_FLAG_UNSEQUENCED); + if (packet == NULL || + enet_peer_queue_incoming_command (peer, command, packet, 0) == NULL) + return -1; + + peer -> unsequencedWindow [index / 32] |= 1 << (index % 32); + + return 0; +} + +static int +enet_protocol_handle_send_unreliable (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + ENetPacket * packet; + size_t dataLength; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16 (command -> sendUnreliable.dataLength); + * currentData += dataLength; + if (* currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + packet = enet_packet_create ((const enet_uint8 *) command + sizeof (ENetProtocolSendUnreliable), + dataLength, + 0); + if (packet == NULL || + enet_peer_queue_incoming_command (peer, command, packet, 0) == NULL) + return -1; + + return 0; +} + +static int +enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + enet_uint32 fragmentNumber, + fragmentCount, + fragmentOffset, + fragmentLength, + startSequenceNumber, + totalLength; + ENetChannel * channel; + enet_uint16 startWindow, currentWindow; + ENetListIterator currentCommand; + ENetIncomingCommand * startCommand = NULL; + + if (command -> header.channelID >= peer -> channelCount || + (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength); + * currentData += fragmentLength; + if (* currentData > & host -> receivedData [host -> receivedDataLength]) + return -1; + + channel = & peer -> channels [command -> header.channelID]; + startSequenceNumber = ENET_NET_TO_HOST_16 (command -> sendFragment.startSequenceNumber); + startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel -> incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (startSequenceNumber < channel -> incomingReliableSequenceNumber) + startWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (startWindow < currentWindow || startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + return 0; + + fragmentNumber = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32 (command -> sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32 (command -> sendFragment.totalLength); + + if (fragmentOffset >= totalLength || + fragmentOffset + fragmentLength > totalLength || + fragmentNumber >= fragmentCount) + return -1; + + for (currentCommand = enet_list_previous (enet_list_end (& channel -> incomingReliableCommands)); + currentCommand != enet_list_end (& channel -> incomingReliableCommands); + currentCommand = enet_list_previous (currentCommand)) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (startSequenceNumber >= channel -> incomingReliableSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < channel -> incomingReliableSequenceNumber) + continue; + } + else + if (incomingCommand -> reliableSequenceNumber >= channel -> incomingReliableSequenceNumber) + break; + + if (incomingCommand -> reliableSequenceNumber <= startSequenceNumber) + { + if (incomingCommand -> reliableSequenceNumber < startSequenceNumber) + break; + + if ((incomingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_FRAGMENT || + totalLength != incomingCommand -> packet -> dataLength || + fragmentCount != incomingCommand -> fragmentCount) + return -1; + + startCommand = incomingCommand; + break; + } + } + + if (startCommand == NULL) + { + ENetProtocol hostCommand = * command; + ENetPacket * packet = enet_packet_create (NULL, totalLength, ENET_PACKET_FLAG_RELIABLE); + if (packet == NULL) + return -1; + + hostCommand.header.reliableSequenceNumber = startSequenceNumber; + hostCommand.sendFragment.startSequenceNumber = startSequenceNumber; + hostCommand.sendFragment.dataLength = fragmentLength; + hostCommand.sendFragment.fragmentNumber = fragmentNumber; + hostCommand.sendFragment.fragmentCount = fragmentCount; + hostCommand.sendFragment.fragmentOffset = fragmentOffset; + hostCommand.sendFragment.totalLength = totalLength; + + startCommand = enet_peer_queue_incoming_command (peer, & hostCommand, packet, fragmentCount); + if (startCommand == NULL) + return -1; + } + + if ((startCommand -> fragments [fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) + { + -- startCommand -> fragmentsRemaining; + + startCommand -> fragments [fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand -> packet -> dataLength) + fragmentLength = startCommand -> packet -> dataLength - fragmentOffset; + + memcpy (startCommand -> packet -> data + fragmentOffset, + (enet_uint8 *) command + sizeof (ENetProtocolSendFragment), + fragmentLength); + + if (startCommand -> fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_reliable_commands (peer, channel); + } + + return 0; +} + +static int +enet_protocol_handle_ping (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + return 0; +} + +static int +enet_protocol_handle_bandwidth_limit (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> bandwidthLimit.incomingBandwidth); + peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> bandwidthLimit.outgoingBandwidth); + + if (peer -> incomingBandwidth == 0 && host -> outgoingBandwidth == 0) + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + peer -> windowSize = (ENET_MIN (peer -> incomingBandwidth, host -> outgoingBandwidth) / + ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (peer -> windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + peer -> windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + if (peer -> windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + return 0; +} + +static int +enet_protocol_handle_throttle_configure (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + peer -> packetThrottleInterval = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleInterval); + peer -> packetThrottleAcceleration = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleAcceleration); + peer -> packetThrottleDeceleration = ENET_NET_TO_HOST_32 (command -> throttleConfigure.packetThrottleDeceleration); + + return 0; +} + +static int +enet_protocol_handle_disconnect (ENetHost * host, ENetPeer * peer, const ENetProtocol * command) +{ + if (peer -> state == ENET_PEER_STATE_ZOMBIE || peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT) + return 0; + + enet_peer_reset_queues (peer); + + if (peer -> state == ENET_PEER_STATE_CONNECTION_SUCCEEDED) + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + else + if (peer -> state != ENET_PEER_STATE_CONNECTED && peer -> state != ENET_PEER_STATE_DISCONNECT_LATER) + { + if (peer -> state == ENET_PEER_STATE_CONNECTION_PENDING) host -> recalculateBandwidthLimits = 1; + + enet_peer_reset (peer); + } + else + if (command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT; + else + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + + if (peer -> state != ENET_PEER_STATE_DISCONNECTED) + peer -> eventData = ENET_NET_TO_HOST_32 (command -> disconnect.data); + + return 0; +} + +static int +enet_protocol_handle_acknowledge (ENetHost * host, ENetEvent * event, ENetPeer * peer, const ENetProtocol * command) +{ + enet_uint32 roundTripTime, + receivedSentTime, + receivedReliableSequenceNumber; + ENetProtocolCommand commandNumber; + + receivedSentTime = ENET_NET_TO_HOST_16 (command -> acknowledge.receivedSentTime); + receivedSentTime |= host -> serviceTime & 0xFFFF0000; + if ((receivedSentTime & 0x8000) > (host -> serviceTime & 0x8000)) + receivedSentTime -= 0x10000; + + if (ENET_TIME_LESS (host -> serviceTime, receivedSentTime)) + return 0; + + peer -> lastReceiveTime = host -> serviceTime; + peer -> earliestTimeout = 0; + + roundTripTime = ENET_TIME_DIFFERENCE (host -> serviceTime, receivedSentTime); + + enet_peer_throttle (peer, roundTripTime); + + peer -> roundTripTimeVariance -= peer -> roundTripTimeVariance / 4; + + if (roundTripTime >= peer -> roundTripTime) + { + peer -> roundTripTime += (roundTripTime - peer -> roundTripTime) / 8; + peer -> roundTripTimeVariance += (roundTripTime - peer -> roundTripTime) / 4; + } + else + { + peer -> roundTripTime -= (peer -> roundTripTime - roundTripTime) / 8; + peer -> roundTripTimeVariance += (peer -> roundTripTime - roundTripTime) / 4; + } + + if (peer -> roundTripTime < peer -> lowestRoundTripTime) + peer -> lowestRoundTripTime = peer -> roundTripTime; + + if (peer -> roundTripTimeVariance > peer -> highestRoundTripTimeVariance) + peer -> highestRoundTripTimeVariance = peer -> roundTripTimeVariance; + + if (peer -> packetThrottleEpoch == 0 || + ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> packetThrottleEpoch) >= peer -> packetThrottleInterval) + { + peer -> lastRoundTripTime = peer -> lowestRoundTripTime; + peer -> lastRoundTripTimeVariance = peer -> highestRoundTripTimeVariance; + peer -> lowestRoundTripTime = peer -> roundTripTime; + peer -> highestRoundTripTimeVariance = peer -> roundTripTimeVariance; + peer -> packetThrottleEpoch = host -> serviceTime; + } + + receivedReliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> acknowledge.receivedReliableSequenceNumber); + + commandNumber = enet_protocol_remove_sent_reliable_command (peer, receivedReliableSequenceNumber, command -> header.channelID); + + switch (peer -> state) + { + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT) + return -1; + + enet_protocol_notify_connect (host, peer, event); + break; + + case ENET_PEER_STATE_DISCONNECTING: + if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT) + return -1; + + enet_protocol_notify_disconnect (host, peer, event); + break; + + case ENET_PEER_STATE_DISCONNECT_LATER: + if (enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + enet_peer_disconnect (peer, peer -> eventData); + break; + } + + return 0; +} + +static int +enet_protocol_handle_verify_connect (ENetHost * host, ENetEvent * event, ENetPeer * peer, const ENetProtocol * command) +{ + enet_uint32 mtu, windowSize; + size_t channelCount; + + if (peer -> state != ENET_PEER_STATE_CONNECTING) + return 0; + + channelCount = ENET_NET_TO_HOST_32 (command -> verifyConnect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT || + ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleInterval) != peer -> packetThrottleInterval || + ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleAcceleration) != peer -> packetThrottleAcceleration || + ENET_NET_TO_HOST_32 (command -> verifyConnect.packetThrottleDeceleration) != peer -> packetThrottleDeceleration || + command -> verifyConnect.connectID != peer -> connectID) + { + peer -> eventData = 0; + + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + + return -1; + } + + enet_protocol_remove_sent_reliable_command (peer, 1, 0xFF); + + if (channelCount < peer -> channelCount) + peer -> channelCount = channelCount; + + peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> verifyConnect.outgoingPeerID); + peer -> incomingSessionID = command -> verifyConnect.incomingSessionID; + peer -> outgoingSessionID = command -> verifyConnect.outgoingSessionID; + + mtu = ENET_NET_TO_HOST_32 (command -> verifyConnect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) + mtu = ENET_PROTOCOL_MINIMUM_MTU; + else + if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + + if (mtu < peer -> mtu) + peer -> mtu = mtu; + + windowSize = ENET_NET_TO_HOST_32 (command -> verifyConnect.windowSize); + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + if (windowSize < peer -> windowSize) + peer -> windowSize = windowSize; + + peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> verifyConnect.incomingBandwidth); + peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> verifyConnect.outgoingBandwidth); + + enet_protocol_notify_connect (host, peer, event); + return 0; +} + +static int +enet_protocol_handle_incoming_commands (ENetHost * host, ENetEvent * event) +{ + ENetProtocolHeader * header; + ENetProtocol * command; + ENetPeer * peer; + enet_uint8 * currentData; + size_t headerSize; + enet_uint16 peerID, flags; + enet_uint8 sessionID; + + if (host -> receivedDataLength < (size_t) & ((ENetProtocolHeader *) 0) -> sentTime) + return 0; + + header = (ENetProtocolHeader *) host -> receivedData; + + peerID = ENET_NET_TO_HOST_16 (header -> peerID); + sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >> ENET_PROTOCOL_HEADER_SESSION_SHIFT; + flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK; + peerID &= ~ (ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK); + + headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME ? sizeof (ENetProtocolHeader) : (size_t) & ((ENetProtocolHeader *) 0) -> sentTime); + if (host -> checksum != NULL) + headerSize += sizeof (enet_uint32); + + if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID) + peer = NULL; + else + if (peerID >= host -> peerCount) + return 0; + else + { + peer = & host -> peers [peerID]; + + if (peer -> state == ENET_PEER_STATE_DISCONNECTED || + peer -> state == ENET_PEER_STATE_ZOMBIE || + (host -> receivedAddress.host != peer -> address.host && + peer -> address.host != ENET_HOST_BROADCAST) || + (peer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && + sessionID != peer -> incomingSessionID)) + return 0; + } + + if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED) + { + size_t originalSize; + if (host -> compressor.context == NULL || host -> compressor.decompress == NULL) + return 0; + + originalSize = host -> compressor.decompress (host -> compressor.context, + host -> receivedData + headerSize, + host -> receivedDataLength - headerSize, + host -> packetData [1] + headerSize, + sizeof (host -> packetData [1]) - headerSize); + if (originalSize <= 0 || originalSize > sizeof (host -> packetData [1]) - headerSize) + return 0; + + memcpy (host -> packetData [1], header, headerSize); + host -> receivedData = host -> packetData [1]; + host -> receivedDataLength = headerSize + originalSize; + } + + if (host -> checksum != NULL) + { + enet_uint32 * checksum = (enet_uint32 *) & host -> receivedData [headerSize - sizeof (enet_uint32)], + desiredChecksum = * checksum; + ENetBuffer buffer; + + * checksum = peer != NULL ? peer -> connectID : 0; + + buffer.data = host -> receivedData; + buffer.dataLength = host -> receivedDataLength; + + if (host -> checksum (& buffer, 1) != desiredChecksum) + return 0; + } + + if (peer != NULL) + { + peer -> address.host = host -> receivedAddress.host; + peer -> address.port = host -> receivedAddress.port; + peer -> incomingDataTotal += host -> receivedDataLength; + } + + currentData = host -> receivedData + headerSize; + + while (currentData < & host -> receivedData [host -> receivedDataLength]) + { + enet_uint8 commandNumber; + size_t commandSize; + + command = (ENetProtocol *) currentData; + + if (currentData + sizeof (ENetProtocolCommandHeader) > & host -> receivedData [host -> receivedDataLength]) + break; + + commandNumber = command -> header.command & ENET_PROTOCOL_COMMAND_MASK; + if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT) + break; + + commandSize = commandSizes [commandNumber]; + if (commandSize == 0 || currentData + commandSize > & host -> receivedData [host -> receivedDataLength]) + break; + + currentData += commandSize; + + if (peer == NULL && commandNumber != ENET_PROTOCOL_COMMAND_CONNECT) + break; + + command -> header.reliableSequenceNumber = ENET_NET_TO_HOST_16 (command -> header.reliableSequenceNumber); + + switch (command -> header.command & ENET_PROTOCOL_COMMAND_MASK) + { + case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE: + if (enet_protocol_handle_acknowledge (host, event, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_CONNECT: + peer = enet_protocol_handle_connect (host, header, command); + if (peer == NULL) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT: + if (enet_protocol_handle_verify_connect (host, event, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_DISCONNECT: + if (enet_protocol_handle_disconnect (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_PING: + if (enet_protocol_handle_ping (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (enet_protocol_handle_send_reliable (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + if (enet_protocol_handle_send_unreliable (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + if (enet_protocol_handle_send_unsequenced (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + if (enet_protocol_handle_send_fragment (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT: + if (enet_protocol_handle_bandwidth_limit (host, peer, command)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE: + if (enet_protocol_handle_throttle_configure (host, peer, command)) + goto commandError; + break; + + default: + goto commandError; + } + + if (peer != NULL && + (command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) + { + enet_uint16 sentTime; + + if (! (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME)) + break; + + sentTime = ENET_NET_TO_HOST_16 (header -> sentTime); + + switch (peer -> state) + { + case ENET_PEER_STATE_DISCONNECTING: + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + break; + + case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT: + if ((command -> header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) + enet_peer_queue_acknowledgement (peer, command, sentTime); + break; + + default: + enet_peer_queue_acknowledgement (peer, command, sentTime); + break; + } + } + } + +commandError: + if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE) + return 1; + + return 0; +} + +static int +enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event) +{ + for (;;) + { + int receivedLength; + ENetBuffer buffer; + + buffer.data = host -> packetData [0]; + buffer.dataLength = sizeof (host -> packetData [0]); + + receivedLength = enet_socket_receive (host -> socket, + & host -> receivedAddress, + & buffer, + 1); + + if (receivedLength < 0) + return -1; + + if (receivedLength == 0) + return 0; + + host -> receivedData = host -> packetData [0]; + host -> receivedDataLength = receivedLength; + + host -> totalReceivedData += receivedLength; + host -> totalReceivedPackets ++; + + switch (enet_protocol_handle_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: + return -1; + + default: + break; + } + } + + return -1; +} + +static void +enet_protocol_send_acknowledgements (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetAcknowledgement * acknowledgement; + ENetListIterator currentAcknowledgement; + + currentAcknowledgement = enet_list_begin (& peer -> acknowledgements); + + while (currentAcknowledgement != enet_list_end (& peer -> acknowledgements)) + { + if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] || + buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || + peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge)) + { + host -> continueSending = 1; + + break; + } + + acknowledgement = (ENetAcknowledgement *) currentAcknowledgement; + + currentAcknowledgement = enet_list_next (currentAcknowledgement); + + buffer -> data = command; + buffer -> dataLength = sizeof (ENetProtocolAcknowledge); + + host -> packetSize += buffer -> dataLength; + + command -> header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE; + command -> header.channelID = acknowledgement -> command.header.channelID; + command -> acknowledge.receivedReliableSequenceNumber = ENET_HOST_TO_NET_16 (acknowledgement -> command.header.reliableSequenceNumber); + command -> acknowledge.receivedSentTime = ENET_HOST_TO_NET_16 (acknowledgement -> sentTime); + + if ((acknowledgement -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) + enet_protocol_dispatch_state (host, peer, ENET_PEER_STATE_ZOMBIE); + + enet_list_remove (& acknowledgement -> acknowledgementList); + enet_free (acknowledgement); + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; +} + +static void +enet_protocol_send_unreliable_outgoing_commands (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + + currentCommand = enet_list_begin (& peer -> outgoingUnreliableCommands); + + while (currentCommand != enet_list_end (& peer -> outgoingUnreliableCommands)) + { + size_t commandSize; + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + commandSize = commandSizes [outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + + if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] || + buffer + 1 >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || + peer -> mtu - host -> packetSize < commandSize || + (outgoingCommand -> packet != NULL && + peer -> mtu - host -> packetSize < commandSize + outgoingCommand -> packet -> dataLength)) + { + host -> continueSending = 1; + + break; + } + + currentCommand = enet_list_next (currentCommand); + + if (outgoingCommand -> packet != NULL) + { + peer -> packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER; + peer -> packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer -> packetThrottleCounter > peer -> packetThrottle) + { + -- outgoingCommand -> packet -> referenceCount; + + if (outgoingCommand -> packet -> referenceCount == 0) + enet_packet_destroy (outgoingCommand -> packet); + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + enet_free (outgoingCommand); + + continue; + } + } + + buffer -> data = command; + buffer -> dataLength = commandSize; + + host -> packetSize += buffer -> dataLength; + + * command = outgoingCommand -> command; + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + ++ buffer; + + buffer -> data = outgoingCommand -> packet -> data; + buffer -> dataLength = outgoingCommand -> packet -> dataLength; + + host -> packetSize += buffer -> dataLength; + + enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand); + } + else + enet_free (outgoingCommand); + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; + + if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && + enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + enet_peer_disconnect (peer, peer -> eventData); +} + +static int +enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event) +{ + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand, insertPosition; + + currentCommand = enet_list_begin (& peer -> sentReliableCommands); + insertPosition = enet_list_begin (& peer -> outgoingReliableCommands); + + while (currentCommand != enet_list_end (& peer -> sentReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + currentCommand = enet_list_next (currentCommand); + + if (ENET_TIME_DIFFERENCE (host -> serviceTime, outgoingCommand -> sentTime) < outgoingCommand -> roundTripTimeout) + continue; + + if (peer -> earliestTimeout == 0 || + ENET_TIME_LESS (outgoingCommand -> sentTime, peer -> earliestTimeout)) + peer -> earliestTimeout = outgoingCommand -> sentTime; + + if (peer -> earliestTimeout != 0 && + (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= ENET_PEER_TIMEOUT_MAXIMUM || + (outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit && + ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= ENET_PEER_TIMEOUT_MINIMUM))) + { + enet_protocol_notify_disconnect (host, peer, event); + + return 1; + } + + if (outgoingCommand -> packet != NULL) + peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; + + ++ peer -> packetsLost; + + outgoingCommand -> roundTripTimeout *= 2; + + enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList)); + + if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) && + ! enet_list_empty (& peer -> sentReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout; + } + } + + return 0; +} + +static void +enet_protocol_send_reliable_outgoing_commands (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + ENetChannel *channel; + enet_uint16 reliableWindow; + size_t commandSize; + + currentCommand = enet_list_begin (& peer -> outgoingReliableCommands); + + while (currentCommand != enet_list_end (& peer -> outgoingReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + channel = outgoingCommand -> command.header.channelID < peer -> channelCount ? & peer -> channels [outgoingCommand -> command.header.channelID] : NULL; + reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (channel != NULL && + outgoingCommand -> sendAttempts < 1 && + ! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) && + (channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE || + channel -> usedReliableWindows & ((((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) << reliableWindow) | + (((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) >> (ENET_PEER_RELIABLE_WINDOW_SIZE - reliableWindow))))) + break; + + commandSize = commandSizes [outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + if (command >= & host -> commands [sizeof (host -> commands) / sizeof (ENetProtocol)] || + buffer + 1 >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || + peer -> mtu - host -> packetSize < commandSize) + { + host -> continueSending = 1; + + break; + } + + if (outgoingCommand -> packet != NULL) + { + if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > peer -> windowSize) + break; + + if ((enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength)) + { + host -> continueSending = 1; + + break; + } + } + + currentCommand = enet_list_next (currentCommand); + + if (channel != NULL && outgoingCommand -> sendAttempts < 1) + { + channel -> usedReliableWindows |= 1 << reliableWindow; + ++ channel -> reliableWindows [reliableWindow]; + } + + ++ outgoingCommand -> sendAttempts; + + if (outgoingCommand -> roundTripTimeout == 0) + { + outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance; + outgoingCommand -> roundTripTimeoutLimit = ENET_PEER_TIMEOUT_LIMIT * outgoingCommand -> roundTripTimeout; + } + + if (enet_list_empty (& peer -> sentReliableCommands)) + peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout; + + enet_list_insert (enet_list_end (& peer -> sentReliableCommands), + enet_list_remove (& outgoingCommand -> outgoingCommandList)); + + outgoingCommand -> sentTime = host -> serviceTime; + + buffer -> data = command; + buffer -> dataLength = commandSize; + + host -> packetSize += buffer -> dataLength; + host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME; + + * command = outgoingCommand -> command; + + if (outgoingCommand -> packet != NULL) + { + ++ buffer; + + buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset; + buffer -> dataLength = outgoingCommand -> fragmentLength; + + host -> packetSize += outgoingCommand -> fragmentLength; + + peer -> reliableDataInTransit += outgoingCommand -> fragmentLength; + } + + ++ peer -> packetsSent; + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; +} + +static int +enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int checkForTimeouts) +{ + enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)]; + ENetProtocolHeader * header = (ENetProtocolHeader *) headerData; + ENetPeer * currentPeer; + int sentLength; + size_t shouldCompress = 0; + + host -> continueSending = 1; + + while (host -> continueSending) + for (host -> continueSending = 0, + currentPeer = host -> peers; + currentPeer < & host -> peers [host -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED || + currentPeer -> state == ENET_PEER_STATE_ZOMBIE) + continue; + + host -> headerFlags = 0; + host -> commandCount = 0; + host -> bufferCount = 1; + host -> packetSize = sizeof (ENetProtocolHeader); + + if (! enet_list_empty (& currentPeer -> acknowledgements)) + enet_protocol_send_acknowledgements (host, currentPeer); + + if (checkForTimeouts != 0 && + ! enet_list_empty (& currentPeer -> sentReliableCommands) && + ENET_TIME_GREATER_EQUAL (host -> serviceTime, currentPeer -> nextTimeout) && + enet_protocol_check_timeouts (host, currentPeer, event) == 1) + return 1; + + if (! enet_list_empty (& currentPeer -> outgoingReliableCommands)) + enet_protocol_send_reliable_outgoing_commands (host, currentPeer); + else + if (enet_list_empty (& currentPeer -> sentReliableCommands) && + ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= ENET_PEER_PING_INTERVAL && + currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing)) + { + enet_peer_ping (currentPeer); + enet_protocol_send_reliable_outgoing_commands (host, currentPeer); + } + + if (! enet_list_empty (& currentPeer -> outgoingUnreliableCommands)) + enet_protocol_send_unreliable_outgoing_commands (host, currentPeer); + + if (host -> commandCount == 0) + continue; + + if (currentPeer -> packetLossEpoch == 0) + currentPeer -> packetLossEpoch = host -> serviceTime; + else + if (ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> packetLossEpoch) >= ENET_PEER_PACKET_LOSS_INTERVAL && + currentPeer -> packetsSent > 0) + { + enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent; + +#ifdef ENET_DEBUG +#ifdef WIN32 + printf ( +#else + fprintf (stderr, +#endif + "peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u/%u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingReliableCommands), enet_list_size (& currentPeer -> outgoingUnreliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0); +#endif + + currentPeer -> packetLossVariance -= currentPeer -> packetLossVariance / 4; + + if (packetLoss >= currentPeer -> packetLoss) + { + currentPeer -> packetLoss += (packetLoss - currentPeer -> packetLoss) / 8; + currentPeer -> packetLossVariance += (packetLoss - currentPeer -> packetLoss) / 4; + } + else + { + currentPeer -> packetLoss -= (currentPeer -> packetLoss - packetLoss) / 8; + currentPeer -> packetLossVariance += (currentPeer -> packetLoss - packetLoss) / 4; + } + + currentPeer -> packetLossEpoch = host -> serviceTime; + currentPeer -> packetsSent = 0; + currentPeer -> packetsLost = 0; + } + + host -> buffers -> data = headerData; + if (host -> headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME) + { + header -> sentTime = ENET_HOST_TO_NET_16 (host -> serviceTime & 0xFFFF); + + host -> buffers -> dataLength = sizeof (ENetProtocolHeader); + } + else + host -> buffers -> dataLength = (size_t) & ((ENetProtocolHeader *) 0) -> sentTime; + + shouldCompress = 0; + if (host -> compressor.context != NULL && host -> compressor.compress != NULL) + { + size_t originalSize = host -> packetSize - sizeof(ENetProtocolHeader), + compressedSize = host -> compressor.compress (host -> compressor.context, + & host -> buffers [1], host -> bufferCount - 1, + originalSize, + host -> packetData [1], + originalSize); + if (compressedSize > 0 && compressedSize < originalSize) + { + host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED; + shouldCompress = compressedSize; +#ifdef ENET_DEBUG_COMPRESS +#ifdef WIN32 + printf ( +#else + fprintf (stderr, +#endif + "peer %u: compressed %u -> %u (%u%%)\n", currentPeer -> incomingPeerID, originalSize, compressedSize, (compressedSize * 100) / originalSize); +#endif + } + } + + if (currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID) + host -> headerFlags |= currentPeer -> outgoingSessionID << ENET_PROTOCOL_HEADER_SESSION_SHIFT; + header -> peerID = ENET_HOST_TO_NET_16 (currentPeer -> outgoingPeerID | host -> headerFlags); + if (host -> checksum != NULL) + { + enet_uint32 * checksum = (enet_uint32 *) & headerData [host -> buffers -> dataLength]; + * checksum = currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer -> connectID : 0; + host -> buffers -> dataLength += sizeof (enet_uint32); + * checksum = host -> checksum (host -> buffers, host -> bufferCount); + } + + if (shouldCompress > 0) + { + host -> buffers [1].data = host -> packetData [1]; + host -> buffers [1].dataLength = shouldCompress; + host -> bufferCount = 2; + } + + currentPeer -> lastSendTime = host -> serviceTime; + + sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount); + + enet_protocol_remove_sent_unreliable_commands (currentPeer); + + if (sentLength < 0) + return -1; + + host -> totalSentData += sentLength; + host -> totalSentPackets ++; + } + + return 0; +} + +/** Sends any queued packets on the host specified to its designated peers. + + @param host host to flush + @remarks this function need only be used in circumstances where one wishes to send queued packets earlier than in a call to enet_host_service(). + @ingroup host +*/ +void +enet_host_flush (ENetHost * host) +{ + host -> serviceTime = enet_time_get (); + + enet_protocol_send_outgoing_commands (host, NULL, 0); +} + +/** Checks for any queued events on the host and dispatches one if available. + + @param host host to check for events + @param event an event structure where event details will be placed if available + @retval > 0 if an event was dispatched + @retval 0 if no events are available + @retval < 0 on failure + @ingroup host +*/ +int +enet_host_check_events (ENetHost * host, ENetEvent * event) +{ + if (event == NULL) return -1; + + event -> type = ENET_EVENT_TYPE_NONE; + event -> peer = NULL; + event -> packet = NULL; + + return enet_protocol_dispatch_incoming_commands (host, event); +} + +/** Waits for events on the host specified and shuttles packets between + the host and its peers. + + @param host host to service + @param event an event structure where event details will be placed if one occurs + if event == NULL then no events will be delivered + @param timeout number of milliseconds that ENet should wait for events + @retval > 0 if an event occurred within the specified time limit + @retval 0 if no event occurred + @retval < 0 on failure + @remarks enet_host_service should be called fairly regularly for adequate performance + @ingroup host +*/ +int +enet_host_service (ENetHost * host, ENetEvent * event, enet_uint32 timeout) +{ + enet_uint32 waitCondition; + + if (event != NULL) + { + event -> type = ENET_EVENT_TYPE_NONE; + event -> peer = NULL; + event -> packet = NULL; + + switch (enet_protocol_dispatch_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: + perror ("Error dispatching incoming packets"); + + return -1; + + default: + break; + } + } + + host -> serviceTime = enet_time_get (); + + timeout += host -> serviceTime; + + do + { + if (ENET_TIME_DIFFERENCE (host -> serviceTime, host -> bandwidthThrottleEpoch) >= ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + enet_host_bandwidth_throttle (host); + + switch (enet_protocol_send_outgoing_commands (host, event, 1)) + { + case 1: + return 1; + + case -1: + perror ("Error sending outgoing packets"); + + return -1; + + default: + break; + } + + switch (enet_protocol_receive_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: + perror ("Error receiving incoming packets"); + + return -1; + + default: + break; + } + + switch (enet_protocol_send_outgoing_commands (host, event, 1)) + { + case 1: + return 1; + + case -1: + perror ("Error sending outgoing packets"); + + return -1; + + default: + break; + } + + if (event != NULL) + { + switch (enet_protocol_dispatch_incoming_commands (host, event)) + { + case 1: + return 1; + + case -1: + perror ("Error dispatching incoming packets"); + + return -1; + + default: + break; + } + } + + host -> serviceTime = enet_time_get (); + + if (ENET_TIME_GREATER_EQUAL (host -> serviceTime, timeout)) + return 0; + + waitCondition = ENET_SOCKET_WAIT_RECEIVE; + + if (enet_socket_wait (host -> socket, & waitCondition, ENET_TIME_DIFFERENCE (timeout, host -> serviceTime)) != 0) + return -1; + + host -> serviceTime = enet_time_get (); + } while (waitCondition == ENET_SOCKET_WAIT_RECEIVE); + + return 0; +} + diff --git a/src/enet/protocol.h b/src/enet/protocol.h new file mode 100644 index 000000000..19f7e45de --- /dev/null +++ b/src/enet/protocol.h @@ -0,0 +1,196 @@ +/** + @file protocol.h + @brief ENet protocol +*/ +#ifndef __ENET_PROTOCOL_H__ +#define __ENET_PROTOCOL_H__ + +#include "enet/types.h" + +enum +{ + ENET_PROTOCOL_MINIMUM_MTU = 576, + ENET_PROTOCOL_MAXIMUM_MTU = 4096, + ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32, + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096, + ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 32768, + ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1, + ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255, + ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF +}; + +typedef enum _ENetProtocolCommand +{ + ENET_PROTOCOL_COMMAND_NONE = 0, + ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1, + ENET_PROTOCOL_COMMAND_CONNECT = 2, + ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3, + ENET_PROTOCOL_COMMAND_DISCONNECT = 4, + ENET_PROTOCOL_COMMAND_PING = 5, + ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7, + ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8, + ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9, + ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10, + ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11, + ENET_PROTOCOL_COMMAND_COUNT = 12, + + ENET_PROTOCOL_COMMAND_MASK = 0x0F +} ENetProtocolCommand; + +typedef enum _ENetProtocolFlag +{ + ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7), + ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6), + + ENET_PROTOCOL_HEADER_FLAG_COMPRESSED = (1 << 14), + ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15), + ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | ENET_PROTOCOL_HEADER_FLAG_SENT_TIME, + + ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12), + ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12 +} ENetProtocolFlag; + +#ifdef _MSC_VER_ +#pragma pack(push, 1) +#define ENET_PACKED +#elif defined(__GNUC__) +#define ENET_PACKED __attribute__ ((packed)) +#else +#define ENET_PACKED +#endif + +typedef struct _ENetProtocolHeader +{ + enet_uint16 peerID; + enet_uint16 sentTime; +} ENET_PACKED ENetProtocolHeader; + +typedef struct _ENetProtocolCommandHeader +{ + enet_uint8 command; + enet_uint8 channelID; + enet_uint16 reliableSequenceNumber; +} ENET_PACKED ENetProtocolCommandHeader; + +typedef struct _ENetProtocolAcknowledge +{ + ENetProtocolCommandHeader header; + enet_uint16 receivedReliableSequenceNumber; + enet_uint16 receivedSentTime; +} ENET_PACKED ENetProtocolAcknowledge; + +typedef struct _ENetProtocolConnect +{ + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; + enet_uint32 data; +} ENET_PACKED ENetProtocolConnect; + +typedef struct _ENetProtocolVerifyConnect +{ + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; +} ENET_PACKED ENetProtocolVerifyConnect; + +typedef struct _ENetProtocolBandwidthLimit +{ + ENetProtocolCommandHeader header; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; +} ENET_PACKED ENetProtocolBandwidthLimit; + +typedef struct _ENetProtocolThrottleConfigure +{ + ENetProtocolCommandHeader header; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; +} ENET_PACKED ENetProtocolThrottleConfigure; + +typedef struct _ENetProtocolDisconnect +{ + ENetProtocolCommandHeader header; + enet_uint32 data; +} ENET_PACKED ENetProtocolDisconnect; + +typedef struct _ENetProtocolPing +{ + ENetProtocolCommandHeader header; +} ENET_PACKED ENetProtocolPing; + +typedef struct _ENetProtocolSendReliable +{ + ENetProtocolCommandHeader header; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendReliable; + +typedef struct _ENetProtocolSendUnreliable +{ + ENetProtocolCommandHeader header; + enet_uint16 unreliableSequenceNumber; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendUnreliable; + +typedef struct _ENetProtocolSendUnsequenced +{ + ENetProtocolCommandHeader header; + enet_uint16 unsequencedGroup; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendUnsequenced; + +typedef struct _ENetProtocolSendFragment +{ + ENetProtocolCommandHeader header; + enet_uint16 startSequenceNumber; + enet_uint16 dataLength; + enet_uint32 fragmentCount; + enet_uint32 fragmentNumber; + enet_uint32 totalLength; + enet_uint32 fragmentOffset; +} ENET_PACKED ENetProtocolSendFragment; + +typedef union _ENetProtocol +{ + ENetProtocolCommandHeader header; + ENetProtocolAcknowledge acknowledge; + ENetProtocolConnect connect; + ENetProtocolVerifyConnect verifyConnect; + ENetProtocolDisconnect disconnect; + ENetProtocolPing ping; + ENetProtocolSendReliable sendReliable; + ENetProtocolSendUnreliable sendUnreliable; + ENetProtocolSendUnsequenced sendUnsequenced; + ENetProtocolSendFragment sendFragment; + ENetProtocolBandwidthLimit bandwidthLimit; + ENetProtocolThrottleConfigure throttleConfigure; +} ENET_PACKED ENetProtocol; + +#ifdef _MSC_VER_ +#pragma pack(pop) +#endif + +#endif /* __ENET_PROTOCOL_H__ */ + diff --git a/src/enet/time.h b/src/enet/time.h new file mode 100644 index 000000000..c82a54603 --- /dev/null +++ b/src/enet/time.h @@ -0,0 +1,18 @@ +/** + @file time.h + @brief ENet time constants and macros +*/ +#ifndef __ENET_TIME_H__ +#define __ENET_TIME_H__ + +#define ENET_TIME_OVERFLOW 86400000 + +#define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_LESS_EQUAL(a, b) (! ENET_TIME_GREATER (a, b)) +#define ENET_TIME_GREATER_EQUAL(a, b) (! ENET_TIME_LESS (a, b)) + +#define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b)) + +#endif /* __ENET_TIME_H__ */ + diff --git a/src/enet/types.h b/src/enet/types.h new file mode 100644 index 000000000..ab010a4b1 --- /dev/null +++ b/src/enet/types.h @@ -0,0 +1,13 @@ +/** + @file types.h + @brief type definitions for ENet +*/ +#ifndef __ENET_TYPES_H__ +#define __ENET_TYPES_H__ + +typedef unsigned char enet_uint8; /**< unsigned 8-bit type */ +typedef unsigned short enet_uint16; /**< unsigned 16-bit type */ +typedef unsigned int enet_uint32; /**< unsigned 32-bit type */ + +#endif /* __ENET_TYPES_H__ */ + diff --git a/src/enet/unix.c b/src/enet/unix.c new file mode 100644 index 000000000..9cb26692f --- /dev/null +++ b/src/enet/unix.c @@ -0,0 +1,443 @@ +/** + @file unix.c + @brief ENet Unix system specific functions +*/ +#ifndef WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +#ifdef HAS_FCNTL +#include +#endif + +#ifdef __APPLE__ +#undef HAS_POLL +#endif + +#ifdef HAS_POLL +#include +#endif + + +#ifndef HAS_SOCKLEN_T +#ifndef __socklen_t_defined +typedef __socklen_t socklen_t; +# define __socklen_t_defined +//typedef int socklen_t; +#endif +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +static enet_uint32 timeBase = 0; + +int +enet_initialize (void) +{ + return 0; +} + +void +enet_deinitialize (void) +{ +} + +enet_uint32 +enet_time_get (void) +{ + struct timeval timeVal; + + gettimeofday (& timeVal, NULL); + + return timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - timeBase; +} + +void +enet_time_set (enet_uint32 newTimeBase) +{ + struct timeval timeVal; + + gettimeofday (& timeVal, NULL); + + timeBase = timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - newTimeBase; +} + +int +enet_address_set_host (ENetAddress * address, const char * name) +{ + struct hostent * hostEntry = NULL; +#ifdef HAS_GETHOSTBYNAME_R + struct hostent hostData; + char buffer [2048]; + int errnum; + +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); +#else + hostEntry = gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & errnum); +#endif +#else + hostEntry = gethostbyname (name); +#endif + + if (hostEntry == NULL || + hostEntry -> h_addrtype != AF_INET) + { +#ifdef HAS_INET_PTON + if (! inet_pton (AF_INET, name, & address -> host)) +#else + if (! inet_aton (name, (struct in_addr *) & address -> host)) +#endif + return -1; + return 0; + } + + address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0]; + + return 0; +} + +int +enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength) +{ +#ifdef HAS_INET_NTOP + if (inet_ntop (AF_INET, & address -> host, name, nameLength) == NULL) +#else + char * addr = inet_ntoa (* (struct in_addr *) & address -> host); + if (addr != NULL) + strncpy (name, addr, nameLength); + else +#endif + return -1; + return 0; +} + +int +enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength) +{ + struct in_addr in; + struct hostent * hostEntry = NULL; +#ifdef HAS_GETHOSTBYADDR_R + struct hostent hostData; + char buffer [2048]; + int errnum; + + in.s_addr = address -> host; + +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); +#else + hostEntry = gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & errnum); +#endif +#else + in.s_addr = address -> host; + + hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET); +#endif + + if (hostEntry == NULL) + return enet_address_get_host_ip (address, name, nameLength); + + strncpy (name, hostEntry -> h_name, nameLength); + + return 0; +} + +int +enet_socket_bind (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + + if (address != NULL) + { + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + else + { + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + } + + return bind (socket, + (struct sockaddr *) & sin, + sizeof (struct sockaddr_in)); +} + +int +enet_socket_listen (ENetSocket socket, int backlog) +{ + return listen (socket, backlog < 0 ? SOMAXCONN : backlog); +} + +ENetSocket +enet_socket_create (ENetSocketType type) +{ + return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); +} + +int +enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value) +{ + int result = -1; + switch (option) + { + case ENET_SOCKOPT_NONBLOCK: +#ifdef HAS_FCNTL + result = fcntl (socket, F_SETFL, O_NONBLOCK | fcntl (socket, F_GETFL)); +#else + result = ioctl (socket, FIONBIO, & value); +#endif + break; + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int)); + break; + + default: + break; + } + return result == -1 ? -1 : 0; +} + +int +enet_socket_connect (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + return connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)); +} + +ENetSocket +enet_socket_accept (ENetSocket socket, ENetAddress * address) +{ + int result; + struct sockaddr_in sin; + socklen_t sinLength = sizeof (struct sockaddr_in); + + result = accept (socket, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL); + + if (result == -1) + return ENET_SOCKET_NULL; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return result; +} + +void +enet_socket_destroy (ENetSocket socket) +{ + close (socket); +} + +int +enet_socket_send (ENetSocket socket, + const ENetAddress * address, + const ENetBuffer * buffers, + size_t bufferCount) +{ + struct msghdr msgHdr; + struct sockaddr_in sin; + int sentLength; + + memset (& msgHdr, 0, sizeof (struct msghdr)); + + if (address != NULL) + { + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + msgHdr.msg_name = & sin; + msgHdr.msg_namelen = sizeof (struct sockaddr_in); + } + + msgHdr.msg_iov = (struct iovec *) buffers; + msgHdr.msg_iovlen = bufferCount; + + sentLength = sendmsg (socket, & msgHdr, MSG_NOSIGNAL); + + if (sentLength == -1) + { + if (errno == EWOULDBLOCK) + return 0; + + return -1; + } + + return sentLength; +} + +int +enet_socket_receive (ENetSocket socket, + ENetAddress * address, + ENetBuffer * buffers, + size_t bufferCount) +{ + struct msghdr msgHdr; + struct sockaddr_in sin; + int recvLength; + + memset (& msgHdr, 0, sizeof (struct msghdr)); + + if (address != NULL) + { + msgHdr.msg_name = & sin; + msgHdr.msg_namelen = sizeof (struct sockaddr_in); + } + + msgHdr.msg_iov = (struct iovec *) buffers; + msgHdr.msg_iovlen = bufferCount; + + recvLength = recvmsg (socket, & msgHdr, MSG_NOSIGNAL); + + if (recvLength == -1) + { + if (errno == EWOULDBLOCK) + return 0; + + return -1; + } + +#ifdef HAS_MSGHDR_FLAGS + if (msgHdr.msg_flags & MSG_TRUNC) + return -1; +#endif + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return recvLength; +} + +int +enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout) +{ + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal); +} + +int +enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout) +{ +#ifdef HAS_POLL + struct pollfd pollSocket; + int pollCount; + + pollSocket.fd = socket; + pollSocket.events = 0; + + if (* condition & ENET_SOCKET_WAIT_SEND) + pollSocket.events |= POLLOUT; + + if (* condition & ENET_SOCKET_WAIT_RECEIVE) + pollSocket.events |= POLLIN; + + pollCount = poll (& pollSocket, 1, timeout); + + if (pollCount < 0) + return -1; + + * condition = ENET_SOCKET_WAIT_NONE; + + if (pollCount == 0) + return 0; + + if (pollSocket.revents & POLLOUT) + * condition |= ENET_SOCKET_WAIT_SEND; + + if (pollSocket.revents & POLLIN) + * condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; +#else + fd_set readSet, writeSet; + struct timeval timeVal; + int selectCount; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + FD_ZERO (& readSet); + FD_ZERO (& writeSet); + + if (* condition & ENET_SOCKET_WAIT_SEND) + FD_SET (socket, & writeSet); + + if (* condition & ENET_SOCKET_WAIT_RECEIVE) + FD_SET (socket, & readSet); + + selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal); + + if (selectCount < 0) + return -1; + + * condition = ENET_SOCKET_WAIT_NONE; + + if (selectCount == 0) + return 0; + + if (FD_ISSET (socket, & writeSet)) + * condition |= ENET_SOCKET_WAIT_SEND; + + if (FD_ISSET (socket, & readSet)) + * condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; +#endif +} + +#endif + diff --git a/src/enet/unix.h b/src/enet/unix.h new file mode 100644 index 000000000..087015e51 --- /dev/null +++ b/src/enet/unix.h @@ -0,0 +1,45 @@ +/** + @file unix.h + @brief ENet Unix header +*/ +#ifndef __ENET_UNIX_H__ +#define __ENET_UNIX_H__ + +#include +#include +#include +#include +#include + +typedef int ENetSocket; + +enum +{ + ENET_SOCKET_NULL = -1 +}; + +#define ENET_HOST_TO_NET_16(value) (htons (value)) /**< macro that converts host to net byte-order of a 16-bit value */ +#define ENET_HOST_TO_NET_32(value) (htonl (value)) /**< macro that converts host to net byte-order of a 32-bit value */ + +#define ENET_NET_TO_HOST_16(value) (ntohs (value)) /**< macro that converts net to host byte-order of a 16-bit value */ +#define ENET_NET_TO_HOST_32(value) (ntohl (value)) /**< macro that converts net to host byte-order of a 32-bit value */ + +typedef struct +{ + void * data; + size_t dataLength; +} ENetBuffer; + +#define ENET_CALLBACK + +#define ENET_API extern + +typedef fd_set ENetSocketSet; + +#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) +#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) +#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLEAR (socket, & (sockset)) +#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) + +#endif /* __ENET_UNIX_H__ */ + diff --git a/src/enet/utility.h b/src/enet/utility.h new file mode 100644 index 000000000..e48a476be --- /dev/null +++ b/src/enet/utility.h @@ -0,0 +1,12 @@ +/** + @file utility.h + @brief ENet utility header +*/ +#ifndef __ENET_UTILITY_H__ +#define __ENET_UTILITY_H__ + +#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) + +#endif /* __ENET_UTILITY_H__ */ + diff --git a/src/enet/win32.c b/src/enet/win32.c new file mode 100644 index 000000000..e1fae2330 --- /dev/null +++ b/src/enet/win32.c @@ -0,0 +1,348 @@ +/** + @file win32.c + @brief ENet Win32 system specific functions +*/ +#ifdef WIN32 + +#include +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +static enet_uint32 timeBase = 0; + +int +enet_initialize (void) +{ + WORD versionRequested = MAKEWORD (1, 1); + WSADATA wsaData; + + if (WSAStartup (versionRequested, & wsaData)) + return -1; + + if (LOBYTE (wsaData.wVersion) != 1|| + HIBYTE (wsaData.wVersion) != 1) + { + WSACleanup (); + + return -1; + } + + timeBeginPeriod (1); + + return 0; +} + +void +enet_deinitialize (void) +{ + timeEndPeriod (1); + + WSACleanup (); +} + +enet_uint32 +enet_time_get (void) +{ + return (enet_uint32) timeGetTime () - timeBase; +} + +void +enet_time_set (enet_uint32 newTimeBase) +{ + timeBase = (enet_uint32) timeGetTime () - newTimeBase; +} + +int +enet_address_set_host (ENetAddress * address, const char * name) +{ + struct hostent * hostEntry; + + hostEntry = gethostbyname (name); + if (hostEntry == NULL || + hostEntry -> h_addrtype != AF_INET) + { + unsigned long host = inet_addr (name); + if (host == INADDR_NONE) + return -1; + address -> host = host; + return 0; + } + + address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0]; + + return 0; +} + +int +enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength) +{ + char * addr = inet_ntoa (* (struct in_addr *) & address -> host); + if (addr == NULL) + return -1; + strncpy (name, addr, nameLength); + return 0; +} + +int +enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength) +{ + struct in_addr in; + struct hostent * hostEntry; + + in.s_addr = address -> host; + + hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET); + if (hostEntry == NULL) + return enet_address_get_host_ip (address, name, nameLength); + + strncpy (name, hostEntry -> h_name, nameLength); + + return 0; +} + +int +enet_socket_bind (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + + if (address != NULL) + { + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + else + { + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + } + + return bind (socket, + (struct sockaddr *) & sin, + sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0; +} + +int +enet_socket_listen (ENetSocket socket, int backlog) +{ + return listen (socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0; +} + +ENetSocket +enet_socket_create (ENetSocketType type) +{ + return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); +} + +int +enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value) +{ + int result = SOCKET_ERROR; + switch (option) + { + case ENET_SOCKOPT_NONBLOCK: + { + u_long nonBlocking = (u_long) value; + result = ioctlsocket (socket, FIONBIO, & nonBlocking); + break; + } + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int)); + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt (socket, SOL_SOCKET, SO_SNDBUF, (char *) & value, sizeof (int)); + break; + + default: + break; + } + return result == SOCKET_ERROR ? -1 : 0; +} + +int +enet_socket_connect (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + return connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0; +} + +ENetSocket +enet_socket_accept (ENetSocket socket, ENetAddress * address) +{ + SOCKET result; + struct sockaddr_in sin; + int sinLength = sizeof (struct sockaddr_in); + + result = accept (socket, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL); + + if (result == INVALID_SOCKET) + return ENET_SOCKET_NULL; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return result; +} + +void +enet_socket_destroy (ENetSocket socket) +{ + closesocket (socket); +} + +int +enet_socket_send (ENetSocket socket, + const ENetAddress * address, + const ENetBuffer * buffers, + size_t bufferCount) +{ + struct sockaddr_in sin; + DWORD sentLength; + + if (address != NULL) + { + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + + if (WSASendTo (socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + & sentLength, + 0, + address != NULL ? (struct sockaddr *) & sin : 0, + address != NULL ? sizeof (struct sockaddr_in) : 0, + NULL, + NULL) == SOCKET_ERROR) + { + if (WSAGetLastError () == WSAEWOULDBLOCK) + return 0; + + return -1; + } + + return (int) sentLength; +} + +int +enet_socket_receive (ENetSocket socket, + ENetAddress * address, + ENetBuffer * buffers, + size_t bufferCount) +{ + INT sinLength = sizeof (struct sockaddr_in); + DWORD flags = 0, + recvLength; + struct sockaddr_in sin; + + if (WSARecvFrom (socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + & recvLength, + & flags, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL, + NULL, + NULL) == SOCKET_ERROR) + { + switch (WSAGetLastError ()) + { + case WSAEWOULDBLOCK: + case WSAECONNRESET: + return 0; + } + + return -1; + } + + if (flags & MSG_PARTIAL) + return -1; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return (int) recvLength; +} + +int +enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout) +{ + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal); +} + +int +enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout) +{ + fd_set readSet, writeSet; + struct timeval timeVal; + int selectCount; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + FD_ZERO (& readSet); + FD_ZERO (& writeSet); + + if (* condition & ENET_SOCKET_WAIT_SEND) + FD_SET (socket, & writeSet); + + if (* condition & ENET_SOCKET_WAIT_RECEIVE) + FD_SET (socket, & readSet); + + selectCount = select (socket + 1, & readSet, & writeSet, NULL, & timeVal); + + if (selectCount < 0) + return -1; + + * condition = ENET_SOCKET_WAIT_NONE; + + if (selectCount == 0) + return 0; + + if (FD_ISSET (socket, & writeSet)) + * condition |= ENET_SOCKET_WAIT_SEND; + + if (FD_ISSET (socket, & readSet)) + * condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; +} + +#endif + diff --git a/src/enet/win32.h b/src/enet/win32.h new file mode 100644 index 000000000..0e1cf0c5a --- /dev/null +++ b/src/enet/win32.h @@ -0,0 +1,58 @@ +/** + @file win32.h + @brief ENet Win32 header +*/ +#ifndef __ENET_WIN32_H__ +#define __ENET_WIN32_H__ + +#ifdef ENET_BUILDING_LIB +#pragma warning (disable: 4996) // 'strncpy' was declared deprecated +#pragma warning (disable: 4267) // size_t to int conversion +#pragma warning (disable: 4244) // 64bit to 32bit int +#pragma warning (disable: 4018) // signed/unsigned mismatch +#endif + +#include +#include + +typedef SOCKET ENetSocket; + +enum +{ + ENET_SOCKET_NULL = INVALID_SOCKET +}; + +#define ENET_HOST_TO_NET_16(value) (htons (value)) +#define ENET_HOST_TO_NET_32(value) (htonl (value)) + +#define ENET_NET_TO_HOST_16(value) (ntohs (value)) +#define ENET_NET_TO_HOST_32(value) (ntohl (value)) + +typedef struct +{ + size_t dataLength; + void * data; +} ENetBuffer; + +#define ENET_CALLBACK __cdecl + +#if defined ENET_DLL +#if defined ENET_BUILDING_LIB +#define ENET_API __declspec( dllexport ) +#else +#define ENET_API __declspec( dllimport ) +#endif /* ENET_BUILDING_LIB */ +#else /* !ENET_DLL */ +#define ENET_API extern +#endif /* ENET_DLL */ + +typedef fd_set ENetSocketSet; + +#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) +#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) +#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLEAR (socket, & (sockset)) +#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) + +#endif /* __ENET_WIN32_H__ */ + + diff --git a/src/equipment.h b/src/equipment.h new file mode 100644 index 000000000..24f7174fd --- /dev/null +++ b/src/equipment.h @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EQUIPMENT_H +#define EQUIPMENT_H + +#define EQUIPMENT_SIZE 13 + +class Item; + +class Equipment +{ + public: + /** + * Constructor. + */ + Equipment(): mBackend(0) + { } + + /** + * Destructor. + */ + ~Equipment() + { mBackend = 0; } + + enum Slot + { + EQUIP_TORSO_SLOT = 0, + EQUIP_GLOVES_SLOT = 1, + EQUIP_HEAD_SLOT = 2, + EQUIP_LEGS_SLOT = 3, + EQUIP_FEET_SLOT = 4, + EQUIP_RING1_SLOT = 5, + EQUIP_RING2_SLOT = 6, + EQUIP_NECK_SLOT = 7, + EQUIP_FIGHT1_SLOT = 8, + EQUIP_FIGHT2_SLOT = 9, + EQUIP_PROJECTILE_SLOT = 10, + EQUIP_EVOL_RING1_SLOT = 11, + EQUIP_EVOL_RING2_SLOT = 12, + EQUIP_VECTOREND + }; + + class Backend + { + public: + virtual Item *getEquipment(int index) const = 0; + virtual void clear() = 0; + virtual ~Backend() { } + }; + + /** + * Get equipment at the given slot. + */ + Item *getEquipment(int index) const + { return mBackend ? mBackend->getEquipment(index) : 0; } + + /** + * Clears equipment. + */ + void clear() + { if (mBackend) mBackend->clear(); } + + /** + * Set equipment at the given slot. + */ + void setEquipment(int index, int id, int quantity = 0); + + void setBackend(Backend *backend) + { mBackend = backend; } + + private: + Backend *mBackend; +}; + +#endif diff --git a/src/event.cpp b/src/event.cpp new file mode 100644 index 000000000..1a48bddf8 --- /dev/null +++ b/src/event.cpp @@ -0,0 +1,142 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "event.h" + +#include "listener.h" +#include "variabledata.h" + +namespace Mana +{ + +ListenMap Event::mBindings; + +Event::~Event() +{ + VariableMap::iterator it = mData.begin(); + while (it != mData.end()) + { + delete it->second; + it->second = 0; + it++; + } +} + +void Event::setInt(const std::string &key, int value) throw (BadEvent) +{ + if (mData.find(key) != mData.end()) + throw KEY_ALREADY_EXISTS; + + mData[key] = new IntData(value); +} + +int Event::getInt(const std::string &key) const throw (BadEvent) +{ + VariableMap::const_iterator it = mData.find(key); + if (it == mData.end()) + throw BAD_KEY; + + if (it->second->getType() != VariableData::DATA_INT) + throw BAD_VALUE; + + return static_cast(it->second)->getData(); +} + +void Event::setString(const std::string &key, + const std::string &value) throw (BadEvent) +{ + if (mData.find(key) != mData.end()) + throw KEY_ALREADY_EXISTS; + + mData[key] = new StringData(value); +} + +const std::string &Event::getString(const std::string &key) + const throw (BadEvent) +{ + VariableMap::const_iterator it = mData.find(key); + if (it == mData.end()) + throw BAD_KEY; + + if (it->second->getType() != VariableData::DATA_STRING) + throw BAD_VALUE; + + return static_cast(it->second)->getData(); +} + + +void Event::setFloat(const std::string &key, double value) throw (BadEvent) +{ + if (mData.find(key) != mData.end()) + throw KEY_ALREADY_EXISTS; + + mData[key] = new FloatData(value); +} + +double Event::getFloat(const std::string &key) const throw (BadEvent) +{ + VariableMap::const_iterator it = mData.find(key); + if (it == mData.end()) + throw BAD_KEY; + + if (it->second->getType() != VariableData::DATA_FLOAT) + throw BAD_VALUE; + + return static_cast(it->second)->getData(); +} + +void Event::trigger(Channels channel, const Event &event) +{ + ListenMap::iterator it = mBindings.find(channel); + + // Make sure something is listening + if (it == mBindings.end()) + return; + + // Loop though all listeners + ListenerSet::iterator lit = it->second.begin(); + while (lit != it->second.end()) + { + (*lit)->event(channel, event); + lit++; + } +} + +void Event::remove(Listener *listener) +{ + ListenMap::iterator it = mBindings.begin(); + while (it != mBindings.end()) + { + it->second.erase(listener); + it++; + } +} + +void Event::bind(Listener *listener, Channels channel) +{ + mBindings[channel].insert(listener); +} + +void Event::unbind(Listener *listener, Channels channel) +{ + mBindings[channel].erase(listener); +} + +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 000000000..2b878537d --- /dev/null +++ b/src/event.h @@ -0,0 +1,175 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EVENT_H +#define EVENT_H + +#include +#include +#include + +enum Channels +{ + CHANNEL_ACTORSPRITE = 0, + CHANNEL_ATTRIBUTES, + CHANNEL_BUYSELL, + CHANNEL_CHAT, + CHANNEL_CLIENT, + CHANNEL_GAME, + CHANNEL_ITEM, + CHANNEL_NOTICES, + CHANNEL_NPC, + CHANNEL_STATUS, + CHANNEL_STORAGE +}; + +enum Events +{ + EVENT_ANNOUNCEMENT = 0, + EVENT_BEING, + EVENT_CLOSE, + EVENT_CLOSEALL, + EVENT_CONSTRUCTED, + EVENT_DBSLOADING, + EVENT_DESTROYED, + EVENT_DESTRUCTED, + EVENT_DESTRUCTING, + EVENT_DOCLOSE, + EVENT_DOCLOSEINVENTORY, + EVENT_DODROP, + EVENT_DOEQUIP, + EVENT_DOINTEGERINPUT, + EVENT_DOMENU, + EVENT_DOMOVE, + EVENT_DONEXT, + EVENT_DOSENDLETTER, + EVENT_DOSPLIT, + EVENT_DOSTRINGINPUT, + EVENT_DOTALK, + EVENT_DOUNEQUIP, + EVENT_DOUSE, + EVENT_END, + EVENT_ENGINESINITALIZED, + EVENT_ENGINESINITALIZING, + EVENT_GUIWINDOWSLOADED, + EVENT_GUIWINDOWSLOADING, + EVENT_GUIWINDOWSUNLOADED, + EVENT_GUIWINDOWSUNLOADING, + EVENT_INTEGERINPUT, + EVENT_MAPLOADED, + EVENT_MENU, + EVENT_MESSAGE, + EVENT_NEXT, + EVENT_NPCCOUNT, + EVENT_PLAYER, + EVENT_POST, + EVENT_POSTCOUNT, + EVENT_SERVERNOTICE, + EVENT_STATECHANGE, + EVENT_STORAGECOUNT, + EVENT_STRINGINPUT, + EVENT_STUN, + EVENT_TRADING, + EVENT_UPDATEATTRIBUTE, + EVENT_UPDATESTAT, + EVENT_UPDATESTATUSEFFECT, + EVENT_WHISPER, + EVENT_WHISPERERROR +}; + +namespace Mana +{ + +// Possible exception that can be thrown +enum BadEvent +{ + BAD_KEY = 0, + BAD_VALUE, + KEY_ALREADY_EXISTS +}; + +class Listener; +class VariableData; +typedef std::map VariableMap; + +typedef std::set ListenerSet; +typedef std::map ListenMap; + +#define SERVER_NOTICE(message) { \ +Mana::Event event(EVENT_SERVERNOTICE); \ +event.setString("message", message); \ +Mana::Event::trigger(CHANNEL_NOTICES, event); } + +class Event +{ + public: + // String passed can be retivered with getName() + // and is to used to identify what type of event + // this is. + Event(Events name) + { mEventName = name; } + + ~Event(); + + Events getName() const + { return mEventName; } + + // Sets or gets a interger with a key to identify + void setInt(const std::string &key, int value) + throw (BadEvent); + + int getInt(const std::string &key) + const throw (BadEvent); + + // Sets or gets a string with a key to identify + void setString(const std::string &key, + const std::string &value) + throw (BadEvent); + + const std::string &getString(const std::string &key) + const throw (BadEvent); + + // Sets or gets a floating point number with key to identify + void setFloat(const std::string &key, double value) + throw (BadEvent); + double getFloat(const std::string &key) + const throw (BadEvent); + + // Sends event to all listener on the channel + static void trigger(Channels channel, const Event &event); + + // Removes a listener from all channels + static void remove(Listener *listener); + + // Adds or removes a listener to a channel. + static void bind(Listener *listener, Channels channel); + static void unbind(Listener *listener, Channels channel); + + private: + Events mEventName; + + static ListenMap mBindings; + + VariableMap mData; +}; + +} // namespace Mana + +#endif diff --git a/src/filefilter.txt b/src/filefilter.txt new file mode 100644 index 000000000..87431bf29 --- /dev/null +++ b/src/filefilter.txt @@ -0,0 +1,26 @@ +~ RULE_3_1_A_do_not_start_filename_with_underbar +~ RULE_3_2_B_do_not_use_same_filename_more_than_once +~ RULE_3_2_CD_do_not_use_special_characters_in_filename +~ RULE_3_2_H_do_not_use_uppercase_for_c_filename +~ RULE_3_3_A_start_function_name_with_lowercase_unix +~ RULE_4_1_A_B_use_space_for_indentation +~ RULE_4_1_B_indent_each_enum_item_in_enum_block +~ RULE_4_1_B_locate_each_enum_item_in_seperate_line +~ RULE_4_1_C_align_long_function_parameter_list +~ RULE_4_1_E_align_conditions +~ RULE_4_2_A_A_space_around_operator +~ RULE_4_2_A_B_space_around_word +~ RULE_4_4_A_do_not_write_over_80_columns_per_line +~ RULE_4_5_A_brace_for_namespace_should_be_located_in_seperate_line +~ RULE_4_5_A_braces_for_function_definition_should_be_located_in_seperate_line +~ RULE_4_5_A_braces_for_type_definition_should_be_located_in_seperate_line +~ RULE_4_5_A_indent_blocks_inside_of_function +~ RULE_4_5_A_matching_braces_inside_of_function_should_be_located_same_column +~ RULE_6_1_A_do_not_omit_function_parameter_names +~ RULE_6_4_B_initialize_first_item_of_enum +~ RULE_6_5_B_do_not_use_lowercase_for_macro_constants +~ RULE_7_1_B_A_do_not_use_double_assignment +~ RULE_7_2_B_do_not_use_goto_statement +~ RULE_8_1_A_provide_file_info_comment +~ RULE_9_1_A_do_not_use_hardcorded_include_path +~ RULE_9_2_D_use_reentrant_function diff --git a/src/filefilter_more.txt b/src/filefilter_more.txt new file mode 100644 index 000000000..f0f37e03a --- /dev/null +++ b/src/filefilter_more.txt @@ -0,0 +1,12 @@ +~ RULE_6_2_A_do_not_use_system_dependent_type +~ RULE_3_2_F_use_representitive_classname_for_cpp_filename +~ RULE_6_5_B_do_not_use_macro_for_constants +~ RULE_A_3_avoid_too_deep_blocks +~ RULE_6_1_G_write_less_than_200_lines_for_function + +~ RULE_5_2_C_provide_doxygen_class_comment_on_class_def +~ RULE_5_2_C_provide_doxygen_namespace_comment_on_namespace_def +~ RULE_5_2_C_provide_doxygen_struct_comment_on_struct_def +~ RULE_5_3_A_provide_doxygen_function_comment_on_function_in_header +~ RULE_5_3_A_provide_doxygen_function_comment_on_function_in_impl +~ RULE_6_1_E_do_not_use_more_than_5_paramters_in_function diff --git a/src/flooritem.cpp b/src/flooritem.cpp new file mode 100644 index 000000000..b669bece3 --- /dev/null +++ b/src/flooritem.cpp @@ -0,0 +1,133 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "flooritem.h" + +#include "client.h" +#include "graphics.h" +#include "item.h" +#include "map.h" +#include "configuration.h" + +#include "gui/gui.h" +#include "gui/truetypefont.h" + +#include "net/net.h" + +#include "resources/itemdb.h" +#include "resources/iteminfo.h" + +FloorItem::FloorItem(int id, + int itemId, + int x, + int y, + Map *map, + int amount): + ActorSprite(id), + mItemId(itemId), + mX(x), + mY(y), + mMap(map), +// mAlpha(1.0f), + mAmount(amount), + mPickupCount(0) +{ + mDropTime = cur_time; + + setMap(map); + if (map) + { + // TODO: Eventually, we probably should fix all sprite offsets so that + // these translations aren't necessary anymore. The sprites know + // best where their base point should be. + mPos.x = static_cast(x * map->getTileWidth() + 16); + mPos.y = static_cast(y * map->getTileHeight() + + ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32)); + } + else + { + mPos.x = 0; + mPos.y = 0; + } + + setupSpriteDisplay(ItemDB::get(itemId).getDisplay()); +} + +const ItemInfo &FloorItem::getInfo() const +{ + return ItemDB::get(mItemId); +} + +bool FloorItem::draw(Graphics *graphics, int offsetX, int offsetY) const +{ + if (!mMap) + return false; + + const int dx = 32; + const int dy = 32; + + const int x = mX * mMap->getTileWidth() + offsetX; + const int y = mY * mMap->getTileHeight() + offsetY; + gcn::Font *font = 0; + const bool highl = config.getBoolValue("floorItemsHighlight"); + + if (highl) + { + int curTime = cur_time; + font = gui->getFont(); + if (mDropTime < curTime) + { + if (curTime > mDropTime + 28 && curTime < mDropTime + 50) + { + graphics->setColor(gcn::Color(80, 200, 20, 200)); + graphics->fillRectangle(gcn::Rectangle( + x, y, dx, dy)); + } + else if (curTime > mDropTime + 19 + && curTime < mDropTime + 28) + { + graphics->setColor(gcn::Color(200, 80, 20, + 80 + 10 * (curTime - mDropTime - 18))); + graphics->fillRectangle(gcn::Rectangle( + x, y, dx, dy)); + } + else if (curTime > mDropTime && curTime < mDropTime + 20) + { + graphics->setColor(gcn::Color(20, 20, 255, + 7 * (curTime - mDropTime))); + graphics->fillRectangle(gcn::Rectangle( + x, y, dx, dy)); + } + } + } + + const bool res = ActorSprite::draw(graphics, offsetX, offsetY); + + if (highl) + { + if (font && mAmount > 1) + { + graphics->setColor(gcn::Color(255, 255, 255, 100)); + font->drawString(graphics, toString(mAmount), x, y); + } + } + return res; +} diff --git a/src/flooritem.h b/src/flooritem.h new file mode 100644 index 000000000..2cb47b9bb --- /dev/null +++ b/src/flooritem.h @@ -0,0 +1,92 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FLOORITEM_H +#define FLOORITEM_H + +#include "actorsprite.h" + +class ItemInfo; + +/** + * An item lying on the floor. + */ +class FloorItem : public ActorSprite +{ + public: + /** + * Constructor. + * + * @param id the unique ID of this item instance + * @param itemId the item ID + * @param x the x position in tiles + * @param y the y position in tiles + * @param map the map this item is on + * @param amount the item amount + */ + FloorItem(int id, + int itemId, + int x, + int y, + Map *map, + int amount); + + Type getType() const + { return FLOOR_ITEM; } + + bool draw(Graphics *graphics, int offsetX, int offsetY) const; + + /** + * Returns the item ID. + */ + int getItemId() const + { return mItemId; } + + /** + * Returns the item info for this floor item. Useful for adding an item + * link for the floor item to chat. + */ + const ItemInfo &getInfo() const; + + virtual int getTileX() const + { return mX; } + + virtual int getTileY() const + { return mY; } + + void incrementPickup() + { mPickupCount ++; } + + unsigned getPickupCount() const + { return mPickupCount; } + + private: + int mItemId; + int mX, mY; +// Item *mItem; + Map *mMap; +// float mAlpha; + int mDropTime; + int mAmount; + unsigned mPickupCount; +}; + +#endif diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 000000000..c0abdf543 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,1501 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "game.h" + +#include "gui/npcdialog.h" + +#include "actorspritemanager.h" +#include "actorsprite.h" +#include "channelmanager.h" +#include "client.h" +#include "commandhandler.h" +#include "configuration.h" +#include "effectmanager.h" +#include "event.h" +#include "spellmanager.h" +#include "emoteshortcut.h" +#include "graphics.h" +#include "itemshortcut.h" +#include "dropshortcut.h" +#include "spellshortcut.h" +#include "joystick.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" +#include "particle.h" +#include "playerrelations.h" +#include "sound.h" + +#include "gui/botcheckerwindow.h" +#include "gui/buysell.h" +#include "gui/chat.h" +#include "gui/confirmdialog.h" +#include "gui/debugwindow.h" +#include "gui/equipmentwindow.h" +#include "gui/gui.h" +#include "gui/help.h" +#include "gui/inventorywindow.h" +#include "gui/killstats.h" +#include "gui/minimap.h" +#include "gui/ministatus.h" +#include "gui/npcpostdialog.h" +#include "gui/okdialog.h" +#include "gui/outfitwindow.h" +#include "gui/sdlinput.h" +#include "gui/setup.h" +#include "gui/shopwindow.h" +#include "gui/shortcutwindow.h" +#include "gui/socialwindow.h" +#include "gui/specialswindow.h" +#include "gui/skilldialog.h" +#include "gui/statuswindow.h" +#include "gui/quitdialog.h" +#include "gui/textdialog.h" +#include "gui/trade.h" +#include "gui/viewport.h" +#include "gui/windowmenu.h" +#include "gui/whoisonline.h" + +#include "gui/widgets/battletab.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/dropshortcutcontainer.h" +#include "gui/widgets/emoteshortcutcontainer.h" +#include "gui/widgets/itemshortcutcontainer.h" +#include "gui/widgets/spellshortcutcontainer.h" +#include "gui/widgets/tradetab.h" + +#include "net/gamehandler.h" +#include "net/generalhandler.h" +#include "net/net.h" +#include "net/packetcounters.h" +#include "net/playerhandler.h" + +#include "resources/imagewriter.h" +#include "resources/mapreader.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/mkdir.h" + +#include +#include + +#include + +#include +#include +#include + +#include "mumblemanager.h" + +Joystick *joystick = NULL; + +OkDialog *weightNotice = NULL; +OkDialog *deathNotice = NULL; +QuitDialog *quitDialog = NULL; +OkDialog *disconnectedDialog = NULL; + +ChatWindow *chatWindow; +StatusWindow *statusWindow; +MiniStatusWindow *miniStatusWindow; +InventoryWindow *inventoryWindow; +ShopWindow *shopWindow; +SkillDialog *skillDialog; +Minimap *minimap; +EquipmentWindow *equipmentWindow; +TradeWindow *tradeWindow; +HelpWindow *helpWindow; +DebugWindow *debugWindow; +ShortcutWindow *itemShortcutWindow; +ShortcutWindow *emoteShortcutWindow; +OutfitWindow *outfitWindow; +SpecialsWindow *specialsWindow; +ShortcutWindow *dropShortcutWindow; +ShortcutWindow *spellShortcutWindow; +WhoIsOnline *whoIsOnline; +KillStats *killStats; +BotCheckerWindow *botCheckerWindow; +SocialWindow *socialWindow; +WindowMenu *windowMenu; + +ActorSpriteManager *actorSpriteManager = NULL; +ChannelManager *channelManager = NULL; +CommandHandler *commandHandler = NULL; +MumbleManager *mumbleManager = NULL; +Particle *particleEngine = NULL; +EffectManager *effectManager = NULL; +SpellManager *spellManager = NULL; +Viewport *viewport = NULL; /**< Viewport on the map. */ + +ChatTab *localChatTab = NULL; +ChatTab *debugChatTab = NULL; +TradeTab *tradeChatTab = NULL; +BattleTab *battleChatTab = NULL; + +/** + * Initialize every game sub-engines in the right order + */ +static void initEngines() +{ + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_ENGINESINITALIZING)); + + actorSpriteManager = new ActorSpriteManager; + commandHandler = new CommandHandler; + channelManager = new ChannelManager; + effectManager = new EffectManager; + + particleEngine = new Particle(NULL); + particleEngine->setupEngine(); + + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_ENGINESINITALIZED)); +} + +/** + * Create all the various globally accessible gui windows + */ +static void createGuiWindows() +{ + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_GUIWINDOWSLOADING)); + + if (setupWindow) + setupWindow->clearWindowsForReset(); + + if (emoteShortcut) + emoteShortcut->load(); + + // Create dialogs + chatWindow = new ChatWindow; + tradeWindow = new TradeWindow; + equipmentWindow = new EquipmentWindow(PlayerInfo::getEquipment()); + statusWindow = new StatusWindow; + miniStatusWindow = new MiniStatusWindow; + inventoryWindow = new InventoryWindow(PlayerInfo::getInventory()); + shopWindow = new ShopWindow; + skillDialog = new SkillDialog; + minimap = new Minimap; + helpWindow = new HelpWindow; + debugWindow = new DebugWindow; + itemShortcutWindow = new ShortcutWindow("ItemShortcut"); + for (int f = 0; f < SHORTCUT_TABS; f ++) + { + itemShortcutWindow->addTab(toString(f + 1), + new ItemShortcutContainer(f)); + } + + emoteShortcutWindow = new ShortcutWindow("EmoteShortcut", + new EmoteShortcutContainer); + outfitWindow = new OutfitWindow(); + specialsWindow = new SpecialsWindow(); + dropShortcutWindow = new ShortcutWindow("DropShortcut", + new DropShortcutContainer); + spellShortcutWindow = new ShortcutWindow("SpellShortcut", + new SpellShortcutContainer, + 265, 310); + botCheckerWindow = new BotCheckerWindow(); + whoIsOnline = new WhoIsOnline(); + killStats = new KillStats; + socialWindow = new SocialWindow(); + + localChatTab = new ChatTab(_("General")); + localChatTab->setAllowHighlight(false); + localChatTab->loadFromLogFile("#General"); + + debugChatTab = new ChatTab(_("Debug")); + debugChatTab->setAllowHighlight(false); + + if (config.getBoolValue("enableTradeTab")) + { + tradeChatTab = new TradeTab; + tradeChatTab->setAllowHighlight(false); + } + else + { + tradeChatTab = 0; + } + + if (config.getBoolValue("enableBattleTab")) + { + battleChatTab = new BattleTab; + battleChatTab->setAllowHighlight(false); + } + else + { + battleChatTab = 0; + } + + if (config.getBoolValue("logToChat")) + logger->setChatWindow(chatWindow); + + if (!isSafeMode && chatWindow) + chatWindow->loadState(); + + if (setupWindow) + setupWindow->externalUpdate(); + + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_GUIWINDOWSLOADED)); +} + +#define del_0(X) { delete X; X = 0; } + +/** + * Destroy all the globally accessible gui windows + */ +static void destroyGuiWindows() +{ + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_GUIWINDOWSUNLOADING)); + + logger->setChatWindow(NULL); + if (whoIsOnline) + whoIsOnline->setAllowUpdate(false); + + del_0(windowMenu); + del_0(localChatTab) // Need to do this first, so it can remove itself + del_0(debugChatTab) + del_0(tradeChatTab) + del_0(battleChatTab) + logger->log("start deleting"); + del_0(chatWindow) + logger->log("end deleting"); + del_0(statusWindow) + del_0(miniStatusWindow) + del_0(inventoryWindow) + del_0(shopWindow) + del_0(skillDialog) + del_0(minimap) + del_0(equipmentWindow) + del_0(tradeWindow) + del_0(helpWindow) + del_0(debugWindow) + del_0(itemShortcutWindow) + del_0(emoteShortcutWindow) + del_0(outfitWindow) + del_0(specialsWindow) + del_0(socialWindow) + del_0(dropShortcutWindow); + del_0(spellShortcutWindow); + del_0(botCheckerWindow); + del_0(whoIsOnline); + del_0(killStats); + + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_GUIWINDOWSUNLOADED)); +} + +Game *Game::mInstance = 0; + +Game::Game(): + mLastTarget(ActorSprite::UNKNOWN), + mCurrentMap(0), mMapName(""), + mValidSpeed(true), mLastAction(0) +{ + spellManager = new SpellManager; + spellShortcut = new SpellShortcut; + + assert(!mInstance); + mInstance = this; + + disconnectedDialog = NULL; + + // Create the viewport + viewport = new Viewport; + viewport->setDimension(gcn::Rectangle(0, 0, graphics->getWidth(), + graphics->getHeight())); + + gcn::Container *top = static_cast(gui->getTop()); + top->add(viewport); + viewport->requestMoveToBottom(); + + createGuiWindows(); + + windowMenu = new WindowMenu; +// mWindowMenu = windowMenu; + + windowContainer->add(windowMenu); + + initEngines(); + + // Initialize beings + actorSpriteManager->setPlayer(player_node); + + /* + * To prevent the server from sending data before the client + * has initialized, I've modified it to wait for a "ping" + * from the client to complete its initialization + * + * Note: This only affects the latest eAthena version. This + * packet is handled by the older version, but its response + * is ignored by the client + */ + Net::getGameHandler()->ping(tick_time); + + Joystick::init(); + // TODO: The user should be able to choose which one to use + // Open the first device + if (Joystick::getNumberOfJoysticks() > 0) + joystick = new Joystick(0); + + if (setupWindow) + setupWindow->setInGame(true); + clearKeysArray(); + + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_CONSTRUCTED)); +} + +Game::~Game() +{ + config.write(); + serverConfig.write(); +// delete mWindowMenu; +// mWindowMenu = 0; + + destroyGuiWindows(); + + del_0(actorSpriteManager) + if (Client::getState() != STATE_CHANGE_MAP) + del_0(player_node) + del_0(channelManager) + del_0(commandHandler) + del_0(effectManager); + del_0(joystick) + del_0(particleEngine) + del_0(viewport) + del_0(mCurrentMap) + del_0(spellManager); + del_0(spellShortcut); + del_0(mumbleManager); + + mInstance = 0; + + Mana::Event::trigger(CHANNEL_GAME, Mana::Event(EVENT_DESTRUCTED)); +} + +static bool saveScreenshot() +{ + static unsigned int screenshotCount = 0; + + SDL_Surface *screenshot = graphics->getScreenshot(); + if (!screenshot) + return false; + + // Search for an unused screenshot name + std::stringstream filenameSuffix; + std::stringstream filename; + std::fstream testExists; + std::string screenshotDirectory = Client::getScreenshotDirectory(); + bool found = false; + + if (mkdir_r(screenshotDirectory.c_str()) != 0) + { + logger->log("Directory %s doesn't exist and can't be created! " + "Setting screenshot directory to home.", + screenshotDirectory.c_str()); + screenshotDirectory = std::string(PHYSFS_getUserDir()); + } + + do + { + screenshotCount++; + filenameSuffix.str(""); + filename.str(""); + filename << screenshotDirectory << "/"; + filenameSuffix << branding.getValue("appShort", "ManaPlus") + << "_Screenshot_" << screenshotCount << ".png"; + filename << filenameSuffix.str(); + testExists.open(filename.str().c_str(), std::ios::in); + found = !testExists.is_open(); + testExists.close(); + } + while (!found); + + const bool success = ImageWriter::writePNG(screenshot, filename.str()); + + if (success) + { + std::stringstream chatlogentry; + // TODO: Make it one complete gettext string below + chatlogentry << _("Screenshot saved as ") << filenameSuffix.str(); + if (localChatTab) + localChatTab->chatLog(chatlogentry.str(), BY_SERVER); + } + else + { + if (localChatTab) + { + localChatTab->chatLog(_("Saving screenshot failed!"), + BY_SERVER); + } + logger->log1("Error: could not save screenshot."); + } + + SDL_FreeSurface(screenshot); + + return success; +} + +void Game::logic() +{ + handleInput(); + + // Handle all necessary game logic + ActorSprite::actorLogic(); + if (actorSpriteManager) + actorSpriteManager->logic(); + if (particleEngine) + particleEngine->update(); + if (mCurrentMap) + mCurrentMap->update(); + + cur_time = static_cast(time(0)); + Being::reReadConfig(); + if (killStats) + killStats->recalcStats(); + if (shopWindow) + shopWindow->updateTimes(); + PacketCounters::update(); + + // Handle network stuff + if (!Net::getGameHandler()->isConnected()) + { + if (Client::getState() == STATE_CHANGE_MAP) + return; // Not a problem here + + if (Client::getState() != STATE_ERROR) + errorMessage = _("The connection to the server was lost."); + + + if (!disconnectedDialog) + { + disconnectedDialog = new OkDialog(_("Network Error"), + errorMessage); + disconnectedDialog->addActionListener(&errorListener); + disconnectedDialog->requestMoveToTop(); + } + } +} + +/** + * The huge input handling method. + */ +void Game::handleInput() +{ + if (joystick) + joystick->update(); + + // Events + SDL_Event event; + while (SDL_PollEvent(&event)) + { + bool used = false; + + updateHistory(event); + checkKeys(); + + // Keyboard events (for discontinuous keys) + if (event.type == SDL_KEYDOWN) + { + gcn::Window *requestedWindow = NULL; + + if (setupWindow && setupWindow->isVisible() && + keyboard.getNewKeyIndex() > keyboard.KEY_NO_VALUE) + { + keyboard.setNewKey((int) event.key.keysym.sym); + keyboard.callbackNewKey(); + keyboard.setNewKeyIndex(keyboard.KEY_NO_VALUE); + return; + } + + // send straight to gui for certain windows + if (quitDialog || TextDialog::isActive() || + NpcPostDialog::isActive()) + { + try + { + guiInput->pushInput(event); + } + catch (gcn::Exception e) + { + const char* err = e.getMessage().c_str(); + logger->log("Warning: guichan input exception: %s", err); + } + return; + } + + if (chatWindow && !chatWindow->isInputFocused() + && keyboard.isKeyActive(keyboard.KEY_RIGHT_CLICK)) + { + int mouseX, mouseY; + SDL_GetMouseState(&mouseX, &mouseY); + + gcn::MouseEvent event2(viewport, false, false, false, false, + 0, gcn::MouseEvent::RIGHT, mouseX, mouseY, 1); + if (viewport) + viewport->mousePressed(event2); + continue; + } + + // Mode switch to emotes + if (keyboard.isKeyActive(keyboard.KEY_EMOTE)) + { + // Emotions + int emotion = keyboard.getKeyEmoteOffset(event.key.keysym.sym); + if (emotion) + { + if (emoteShortcut) + emoteShortcut->useEmote(emotion); + used = true; + setValidSpeed(); + return; + } + } + + if (keyboard.isEnabled() + && !chatWindow->isInputFocused() + && !setupWindow->isVisible() + && !player_node->getAwayMode() + && !NpcDialog::isAnyInputFocused()) + { + bool wearOutfit = false; + bool copyOutfit = false; + if (keyboard.isKeyActive(keyboard.KEY_WEAR_OUTFIT)) + wearOutfit = true; + + if (keyboard.isKeyActive(keyboard.KEY_COPY_OUTFIT)) + copyOutfit = true; + + if (wearOutfit || copyOutfit) + { + int outfitNum = outfitWindow->keyToNumber( + event.key.keysym.sym); + if (outfitNum >= 0) + { + used = true; + if (wearOutfit) + outfitWindow->wearOutfit(outfitNum); + else if (copyOutfit) + outfitWindow->copyOutfit(outfitNum); + } + else + { + if (keyboard.isKeyActive(keyboard.KEY_MOVE_RIGHT)) + outfitWindow->wearNextOutfit(); + else if (keyboard.isKeyActive(keyboard.KEY_MOVE_LEFT)) + outfitWindow->wearPreviousOutfit(); + } + setValidSpeed(); + continue; + } + else if (keyboard.isKeyActive(keyboard.KEY_MOVE_TO_POINT)) + { + int num = outfitWindow->keyToNumber( + event.key.keysym.sym); + if (socialWindow && num >= 0) + { + socialWindow->selectPortal(num); + continue; + } + } + } + + if (!chatWindow->isInputFocused() + && !gui->getFocusHandler()->getModalFocused() + && !player_node->getAwayMode()) + { + NpcDialog *dialog = NpcDialog::getActive(); + if (keyboard.isKeyActive(keyboard.KEY_OK) + && (!dialog || !dialog->isTextInputFocused())) + { + // Close the Browser if opened + if (helpWindow->isVisible()) + helpWindow->setVisible(false); + // Close the config window, cancelling changes if opened + else if (setupWindow->isVisible()) + setupWindow->action(gcn::ActionEvent(NULL, "cancel")); + else if (dialog) + dialog->action(gcn::ActionEvent(NULL, "ok")); + } + if (keyboard.isKeyActive(keyboard.KEY_TOGGLE_CHAT)) + { + if (chatWindow->requestChatFocus()) + used = true; + } + if (dialog) + { + if (keyboard.isKeyActive(keyboard.KEY_MOVE_UP)) + dialog->move(1); + else if (keyboard.isKeyActive(keyboard.KEY_MOVE_DOWN)) + dialog->move(-1); + } + } + + if ((!chatWindow->isInputFocused() && + !NpcDialog::isAnyInputFocused()) + || (event.key.keysym.mod & KMOD_ALT)) + { + if (keyboard.isKeyActive(keyboard.KEY_PREV_CHAT_TAB)) + { + chatWindow->prevTab(); + return; + } + else if (keyboard.isKeyActive(keyboard.KEY_NEXT_CHAT_TAB)) + { + chatWindow->nextTab(); + return; + } + else if (keyboard.isKeyActive(keyboard.KEY_PREV_SOCIAL_TAB)) + { + socialWindow->prevTab(); + return; + } + else if (keyboard.isKeyActive(keyboard.KEY_NEXT_SOCIAL_TAB)) + { + socialWindow->nextTab(); + return; + } + } + + const int tKey = keyboard.getKeyIndex(event.key.keysym.sym); + switch (tKey) + { + case KeyboardConfig::KEY_SCROLL_CHAT_UP: + if (chatWindow->isVisible()) + { + chatWindow->scroll(-DEFAULT_CHAT_WINDOW_SCROLL); + used = true; + } + break; + case KeyboardConfig::KEY_SCROLL_CHAT_DOWN: + if (chatWindow->isVisible()) + { + chatWindow->scroll(DEFAULT_CHAT_WINDOW_SCROLL); + used = true; + return; + } + break; + case KeyboardConfig::KEY_WINDOW_HELP: + // In-game Help + if (helpWindow->isVisible()) + helpWindow->setVisible(false); + else + { + helpWindow->loadHelp("index"); + helpWindow->requestMoveToTop(); + } + used = true; + break; + + + + // Quitting confirmation dialog + case KeyboardConfig::KEY_QUIT: + if (!chatWindow->isInputFocused()) + { + quitDialog = new QuitDialog(&quitDialog); + quitDialog->requestMoveToTop(); + return; + } + break; + default: + break; + } + + if (keyboard.isEnabled() && !chatWindow->isInputFocused() + && !gui->getFocusHandler()->getModalFocused() + && mValidSpeed + && !setupWindow->isVisible() + && !player_node->getAwayMode() + && !NpcDialog::isAnyInputFocused()) + { + switch (tKey) + { + case KeyboardConfig::KEY_QUICK_DROP: + dropShortcut->dropFirst(); + break; + + case KeyboardConfig::KEY_QUICK_DROPN: + dropShortcut->dropItems(); + break; + + case KeyboardConfig::KEY_SWITCH_QUICK_DROP: + if (!player_node->getDisableGameModifiers()) + player_node->changeQuickDropCounter(); + break; + + case KeyboardConfig::KEY_MAGIC_INMA1: + actorSpriteManager->healTarget(player_node); + setValidSpeed(); + break; + + case KeyboardConfig::KEY_MAGIC_ITENPLZ: + if (Net::getPlayerHandler()->canUseMagic() + && PlayerInfo::getAttribute(MP) >= 3) + { + actorSpriteManager->itenplz(); + } + setValidSpeed(); + break; + + case KeyboardConfig::KEY_CRAZY_MOVES: + player_node->crazyMove(); + break; + + case KeyboardConfig::KEY_CHANGE_CRAZY_MOVES_TYPE: + if (!player_node->getDisableGameModifiers()) + player_node->changeCrazyMoveType(); + break; + + case KeyboardConfig::KEY_CHANGE_PICKUP_TYPE: + if (!player_node->getDisableGameModifiers()) + player_node->changePickUpType(); + break; + + case KeyboardConfig::KEY_MOVE_TO_TARGET: + if (!keyboard.isKeyActive(keyboard.KEY_TARGET_ATTACK) + && !keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->moveToTarget(); + } + break; + + case KeyboardConfig::KEY_MOVE_TO_HOME: + if (!keyboard.isKeyActive(keyboard.KEY_TARGET_ATTACK) + && !keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->moveToHome(); + } + setValidSpeed(); + break; + + case KeyboardConfig::KEY_SET_HOME: + player_node->setHome(); + break; + + case KeyboardConfig::KEY_INVERT_DIRECTION: + if (!player_node->getDisableGameModifiers()) + player_node->invertDirection(); + break; + + case KeyboardConfig::KEY_CHANGE_ATTACK_WEAPON_TYPE: + if (!player_node->getDisableGameModifiers()) + player_node->changeAttackWeaponType(); + break; + + case KeyboardConfig::KEY_CHANGE_ATTACK_TYPE: + if (!player_node->getDisableGameModifiers()) + player_node->changeAttackType(); + break; + + case KeyboardConfig::KEY_CHANGE_FOLLOW_MODE: + if (!player_node->getDisableGameModifiers()) + player_node->changeFollowMode(); + break; + + case KeyboardConfig::KEY_CHANGE_IMITATION_MODE: + if (!player_node->getDisableGameModifiers()) + player_node->changeImitationMode(); + break; + + case KeyboardConfig::KEY_MAGIC_ATTACK: + player_node->magicAttack(); + break; + + case KeyboardConfig::KEY_SWITCH_MAGIC_ATTACK: + if (!player_node->getDisableGameModifiers()) + player_node->switchMagicAttack(); + break; + + case KeyboardConfig::KEY_CHANGE_MOVE_TO_TARGET: + if (!player_node->getDisableGameModifiers()) + player_node->changeMoveToTargetType(); + break; + + case KeyboardConfig::KEY_COPY_EQUIPED_OUTFIT: + outfitWindow->copyFromEquiped(); + break; + + case KeyboardConfig::KEY_DISABLE_GAME_MODIFIERS: + player_node->switchGameModifiers(); + break; + + case KeyboardConfig::KEY_CHANGE_AUDIO: + sound.changeAudio(); + break; + + case KeyboardConfig::KEY_AWAY: + player_node->changeAwayMode(); + setValidSpeed(); + break; + + case KeyboardConfig::KEY_CAMERA: + if (!player_node->getDisableGameModifiers()) + viewport->toggleCameraMode(); + setValidSpeed(); + break; + + default: + break; + } + } + + if (keyboard.isEnabled() + && !chatWindow->isInputFocused() + && !NpcDialog::isAnyInputFocused() + && !setupWindow->isVisible() + && !player_node->getAwayMode() + && !keyboard.isKeyActive(keyboard.KEY_TARGET)) + { + const int tKey = keyboard.getKeyIndex(event.key.keysym.sym); + + // Do not activate shortcuts if tradewindow is visible + if (itemShortcutWindow && !tradeWindow->isVisible() + && !setupWindow->isVisible()) + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS) + { + // Checks if any item shortcut is pressed. + for (int i = KeyboardConfig::KEY_SHORTCUT_1; + i <= KeyboardConfig::KEY_SHORTCUT_20; + i++) + { + if (tKey == i && !used) + { + itemShortcut[num]->useItem( + i - KeyboardConfig::KEY_SHORTCUT_1); + break; + } + } + } + } + + switch (tKey) + { + case KeyboardConfig::KEY_PICKUP: + player_node->pickUpItems(); + used = true; + break; + case KeyboardConfig::KEY_SIT: + // Player sit action + if (keyboard.isKeyActive(keyboard.KEY_EMOTE)) + player_node->updateSit(); + else + player_node->toggleSit(); + used = true; + break; + case KeyboardConfig::KEY_HIDE_WINDOWS: + // Hide certain windows + if (!chatWindow->isInputFocused()) + { + statusWindow->setVisible(false); + inventoryWindow->setVisible(false); + shopWindow->setVisible(false); + skillDialog->setVisible(false); + setupWindow->setVisible(false); + equipmentWindow->setVisible(false); + helpWindow->setVisible(false); + debugWindow->setVisible(false); + outfitWindow->setVisible(false); + dropShortcutWindow->setVisible(false); + spellShortcutWindow->setVisible(false); + botCheckerWindow->setVisible(false); + socialWindow->setVisible(false); + } + break; + case KeyboardConfig::KEY_WINDOW_STATUS: + requestedWindow = statusWindow; + break; + case KeyboardConfig::KEY_WINDOW_INVENTORY: + requestedWindow = inventoryWindow; + break; + case KeyboardConfig::KEY_WINDOW_SHOP: + requestedWindow = shopWindow; + break; + case KeyboardConfig::KEY_WINDOW_EQUIPMENT: + requestedWindow = equipmentWindow; + break; + case KeyboardConfig::KEY_WINDOW_SKILL: + requestedWindow = skillDialog; + break; + case KeyboardConfig::KEY_WINDOW_KILLS: + requestedWindow = killStats; + break; + case KeyboardConfig::KEY_WINDOW_MINIMAP: + minimap->toggle(); + break; + case KeyboardConfig::KEY_WINDOW_CHAT: + requestedWindow = chatWindow; + break; + case KeyboardConfig::KEY_WINDOW_SHORTCUT: + requestedWindow = itemShortcutWindow; + break; + case KeyboardConfig::KEY_WINDOW_SETUP: + requestedWindow = setupWindow; + break; + case KeyboardConfig::KEY_WINDOW_DEBUG: + requestedWindow = debugWindow; + break; + case KeyboardConfig::KEY_WINDOW_SOCIAL: + requestedWindow = socialWindow; + break; + case KeyboardConfig::KEY_WINDOW_EMOTE_SHORTCUT: + requestedWindow = emoteShortcutWindow; + break; + case KeyboardConfig::KEY_WINDOW_OUTFIT: + requestedWindow = outfitWindow; + break; + case KeyboardConfig::KEY_WINDOW_DROP: + requestedWindow = dropShortcutWindow; + break; + case KeyboardConfig::KEY_WINDOW_SPELLS: + requestedWindow = spellShortcutWindow; + break; + case KeyboardConfig::KEY_WINDOW_BOT_CHECKER: + requestedWindow = botCheckerWindow; + break; + case KeyboardConfig::KEY_WINDOW_ONLINE: + requestedWindow = whoIsOnline; + break; + case KeyboardConfig::KEY_SCREENSHOT: + // Screenshot (picture, hence the p) + saveScreenshot(); + used = true; + break; + case KeyboardConfig::KEY_PATHFIND: + // Find path to mouse (debug purpose) + if (!player_node->getDisableGameModifiers()) + { + viewport->toggleDebugPath(); + miniStatusWindow->updateStatus(); + used = true; + } + break; + case KeyboardConfig::KEY_TRADE: + { + // Toggle accepting of incoming trade requests + unsigned int deflt = player_relations.getDefault(); + if (deflt & PlayerRelation::TRADE) + { + localChatTab->chatLog( + _("Ignoring incoming trade requests"), + BY_SERVER); + deflt &= ~PlayerRelation::TRADE; + } + else + { + localChatTab->chatLog( + _("Accepting incoming trade requests"), + BY_SERVER); + deflt |= PlayerRelation::TRADE; + } + + player_relations.setDefault(deflt); + + used = true; + } + break; + default: + break; + } + } + + if (requestedWindow) + { + requestedWindow->setVisible(!requestedWindow->isVisible()); + if (requestedWindow->isVisible()) + requestedWindow->requestMoveToTop(); + used = true; + } + } + // Active event + else if (event.type == SDL_ACTIVEEVENT) + { +// logger->log("SDL_ACTIVEEVENT"); +// logger->log("state: %d", (int)event.active.state); +// logger->log("gain: %d", (int)event.active.gain); + + int fpsLimit = 0; + if (event.active.state & SDL_APPACTIVE) + { + if (event.active.gain) + { // window restore + Client::setIsMinimized(false); + if (player_node && !player_node->getAwayMode()) + fpsLimit = config.getIntValue("fpslimit"); + } + else + { // window minimisation + Client::setIsMinimized(true); + if (player_node && !player_node->getAwayMode()) + fpsLimit = config.getIntValue("altfpslimit"); + } + Client::setFramerate(fpsLimit); + } + + if (event.active.state & SDL_APPINPUTFOCUS) + Client::setInputFocused(event.active.gain); + if (event.active.state & SDL_APPMOUSEFOCUS) + Client::setMouseFocused(event.active.gain); + + if (player_node->getAwayMode()) + { + if (Client::getInputFocused() || Client::getMouseFocused()) + fpsLimit = config.getIntValue("fpslimit"); + else + fpsLimit = config.getIntValue("altfpslimit"); + Client::setFramerate(fpsLimit); + } + } + // Quit event + else if (event.type == SDL_QUIT) + { + Client::setState(STATE_EXIT); + } + + // Push input to GUI when not used + if (!used) + { + try + { + guiInput->pushInput(event); + } + catch (gcn::Exception e) + { + const char *err = e.getMessage().c_str(); + logger->log("Warning: guichan input exception: %s", err); + } + } + + } // End while + + // If the user is configuring the keys then don't respond. + if (!keyboard.isEnabled() || player_node->getAwayMode()) + return; + + if (keyboard.isKeyActive(keyboard.KEY_WEAR_OUTFIT) + || keyboard.isKeyActive(keyboard.KEY_COPY_OUTFIT) + || setupWindow->isVisible()) + { + return; + } + + // Moving player around + if (player_node->isAlive() && (!Being::isTalking() + || keyboard.getKeyIndex(event.key.keysym.sym) + == KeyboardConfig::KEY_TALK) + && !chatWindow->isInputFocused() && !quitDialog) + { + // Get the state of the keyboard keys + keyboard.refreshActiveKeys(); + + // Ignore input if either "ignore" key is pressed + // Stops the character moving about if the user's window manager + // uses "ignore+arrow key" to switch virtual desktops. + if (keyboard.isKeyActive(keyboard.KEY_IGNORE_INPUT_1) || + keyboard.isKeyActive(keyboard.KEY_IGNORE_INPUT_2)) + { + return; + } + + unsigned char direction = 0; + + // Translate pressed keys to movement and direction + if (keyboard.isKeyActive(keyboard.KEY_MOVE_UP) || + (joystick && joystick->isUp())) + { + direction |= Being::UP; + setValidSpeed(); + player_node->cancelFollow(); + } + else if (keyboard.isKeyActive(keyboard.KEY_MOVE_DOWN) || + (joystick && joystick->isDown())) + { + direction |= Being::DOWN; + setValidSpeed(); + player_node->cancelFollow(); + } + + if (keyboard.isKeyActive(keyboard.KEY_MOVE_LEFT) || + (joystick && joystick->isLeft())) + { + direction |= Being::LEFT; + setValidSpeed(); + player_node->cancelFollow(); + } + else if (keyboard.isKeyActive(keyboard.KEY_MOVE_RIGHT) || + (joystick && joystick->isRight())) + { + direction |= Being::RIGHT; + setValidSpeed(); + player_node->cancelFollow(); + } + else if (keyboard.isKeyActive(keyboard.KEY_DIRECT_UP)) + { + if (player_node->getDirection() != Being::UP) + { + if (Client::limitPackets(PACKET_DIRECTION)) + { + player_node->setDirection(Being::UP); + Net::getPlayerHandler()->setDirection(Being::UP); + } + } + } + else if (keyboard.isKeyActive(keyboard.KEY_DIRECT_DOWN)) + { + if (player_node->getDirection() != Being::DOWN) + { + if (Client::limitPackets(PACKET_DIRECTION)) + { + player_node->setDirection(Being::DOWN); + Net::getPlayerHandler()->setDirection(Being::DOWN); + } + } + } + else if (keyboard.isKeyActive(keyboard.KEY_DIRECT_LEFT)) + { + if (player_node->getDirection() != Being::LEFT) + { + if (Client::limitPackets(PACKET_DIRECTION)) + { + player_node->setDirection(Being::LEFT); + Net::getPlayerHandler()->setDirection(Being::LEFT); + } + } + } + else if (keyboard.isKeyActive(keyboard.KEY_DIRECT_RIGHT)) + { + if (player_node->getDirection() != Being::RIGHT) + { + if (Client::limitPackets(PACKET_DIRECTION)) + { + player_node->setDirection(Being::RIGHT); + Net::getPlayerHandler()->setDirection(Being::RIGHT); + } + } + } + + if (keyboard.isKeyActive(keyboard.KEY_EMOTE) && direction != 0) + { + if (player_node->getDirection() != direction) + { + if (Client::limitPackets(PACKET_DIRECTION)) + { + player_node->setDirection(direction); + Net::getPlayerHandler()->setDirection(direction); + } + } + direction = 0; + } + else + { + if (!viewport->getCameraMode()) + { + player_node->specialMove(direction); + } + else + { + int dx = 0; + int dy = 0; + if (direction & Being::LEFT) + dx = -5; + else if (direction & Being::RIGHT) + dx = 5; + + if (direction & Being::UP) + dy = -5; + else if (direction & Being::DOWN) + dy = 5; + viewport->moveCamera(dx, dy); + } + } + + if (((player_node->getAttackType() == 0 + && player_node->getFollow().empty()) || event.type == SDL_KEYDOWN) + && mValidSpeed) + { + // Attacking monsters + if (keyboard.isKeyActive(keyboard.KEY_ATTACK) || + (joystick && joystick->buttonPressed(0))) + { + if (player_node->getTarget()) + player_node->attack(player_node->getTarget(), true); + } + + if (keyboard.isKeyActive(keyboard.KEY_TARGET_ATTACK) + && !keyboard.isKeyActive(keyboard.KEY_MOVE_TO_TARGET)) + { + Being *target = 0; + + bool newTarget = !keyboard.isKeyActive(keyboard.KEY_TARGET); + // A set target has highest priority + if (!player_node->getTarget()) + { + // Only auto target Monsters + target = actorSpriteManager->findNearestLivingBeing( + player_node, 90, ActorSprite::MONSTER); + } + else + { + target = player_node->getTarget(); + } + + player_node->attack2(target, newTarget); + } + } + + if (!keyboard.isKeyActive(keyboard.KEY_EMOTE)) + { + // Target the nearest player/monster/npc + if ((keyboard.isKeyActive(keyboard.KEY_TARGET_PLAYER) || + keyboard.isKeyActive(keyboard.KEY_TARGET_CLOSEST) || + keyboard.isKeyActive(keyboard.KEY_TARGET_NPC) || + (joystick && joystick->buttonPressed(3))) && + !keyboard.isKeyActive(keyboard.KEY_TARGET)) + { + ActorSprite::Type currentTarget = ActorSprite::UNKNOWN; + if (keyboard.isKeyActive(keyboard.KEY_TARGET_CLOSEST) || + (joystick && joystick->buttonPressed(3))) + { + currentTarget = ActorSprite::MONSTER; + } + else if (keyboard.isKeyActive(keyboard.KEY_TARGET_PLAYER)) + { + currentTarget = ActorSprite::PLAYER; + } + else if (keyboard.isKeyActive(keyboard.KEY_TARGET_NPC)) + { + currentTarget = ActorSprite::NPC; + } + + Being *target = actorSpriteManager->findNearestLivingBeing( + player_node, 20, currentTarget); + + if (target && (target != player_node->getTarget() || + currentTarget != mLastTarget)) + { + player_node->setTarget(target); + mLastTarget = currentTarget; + } + } + else + { + mLastTarget = ActorSprite::UNKNOWN; // Reset last target + } + } + + // Talk to the nearest NPC if 't' pressed + if (event.type == SDL_KEYDOWN && + keyboard.getKeyIndex(event.key.keysym.sym) + == KeyboardConfig::KEY_TALK && + !keyboard.isKeyActive(keyboard.KEY_EMOTE)) + { + Being *target = player_node->getTarget(); + + if (target) + { + if (target->canTalk()) + target->talkTo(); + else if (target->getType() == Being::PLAYER) + new BuySellDialog(target->getName()); + } + } + + // Stop attacking if the right key is pressed + if (!keyboard.isKeyActive(keyboard.KEY_ATTACK) + && keyboard.isKeyActive(keyboard.KEY_TARGET) + && !keyboard.isKeyActive(keyboard.KEY_EMOTE)) + { + player_node->stopAttack(); + } + + if (joystick) + { + if (joystick->buttonPressed(1)) + player_node->pickUpItems(); + else if (joystick->buttonPressed(2)) + player_node->toggleSit(); + } + } +} + +/** + * Changes the currently active map. Should only be called while the game is + * running. + */ +void Game::changeMap(const std::string &mapPath) +{ + // Clean up floor items, beings and particles + actorSpriteManager->clear(); + + // Close the popup menu on map change so that invalid options can't be + // executed. + viewport->closePopupMenu(); + viewport->cleanHoverItems(); + + // Unset the map of the player so that its particles are cleared before + // being deleted in the next step + if (player_node) + player_node->setMap(0); + + particleEngine->clear(); + + mMapName = mapPath; + + std::string fullMap = paths.getValue("maps", "maps/") + + mMapName + ".tmx"; + ResourceManager *resman = ResourceManager::getInstance(); + if (!resman->exists(fullMap)) + fullMap += ".gz"; + + // Attempt to load the new map + Map *newMap = MapReader::readMap(fullMap); + + if (!newMap) + { + logger->log("Error while loading %s", fullMap.c_str()); + new OkDialog(_("Could Not Load Map"), + strprintf(_("Error while loading %s"), fullMap.c_str())); + } + + if (newMap) + newMap->addExtraLayer(); + + if (socialWindow) + socialWindow->setMap(newMap); + + // Notify the minimap and actorSpriteManager about the map change + minimap->setMap(newMap); + actorSpriteManager->setMap(newMap); + particleEngine->setMap(newMap); + viewport->setMap(newMap); + + // Initialize map-based particle effects + if (newMap) + newMap->initializeParticleEffects(particleEngine); + + // Start playing new music file when necessary + std::string oldMusic = mCurrentMap ? mCurrentMap->getMusicFile() : ""; + std::string newMusic = newMap ? newMap->getMusicFile() : ""; + if (newMusic != oldMusic) + sound.playMusic(newMusic); + + if (mCurrentMap) + mCurrentMap->saveExtraLayer(); + + delete mCurrentMap; + mCurrentMap = newMap; +// mCurrentMap = 0; + + if (mumbleManager) + mumbleManager->setMap(mapPath); + Mana::Event event(EVENT_MAPLOADED); + event.setString("mapPath", mapPath); + Mana::Event::trigger(CHANNEL_GAME, event); +} + +void Game::updateHistory(SDL_Event &event) +{ + if (!player_node->getAttackType()) + return; + + bool old = false; + + if (event.key.keysym.sym != -1) + { + int key = keyboard.getKeyIndex(event.key.keysym.sym); + int time = cur_time; + int idx = -1; + for (int f = 0; f < MAX_LASTKEYS; f ++) + { + if (mLastKeys[f].key == key) + { + idx = f; + old = true; + break; + } + else if (idx >= 0 && mLastKeys[f].time < mLastKeys[idx].time) + idx = f; + } + if (idx < 0) + { + idx = 0; + for (int f = 0; f < MAX_LASTKEYS; f ++) + { + if (mLastKeys[f].key == -1 + || mLastKeys[f].time < mLastKeys[idx].time) + { + idx = f; + } + } + } + + if (idx < 0) + idx = 0; + + if (!old) + { + mLastKeys[idx].time = time; + mLastKeys[idx].key = key; + mLastKeys[idx].cnt = 0; + } + else + { + mLastKeys[idx].cnt++; + } + } +} + +void Game::checkKeys() +{ + const int timeRange = 120; + const int cntInTime = 130; + + if (!player_node->getAttackType()) + return; + + for (int f = 0; f < MAX_LASTKEYS; f ++) + { + if (mLastKeys[f].key != -1) + { + if (mLastKeys[f].time + timeRange < cur_time) + { + if (mLastKeys[f].cnt > cntInTime) + mValidSpeed = false; + mLastKeys[f].key = -1; + } + } + } +} + +void Game::setValidSpeed() +{ + clearKeysArray(); + mValidSpeed = true; +} + +void Game::clearKeysArray() +{ + for (int f = 0; f < MAX_LASTKEYS; f ++) + { + mLastKeys[f].time = 0; + mLastKeys[f].key = -1; + mLastKeys[f].cnt = 0; + } +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 000000000..53ec2ae15 --- /dev/null +++ b/src/game.h @@ -0,0 +1,110 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GAME_H +#define GAME_H + +#include +#include "gui/sdlinput.h" + +#define MAX_LASTKEYS 10 + +extern volatile int cur_time; +//extern std::string map_path; // TODO: Get rid of this global + +class Map; +class WindowMenu; + +struct LastKey +{ + int key; + int time; + int cnt; +}; + +/** + * The main class responsible for running the game. The game starts after you + * have selected your character. + */ +class Game +{ + public: + /** + * Constructs the game, creating all the managers, handlers, engines + * and GUI windows that make up the game. + */ + Game(); + + /** + * Destructor, cleans up the game. + */ + ~Game(); + + /** + * Provides access to the game instance. + */ + static Game *instance() + { return mInstance; } + + /** + * This method takes the game a small step further. It is called 100 + * times per second. + */ + void logic(); + + void handleInput(); + + void changeMap(const std::string &mapName); + + /** + * Returns the currently active map. + */ + Map *getCurrentMap() + { return mCurrentMap; } + + const std::string &getCurrentMapName() const + { return mMapName; } + + void setValidSpeed(); + + private: + + void updateHistory(SDL_Event &event); + + void checkKeys(); + + void clearKeysArray(); + + + int mLastTarget; + +// WindowMenu *mWindowMenu; + + Map *mCurrentMap; + std::string mMapName; + bool mValidSpeed; + int mLastAction; + LastKey mLastKeys[MAX_LASTKEYS]; + + static Game *mInstance; +}; + +#endif diff --git a/src/graphics.cpp b/src/graphics.cpp new file mode 100644 index 000000000..afda085dd --- /dev/null +++ b/src/graphics.cpp @@ -0,0 +1,426 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "graphics.h" +#include "log.h" + +#include "resources/image.h" +#include "resources/imageloader.h" + +#include "utils/stringutils.h" + +// +#include "SDL_gfxBlitFunc.h" + +Graphics::Graphics(): + mWidth(0), + mHeight(0), + mBpp(0), + mFullscreen(false), + mHWAccel(false), + mBlitMode(BLIT_NORMAL) +{ +} + +Graphics::~Graphics() +{ + _endDraw(); +} + +bool Graphics::setVideoMode(int w, int h, int bpp, bool fs, bool hwaccel) +{ + logger->log("Setting video mode %dx%d %s", + w, h, fs ? "fullscreen" : "windowed"); + + logger->log("Bits per pixel: %d", bpp); + + int displayFlags = SDL_ANYFORMAT; + + mWidth = w; + mHeight = h; + mBpp = bpp; + mFullscreen = fs; + mHWAccel = hwaccel; + + if (fs) + displayFlags |= SDL_FULLSCREEN; + + if (hwaccel) + displayFlags |= SDL_HWSURFACE | SDL_DOUBLEBUF; + else + displayFlags |= SDL_SWSURFACE; + + setTarget(SDL_SetVideoMode(w, h, bpp, displayFlags)); + + if (!mTarget) + return false; + + char videoDriverName[65]; + + if (SDL_VideoDriverName(videoDriverName, 64)) + logger->log("Using video driver: %s", videoDriverName); + else + logger->log1("Using video driver: unknown"); + + const SDL_VideoInfo *vi = SDL_GetVideoInfo(); + + logger->log("Possible to create hardware surfaces: %s", + ((vi->hw_available) ? "yes" : "no")); + logger->log("Window manager available: %s", + ((vi->wm_available) ? "yes" : "no")); + logger->log("Accelerated hardware to hardware blits: %s", + ((vi->blit_hw) ? "yes" : "no")); + logger->log("Accelerated hardware to hardware colorkey blits: %s", + ((vi->blit_hw_CC) ? "yes" : "no")); + logger->log("Accelerated hardware to hardware alpha blits: %s", + ((vi->blit_hw_A) ? "yes" : "no")); + logger->log("Accelerated software to hardware blits: %s", + ((vi->blit_sw) ? "yes" : "no")); + logger->log("Accelerated software to hardware colorkey blits: %s", + ((vi->blit_sw_CC) ? "yes" : "no")); + logger->log("Accelerated software to hardware alpha blits: %s", + ((vi->blit_sw_A) ? "yes" : "no")); + logger->log("Accelerated color fills: %s", + ((vi->blit_fill) ? "yes" : "no")); + logger->log("Available video memory: %d", vi->video_mem); + + return true; +} + +bool Graphics::setFullscreen(bool fs) +{ + if (mFullscreen == fs) + return true; + + return setVideoMode(mWidth, mHeight, mBpp, fs, mHWAccel); +} + +int Graphics::getWidth() const +{ + return mWidth; +} + +int Graphics::getHeight() const +{ + return mHeight; +} + +bool Graphics::drawImage(Image *image, int x, int y) +{ + if (image) + { + return drawImage(image, 0, 0, x, y, + image->mBounds.w, image->mBounds.h); + } + else + { + return false; + } +} + +bool Graphics::drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor _UNUSED_) +{ + // Check that preconditions for blitting are met. + if (!mTarget || !image) + return false; + if (!image->mSDLSurface) + return false; + + Image *tmpImage = image->SDLgetScaledImage(desiredWidth, desiredHeight); +// logger->log("SDLgetScaledImage " + tmpImage->getIdPath() +// + toString(desiredWidth) + "," + toString(desiredHeight)); + + bool returnValue = false; + + if (!tmpImage) + return false; + if (!tmpImage->mSDLSurface) + return false; + + dstX += mClipStack.top().xOffset; + dstY += mClipStack.top().yOffset; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + SDL_Rect dstRect; + SDL_Rect srcRect; + dstRect.x = static_cast(dstX); + dstRect.y = static_cast(dstY); + srcRect.x = static_cast(srcX); + srcRect.y = static_cast(srcY); + srcRect.w = static_cast(width); + srcRect.h = static_cast(height); + + returnValue = !(SDL_BlitSurface(tmpImage->mSDLSurface, + &srcRect, mTarget, &dstRect) < 0); + + delete tmpImage; + + return returnValue; +} + +bool Graphics::drawImage(Image *image, int srcX, int srcY, int dstX, int dstY, + int width, int height, bool) +{ + // Check that preconditions for blitting are met. + if (!mTarget || !image || !image->mSDLSurface) + return false; + + dstX += mClipStack.top().xOffset; + dstY += mClipStack.top().yOffset; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + SDL_Rect dstRect; + SDL_Rect srcRect; + dstRect.x = static_cast(dstX); + dstRect.y = static_cast(dstY); + srcRect.x = static_cast(srcX); + srcRect.y = static_cast(srcY); + srcRect.w = static_cast(width); + srcRect.h = static_cast(height); + + if (mBlitMode == BLIT_NORMAL) + { + return !(SDL_BlitSurface(image->mSDLSurface, &srcRect, + mTarget, &dstRect) < 0); + } + else + { + return !(SDL_gfxBlitRGBA(image->mSDLSurface, &srcRect, + mTarget, &dstRect) < 0); + } +} + +void Graphics::drawImage(gcn::Image const *image, int srcX, int srcY, + int dstX, int dstY, int width, int height) +{ + ProxyImage const *srcImage = + dynamic_cast< ProxyImage const * >(image); + if (!srcImage) + return; + drawImage(srcImage->getImage(), srcX, srcY, dstX, dstY, + width, height, true); +} + +void Graphics::drawImagePattern(Image *image, int x, int y, int w, int h) +{ + // Check that preconditions for blitting are met. + if (!mTarget || !image) + return; + if (!image->mSDLSurface) + return; + + const int iw = image->getWidth(); + const int ih = image->getHeight(); + + if (iw == 0 || ih == 0) return; + + for (int py = 0; py < h; py += ih) // Y position on pattern plane + { + int dh = (py + ih >= h) ? h - py : ih; + int srcY = image->mBounds.y; + int dstY = y + py + mClipStack.top().yOffset; + + for (int px = 0; px < w; px += iw) // X position on pattern plane + { + int dw = (px + iw >= w) ? w - px : iw; + int srcX = image->mBounds.x; + int dstX = x + px + mClipStack.top().xOffset; + + SDL_Rect dstRect; + SDL_Rect srcRect; + dstRect.x = static_cast(dstX); + dstRect.y = static_cast(dstY); + srcRect.x = static_cast(srcX); + srcRect.y = static_cast(srcY); + srcRect.w = static_cast(dw); + srcRect.h = static_cast(dh); + + SDL_BlitSurface(image->mSDLSurface, &srcRect, mTarget, &dstRect); + } + } +} + +void Graphics::drawRescaledImagePattern(Image *image, int x, int y, + int w, int h, int scaledWidth, + int scaledHeight) +{ + // Check that preconditions for blitting are met. + if (!mTarget || !image) + return; + if (!image->mSDLSurface) + return; + + if (scaledHeight == 0 || scaledWidth == 0) + return; + + Image *tmpImage = image->SDLgetScaledImage(scaledWidth, scaledHeight); + if (!tmpImage) + return; + +// logger->log("SDLgetScaledImageS " + tmpImage->getIdPath() +// + toString(scaledWidth) + "," + toString(scaledHeight)); + + const int iw = tmpImage->getWidth(); + const int ih = tmpImage->getHeight(); + + if (iw == 0 || ih == 0) + return; + + for (int py = 0; py < h; py += ih) // Y position on pattern plane + { + int dh = (py + ih >= h) ? h - py : ih; + int srcY = tmpImage->mBounds.y; + int dstY = y + py + mClipStack.top().yOffset; + + for (int px = 0; px < w; px += iw) // X position on pattern plane + { + int dw = (px + iw >= w) ? w - px : iw; + int srcX = tmpImage->mBounds.x; + int dstX = x + px + mClipStack.top().xOffset; + + SDL_Rect dstRect; + SDL_Rect srcRect; + dstRect.x = static_cast(dstX); + dstRect.y = static_cast(dstY); + srcRect.x = static_cast(srcX); + srcRect.y = static_cast(srcY); + srcRect.w = static_cast(dw); + srcRect.h = static_cast(dh); + + SDL_BlitSurface(tmpImage->mSDLSurface, &srcRect, + mTarget, &dstRect); + } + } + + delete tmpImage; +} + +void Graphics::drawImageRect(int x, int y, int w, int h, + Image *topLeft, Image *topRight, + Image *bottomLeft, Image *bottomRight, + Image *top, Image *right, + Image *bottom, Image *left, + Image *center) +{ + pushClipArea(gcn::Rectangle(x, y, w, h)); + + const bool drawMain = center && topLeft && topRight + && bottomLeft && bottomRight; + + // Draw the center area + if (center && drawMain) + { + drawImagePattern(center, + topLeft->getWidth(), topLeft->getHeight(), + w - topLeft->getWidth() - topRight->getWidth(), + h - topLeft->getHeight() - bottomLeft->getHeight()); + } + + // Draw the sides + if (top && left && bottom && right) + { + drawImagePattern(top, + left->getWidth(), 0, + w - left->getWidth() - right->getWidth(), top->getHeight()); + drawImagePattern(bottom, + left->getWidth(), h - bottom->getHeight(), + w - left->getWidth() - right->getWidth(), + bottom->getHeight()); + drawImagePattern(left, + 0, top->getHeight(), + left->getWidth(), + h - top->getHeight() - bottom->getHeight()); + drawImagePattern(right, + w - right->getWidth(), top->getHeight(), + right->getWidth(), + h - top->getHeight() - bottom->getHeight()); + } + + // Draw the corners + if (drawMain) + { + drawImage(topLeft, 0, 0); + drawImage(topRight, w - topRight->getWidth(), 0); + drawImage(bottomLeft, 0, h - bottomLeft->getHeight()); + drawImage(bottomRight, + w - bottomRight->getWidth(), + h - bottomRight->getHeight()); + } + + popClipArea(); +} + +void Graphics::drawImageRect(int x, int y, int w, int h, + const ImageRect &imgRect) +{ + drawImageRect(x, y, w, h, + imgRect.grid[0], imgRect.grid[2], imgRect.grid[6], imgRect.grid[8], + imgRect.grid[1], imgRect.grid[5], imgRect.grid[7], imgRect.grid[3], + imgRect.grid[4]); +} + +void Graphics::updateScreen() +{ + SDL_Flip(mTarget); +} + +SDL_Surface *Graphics::getScreenshot() +{ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + int rmask = 0xff000000; + int gmask = 0x00ff0000; + int bmask = 0x0000ff00; +#else + int rmask = 0x000000ff; + int gmask = 0x0000ff00; + int bmask = 0x00ff0000; +#endif + int amask = 0x00000000; + + SDL_Surface *screenshot = SDL_CreateRGBSurface(SDL_SWSURFACE, mTarget->w, + mTarget->h, 24, rmask, gmask, bmask, amask); + + if (screenshot) + SDL_BlitSurface(mTarget, NULL, screenshot, NULL); + + return screenshot; +} + +bool Graphics::drawNet(int x1, int y1, int x2, int y2, int width, int height) +{ + for (int y = y1; y < y2; y += height) + drawLine(x1, y, x2, y); + + for (int x = x1; x < x2; x += width) + drawLine(x, y1, x, y2); + + return true; +} diff --git a/src/graphics.h b/src/graphics.h new file mode 100644 index 000000000..01224533e --- /dev/null +++ b/src/graphics.h @@ -0,0 +1,241 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class ImageRect; + +struct SDL_Surface; + +static const int defaultScreenWidth = 800; +static const int defaultScreenHeight = 600; + +/** + * 9 images defining a rectangle. 4 corners, 4 sides and a middle area. The + * topology is as follows: + * + *
+ *  !-----!-----------------!-----!
+ *  !  0  !        1        !  2  !
+ *  !-----!-----------------!-----!
+ *  !  3  !        4        !  5  !
+ *  !-----!-----------------!-----!
+ *  !  6  !        7        !  8  !
+ *  !-----!-----------------!-----!
+ * 
+ * + * Sections 0, 2, 6 and 8 will remain as is. 1, 3, 4, 5 and 7 will be + * repeated to fit the size of the widget. + */ +struct ImageRect +{ + enum ImagePosition + { + UPPER_LEFT = 0, + UPPER_CENTER = 1, + UPPER_RIGHT = 2, + LEFT = 3, + CENTER = 4, + RIGHT = 5, + LOWER_LEFT = 6, + LOWER_CENTER = 7, + LOWER_RIGHT = 8 + }; + + Image *grid[9]; +}; + +/** + * A central point of control for graphics. + */ +class Graphics : public gcn::SDLGraphics +{ + public: + enum BlitMode + { + BLIT_NORMAL = 0, + BLIT_GFX + }; + + /** + * Constructor. + */ + Graphics(); + + /** + * Destructor. + */ + virtual ~Graphics(); + + /** + * Try to create a window with the given settings. + */ + virtual bool setVideoMode(int w, int h, int bpp, + bool fs, bool hwaccel); + + /** + * Set fullscreen mode. + */ + bool setFullscreen(bool fs); + + /** + * Blits an image onto the screen. + * + * @return true if the image was blitted properly + * false otherwise. + */ + bool drawImage(Image *image, int x, int y); + + /** + * Overrides with our own drawing method. + */ + void drawImage(gcn::Image const *image, int srcX, int srcY, + int dstX, int dstY, int width, int height); + + /** + * Draws a resclaled version of the image + */ + bool drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight) + { + return drawRescaledImage(image, srcX, srcY, + dstX, dstY, + width, height, + desiredWidth, desiredHeight, + false); + }; + + /** + * Draws a resclaled version of the image + */ + virtual bool drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor = false); + + /** + * Blits an image onto the screen. + * + * @return true if the image was blitted properly + * false otherwise. + */ + virtual bool drawImage(Image *image, + int srcX, int srcY, + int dstX, int dstY, + int width, int height, + bool useColor = false); + + virtual void drawImagePattern(Image *image, + int x, int y, + int w, int h); + + /** + * Draw a pattern based on a rescaled version of the given image... + */ + virtual void drawRescaledImagePattern(Image *image, + int x, int y, int w, int h, + int scaledWidth, + int scaledHeight); + + /** + * Draws a rectangle using images. 4 corner images, 4 side images and 1 + * image for the inside. + */ + void drawImageRect(int x, int y, int w, int h, + Image *topLeft, Image *topRight, + Image *bottomLeft, Image *bottomRight, + Image *top, Image *right, + Image *bottom, Image *left, + Image *center); + + /** + * Draws a rectangle using images. 4 corner images, 4 side images and 1 + * image for the inside. + */ + void drawImageRect(int x, int y, int w, int h, + const ImageRect &imgRect); + + /** + * Draws a rectangle using images. 4 corner images, 4 side images and 1 + * image for the inside. + */ + inline void drawImageRect(const gcn::Rectangle &area, + const ImageRect &imgRect) + { drawImageRect(area.x, area.y, area.width, area.height, imgRect); } + + void setBlitMode(BlitMode mode) + { mBlitMode = mode; } + + BlitMode getBlitMode() + { return mBlitMode; } + + /** + * Updates the screen. This is done by either copying the buffer to the + * screen or swapping pages. + */ + virtual void updateScreen(); + + /** + * Returns the width of the screen. + */ + int getWidth() const; + + /** + * Returns the height of the screen. + */ + int getHeight() const; + + /** + * Takes a screenshot and returns it as SDL surface. + */ + virtual SDL_Surface *getScreenshot(); + + virtual bool drawNet(int x1, int y1, int x2, int y2, + int width, int height); + + gcn::Font *getFont() const + { return mFont; } + + protected: + int mWidth; + int mHeight; + int mBpp; + bool mFullscreen; + bool mHWAccel; + BlitMode mBlitMode; +}; + +extern Graphics *graphics; + +#endif diff --git a/src/gui/beingpopup.cpp b/src/gui/beingpopup.cpp new file mode 100644 index 000000000..6b3a25ee4 --- /dev/null +++ b/src/gui/beingpopup.cpp @@ -0,0 +1,142 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/beingpopup.h" + +#include "being.h" +#include "graphics.h" +#include "units.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "gui/widgets/label.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + + +BeingPopup::BeingPopup(): + Popup("BeingPopup") +{ + // Being Name + mBeingName = new Label("A"); + mBeingName->setFont(boldFont); + mBeingName->setPosition(getPadding(), getPadding()); + + const int fontHeight = mBeingName->getHeight() + getPadding(); + + // Being's party + mBeingParty = new Label("A"); + mBeingParty->setPosition(getPadding(), fontHeight); + + // Being's party + mBeingGuild = new Label("A"); + mBeingGuild->setPosition(getPadding(), 2 * fontHeight); + + mBeingRank = new Label("A"); + mBeingRank->setPosition(getPadding(), 3 * fontHeight); + + add(mBeingName); + add(mBeingParty); + add(mBeingGuild); + add(mBeingRank); +} + +BeingPopup::~BeingPopup() +{ +} + +void BeingPopup::show(int x, int y, Being *b) +{ + if (!b) + { + setVisible(false); + return; + } + + Label *label1 = mBeingParty; + Label *label2 = mBeingGuild; + Label *label3 = mBeingRank; + + mBeingName->setCaption(b->getName()); + mBeingName->adjustSize(); + label1->setCaption(""); + label2->setCaption(""); + label3->setCaption(""); + + if (!(b->getPartyName().empty())) + { + label1->setCaption(strprintf(_("Party: %s"), + b->getPartyName().c_str())); + label1->adjustSize(); + } + else + { + label3 = label2; + label2 = label1; + label1 = 0; + } + + if (!(b->getGuildName().empty())) + { + label2->setCaption(strprintf(_("Guild: %s"), + b->getGuildName().c_str())); + label2->adjustSize(); + } + else + { + label3 = label2; + label2 = 0; + } + + if (b->getPvpRank() > 0) + { + label3->setCaption(strprintf(_("Pvp rank: %d"), b->getPvpRank())); + label3->adjustSize(); + } + else + { + label3 = 0; + } + + int minWidth = mBeingName->getWidth(); + if (label1 && label1->getWidth() > minWidth) + minWidth = label1->getWidth(); + if (label2 && label2->getWidth() > minWidth) + minWidth = label2->getWidth(); + if (label3 && label3->getWidth() > minWidth) + minWidth = label3->getWidth(); + + int height = getFont()->getHeight(); + if (label1) + height += getFont()->getHeight(); + if (label2) + height += getFont()->getHeight(); + if (label3) + height += getFont()->getHeight(); + + setContentSize(minWidth + 10, height + 10); + + position(x, y); + return; +} diff --git a/src/gui/beingpopup.h b/src/gui/beingpopup.h new file mode 100644 index 000000000..d43f8e105 --- /dev/null +++ b/src/gui/beingpopup.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BEINGPOPUP_H +#define BEINGPOPUP_H + +#include "gui/widgets/popup.h" + +class Being; +class Label; + +/** + * A popup that displays information about a being. + */ +class BeingPopup : public Popup +{ + public: + /** + * Constructor. Initializes the being popup. + */ + BeingPopup(); + + /** + * Destructor. Cleans up the being popup on deletion. + */ + ~BeingPopup(); + + /** + * Sets the info to be displayed given a particular player. + */ + void show(int x, int y, Being *b); + + // TODO: Add a version for monsters, NPCs, etc? + + private: + Label *mBeingName; + Label *mBeingParty; + Label *mBeingGuild; + Label *mBeingRank; +}; + +#endif // BEINGPOPUP_H diff --git a/src/gui/botcheckerwindow.cpp b/src/gui/botcheckerwindow.cpp new file mode 100644 index 000000000..f7bd7d060 --- /dev/null +++ b/src/gui/botcheckerwindow.cpp @@ -0,0 +1,413 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "botcheckerwindow.h" + +#include +#include +#include +#include + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/table.h" + +#include "actorspritemanager.h" +#include "chat.h" +#include "configuration.h" +#include "localplayer.h" +#include "main.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + + +#define COLUMNS_NR 5 // name plus listbox +#define NAME_COLUMN 0 +#define TIME_COLUMN 1 + +#define ROW_HEIGHT 12 +// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... excep +// that, right now, the widget doesn't exactly scale either. +#define NAME_COLUMN_WIDTH 185 +#define TIME_COLUMN_WIDTH 70 + +#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column) + +class UsersTableModel : public TableModel +{ +public: + UsersTableModel() : + mPlayers(0) + { + playersUpdated(); + } + + virtual ~UsersTableModel() + { + freeWidgets(); + } + + virtual int getRows() const + { + return static_cast(mPlayers.size()); + } + + virtual int getColumns() const + { + return COLUMNS_NR; + } + + virtual int getRowHeight() const + { + return ROW_HEIGHT; + } + + virtual int getColumnWidth(int index) const + { + if (index == NAME_COLUMN) + return NAME_COLUMN_WIDTH; + else + return TIME_COLUMN_WIDTH; + } + + virtual void playersUpdated() + { + signalBeforeUpdate(); + + freeWidgets(); + mPlayers.clear(); + if (actorSpriteManager && botCheckerWindow + && botCheckerWindow->mEnabled) + { + std::set beings = actorSpriteManager->getAll(); + ActorSprites::iterator i = beings.begin(); + for (ActorSprites::const_iterator i = beings.begin(); + i != beings.end(); i++) + { + Being *being = dynamic_cast(*i); + + if (being && being->getType() == Being::PLAYER + && being != player_node && being->getName() != "") + { + mPlayers.push_back(being); + } + } + } + + unsigned int curTime = cur_time; + // set up widgets + for (unsigned int r = 0; r < mPlayers.size(); ++r) + { + if (!mPlayers.at(r)) + continue; + + std::string name = mPlayers.at(r)->getName(); + gcn::Widget *widget = new Label(name); + + mWidgets.push_back(widget); + + if (mPlayers.at(r)->getAttackTime() != 0) + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getAttackTime())); + } + else + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTestTime()) + "?"); + } + mWidgets.push_back(widget); + + if (mPlayers.at(r)->getTalkTime() != 0) + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTalkTime())); + } + else + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTestTime()) + "?"); + } + mWidgets.push_back(widget); + + if (mPlayers.at(r)->getMoveTime() != 0) + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getMoveTime())); + } + else + { + widget = new Label(toString(curTime + - mPlayers.at(r)->getTestTime()) + "?"); + } + mWidgets.push_back(widget); + + std::string str; + bool talkBot = false; + bool moveBot = false; + bool attackBot = false; + bool otherBot = false; + + if (curTime - mPlayers.at(r)->getTestTime() > 2 * 60) + { + int attack = curTime - (mPlayers.at(r)->getAttackTime() + ? mPlayers.at(r)->getAttackTime() + : mPlayers.at(r)->getTestTime()); + int talk = curTime - (mPlayers.at(r)->getTalkTime() + ? mPlayers.at(r)->getTalkTime() + : mPlayers.at(r)->getTestTime()) - attack; + int move = curTime - (mPlayers.at(r)->getMoveTime() + ? mPlayers.at(r)->getMoveTime() + : mPlayers.at(r)->getTestTime()) - attack; + int other = curTime - (mPlayers.at(r)->getOtherTime() + ? mPlayers.at(r)->getMoveTime() + : mPlayers.at(r)->getOtherTime()) - attack; + + if (attack < 2 * 60) + attackBot = true; + + // attacking but not talking more than 2 minutes + if (talk > 2 * 60 && talk > 2 * 60) + { + talkBot = true; + str += toString((talk) / 60) + " "; + } + + // attacking but not moving more than 2 minutes + if (move > 2 * 60 && move > 2 * 60) + { + moveBot = true; + str += toString((move) / 60); + } + + // attacking but not other activity more than 2 minutes + if (move > 2 * 60 && other > 2 * 60) + otherBot = true; + } + + if (str.length() > 0) + { + if (attackBot && talkBot && moveBot && otherBot) + str = "bot!! " + str; + else if (attackBot && talkBot && moveBot) + str = "bot! " + str; + else if (talkBot && moveBot) + str = "bot " + str; + else if (talkBot || moveBot) + str = "bot? " + str; + } + else + { + str = "ok"; + } + + widget = new Label(str); + mWidgets.push_back(widget); + + } + + signalAfterUpdate(); + } + + virtual void updateModelInRow(int row _UNUSED_) + { + } + + + virtual gcn::Widget *getElementAt(int row, int column) const + { + return mWidgets[WIDGET_AT(row, column)]; + } + + virtual void freeWidgets() + { + for (std::vector::const_iterator it = mWidgets.begin(); + it != mWidgets.end(); it++) + { + delete *it; + } + + mWidgets.clear(); + } + +protected: + std::vector mPlayers; + std::vector mWidgets; +}; + + +BotCheckerWindow::BotCheckerWindow(): + Window(_("Bot Checker")), + mEnabled(false) +{ + int w = 500; + int h = 250; + + mLastUpdateTime = 0; + mNeedUpdate = false; + + mTableModel = new UsersTableModel(); + mTable = new GuiTable(mTableModel); + mTable->setOpaque(false); + mTable->setLinewiseSelection(true); + mTable->setWrappingEnabled(true); + mTable->setActionEventId("skill"); + mTable->addActionListener(this); + + mPlayerTableTitleModel = new StaticTableModel(1, COLUMNS_NR); + mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH); + + for (int f = 0; f < 4; f++) + { + mPlayerTableTitleModel->fixColumnWidth(TIME_COLUMN + f, + TIME_COLUMN_WIDTH); + } + + mPlayerTitleTable = new GuiTable(mPlayerTableTitleModel); + //mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf)); + mPlayerTitleTable->setHeight(1); + + mPlayerTableTitleModel->set(0, 0, new Label(_("Name"))); + mPlayerTableTitleModel->set(0, 1, new Label(_("Attack"))); + mPlayerTableTitleModel->set(0, 2, new Label(_("Talk"))); + mPlayerTableTitleModel->set(0, 3, new Label(_("Move"))); + mPlayerTableTitleModel->set(0, 4, new Label(_("Result"))); + + mPlayerTitleTable->setLinewiseSelection(true); + + setWindowName("BotCheckerWindow"); + setCloseButton(true); + setDefaultSize(w, h, ImageRect::CENTER); + + playersScrollArea = new ScrollArea(mTable); + + mIncButton = new Button(_("Reset"), "reset", this); + playersScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mPlayerTitleTable->setPosition(getPadding(), getPadding()); + mPlayerTitleTable->setWidth(w - 10); + mPlayerTitleTable->setHeight(20); + + playersScrollArea->setPosition(getPadding(), 20 + 2*getPadding()); + playersScrollArea->setWidth(w - 15); + playersScrollArea->setHeight(h - 80); + + mIncButton->setPosition(getPadding(), 190 + 3*getPadding()); + mIncButton->setWidth(80); + mIncButton->setHeight(20); + + add(mPlayerTitleTable); + add(playersScrollArea); + add(mIncButton); + + center(); + + setWidth(w); + setHeight(h); + loadWindowState(); + + config.addListener("enableBotCheker", this); + mEnabled = config.getBoolValue("enableBotCheker"); +} + +BotCheckerWindow::~BotCheckerWindow() +{ + config.removeListener("enableBotCheker", this); +} + +void BotCheckerWindow::logic() +{ + if (mEnabled && mTableModel) + { + unsigned int nowTime = cur_time; + if (nowTime - mLastUpdateTime > 5 && mNeedUpdate) + { + mTableModel->playersUpdated(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + else if (nowTime - mLastUpdateTime > 15) + { + mTableModel->playersUpdated(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + } + + Window::logic(); +} + +void BotCheckerWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "reset") + { + reset(); + mNeedUpdate = true; + } + +} + +void BotCheckerWindow::update() +{ +} + +void BotCheckerWindow::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); +} + +void BotCheckerWindow::updateList() +{ + if (mTableModel) + mNeedUpdate = true; +} + +void BotCheckerWindow::reset() +{ + if (actorSpriteManager) + { + std::set beings = actorSpriteManager->getAll(); + ActorSprites::iterator i = beings.begin(); + for (ActorSprites::const_iterator i = beings.begin(); + i != beings.end(); i++) + { + Being *being = dynamic_cast(*i); + + if (being && being->getType() == Being::PLAYER + && being != player_node && being->getName() != "") + { + being->resetCounters(); + } + } + } +} + +void BotCheckerWindow::optionChanged(const std::string &name) +{ + if (name == "enableBotCheker") + mEnabled = config.getBoolValue("enableBotCheker"); +} diff --git a/src/gui/botcheckerwindow.h b/src/gui/botcheckerwindow.h new file mode 100644 index 000000000..cb225d3e7 --- /dev/null +++ b/src/gui/botcheckerwindow.h @@ -0,0 +1,95 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef BOTCHECKER_H +#define BOTCHECKER_H + +#include "configlistener.h" + +#include "gui/widgets/window.h" + +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +struct BOTCHK +{ + short id; /**< Index into "botchecker_db" array */ + short lv, sp; +}; + +class GuiTable; +class ScrollArea; +class UsersTableModel; +class StaticTableModel; + +class BotCheckerWindow : public Window, public gcn::ActionListener, + public ConfigListener +{ + public: + friend class UsersTableModel; + + /** + * Constructor. + */ + BotCheckerWindow(); + + /** + * Destructor. + */ + ~BotCheckerWindow(); + + void action(const gcn::ActionEvent &event); + + void update(); + + void logic(); + + void widgetResized(const gcn::Event &event); + + void updateList(); + + void reset(); + + void optionChanged(const std::string &name); + + private: + GuiTable *mTable; + ScrollArea *playersScrollArea; + UsersTableModel *mTableModel; + StaticTableModel *mPlayerTableTitleModel; + GuiTable *mPlayerTitleTable; + gcn::Button *mIncButton; + int mLastUpdateTime; + bool mNeedUpdate; + bool mEnabled; +}; + +extern BotCheckerWindow *botCheckerWindow; + +#endif diff --git a/src/gui/buy.cpp b/src/gui/buy.cpp new file mode 100644 index 000000000..2be6c65a4 --- /dev/null +++ b/src/gui/buy.cpp @@ -0,0 +1,323 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/buy.h" + +#include "shopitem.h" +#include "units.h" + +#include "gui/setup.h" +#include "gui/trade.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shopitems.h" +#include "gui/widgets/shoplistbox.h" +#include "gui/widgets/slider.h" + +#include "shopitem.h" +#include "units.h" + +#include "net/buysellhandler.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +BuyDialog::DialogList BuyDialog::instances; + +BuyDialog::BuyDialog(int npcId): + Window(_("Buy")), + mNpcId(npcId), mMoney(0), mAmountItems(0), mMaxItems(0), mNick("") +{ + init(); +} + +BuyDialog::BuyDialog(std::string nick): + Window(_("Buy")), + mNpcId(-1), mMoney(0), mAmountItems(0), mMaxItems(0), mNick(nick) +{ + init(); + logger->log("BuyDialog::BuyDialog nick:" + mNick); +} + +void BuyDialog::init() +{ + setWindowName("Buy"); + setResizable(true); + setCloseButton(true); + setMinWidth(260); + setMinHeight(230); + setDefaultSize(260, 230, ImageRect::CENTER); + + mShopItems = new ShopItems; + + mShopItemList = new ShopListBox(mShopItems, mShopItems); + mScrollArea = new ScrollArea(mShopItemList); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mSlider = new Slider(1.0); + mQuantityLabel = new Label(strprintf("%d / %d", mAmountItems, mMaxItems)); + mQuantityLabel->setAlignment(gcn::Graphics::CENTER); + mMoneyLabel = new Label(strprintf(_("Price: %s / Total: %s"), + "", "")); + + // TRANSLATORS: This is a narrow symbol used to denote 'increasing'. + // You may change this symbol if your language uses another. + mIncreaseButton = new Button(_("+"), "inc", this); + // TRANSLATORS: This is a narrow symbol used to denote 'decreasing'. + // You may change this symbol if your language uses another. + mDecreaseButton = new Button(_("-"), "dec", this); + mBuyButton = new Button(_("Buy"), "buy", this); + mQuitButton = new Button(_("Quit"), "quit", this); + mAddMaxButton = new Button(_("Max"), "max", this); + + mDecreaseButton->adjustSize(); + mDecreaseButton->setWidth(mIncreaseButton->getWidth()); + + mIncreaseButton->setEnabled(false); + mDecreaseButton->setEnabled(false); + mBuyButton->setEnabled(false); + mSlider->setEnabled(false); + + mSlider->setActionEventId("slider"); + mSlider->addActionListener(this); + mShopItemList->addSelectionListener(this); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mScrollArea, 8, 5).setPadding(3); + place(0, 5, mDecreaseButton); + place(1, 5, mSlider, 3); + place(4, 5, mIncreaseButton); + place(5, 5, mQuantityLabel, 2); + place(7, 5, mAddMaxButton); + place(0, 6, mMoneyLabel, 8); + place(6, 7, mBuyButton); + place(7, 7, mQuitButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); +} + +BuyDialog::~BuyDialog() +{ + delete mShopItems; + mShopItems = 0; + + instances.remove(this); +} + +void BuyDialog::setMoney(int amount) +{ + mMoney = amount; + mShopItemList->setPlayersMoney(amount); + + updateButtonsAndLabels(); +} + +void BuyDialog::reset() +{ + mShopItems->clear(); + mShopItemList->adjustSize(); + + // Reset previous selected items to prevent failing asserts + mShopItemList->setSelected(-1); + mSlider->setValue(0); + + setMoney(0); +} + +void BuyDialog::addItem(int id, int amount, int price) +{ + mShopItems->addItem(id, amount, price); + mShopItemList->adjustSize(); +} + +void BuyDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "quit") + { + close(); + return; + } + + int selectedItem = mShopItemList->getSelected(); + + // The following actions require a valid selection + if (selectedItem < 0 || + selectedItem >= static_cast(mShopItems->getNumberOfElements())) + { + return; + } + + if (event.getId() == "slider") + { + mAmountItems = static_cast(mSlider->getValue()); + updateButtonsAndLabels(); + } + else if (event.getId() == "inc" && mAmountItems < mMaxItems) + { + mAmountItems++; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "dec" && mAmountItems > 1) + { + mAmountItems--; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "max") + { + mAmountItems = mMaxItems; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + // TODO: Actually we'd have a bug elsewhere if this check for the number + // of items to be bought ever fails, Bertram removed the assertions, is + // there a better way to ensure this fails in an _obvious_ way in C++? + else if (event.getId() == "buy" && mAmountItems > 0 && + mAmountItems <= mMaxItems) + { + if (mNpcId != -1) + { + Net::getNpcHandler()->buyItem(mNpcId, + mShopItems->at(selectedItem)->getId(), mAmountItems); + + // Update money and adjust the max number of items that can be bought + mMaxItems -= mAmountItems; + setMoney(mMoney - + mAmountItems * mShopItems->at(selectedItem)->getPrice()); + + // Reset selection + mAmountItems = 1; + mSlider->setValue(1); + mSlider->gcn::Slider::setScale(1, mMaxItems); + } + else if (tradeWindow) + { + ShopItem *item = mShopItems->at(selectedItem); + if (item) + { + Net::getBuySellHandler()->sendBuyRequest(mNick, + item, mAmountItems); +// logger->log("buy button mNick:" + mNick); + if (tradeWindow) + { + tradeWindow->addAutoMoney(mNick, + item->getPrice() * mAmountItems); + } + } + } + } +} + +void BuyDialog::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + // Reset amount of items and update labels + mAmountItems = 1; + mSlider->setValue(1); + + updateButtonsAndLabels(); + mSlider->gcn::Slider::setScale(1, mMaxItems); +} + +void BuyDialog::updateButtonsAndLabels() +{ + const int selectedItem = mShopItemList->getSelected(); + int price = 0; + + if (selectedItem > -1) + { + ShopItem * item = mShopItems->at(selectedItem); + if (item) + { + int itemPrice = item->getPrice(); + + // Calculate how many the player can afford + if (itemPrice) + mMaxItems = mMoney / itemPrice; + else + mMaxItems = 1; + + if (item->getQuantity() > 0 && mMaxItems > item->getQuantity()) + mMaxItems = item->getQuantity(); + + if (mAmountItems > mMaxItems) + mAmountItems = mMaxItems; + + // Calculate price of pending purchase + price = mAmountItems * itemPrice; + } + } + else + { + mMaxItems = 0; + mAmountItems = 0; + } + + // Enable or disable buttons and slider + mIncreaseButton->setEnabled(mAmountItems < mMaxItems); + mDecreaseButton->setEnabled(mAmountItems > 1); + mBuyButton->setEnabled(mAmountItems > 0); + mSlider->setEnabled(mMaxItems > 1); + + // Update quantity and money labels + mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems)); + mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"), + Units::formatCurrency(price).c_str(), + Units::formatCurrency(mMoney - price).c_str())); +} + +void BuyDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (visible && mShopItemList) + mShopItemList->requestFocus(); + else + scheduleDelete(); +} + +void BuyDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if (*it) + (*it)->close(); + } +} diff --git a/src/gui/buy.h b/src/gui/buy.h new file mode 100644 index 000000000..5133414c1 --- /dev/null +++ b/src/gui/buy.h @@ -0,0 +1,151 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BUY_H +#define BUY_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ShopItems; +class ShopListBox; +class ListBox; + +/** + * The buy dialog. + * + * \ingroup Interface + */ +class BuyDialog : public Window, public gcn::ActionListener, + public gcn::SelectionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + BuyDialog(int npcId); + + /** + * Constructor. + */ + BuyDialog(std::string nick); + + /** + * Destructor + */ + ~BuyDialog(); + + void init(); + + /** + * Resets the dialog, clearing shop inventory. + */ + void reset(); + + /** + * Sets the amount of available money. + */ + void setMoney(int amount); + + /** + * Adds an item to the shop inventory. + */ + void addItem(int id, int amount, int price); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Returns the number of items in the shop inventory. + */ + int getNumberOfElements(); + + /** + * Updates the labels according to the selected item. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Returns the name of item number i in the shop inventory. + */ + std::string getElementAt(int i); + + /** + * Updates the state of buttons and labels. + */ + void updateButtonsAndLabels(); + + /** + * Sets the visibility of this window. + */ + void setVisible(bool visible); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list DialogList; + static DialogList instances; + + int mNpcId; + + gcn::Button *mBuyButton; + gcn::Button *mQuitButton; + gcn::Button *mAddMaxButton; + gcn::Button *mIncreaseButton; + gcn::Button *mDecreaseButton; + ShopListBox *mShopItemList; + gcn::ScrollArea *mScrollArea; + gcn::Label *mMoneyLabel; + gcn::Label *mQuantityLabel; + gcn::Slider *mSlider; + + ShopItems *mShopItems; + + int mMoney; + int mAmountItems; + int mMaxItems; + std::string mNick; +}; + +#endif diff --git a/src/gui/buysell.cpp b/src/gui/buysell.cpp new file mode 100644 index 000000000..f6a1fc193 --- /dev/null +++ b/src/gui/buysell.cpp @@ -0,0 +1,137 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/buysell.h" + +#include "gui/setup.h" + +#include "gui/widgets/button.h" + +#include "net/buysellhandler.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "utils/gettext.h" + +BuySellDialog::DialogList BuySellDialog::instances; + +BuySellDialog::BuySellDialog(int npcId): + Window(_("Shop")), + mNpcId(npcId), + mNick(""), + mBuyButton(0) +{ + init(); +} + +BuySellDialog::BuySellDialog(std::string nick): + Window(_("Shop")), + mNpcId(-1), + mNick(nick), + mBuyButton(0) +{ + init(); +} + +void BuySellDialog::init() +{ + setWindowName("BuySell"); + //setupWindow->registerWindowForReset(this); + setCloseButton(true); + + static const char *buttonNames[] = + { + N_("Buy"), N_("Sell"), N_("Cancel"), 0 + }; + int x = 10, y = 10; + + for (const char **curBtn = buttonNames; *curBtn; curBtn++) + { + Button *btn = new Button(gettext(*curBtn), *curBtn, this); + if (!mBuyButton) + mBuyButton = btn; // For focus request + btn->setPosition(x, y); + add(btn); + x += btn->getWidth() + 10; + } + mBuyButton->requestFocus(); + + setContentSize(x, 2 * y + mBuyButton->getHeight()); + + center(); + setDefaultSize(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); +} + +BuySellDialog::~BuySellDialog() +{ + instances.remove(this); +} + +void BuySellDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (visible) + { + if (mBuyButton) + mBuyButton->requestFocus(); + } + else + { + scheduleDelete(); + } +} + +void BuySellDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "Buy") + { + if (mNpcId != -1) + Net::getNpcHandler()->buy(mNpcId); + else + Net::getBuySellHandler()->requestSellList(mNick); + } + else if (event.getId() == "Sell") + { + if (mNpcId != -1) + Net::getNpcHandler()->sell(mNpcId); + else + Net::getBuySellHandler()->requestBuyList(mNick); + } + + close(); +} + +void BuySellDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if (*it) + (*it)->close(); + } +} diff --git a/src/gui/buysell.h b/src/gui/buysell.h new file mode 100644 index 000000000..f533252ee --- /dev/null +++ b/src/gui/buysell.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BUYSELL_H +#define BUYSELL_H + +#include "gui/widgets/window.h" + +#include + +/** + * A dialog to choose between buying or selling at a shop. + * + * \ingroup Interface + */ +class BuySellDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. The action listener passed will receive "sell", "buy" + * or "cancel" events when the respective buttons are pressed. + * + * @see Window::Window + */ + BuySellDialog(int npcId); + + BuySellDialog(std::string nick); + + virtual ~BuySellDialog(); + + void init(); + + void setVisible(bool visible); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list DialogList; + static DialogList instances; + + int mNpcId; + std::string mNick; + gcn::Button *mBuyButton; +}; + +#endif diff --git a/src/gui/changeemaildialog.cpp b/src/gui/changeemaildialog.cpp new file mode 100644 index 000000000..4485e0199 --- /dev/null +++ b/src/gui/changeemaildialog.cpp @@ -0,0 +1,167 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/changeemaildialog.h" + +#include "client.h" +#include "log.h" + +#include "gui/register.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +ChangeEmailDialog::ChangeEmailDialog(LoginData *loginData): + Window(_("Change Email Address"), true), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + gcn::Label *accountLabel = new Label(strprintf(_("Account: %s"), + mLoginData->username.c_str())); + gcn::Label *newEmailLabel = new Label(_("Type new email address twice:")); + mFirstEmailField = new TextField; + mSecondEmailField = new TextField; + mChangeEmailButton = new Button(_("Change Email Address"), + "change_email", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + const int width = 200; + const int height = 130; + setContentSize(width, height); + + accountLabel->setPosition(5, 5); + accountLabel->setWidth(130); + + newEmailLabel->setPosition( + 5, accountLabel->getY() + accountLabel->getHeight() + 7); + newEmailLabel->setWidth(width - 5); + + mFirstEmailField->setPosition( + 5, newEmailLabel->getY() + newEmailLabel->getHeight() + 7); + mFirstEmailField->setWidth(130); + + mSecondEmailField->setPosition( + 5, mFirstEmailField->getY() + mFirstEmailField->getHeight() + 7); + mSecondEmailField->setWidth(130); + + mCancelButton->setPosition( + width - 5 - mCancelButton->getWidth(), + height - 5 - mCancelButton->getHeight()); + mChangeEmailButton->setPosition( + mCancelButton->getX() - 5 - mChangeEmailButton->getWidth(), + mCancelButton->getY()); + + add(accountLabel); + add(newEmailLabel); + add(mFirstEmailField); + add(mSecondEmailField); + add(mChangeEmailButton); + add(mCancelButton); + + center(); + setVisible(true); + mFirstEmailField->requestFocus(); + + mFirstEmailField->setActionEventId("change_email"); + mSecondEmailField->setActionEventId("change_email"); +} + +ChangeEmailDialog::~ChangeEmailDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void ChangeEmailDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_CHAR_SELECT); + } + else if (event.getId() == "change_email") + { + + const std::string username = mLoginData->username.c_str(); + const std::string newFirstEmail = mFirstEmailField->getText(); + const std::string newSecondEmail = mSecondEmailField->getText(); + logger->log("ChangeEmailDialog::Email change, Username is %s", + username.c_str()); + + std::stringstream errorMessage; + int error = 0; + + unsigned int min = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int max = Net::getLoginHandler()->getMaxPasswordLength(); + + if (newFirstEmail.length() < min) + { + // First email address too short + errorMessage << strprintf(_("The new email address needs to be at " + "least %d characters long."), min); + error = 1; + } + else if (newFirstEmail.length() > max - 1 ) + { + // First email address too long + errorMessage << strprintf(_("The new email address needs to be " + "less than %d characters long."), max); + error = 1; + } + else if (newFirstEmail != newSecondEmail) + { + // Second Pass mismatch + errorMessage << _("The email address entries mismatch."); + error = 2; + } + + if (error > 0) + { + if (error == 1) + mWrongDataNoticeListener->setTarget(this->mFirstEmailField); + else if (error == 2) + mWrongDataNoticeListener->setTarget(this->mSecondEmailField); + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str()); + if (dlg) + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, change account password. + mChangeEmailButton->setEnabled(false); + // Set the new email address + mLoginData->email = newFirstEmail; + Client::setState(STATE_CHANGEEMAIL_ATTEMPT); + } + } +} diff --git a/src/gui/changeemaildialog.h b/src/gui/changeemaildialog.h new file mode 100644 index 000000000..7e5f04fa4 --- /dev/null +++ b/src/gui/changeemaildialog.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_CHANGEEMAIL_H +#define GUI_CHANGEEMAIL_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include + +class LoginData; +class OkDialog; +class WrongDataNoticeListener; + +/** + * The Change email dialog. + * + * \ingroup Interface + */ +class ChangeEmailDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + ChangeEmailDialog(LoginData *loginData); + + /** + * Destructor. + */ + ~ChangeEmailDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * This is used to pass the pointer to where the new email should be + * put when the dialog finishes. + */ + static void setEmail(std::string *email); + + private: + gcn::TextField *mFirstEmailField; + gcn::TextField *mSecondEmailField; + + gcn::Button *mChangeEmailButton; + gcn::Button *mCancelButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif // GUI_CHANGEEMAIL_H diff --git a/src/gui/changepassworddialog.cpp b/src/gui/changepassworddialog.cpp new file mode 100644 index 000000000..bc29c9264 --- /dev/null +++ b/src/gui/changepassworddialog.cpp @@ -0,0 +1,157 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "changepassworddialog.h" + +#include "client.h" +#include "log.h" + +#include "gui/register.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +ChangePasswordDialog::ChangePasswordDialog(LoginData *loginData): + Window(_("Change Password"), true), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + gcn::Label *accountLabel = new Label( + strprintf(_("Account: %s"), mLoginData->username.c_str())); + mOldPassField = new PasswordField; + mFirstPassField = new PasswordField; + mSecondPassField = new PasswordField; + mChangePassButton = new Button(_("Change Password"), "change_password", + this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + place(0, 0, accountLabel, 3); + place(0, 1, new Label(_("Password:")), 3); + place(0, 2, mOldPassField, 3).setPadding(1); + place(0, 3, new Label(_("Type new password twice:")), 3); + place(0, 4, mFirstPassField, 3).setPadding(1); + place(0, 5, mSecondPassField, 3).setPadding(1); + place(1, 6, mCancelButton); + place(2, 6, mChangePassButton); + reflowLayout(200); + + center(); + setVisible(true); + mOldPassField->requestFocus(); + + mOldPassField->setActionEventId("change_password"); + mFirstPassField->setActionEventId("change_password"); + mSecondPassField->setActionEventId("change_password"); +} + +ChangePasswordDialog::~ChangePasswordDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void ChangePasswordDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_CHAR_SELECT); + } + else if (event.getId() == "change_password") + { + + const std::string username = mLoginData->username.c_str(); + const std::string oldPassword = mOldPassField->getText(); + const std::string newFirstPass = mFirstPassField->getText(); + const std::string newSecondPass = mSecondPassField->getText(); + logger->log("ChangePasswordDialog::Password change, Username is %s", + username.c_str()); + + std::stringstream errorMessage; + int error = 0; + + unsigned int min = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int max = Net::getLoginHandler()->getMaxPasswordLength(); + + // Check old Password + if (oldPassword.empty()) + { + // No old password + errorMessage << _("Enter the old password first."); + error = 1; + } + else if (newFirstPass.length() < min) + { + // First password too short + errorMessage << strprintf(_("The new password needs to be at least" + " %d characters long."), min); + error = 2; + } + else if (newFirstPass.length() > max - 1 ) + { + // First password too long + errorMessage << strprintf(_("The new password needs to be less " + "than %d characters long."), max); + error = 2; + } + else if (newFirstPass != newSecondPass) + { + // Second Pass mismatch + errorMessage << _("The new password entries mismatch."); + error = 3; + } + + if (error > 0) + { + if (error == 1) + mWrongDataNoticeListener->setTarget(this->mOldPassField); + else if (error == 2) + mWrongDataNoticeListener->setTarget(this->mFirstPassField); + else if (error == 3) + mWrongDataNoticeListener->setTarget(this->mSecondPassField); + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str()); + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, change account password. + mChangePassButton->setEnabled(false); + // Set the new password + mLoginData->password = oldPassword; + mLoginData->newPassword = newFirstPass; + Client::setState(STATE_CHANGEPASSWORD_ATTEMPT); + } + } +} diff --git a/src/gui/changepassworddialog.h b/src/gui/changepassworddialog.h new file mode 100644 index 000000000..361debe45 --- /dev/null +++ b/src/gui/changepassworddialog.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHANGEPASSWORDDIALOG_H +#define CHANGEPASSWORDDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include + +class LoginData; +class OkDialog; +class WrongDataNoticeListener; + +/** + * The Change password dialog. + * + * \ingroup Interface + */ +class ChangePasswordDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + ChangePasswordDialog(LoginData *loginData); + + /** + * Destructor + */ + ~ChangePasswordDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + gcn::TextField *mOldPassField; + gcn::TextField *mFirstPassField; + gcn::TextField *mSecondPassField; + + gcn::Button *mChangePassButton; + gcn::Button *mCancelButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif diff --git a/src/gui/charcreatedialog.cpp b/src/gui/charcreatedialog.cpp new file mode 100644 index 000000000..4dc6251b9 --- /dev/null +++ b/src/gui/charcreatedialog.cpp @@ -0,0 +1,372 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/charcreatedialog.h" + +#include "game.h" +#include "localplayer.h" +#include "main.h" +#include "units.h" + +#include "gui/charselectdialog.h" +#include "gui/confirmdialog.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/playerbox.h" +#include "gui/widgets/radiobutton.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/textfield.h" + +#include "net/charhandler.h" +#include "net/messageout.h" +#include "net/net.h" + +#include "resources/colordb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot): + Window(_("Create Character"), true, parent), + mCharSelectDialog(parent), + mSlot(slot) +{ + mPlayer = new Being(0, ActorSprite::PLAYER, 0, NULL); + mPlayer->setGender(GENDER_MALE); + + int numberOfHairColors = ColorDB::size(); + + mHairStyle = rand() % mPlayer->getNumOfHairstyles(); + mHairColor = rand() % numberOfHairColors; + updateHair(); + + mNameField = new TextField(""); + mNameLabel = new Label(_("Name:")); + // TRANSLATORS: This is a narrow symbol used to denote 'next'. + // You may change this symbol if your language uses another. + mNextHairColorButton = new Button(_(">"), "nextcolor", this); + // TRANSLATORS: This is a narrow symbol used to denote 'previous'. + // You may change this symbol if your language uses another. + mPrevHairColorButton = new Button(_("<"), "prevcolor", this); + mHairColorLabel = new Label(_("Hair color:")); + mNextHairStyleButton = new Button(_(">"), "nextstyle", this); + mPrevHairStyleButton = new Button(_("<"), "prevstyle", this); + mHairStyleLabel = new Label(_("Hair style:")); + mCreateButton = new Button(_("Create"), "create", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + mMale = new RadioButton(_("Male"), "gender"); + mFemale = new RadioButton(_("Female"), "gender"); + + // Default to a Male character + mMale->setSelected(true); + + mMale->setActionEventId("gender"); + mFemale->setActionEventId("gender"); + + mMale->addActionListener(this); + mFemale->addActionListener(this); + + mPlayerBox = new PlayerBox(mPlayer); + mPlayerBox->setWidth(74); + + mNameField->setActionEventId("create"); + mNameField->addActionListener(this); + + mAttributesLeft = new Label( + strprintf(_("Please distribute %d points"), 99)); + + int w = 200; + int h = 330; + setContentSize(w, h); + mPlayerBox->setDimension(gcn::Rectangle(80, 30, 110, 85)); + mNameLabel->setPosition(5, 5); + mNameField->setDimension( + gcn::Rectangle(45, 5, w - 45 - 7, mNameField->getHeight())); + mPrevHairColorButton->setPosition(90, 35); + mNextHairColorButton->setPosition(165, 35); + mHairColorLabel->setPosition(5, 40); + mPrevHairStyleButton->setPosition(90, 64); + mNextHairStyleButton->setPosition(165, 64); + mHairStyleLabel->setPosition(5, 70); + mAttributesLeft->setPosition(15, 280); + updateSliders(); + mCancelButton->setPosition( + w - 5 - mCancelButton->getWidth(), + h - 5 - mCancelButton->getHeight()); + mCreateButton->setPosition( + mCancelButton->getX() - 5 - mCreateButton->getWidth(), + h - 5 - mCancelButton->getHeight()); + + mMale->setPosition(30, 120); + mFemale->setPosition(100, 120); + + add(mPlayerBox); + add(mNameField); + add(mNameLabel); + add(mNextHairColorButton); + add(mPrevHairColorButton); + add(mHairColorLabel); + add(mNextHairStyleButton); + add(mPrevHairStyleButton); + add(mHairStyleLabel); + add(mAttributesLeft); + add(mCreateButton); + add(mCancelButton); + + add(mMale); + add(mFemale); + + center(); + setVisible(true); + mNameField->requestFocus(); +} + +CharCreateDialog::~CharCreateDialog() +{ + delete mPlayer; + mPlayer = 0; + + // Make sure the char server handler knows that we're gone + Net::getCharHandler()->setCharCreateDialog(0); +} + +void CharCreateDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "create") + { + if (Net::getNetworkType() == ServerInfo::MANASERV + || getName().length() >= 4) + { + // Attempt to create the character + mCreateButton->setEnabled(false); + + std::vector atts; + for (unsigned i = 0; i < mAttributeSlider.size(); i++) + { + atts.push_back(static_cast( + mAttributeSlider[i]->getValue())); + } + + int characterSlot = mSlot; + // On Manaserv, the slots start at 1, so we offset them. + if (Net::getNetworkType() == ServerInfo::MANASERV) + ++characterSlot; + + Net::getCharHandler()->newCharacter(getName(), characterSlot, + mFemale->isSelected(), + mHairStyle, + mHairColor, atts); + } + else + { + new OkDialog(_("Error"), + _("Your name needs to be at least 4 characters."), + true, this); + } + } + else if (event.getId() == "cancel") + { + scheduleDelete(); + } + else if (event.getId() == "nextcolor") + { + mHairColor++; + updateHair(); + } + else if (event.getId() == "prevcolor") + { + mHairColor--; + updateHair(); + } + else if (event.getId() == "nextstyle") + { + mHairStyle++; + updateHair(); + } + else if (event.getId() == "prevstyle") + { + mHairStyle--; + updateHair(); + } + else if (event.getId() == "statslider") + { + updateSliders(); + } + else if (event.getId() == "gender") + { + if (mMale->isSelected()) + mPlayer->setGender(GENDER_MALE); + else + mPlayer->setGender(GENDER_FEMALE); + } +} + +std::string CharCreateDialog::getName() const +{ + std::string name = mNameField->getText(); + trim(name); + return name; +} + +void CharCreateDialog::updateSliders() +{ + for (unsigned i = 0; i < mAttributeSlider.size(); i++) + { + // Update captions + mAttributeValue[i]->setCaption( + toString(static_cast(mAttributeSlider[i]->getValue()))); + mAttributeValue[i]->adjustSize(); + } + + // Update distributed points + int pointsLeft = mMaxPoints - getDistributedPoints(); + if (pointsLeft == 0) + { + mAttributesLeft->setCaption(_("Character stats OK")); + mCreateButton->setEnabled(true); + } + else + { + mCreateButton->setEnabled(false); + if (pointsLeft > 0) + { + mAttributesLeft->setCaption( + strprintf(_("Please distribute %d points"), pointsLeft)); + } + else + { + mAttributesLeft->setCaption( + strprintf(_("Please remove %d points"), -pointsLeft)); + } + } + + mAttributesLeft->adjustSize(); +} + +void CharCreateDialog::unlock() +{ + mCreateButton->setEnabled(true); +} + +int CharCreateDialog::getDistributedPoints() const +{ + int points = 0; + + for (unsigned i = 0; i < mAttributeSlider.size(); i++) + points += static_cast(mAttributeSlider[i]->getValue()); + return points; +} + +void CharCreateDialog::setAttributes(const std::vector &labels, + int available, int min, int max) +{ + mMaxPoints = available; + + for (unsigned i = 0; i < mAttributeLabel.size(); i++) + { + remove(mAttributeLabel[i]); + delete mAttributeLabel[i]; + mAttributeLabel[i] = 0; + remove(mAttributeSlider[i]); + delete mAttributeSlider[i]; + mAttributeSlider[i] = 0; + remove(mAttributeValue[i]); + delete mAttributeValue[i]; + mAttributeValue[i] = 0; + } + + mAttributeLabel.resize(labels.size()); + mAttributeSlider.resize(labels.size()); + mAttributeValue.resize(labels.size()); + + int w = 200; + int h = 330; + + for (unsigned i = 0; i < labels.size(); i++) + { + mAttributeLabel[i] = new Label(labels[i]); + mAttributeLabel[i]->setWidth(70); + mAttributeLabel[i]->setPosition(5, 140 + i*20); + add(mAttributeLabel[i]); + + mAttributeSlider[i] = new Slider(min, max); + mAttributeSlider[i]->setDimension(gcn::Rectangle(75, 140 + i * 20, + 100, 10)); + mAttributeSlider[i]->setActionEventId("statslider"); + mAttributeSlider[i]->addActionListener(this); + add(mAttributeSlider[i]); + + mAttributeValue[i] = new Label(toString(min)); + mAttributeValue[i]->setPosition(180, 140 + i*20); + add(mAttributeValue[i]); + } + + mAttributesLeft->setPosition(15, 280); + updateSliders(); + + mCancelButton->setPosition( + w - 5 - mCancelButton->getWidth(), + h - 5 - mCancelButton->getHeight()); + mCreateButton->setPosition( + mCancelButton->getX() - 5 - mCreateButton->getWidth(), + h - 5 - mCancelButton->getHeight()); +} + +void CharCreateDialog::setFixedGender(bool fixed, Gender gender) +{ + if (gender == GENDER_FEMALE) + { + mFemale->setSelected(true); + mMale->setSelected(false); + } + else + { + mMale->setSelected(true); + mFemale->setSelected(false); + } + + mPlayer->setGender(gender); + + if (fixed) + { + mMale->setEnabled(false); + mFemale->setEnabled(false); + } +} + +void CharCreateDialog::updateHair() +{ + mHairStyle %= Being::getNumOfHairstyles(); + if (mHairStyle < 0) + mHairStyle += Being::getNumOfHairstyles(); + + mHairColor %= ColorDB::size(); + if (mHairColor < 0) + mHairColor += ColorDB::size(); + + mPlayer->setSprite(Net::getCharHandler()->hairSprite(), + mHairStyle * -1, ColorDB::get(mHairColor)); +} diff --git a/src/gui/charcreatedialog.h b/src/gui/charcreatedialog.h new file mode 100644 index 000000000..018de3f59 --- /dev/null +++ b/src/gui/charcreatedialog.h @@ -0,0 +1,122 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHAR_CREATE_DIALOG_H +#define CHAR_CREATE_DIALOG_H + +#include "being.h" +#include "guichanfwd.h" + +#include "gui/charselectdialog.h" + +#include "gui/widgets/window.h" + +#include + +#include +#include + +class LocalPlayer; +class PlayerBox; + +/** + * Character creation dialog. + * + * \ingroup Interface + */ +class CharCreateDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + CharCreateDialog(CharSelectDialog *parent, int slot); + + /** + * Destructor. + */ + ~CharCreateDialog(); + + void action(const gcn::ActionEvent &event); + + /** + * Unlocks the dialog, enabling the create character button again. + */ + void unlock(); + + void setAttributes(const std::vector &labels, + int available, + int min, int max); + + void setFixedGender(bool fixed, Gender gender = GENDER_FEMALE); + + private: + int getDistributedPoints() const; + + void updateSliders(); + + /** + * Returns the name of the character to create. + */ + std::string getName() const; + + /** + * Communicate character creation to the server. + */ + void attemptCharCreate(); + + void updateHair(); + + CharSelectDialog *mCharSelectDialog; + + gcn::TextField *mNameField; + gcn::Label *mNameLabel; + gcn::Button *mNextHairColorButton; + gcn::Button *mPrevHairColorButton; + gcn::Label *mHairColorLabel; + gcn::Button *mNextHairStyleButton; + gcn::Button *mPrevHairStyleButton; + gcn::Label *mHairStyleLabel; + + gcn::RadioButton *mMale; + gcn::RadioButton *mFemale; + + std::vector mAttributeSlider; + std::vector mAttributeLabel; + std::vector mAttributeValue; + gcn::Label *mAttributesLeft; + + int mMaxPoints; + int mUsedPoints; + + gcn::Button *mCreateButton; + gcn::Button *mCancelButton; + + Being *mPlayer; + PlayerBox *mPlayerBox; + + int mHairStyle; + int mHairColor; + + int mSlot; +}; + +#endif // CHAR_CREATE_DIALOG_H diff --git a/src/gui/charselectdialog.cpp b/src/gui/charselectdialog.cpp new file mode 100644 index 000000000..d95930108 --- /dev/null +++ b/src/gui/charselectdialog.cpp @@ -0,0 +1,456 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/charselectdialog.h" + +#include "client.h" +#include "game.h" +#include "localplayer.h" +#include "units.h" +#include "log.h" + +#include "gui/changeemaildialog.h" +#include "gui/changepassworddialog.h" +#include "gui/charcreatedialog.h" +#include "gui/confirmdialog.h" +#include "gui/okdialog.h" +#include "gui/sdlinput.h" +#include "gui/unregisterdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/container.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/playerbox.h" +#include "gui/widgets/textfield.h" + +#include "net/charhandler.h" +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/messageout.h" +#include "net/net.h" + +#include "resources/colordb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#include +#include + +// Character slots per row in the dialog +static const int SLOTS_PER_ROW = 5; + +/** + * Listener for confirming character deletion. + */ +class CharDeleteConfirm : public ConfirmDialog +{ + public: + CharDeleteConfirm(CharSelectDialog *m, int index): + ConfirmDialog(_("Confirm Character Delete"), + _("Are you sure you want to delete this character?"), + false, false, m), + mMaster(m), + mIndex(index) + { + } + + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "yes" && mMaster) + mMaster->attemptCharacterDelete(mIndex); + + ConfirmDialog::action(event); + } + + private: + CharSelectDialog *mMaster; + int mIndex; +}; + +class CharacterDisplay : public Container +{ + public: + CharacterDisplay(CharSelectDialog *charSelectDialog); + + void setCharacter(Net::Character *character); + + Net::Character *getCharacter() const + { return mCharacter; } + + void requestFocus(); + + void setActive(bool active); + + private: + void update(); + + Net::Character *mCharacter; + + PlayerBox *mPlayerBox; + Label *mName; + Label *mLevel; + Label *mMoney; + Button *mButton; + Button *mDelete; +}; + +CharSelectDialog::CharSelectDialog(LoginData *loginData): + Window(_("Account and Character Management")), + mLocked(false), + mUnregisterButton(0), + mChangeEmailButton(0), + mCharacterEntries(0), + mLoginData(loginData), + mCharHandler(Net::getCharHandler()) +{ + setCloseButton(false); + + mAccountNameLabel = new Label(loginData->username); + mSwitchLoginButton = new Button(_("Switch Login"), "switch", this); + mChangePasswordButton = new Button(_("Change Password"), "change_password", + this); + + int optionalActions = Net::getLoginHandler()->supportedOptionalActions(); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mAccountNameLabel, 2); + place(0, 1, mSwitchLoginButton); + + if (optionalActions & Net::LoginHandler::Unregister) + { + mUnregisterButton = new Button(_("Unregister"), + "unregister", this); + place(3, 1, mUnregisterButton); + } + + place(0, 2, mChangePasswordButton); + + if (optionalActions & Net::LoginHandler::ChangeEmail) + { + mChangeEmailButton = new Button(_("Change Email"), + "change_email", this); + place(3, 2, mChangeEmailButton); + } + + place = getPlacer(0, 1); + + for (int i = 0; i < (int)mLoginData->characterSlots; i++) + { + mCharacterEntries.push_back(new CharacterDisplay(this)); + place(i % SLOTS_PER_ROW, (int)i / SLOTS_PER_ROW, mCharacterEntries[i]); + } + + reflowLayout(); + + addKeyListener(this); + + center(); + setVisible(true); + + Net::getCharHandler()->setCharSelectDialog(this); + if (mCharacterEntries[0]) + mCharacterEntries[0]->requestFocus(); +} + +CharSelectDialog::~CharSelectDialog() +{ +} + +void CharSelectDialog::action(const gcn::ActionEvent &event) +{ + // Check if a button of a character was pressed + const gcn::Widget *sourceParent = event.getSource()->getParent(); + int selected = -1; + for (int i = 0; i < (int)mCharacterEntries.size(); ++i) + { + if (mCharacterEntries[i] == sourceParent) + { + selected = i; + break; + } + } + + const std::string &eventId = event.getId(); + + if (selected != -1) + { + if (eventId == "use") + { + attemptCharacterSelect(selected); + } + else if (eventId == "new" && + !mCharacterEntries[selected]->getCharacter()) + { + // Start new character dialog + CharCreateDialog *charCreateDialog = + new CharCreateDialog(this, selected); + mCharHandler->setCharCreateDialog(charCreateDialog); + } + else if (eventId == "delete" + && mCharacterEntries[selected]->getCharacter()) + { + new CharDeleteConfirm(this, selected); + } + } + else if (eventId == "switch") + { + Client::setState(STATE_SWITCH_LOGIN); + } + else if (eventId == "change_password") + { + Client::setState(STATE_CHANGEPASSWORD); + } + else if (eventId == "change_email") + { + Client::setState(STATE_CHANGEEMAIL); + } + else if (eventId == "unregister") + { + Client::setState(STATE_UNREGISTER); + } +} + +void CharSelectDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + { + action(gcn::ActionEvent(mSwitchLoginButton, + mSwitchLoginButton->getActionEventId())); + } +} + +/** + * Communicate character deletion to the server. + */ +void CharSelectDialog::attemptCharacterDelete(int index) +{ + if (mLocked) + return; + + mCharHandler->deleteCharacter(mCharacterEntries[index]->getCharacter()); + lock(); +} + +/** + * Communicate character selection to the server. + */ +void CharSelectDialog::attemptCharacterSelect(int index) +{ + if (mLocked || !mCharacterEntries[index]) + return; + + setVisible(false); + if (mCharHandler && mCharacterEntries[index]) + { + mCharHandler->chooseCharacter( + mCharacterEntries[index]->getCharacter()); + } + lock(); +} + +void CharSelectDialog::setCharacters(const Net::Characters &characters) +{ + // Reset previous characters + std::vector::iterator iter, iter_end; + for (iter = mCharacterEntries.begin(), iter_end = mCharacterEntries.end(); + iter != iter_end; ++iter) + (*iter)->setCharacter(0); + + Net::Characters::const_iterator i, i_end = characters.end(); + for (i = characters.begin(); i != i_end; ++i) + { + if (!*i) + continue; + + Net::Character *character = *i; + + // Slots Number start at 1 for Manaserv, so we offset them by one. + int characterSlot = character->slot; + if (Net::getNetworkType() == ServerInfo::MANASERV && characterSlot > 0) + --characterSlot; + + if (characterSlot >= (int)mCharacterEntries.size()) + { + logger->log("Warning: slot out of range: %d", character->slot); + continue; + } + + mCharacterEntries[characterSlot]->setCharacter(character); + } +} + +void CharSelectDialog::lock() +{ + assert(!mLocked); + setLocked(true); +} + +void CharSelectDialog::unlock() +{ + setLocked(false); +} + +void CharSelectDialog::setLocked(bool locked) +{ + mLocked = locked; + + if (mSwitchLoginButton) + mSwitchLoginButton->setEnabled(!locked); + if (mChangePasswordButton) + mChangePasswordButton->setEnabled(!locked); + if (mUnregisterButton) + mUnregisterButton->setEnabled(!locked); + if (mChangeEmailButton) + mChangeEmailButton->setEnabled(!locked); + + for (int i = 0; i < (int)mCharacterEntries.size(); ++i) + { + if (mCharacterEntries[i]) + mCharacterEntries[i]->setActive(!mLocked); + } +} + +bool CharSelectDialog::selectByName(const std::string &name, + SelectAction action) +{ + if (mLocked) + return false; + + for (int i = 0; i < (int)mCharacterEntries.size(); ++i) + { + Net::Character *character = mCharacterEntries[i]->getCharacter(); + if (mCharacterEntries[i] && character) + { + if (character->dummy->getName() == name) + { + if (mCharacterEntries[i]) + mCharacterEntries[i]->requestFocus(); + if (action == Choose) + attemptCharacterSelect(i); + return true; + } + } + } + + return false; +} + + +CharacterDisplay::CharacterDisplay(CharSelectDialog *charSelectDialog): + mCharacter(0), + mPlayerBox(new PlayerBox) +{ + mButton = new Button("wwwwwwwww", "go", charSelectDialog); + mName = new Label("wwwwwwwwwwwwwwwwwwwwwwww"); + mLevel = new Label("(888)"); + mMoney = new Label("wwwwwwwww"); + + mDelete = new Button(_("Delete"), "delete", charSelectDialog); + + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mPlayerBox, 3, 5); + place(0, 5, mName, 3); + place(0, 6, mLevel, 3); + place(0, 7, mMoney, 3); + place(0, 8, mButton, 3); + place(0, 9, mDelete, 3); + + update(); + + + // Setting the width so that the largest label fits. + mName->adjustSize(); + mMoney->adjustSize(); +/* + int width = 74; + if (width - 20 < mName->getWidth()) + width = 20 + mName->getWidth(); + if (width - 20 < mMoney->getWidth()) + width = 20 + mMoney->getWidth(); + h.reflowLayout(width, 112 + mName->getHeight() + mLevel->getHeight() + + mMoney->getHeight() + mButton->getHeight() + mDelete->getHeight()); +*/ + + setWidth(100); + setHeight(200); +} + +void CharacterDisplay::setCharacter(Net::Character *character) +{ + if (mCharacter == character) + return; + + mCharacter = character; + mPlayerBox->setPlayer(character ? character->dummy : 0); + update(); +} + +void CharacterDisplay::requestFocus() +{ + mButton->requestFocus(); +} + +void CharacterDisplay::setActive(bool active) +{ + mButton->setEnabled(active); + mDelete->setEnabled(active); +} + +void CharacterDisplay::update() +{ + if (mCharacter) + { + const LocalPlayer *character = mCharacter->dummy; + mButton->setCaption(_("Choose")); + mButton->setActionEventId("use"); + mName->setCaption(strprintf("%s", character->getName().c_str())); + mLevel->setCaption(strprintf("Level %d", + mCharacter->data.mAttributes[LEVEL])); + mMoney->setCaption(Units::formatCurrency( + mCharacter->data.mAttributes[MONEY])); + + mDelete->setVisible(true); + } + else + { + mButton->setCaption(_("Create")); + mButton->setActionEventId("new"); + mName->setCaption(_("(empty)")); + mLevel->setCaption(_("(empty)")); + mMoney->setCaption(Units::formatCurrency(0)); + + mDelete->setVisible(false); + } + + // Recompute layout + distributeResizedEvent(); +} diff --git a/src/gui/charselectdialog.h b/src/gui/charselectdialog.h new file mode 100644 index 000000000..7596e755b --- /dev/null +++ b/src/gui/charselectdialog.h @@ -0,0 +1,113 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHAR_SELECT_H +#define CHAR_SELECT_H + +#include "being.h" +#include "guichanfwd.h" +#include "main.h" + +#include "gui/widgets/window.h" + +#include "net/charhandler.h" + +#include +#include + +class CharacterDisplay; +class LocalPlayer; +class LoginData; +class PlayerBox; + +namespace Net +{ + class CharHandler; +} + +/** + * Character selection dialog. + * + * \ingroup Interface + */ +class CharSelectDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + friend class CharDeleteConfirm; + friend class Net::CharHandler; + + /** + * Constructor. + */ + CharSelectDialog(LoginData *loginData); + + ~CharSelectDialog(); + + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + enum SelectAction + { + Focus = 0, + Choose + }; + + /** + * Attempt to select the character with the given name. Returns whether + * a character with the given name was found. + * + * \param action determines what to do when a character with the given + * name was found (just focus or also try to choose this + * character). + */ + bool selectByName(const std::string &name, + SelectAction action = Focus); + + private: + void attemptCharacterDelete(int index); + void attemptCharacterSelect(int index); + + void setCharacters(const Net::Characters &characters); + + void lock(); + void unlock(); + void setLocked(bool locked); + + bool mLocked; + + gcn::Label *mAccountNameLabel; + + gcn::Button *mSwitchLoginButton; + gcn::Button *mChangePasswordButton; + gcn::Button *mUnregisterButton; + gcn::Button *mChangeEmailButton; + + /** The player boxes */ + std::vector mCharacterEntries; + + LoginData *mLoginData; + + Net::CharHandler *mCharHandler; +}; + +#endif diff --git a/src/gui/chat.cpp b/src/gui/chat.cpp new file mode 100644 index 000000000..3ffa018c3 --- /dev/null +++ b/src/gui/chat.cpp @@ -0,0 +1,1350 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "chat.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "guild.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "party.h" +#include "playerinfo.h" +#include "playerrelations.h" +#include "spellshortcut.h" +#include "sound.h" + +#include "gui/setup.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/battletab.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tabbedarea.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/tradetab.h" +#include "gui/widgets/whispertab.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +#include + +#include + +/** + * The chat input hides when it loses focus. It is also invisible by default. + */ +class ChatInput : public TextField, public gcn::FocusListener +{ + public: + ChatInput(): + TextField("", false) + { + setVisible(false); + addFocusListener(this); + } + + /** + * Called if the chat input loses focus. It will set itself to + * invisible as result. + */ + void focusLost(const gcn::Event &event _UNUSED_) + { + setVisible(false); + } +}; + +const char *COLOR_NAME[14] = +{ + N_("default"), + N_("black"), + N_("red"), + N_("green"), + N_("blue"), + N_("gold"), + N_("yellow"), + N_("pink"), + N_("purple"), + N_("grey"), + N_("brown"), + N_("rainbow 1"), + N_("rainbow 2"), + N_("rainbow 3"), +}; + + +class ColorListModel : public gcn::ListModel +{ +public: + virtual ~ColorListModel() { } + + virtual int getNumberOfElements() + { + return 14; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return COLOR_NAME[i]; + } +}; + +#define ACTION_COLOR_PICKER "color picker" + + +ChatWindow::ChatWindow(): + Window(_("Chat")), + mTmpVisible(false), + mChatHistoryIndex(0) +{ + listen(CHANNEL_NOTICES); + listen(CHANNEL_ATTRIBUTES); + + setWindowName("Chat"); + + if (setupWindow) + setupWindow->registerWindowForReset(this); + + // no title presented, title bar is padding so window can be moved. + gcn::Window::setTitleBarHeight(gcn::Window::getPadding() + 4); + setShowTitle(false); + setResizable(true); + setDefaultVisible(true); + setSaveVisible(true); + setDefaultSize(600, 123, ImageRect::LOWER_LEFT); + setMinWidth(150); + setMinHeight(90); + + mItemLinkHandler = new ItemLinkHandler; + + mChatInput = new ChatInput; + mChatInput->setActionEventId("chatinput"); + mChatInput->addActionListener(this); + + mChatTabs = new TabbedArea; + + mChatColor = config.getIntValue("chatColor"); + mColorListModel = new ColorListModel; + mColorPicker = new DropDown(mColorListModel); + + mColorPicker->setActionEventId(ACTION_COLOR_PICKER); + mColorPicker->addActionListener(this); + mColorPicker->setSelected(mChatColor); + + add(mChatTabs); + add(mChatInput); + add(mColorPicker); + + loadWindowState(); + + mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth() + - 2*getPadding() - 8, getPadding()); + + // Add key listener to chat input to be able to respond to up/down + mChatInput->addKeyListener(this); + mCurHist = mHistory.end(); + + mReturnToggles = config.getBoolValue("ReturnToggles"); + + mRainbowColor = 0; + + mColorPicker->setVisible(config.getBoolValue("showChatColorsList")); + + fillCommands(); + initTradeFilter(); +} + +ChatWindow::~ChatWindow() +{ + config.setValue("ReturnToggles", mReturnToggles); + removeAllWhispers(); + delete mItemLinkHandler; + mItemLinkHandler = 0; + delete mColorPicker; + mColorPicker = 0; + delete mColorListModel; + mColorListModel = 0; +} + +void ChatWindow::fillCommands() +{ + mCommands.push_back("/all"); + mCommands.push_back("/away "); + mCommands.push_back("/closeall"); + mCommands.push_back("/clear"); + mCommands.push_back("/create "); + mCommands.push_back("/close"); + mCommands.push_back("/cacheinfo"); + mCommands.push_back("/erase "); + mCommands.push_back("/follow "); + mCommands.push_back("/heal "); + mCommands.push_back("/ignoreall"); + mCommands.push_back("/help"); + mCommands.push_back("/announce "); + mCommands.push_back("/where"); + mCommands.push_back("/who"); + mCommands.push_back("/msg "); + mCommands.push_back("/mail "); + mCommands.push_back("/whisper "); + mCommands.push_back("/w "); + mCommands.push_back("/query "); + mCommands.push_back("/ignore "); + mCommands.push_back("/unignore "); + mCommands.push_back("/join "); + mCommands.push_back("/list"); + mCommands.push_back("/party"); + mCommands.push_back("/createparty "); + mCommands.push_back("/createguild "); + mCommands.push_back("/me "); + mCommands.push_back("/toggle"); + mCommands.push_back("/present"); + mCommands.push_back("/quit"); + mCommands.push_back("/move "); + mCommands.push_back("/target "); + mCommands.push_back("/invite "); + mCommands.push_back("/leave"); + mCommands.push_back("/kick "); + mCommands.push_back("/item"); + mCommands.push_back("/imitation"); + mCommands.push_back("/exp"); + mCommands.push_back("/ping"); + mCommands.push_back("/outfit "); + mCommands.push_back("/emote "); + mCommands.push_back("/navigate "); + mCommands.push_back("/priceload"); + mCommands.push_back("/pricesave"); + mCommands.push_back("/trade "); + mCommands.push_back("/friend "); + mCommands.push_back("/befriend "); + mCommands.push_back("/disregard "); + mCommands.push_back("/neutral "); + mCommands.push_back("/raw "); + mCommands.push_back("/disconnect"); + mCommands.push_back("/undress "); + mCommands.push_back("/attack"); + mCommands.push_back("/dirs"); + mCommands.push_back("/info"); + mCommands.push_back("/wait"); +} + +void ChatWindow::resetToDefaultSize() +{ + Window::resetToDefaultSize(); +} + +void ChatWindow::adjustTabSize() +{ + const gcn::Rectangle area = getChildrenArea(); + + mChatInput->setPosition(mChatInput->getFrameSize(), + area.height - mChatInput->getHeight() - + mChatInput->getFrameSize()); + mChatInput->setWidth(area.width - 2 * mChatInput->getFrameSize()); + + mChatTabs->setWidth(area.width - 2 * mChatTabs->getFrameSize()); + mChatTabs->setHeight(area.height - 2 * mChatTabs->getFrameSize() - + (mChatInput->getHeight() + mChatInput->getFrameSize() * 2)); + + ChatTab *tab = getFocused(); + if (tab) + { + gcn::Widget *content = tab->mScrollArea; + if (content) + { + content->setSize(mChatTabs->getWidth() + - 2 * content->getFrameSize(), + mChatTabs->getContainerHeight() + - 2 * content->getFrameSize()); + content->logic(); + } + } + + mColorPicker->setPosition(this->getWidth() - mColorPicker->getWidth() + - 2*getPadding() - 8, getPadding()); + +} + +void ChatWindow::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + + adjustTabSize(); +} + +/* +void ChatWindow::logic() +{ + Window::logic(); + + Tab *tab = getFocused(); + if (tab != mCurrentTab) + mCurrentTab = tab; +} +*/ + +ChatTab *ChatWindow::getFocused() const +{ + return static_cast(mChatTabs->getSelectedTab()); +} + +void ChatWindow::clearTab(ChatTab *tab) +{ + if (tab) + tab->clearText(); +} + +void ChatWindow::clearTab() +{ + clearTab(getFocused()); +} + +void ChatWindow::prevTab() +{ + if (!mChatTabs) + return; + + int tab = mChatTabs->getSelectedTabIndex(); + + if (tab == 0) + tab = mChatTabs->getNumberOfTabs(); + tab--; + + mChatTabs->setSelectedTab(tab); +} + +void ChatWindow::nextTab() +{ + if (!mChatTabs) + return; + + int tab = mChatTabs->getSelectedTabIndex(); + + tab++; + if (tab == mChatTabs->getNumberOfTabs()) + tab = 0; + + mChatTabs->setSelectedTab(tab); +} + +void ChatWindow::defaultTab() +{ + if (mChatTabs) + mChatTabs->setSelectedTab((unsigned)0); +} + +void ChatWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "chatinput") + { + std::string message = mChatInput->getText(); + + if (!message.empty()) + { + // If message different from previous, put it in the history + if (mHistory.empty() || message != mHistory.back()) + mHistory.push_back(message); + + // Reset history iterator + mCurHist = mHistory.end(); + + // Send the message to the server + chatInput(addColors(message)); + + // Clear the text from the chat input + mChatInput->setText(""); + } + + if (message.empty() || !mReturnToggles) + { + // Remove focus and hide input + if (mFocusHandler) + mFocusHandler->focusNone(); + + // If the chatWindow is shown up because you want to send a message + // It should hide now + if (mTmpVisible) + setVisible(false); + } + } + else if (event.getId() == ACTION_COLOR_PICKER) + { + mChatColor = mColorPicker->getSelected(); + config.setValue("chatColor", mChatColor); + } + + if (mColorPicker && mColorPicker->isVisible() + != config.getBoolValue("showChatColorsList")) + { + mColorPicker->setVisible(config.getBoolValue( + "showChatColorsList")); + } +} + +bool ChatWindow::requestChatFocus() +{ + // Make sure chatWindow is visible + if (!isVisible()) + { + setVisible(true); + + /* + * This is used to hide chatWindow after sending the message. There is + * a trick here, because setVisible will set mTmpVisible to false, you + * have to put this sentence *after* setVisible, not before it + */ + mTmpVisible = true; + } + + // Don't do anything else if the input is already visible and has focus + if (mChatInput->isVisible() && mChatInput->isFocused()) + return false; + + // Give focus to the chat input + mChatInput->setVisible(true); + mChatInput->requestFocus(); + return true; +} + +bool ChatWindow::isInputFocused() const +{ + return mChatInput->isFocused(); +} + +void ChatWindow::removeTab(ChatTab *tab) +{ + mChatTabs->removeTab(tab); +} + +void ChatWindow::addTab(ChatTab *tab) +{ + mChatTabs->addTab(tab, tab->mScrollArea); + + // Update UI + logic(); +} + +void ChatWindow::removeWhisper(const std::string &nick) +{ + std::string tempNick = nick; + toLower(tempNick); + mWhispers.erase(tempNick); +} + +void ChatWindow::removeAllWhispers() +{ + TabMap::iterator iter; + std::list tabs; + + for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter) + tabs.push_back(iter->second); + + for (std::list::iterator it = tabs.begin(); + it != tabs.end(); ++it) + { + delete *it; + } + + mWhispers.clear(); +} + +void ChatWindow::ignoreAllWhispers() +{ + TabMap::iterator iter; + for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter) + { + WhisperTab *tab = dynamic_cast(iter->second); + if (tab && player_relations.getRelation(tab->getNick()) + != PlayerRelation::IGNORED) + { + player_relations.setRelation(tab->getNick(), + PlayerRelation::IGNORED); + } + + delete(iter->second); + iter->second = 0; + } +} + +void ChatWindow::chatInput(const std::string &message) +{ + ChatTab *tab = NULL; + std::string msg = message; + trim(msg); + + if (config.getBoolValue("allowCommandsInChatTabs") + && msg.length() > 1 + && ((msg.at(0) == '#' && msg.at(1) != '#') || msg.at(0) == '@') + && localChatTab) + { + tab = localChatTab; + } + else + { + tab = getFocused(); + } + if (tab) + tab->chatInput(msg); + Game::instance()->setValidSpeed(); +} + +void ChatWindow::localChatInput(const std::string &msg) +{ + if (localChatTab) + localChatTab->chatInput(msg); + else + chatInput(msg); +} + +void ChatWindow::doPresent() +{ + if (!actorSpriteManager) + return; + + const ActorSprites &actors = actorSpriteManager->getAll(); + std::string response = ""; + int playercount = 0; + + for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); + it != it_end; it++) + { + if ((*it)->getType() == ActorSprite::PLAYER) + { + if (!response.empty()) + response += ", "; + response += static_cast(*it)->getName(); + ++playercount; + } + } + + std::string log = strprintf(_("Present: %s; %d players are present."), + response.c_str(), playercount); + + if (getFocused()) + getFocused()->chatLog(log, BY_SERVER); +} + +void ChatWindow::scroll(int amount) +{ + if (!isVisible()) + return; + + ChatTab *tab = getFocused(); + if (tab) + tab->scroll(amount); +} + +void ChatWindow::mousePressed(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + if (viewport) + { + gcn::Tab *tab = mChatTabs->getSelectedTab(); + if (tab) + { + ChatTab *cTab = dynamic_cast(tab); + if (cTab) + viewport->showChatPopup(cTab); + } + } + } + + Window::mousePressed(event); + + if (event.isConsumed()) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + ChatTab *tab = getFocused(); + if (tab) + mMoved = !isResizeAllowed(event); + } + + mDragOffsetX = event.getX(); + mDragOffsetY = event.getY(); +} + +void ChatWindow::mouseDragged(gcn::MouseEvent &event) +{ + Window::mouseDragged(event); + + if (event.isConsumed()) + return; + + if (isMovable() && mMoved) + { + int newX = std::max(0, getX() + event.getX() - mDragOffsetX); + int newY = std::max(0, getY() + event.getY() - mDragOffsetY); + newX = std::min(graphics->getWidth() - getWidth(), newX); + newY = std::min(graphics->getHeight() - getHeight(), newY); + setPosition(newX, newY); + } +} + +/* +void ChatWindow::mouseReleased(gcn::MouseEvent &event _UNUSED_) +{ + +} +*/ + +void ChatWindow::keyPressed(gcn::KeyEvent &event) +{ + if (event.getKey().getValue() == Key::DOWN) + { + if (mCurHist != mHistory.end()) + { + // Move forward through the history + HistoryIterator prevHist = mCurHist++; + + if (mCurHist != mHistory.end()) + { + mChatInput->setText(*mCurHist); + mChatInput->setCaretPosition(static_cast( + mChatInput->getText().length())); + } + else + { + mChatInput->setText(""); + mCurHist = prevHist; + } + } + else if (mChatInput->getText() != "") + { + mChatInput->setText(""); + } + } + else if (event.getKey().getValue() == Key::UP && + mCurHist != mHistory.begin() && !mHistory.empty()) + { + // Move backward through the history + mCurHist--; + mChatInput->setText(*mCurHist); + mChatInput->setCaretPosition(static_cast( + mChatInput->getText().length())); + } + else if (event.getKey().getValue() == Key::INSERT && + mChatInput->getText() != "") + { + // Add the current message to the history and clear the text + if (mHistory.empty() || mChatInput->getText() != mHistory.back()) + mHistory.push_back(mChatInput->getText()); + mCurHist = mHistory.end(); + mChatInput->setText(""); + } + else if (keyboard.isKeyActive(keyboard.KEY_AUTOCOMPLETE_CHAT) && + mChatInput->getText() != "") + { + autoComplete(); + return; + } + else if (keyboard.isKeyActive(keyboard.KEY_DEACTIVATE_CHAT) && + mChatInput->isVisible()) + { + mChatInput->setVisible(false); + } + else if (keyboard.isKeyActive(keyboard.KEY_CHAT_PREV_HISTORY) && + mChatInput->isVisible()) + { + ChatTab *tab = getFocused(); + if (tab && tab->getRows().size() > 0) + { + if (!mChatHistoryIndex) + { + mChatHistoryIndex = static_cast( + tab->getRows().size()); + + mChatInput->setText(""); + mChatInput->setCaretPosition(0); + return; + } + else + { + mChatHistoryIndex --; + } + + std::list::iterator it; + unsigned int f = 0; + for (it = tab->getRows().begin(); + it != tab->getRows().end(); ++it, f++) + { + if (f == mChatHistoryIndex) + mChatInput->setText(*it); + } + mChatInput->setCaretPosition(static_cast( + mChatInput->getText().length())); + } + } + else if (keyboard.isKeyActive(keyboard.KEY_CHAT_NEXT_HISTORY) && + mChatInput->isVisible()) + { + ChatTab *tab = getFocused(); + if (tab && !tab->getRows().empty()) + { + if (mChatHistoryIndex + 1 < tab->getRows().size()) + { + mChatHistoryIndex ++; + } + else if (mChatHistoryIndex < tab->getRows().size()) + { + mChatHistoryIndex ++; + mChatInput->setText(""); + mChatInput->setCaretPosition(0); + return; + } + else + { + mChatHistoryIndex = 0; + } + + std::list::iterator it; + unsigned int f = 0; + for (it = tab->getRows().begin(); + it != tab->getRows().end(); ++it, f++) + { + if (f == mChatHistoryIndex) + mChatInput->setText(*it); + } + mChatInput->setCaretPosition(static_cast( + mChatInput->getText().length())); + } + } + + std::string Temp; + switch (event.getKey().getValue()) + { + case Key::F2: Temp = "\u2318"; break; + case Key::F3: Temp = "\u263A"; break; + case Key::F4: Temp = "\u2665"; break; + case Key::F5: Temp = "\u266A"; break; + case Key::F6: Temp = "\u266B"; break; + case Key::F7: Temp = "\u26A0"; break; + case Key::F8: Temp = "\u2622"; break; + case Key::F9: Temp = "\u262E"; break; + case Key::F10: Temp = "\u2605"; break; + case Key::F11: Temp = "\u2618"; break; + case Key::F12: Temp = "\u2592"; break; + default: break; + } + + if (Temp != "") + addInputText(Temp, false); +} + +void ChatWindow::event(Channels channel, const Mana::Event &event) +{ + if (channel == CHANNEL_NOTICES) + { + if (event.getName() == EVENT_SERVERNOTICE && localChatTab) + localChatTab->chatLog(event.getString("message"), BY_SERVER); + } + else if (channel == CHANNEL_ATTRIBUTES) + { + if (!config.getBoolValue("showBattleEvents")) + return; + + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + switch (event.getInt("id")) + { + case EXP: + { + if (event.getInt("oldValue") > event.getInt("newValue")) + break; + + int change = event.getInt("newValue") + - event.getInt("oldValue"); + + if (change != 0) + battleChatLog("+" + toString(change) + " xp"); + break; + } + case LEVEL: + battleChatLog("Level: " + toString( + event.getInt("newValue"))); + break; + default: + break; + }; + } + } +} + +void ChatWindow::addInputText(const std::string &text, bool space) +{ + int caretPos = mChatInput->getCaretPosition(); + const std::string inputText = mChatInput->getText(); + + std::ostringstream ss; + ss << inputText.substr(0, caretPos) << text; + if (space) + ss << " "; + + ss << inputText.substr(caretPos); + + mChatInput->setText(ss.str()); + mChatInput->setCaretPosition(caretPos + static_cast( + text.length()) + static_cast(space)); + requestChatFocus(); +} + +void ChatWindow::addItemText(const std::string &item) +{ + std::ostringstream text; + text << "[" << item << "]"; + addInputText(text.str()); +} + +void ChatWindow::setVisible(bool isVisible) +{ + Window::setVisible(isVisible); + + /* + * For whatever reason, if setVisible is called, the mTmpVisible effect + * should be disabled. + */ + mTmpVisible = false; +} + +void ChatWindow::whisper(const std::string &nick, + const std::string &mes, Own own) +{ + if (mes.empty() || !player_node) + return; + + std::string playerName = player_node->getName(); + std::string tempNick = nick; + + toLower(playerName); + toLower(tempNick); + + if (tempNick.compare(playerName) == 0) + return; + + ChatTab *tab = 0; + TabMap::const_iterator i = mWhispers.find(tempNick); + + if (i != mWhispers.end()) + tab = i->second; + else if (config.getBoolValue("whispertab")) + tab = addWhisperTab(nick); + + if (tab) + { + if (own == BY_PLAYER) + { + tab->chatInput(mes); + } + else if (own == BY_SERVER) + { + tab->chatLog(mes); + } + else + { + tab->chatLog(nick, mes); + player_node->afkRespond(tab, nick); + } + } + else if (localChatTab) + { + if (own == BY_PLAYER) + { + Net::getChatHandler()->privateMessage(nick, mes); + + localChatTab->chatLog(strprintf(_("Whispering to %s: %s"), + nick.c_str(), mes.c_str()), BY_PLAYER); + } + else + { + localChatTab->chatLog(nick + " : " + mes, ACT_WHISPER, false); + if (player_node) + player_node->afkRespond(0, nick); + } + } +} + +ChatTab *ChatWindow::addWhisperTab(const std::string &nick, bool switchTo) +{ + if (!player_node) + return NULL; + + std::string playerName = player_node->getName(); + std::string tempNick = nick; + + toLower(playerName); + toLower(tempNick); + + TabMap::const_iterator i = mWhispers.find(tempNick); + ChatTab *ret; + + if (tempNick.compare(playerName) == 0) + return NULL; + + if (i != mWhispers.end()) + { + ret = i->second; + } + else + { + ret = new WhisperTab(nick); + mWhispers[tempNick] = ret; + if (config.getBoolValue("showChatHistory")) + ret->loadFromLogFile(nick); + } + + if (switchTo) + mChatTabs->setSelectedTab(ret); + + return ret; +} + +ChatTab *ChatWindow::getWhisperTab(const std::string &nick) const +{ + if (!player_node) + return NULL; + + std::string playerName = player_node->getName(); + std::string tempNick = nick; + + toLower(playerName); + toLower(tempNick); + + TabMap::const_iterator i = mWhispers.find(tempNick); + ChatTab *ret = 0; + + if (tempNick.compare(playerName) == 0) + return NULL; + + if (i != mWhispers.end()) + ret = i->second; + + return ret; +} + +std::string ChatWindow::addColors(std::string &msg) +{ + // default color or chat command + if (mChatColor == 0 || msg.length() == 0 || msg.at(0) == '#' + || msg.at(0) == '/' || msg.at(0) == '@' || msg.at(0) == '!') + { + return msg; + } + + std::string newMsg = ""; + int cMap[] = {1, 4, 5, 2, 3, 6, 7, 9, 0, 8}; + + // rainbow + switch(mChatColor) + { + case 11: + msg = removeColors(msg); + for (unsigned int f = 0; f < msg.length(); f ++) + { + newMsg += "##" + toString(mRainbowColor++) + msg.at(f); + if (mRainbowColor > 9) + mRainbowColor = 0; + } + return newMsg; + case 12: + msg = removeColors(msg); + for (unsigned int f = 0; f < msg.length(); f ++) + { + newMsg += "##" + toString(cMap[mRainbowColor++]) + msg.at(f); + if (mRainbowColor > 9) + mRainbowColor = 0; + } + return newMsg; + case 13: + msg = removeColors(msg); + for (unsigned int f = 0; f < msg.length(); f ++) + { + newMsg += "##" + toString(cMap[9-mRainbowColor++]) + msg.at(f); + if (mRainbowColor > 9) + mRainbowColor = 0; + } + return newMsg; + default: + break; + } + + // simple colors + return "##" + toString(mChatColor - 1) + msg; +} + +void ChatWindow::autoComplete() +{ + int caretPos = mChatInput->getCaretPosition(); + int startName = 0; + const std::string inputText = mChatInput->getText(); + std::string name = inputText.substr(0, caretPos); + std::string newName(""); + + for (int f = caretPos - 1; f > -1; f --) + { + if (isWordSeparator(inputText[f])) + { + startName = f + 1; + name = inputText.substr(f + 1, caretPos - f); + break; + } + } + + if (caretPos - 1 + 1 == startName) + return; + + ChatTab *cTab = static_cast(mChatTabs->getSelectedTab()); + std::vector nameList; + + cTab->getAutoCompleteList(nameList); + newName = autoComplete(nameList, name); + + if (newName == "" && actorSpriteManager) + { + actorSpriteManager->getPlayerNames(nameList, true); + newName = autoComplete(nameList, name); + } + if (newName == "") + newName = autoCompleteHistory(name); + if (newName == "" && spellManager) + newName = spellManager->autoComplete(name); + if (newName == "") + newName = autoCompleteCommands(name); + if (newName == "" && actorSpriteManager) + { + actorSpriteManager->getMobNames(nameList); + newName = autoComplete(nameList, name); + } + + if (newName != "") + { + mChatInput->setText(inputText.substr(0, startName) + newName + + inputText.substr(caretPos, inputText.length() - caretPos)); + + int len = caretPos - static_cast(name.length()) + + static_cast(newName.length()); + + if (startName > 0) + mChatInput->setCaretPosition(len + 1); + else + mChatInput->setCaretPosition(len); + } +} + +std::string ChatWindow::autoComplete(std::vector &names, + std::string partName) const +{ + std::vector::iterator i = names.begin(); + toLower(partName); + std::string newName(""); + + while (i != names.end()) + { + if (!i->empty()) + { + std::string name = *i; + toLower(name); + + std::string::size_type pos = name.find(partName, 0); + if (pos == 0) + { + if (newName != "") + { + toLower(newName); + newName = findSameSubstring(name, newName); + } + else + { + newName = *i; + } + } + } + ++i; + } + + return newName; +} + +std::string ChatWindow::autoCompleteCommands(std::string partName) +{ + Commands::iterator i = mCommands.begin(); + std::vector nameList; + + while (i != mCommands.end()) + { + std::string line = *i; + std::string::size_type pos = line.find(partName, 0); + if (pos == 0) + nameList.push_back(line); + ++i; + } + return autoComplete(nameList, partName); +} + +/* +void ChatWindow::moveTabLeft(ChatTab *tab) +{ + mChatTabs->moveLeft(tab); +} + +void ChatWindow::moveTabRight(ChatTab *tab) +{ + mChatTabs->moveRight(tab); +} +*/ + +std::string ChatWindow::autoCompleteHistory(std::string partName) +{ + History::iterator i = mHistory.begin(); + std::vector nameList; + + while (i != mHistory.end()) + { + std::string line = *i; + unsigned int f = 0; + while (f < line.length() && !isWordSeparator(line.at(f))) + f++; + + line = line.substr(0, f); + if (line != "") + nameList.push_back(line); + + ++i; + } + return autoComplete(nameList, partName); +} + +void ChatWindow::resortChatLog(std::string line, Own own, + bool ignoreRecord, bool tryRemoveColors) +{ + if (own == -1) + own = BY_SERVER; + + if (tradeChatTab) + { + if (findI(line, mTradeFilter) != std::string::npos) + { +// logger->log("trade: " + line); + tradeChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + return; + } + + unsigned long idx = line.find(": \302\202"); + if (idx != std::string::npos) + { + line = line.erase(idx + 2, 2); + tradeChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + return; + } + + unsigned long idx1 = line.find("@@"); + if (idx1 != std::string::npos) + { + unsigned long idx2 = line.find("|", idx1); + if (idx2 != std::string::npos) + { + unsigned long idx3 = line.find("@@", idx2); + if (idx3 != std::string::npos) + { + tradeChatTab->chatLog(line, own, ignoreRecord, + tryRemoveColors); + return; + } + } + } + } + + if (localChatTab) + localChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); +} + +void ChatWindow::battleChatLog(std::string line, Own own, + bool ignoreRecord, bool tryRemoveColors) +{ + if (own == -1) + own = BY_SERVER; + if (battleChatTab) + battleChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + else if (debugChatTab) + debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); +} + +void ChatWindow::initTradeFilter() +{ + std::string tradeListName = Client::getServerConfigDirectory() + + "/tradefilter.txt"; + + std::ifstream tradeFile; + struct stat statbuf; + + if (!stat(tradeListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + tradeFile.open(tradeListName.c_str(), std::ios::in); + char line[100]; + while (tradeFile.getline(line, 100)) + { + std::string str = line; + if (!str.empty()) + mTradeFilter.push_back(str); + } + tradeFile.close(); + } +} + +void ChatWindow::updateOnline(std::set &onlinePlayers) +{ + TabMap::iterator iter; + const Party *party = 0; + const Guild *guild = 0; + if (player_node) + { + party = player_node->getParty(); + guild = player_node->getGuild(); + } + for (iter = mWhispers.begin(); iter != mWhispers.end(); ++iter) + { + if (!iter->second) + return; + + WhisperTab *tab = static_cast(iter->second); + + if (!tab) + continue; + + if (onlinePlayers.find(tab->getNick()) != onlinePlayers.end()) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + } + else + { + const std::string nick = tab->getNick(); + if (actorSpriteManager) + { + const Being *being = actorSpriteManager->findBeingByName( + nick, ActorSprite::PLAYER); + if (being) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + continue; + } + } + if (party) + { + const PartyMember *pm = party->getMember(nick); + if (pm && pm->getOnline()) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + continue; + } + } + if (guild) + { + const GuildMember *gm = guild->getMember(nick); + if (gm && gm->getOnline()) + { + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + continue; + } + } + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER_OFFLINE)); + } + } +} + +void ChatWindow::loadState() +{ + int num = 0; + while (num < 50) + { + std::string nick = serverConfig.getValue( + "chatWhisper" + toString(num), ""); + + if (nick.empty()) + break; + addWhisperTab(nick); + serverConfig.deleteKey("chatWhisper" + toString(num)); + num ++; + } + + while (num < 50) + { + serverConfig.deleteKey("chatWhisper" + toString(num)); + num ++; + } +} + +void ChatWindow::saveState() +{ + int num = 0; + TabMap::iterator iter; + for (iter = mWhispers.begin(); iter != mWhispers.end() && num < 50; ++iter) + { + if (!iter->second) + return; + + WhisperTab *tab = static_cast(iter->second); + + if (!tab) + continue; + + serverConfig.setValue("chatWhisper" + toString(num), + tab->getNick()); + + num ++; + } + + while (num < 50) + { + serverConfig.deleteKey("chatWhisper" + toString(num)); + num ++; + } +} + +std::string ChatWindow::doReplace(const std::string &msg) +{ + if (Client::getServerName() == "server.themanaworld.org" + || Client::getServerName() == "themanaworld.org" + || Client::getServerName() == "81.161.192.4") + { + return msg; + } + + std::string str = msg; + replaceSpecialChars(str); + return str; +} diff --git a/src/gui/chat.h b/src/gui/chat.h new file mode 100644 index 000000000..444057b57 --- /dev/null +++ b/src/gui/chat.h @@ -0,0 +1,318 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHAT_H +#define CHAT_H + +#include "listener.h" + +#include "gui/widgets/window.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class BrowserBox; +class ChatTab; +class Channel; +class ChatInput; +class ColorListModel; +class ScrollArea; +class TabbedArea; +class ItemLinkHandler; +class Tab; +class WhisperTab; + +#define DEFAULT_CHAT_WINDOW_SCROLL 7 // 1 means `1/8th of the window size'. + +enum Own +{ + BY_GM = 0, + BY_PLAYER, + BY_OTHER, + BY_SERVER, + BY_CHANNEL, + ACT_WHISPER, // getting whispered at + ACT_IS, // equivalent to "/me" on IRC + BY_LOGGER, + BY_UNKNOWN = -1 +}; + +/** One item in the chat log */ +struct CHATLOG +{ + std::string nick; + std::string text; + Own own; +}; + +/** + * The chat window. + * + * \ingroup Interface + */ +class ChatWindow : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public Mana::Listener +{ + public: + /** + * Constructor. + */ + ChatWindow(); + + /** + * Destructor: used to write back values to the config file + */ + ~ChatWindow(); + + /** + * Reset the chat window to default positions. + */ + void resetToDefaultSize(); + + /** + * Gets the focused tab. + */ + ChatTab *getFocused() const; + + /** + * Clear the given tab. + */ + void clearTab(ChatTab *tab); + + /** + * Clear the current tab. + */ + void clearTab(); + + /** + * Switch to the previous tab in order + */ + void prevTab(); + + /** + * Switch to the next tab in order + */ + void nextTab(); + + /** + * Switch to the default tab + */ + void defaultTab(); + + /** + * Performs action. + */ + void action(const gcn::ActionEvent &event); + + /** + * Request focus for typing chat message. + * + * \returns true if the input was shown + * false otherwise + */ + bool requestChatFocus(); + + /** + * Checks whether ChatWindow is Focused or not. + */ + bool isInputFocused() const; + + /** + * Passes the text to the current tab as input + * + * @param msg The message text which is to be sent. + */ + void chatInput(const std::string &msg); + + /** + * Passes the text to the local chat tab as input + * + * @param msg The message text which is to be sent. + */ + void localChatInput(const std::string &msg); + + /** Called when key is pressed */ + void keyPressed(gcn::KeyEvent &event); + + /** Set the chat input as the given text. */ + void setInputText(const std::string &text); + + /** Add the given text to the chat input. */ + void addInputText(const std::string &text, bool space = true); + + /** Called to add item to chat */ + void addItemText(const std::string &item); + + /** Override to reset mTmpVisible */ + void setVisible(bool visible); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + void event(Channels channel, const Mana::Event &event); + + /** + * Scrolls the chat window + * + * @param amount direction and amount to scroll. Negative numbers scroll + * up, positive numbers scroll down. The absolute amount indicates the + * amount of 1/8ths of chat window real estate that should be scrolled. + */ + void scroll(int amount); + + /** + * Sets the file being recorded to + * + * @param msg The file to write out to. If null, then stop recording. + */ + void setRecordingFile(const std::string &msg); + + bool getReturnTogglesChat() const + { return mReturnToggles; } + + void setReturnTogglesChat(bool toggles) + { mReturnToggles = toggles; } + + void doPresent(); + + void whisper(const std::string &nick, const std::string &mes, + Own own = BY_OTHER); + + ChatTab *addWhisperTab(const std::string &nick, bool switchTo = false); + + ChatTab *getWhisperTab(const std::string &nick) const; + + void removeAllWhispers(); + + void ignoreAllWhispers(); + + void resortChatLog(std::string line, Own own = BY_UNKNOWN, + bool ignoreRecord = false, + bool tryRemoveColors = true); + + void battleChatLog(std::string line, Own own = BY_UNKNOWN, + bool ignoreRecord = false, + bool tryRemoveColors = true); + + void updateOnline(std::set &onlinePlayers); + + void loadState(); + + void saveState(); + + std::string doReplace(const std::string &msg); + + protected: + friend class ChatTab; + friend class WhisperTab; + friend class PopupMenu; + + /** Remove the given tab from the window */ + void removeTab(ChatTab *tab); + + /** Add the tab to the window */ + void addTab(ChatTab *tab); + + void removeWhisper(const std::string &nick); + + void adjustTabSize(); + + void autoComplete(); + + std::string addColors(std::string &msg); + + std::string autoCompleteHistory(std::string partName); + + std::string autoCompleteCommands(std::string partName); + + std::string autoComplete(std::vector &names, + std::string partName) const; + + /** Used for showing item popup on clicking links **/ + ItemLinkHandler *mItemLinkHandler; + + /** Input box for typing chat messages. */ + ChatInput *mChatInput; + + void widgetResized(const gcn::Event &event); + + void initTradeFilter(); + + int mRainbowColor; + + private: + void fillCommands(); + + bool mTmpVisible; + + /** Tabbed area for holding each channel. */ + TabbedArea *mChatTabs; + + typedef std::map TabMap; + /** Manage whisper tabs */ + TabMap mWhispers; + + typedef std::list History; + typedef History::iterator HistoryIterator; + History mHistory; /**< Command history. */ + HistoryIterator mCurHist; /**< History iterator. */ + + typedef std::list Commands; + typedef Commands::iterator CommandsIterator; + History mCommands; /**< Command list. */ + + bool mReturnToggles; /**< Marks whether toggles the chat log + or not */ + + std::list mTradeFilter; + + gcn::DropDown *mColorPicker; + ColorListModel *mColorListModel; + int mChatColor; + unsigned int mChatHistoryIndex; +}; + +extern ChatWindow *chatWindow; + +#endif diff --git a/src/gui/confirmdialog.cpp b/src/gui/confirmdialog.cpp new file mode 100644 index 000000000..1d24a25bd --- /dev/null +++ b/src/gui/confirmdialog.cpp @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/confirmdialog.h" + +#include "sound.h" + +#include "gui/gui.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/textbox.h" + +#include "utils/gettext.h" + +#include + +ConfirmDialog::ConfirmDialog(const std::string &title, const std::string &msg, + bool ignore, bool modal, Window *parent): + Window(title, modal, parent) +{ + mTextBox = new TextBox; + mTextBox->setEditable(false); + mTextBox->setOpaque(false); + mTextBox->setTextWrapped(msg, 260); + + gcn::Button *yesButton = new Button(_("Yes"), "yes", this); + gcn::Button *noButton = new Button(_("No"), "no", this); + gcn::Button *ignoreButton = NULL; + + if (ignore) + ignoreButton = new Button(_("Ignore"), "ignore", this); + + const int numRows = mTextBox->getNumberOfRows(); + int inWidth = yesButton->getWidth() + noButton->getWidth() + + (2 * getPadding()); + + if (ignoreButton) + inWidth += ignoreButton->getWidth(); + + const int fontHeight = getFont()->getHeight(); + const int height = numRows * fontHeight; + int width = getFont()->getWidth(title); + + if (width < mTextBox->getMinWidth()) + width = mTextBox->getMinWidth(); + if (width < inWidth) + width = inWidth; + + setContentSize(mTextBox->getMinWidth() + fontHeight, height + fontHeight + + noButton->getHeight()); + mTextBox->setPosition(getPadding(), getPadding()); + + // 8 is the padding that GUIChan adds to button widgets + // (top and bottom combined) + yesButton->setPosition((width - inWidth) / 2, height + 8); + noButton->setPosition(yesButton->getX() + yesButton->getWidth() + + (2 * getPadding()), + height + 8); + if (ignoreButton) + { + ignoreButton->setPosition(noButton->getX() + noButton->getWidth() + + (2 * getPadding()), height + 8); + } + + add(mTextBox); + add(yesButton); + add(noButton); + + if (ignore && ignoreButton) + add(ignoreButton); + + if (getParent()) + { + center(); + getParent()->moveToTop(this); + } + setVisible(true); + yesButton->requestFocus(); + sound.playGuiSfx("system/newmessage.ogg"); +} + +void ConfirmDialog::action(const gcn::ActionEvent &event) +{ + setActionEventId(event.getId()); + distributeActionEvent(); + + // Can we receive anything else anyway? + if (event.getId() == "yes" || event.getId() == "no" + || event.getId() == "ignore") + { + scheduleDelete(); + } +} + diff --git a/src/gui/confirmdialog.h b/src/gui/confirmdialog.h new file mode 100644 index 000000000..0ba9c869b --- /dev/null +++ b/src/gui/confirmdialog.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPTION_DIALOG_H +#define OPTION_DIALOG_H + +#include "gui/widgets/window.h" + +#include + +class TextBox; + +/** + * An option dialog. + * + * \ingroup GUI + */ +class ConfirmDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + ConfirmDialog(const std::string &title, const std::string &msg, + bool ignore = false, bool modal = false, + Window *parent = NULL); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + TextBox *mTextBox; +}; + +#endif diff --git a/src/gui/connectiondialog.cpp b/src/gui/connectiondialog.cpp new file mode 100644 index 000000000..8f960d335 --- /dev/null +++ b/src/gui/connectiondialog.cpp @@ -0,0 +1,65 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "connectiondialog.h" + +#include "log.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/progressindicator.h" + +#include "utils/gettext.h" + +ConnectionDialog::ConnectionDialog(const std::string &text, + State cancelState): + Window(""), + mCancelState(cancelState) +{ + setTitleBarHeight(0); + setMovable(false); + setMinWidth(0); + + ProgressIndicator *progressIndicator = new ProgressIndicator; + gcn::Label *label = new Label(text); + Button *cancelButton = new Button(_("Cancel"), "cancelButton", this); + + place(0, 0, progressIndicator); + place(0, 1, label); + place(0, 2, cancelButton).setHAlign(LayoutCell::CENTER); + reflowLayout(); + + center(); + setVisible(true); +} + +void ConnectionDialog::action(const gcn::ActionEvent &) +{ + logger->log1("Cancel pressed"); + Client::setState(mCancelState); +} + +void ConnectionDialog::draw(gcn::Graphics *graphics) +{ + // Don't draw the window background, only draw the children + drawChildren(graphics); +} diff --git a/src/gui/connectiondialog.h b/src/gui/connectiondialog.h new file mode 100644 index 000000000..623a66455 --- /dev/null +++ b/src/gui/connectiondialog.h @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "client.h" + +#include "gui/widgets/window.h" + +#include + +/** + * The connection dialog. + * + * \ingroup Interface + */ +class ConnectionDialog : public Window, gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @param text The text to display + * @param cancelState The state to enter when Cancel is pressed + * + * @see Window::Window + */ + ConnectionDialog(const std::string &text, State cancelState); + + /** + * Called when the user presses Cancel. Restores the global state to + * the previous one. + */ + void action(const gcn::ActionEvent &); + + void draw(gcn::Graphics *graphics); + + private: + gcn::Label *mLabel; + State mCancelState; +}; + +#endif diff --git a/src/gui/debugwindow.cpp b/src/gui/debugwindow.cpp new file mode 100644 index 000000000..2feb30c80 --- /dev/null +++ b/src/gui/debugwindow.cpp @@ -0,0 +1,248 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/debugwindow.h" + +#include "client.h" +#include "game.h" +#include "localplayer.h" +#include "main.h" +#include "map.h" +#include "particle.h" + +#include "gui/setup.h" +#include "gui/setup_video.h" +#include "gui/viewport.h" + +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/chattab.h" + +#include "resources/image.h" + +#include "net/packetcounters.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +DebugWindow::DebugWindow(): + Window(_("Debug")) +{ + setWindowName("Debug"); + if (setupWindow) + setupWindow->registerWindowForReset(this); + + mUpdateTime = 0; + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + setDefaultSize(500, 150, ImageRect::CENTER); + +#ifdef USE_OPENGL + switch (Image::getLoadAsOpenGL()) + { + case 0: + mFPSText = _("%d FPS (Software)"); + break; + case 1: + default: + mFPSText = _("%d FPS (fast OpenGL)"); + break; + case 2: + mFPSText = _("%d FPS (old OpenGL)"); + break; + }; +#else + mFPSText = _("%d FPS (Software)"); +#endif + + mFPSLabel = new Label(strprintf(_("%d FPS"), 0)); + mMusicFileLabel = new Label(strprintf(_("Music:"))); + mMapLabel = new Label(strprintf(_("Map:"))); + mMinimapLabel = new Label(strprintf(_("Minimap:"))); + mTileMouseLabel = new Label(strprintf(_("Cursor: (%d, %d)"), 0, 0)); + mParticleCountLabel = new Label(strprintf(_("Particle count: %d"), 88888)); + mMapActorCountLabel = new Label(strprintf( + _("Map actors count: %d"), 88888)); + + mPingLabel = new Label(" "); + mInPackets1Label = new Label(" "); + mOutPackets1Label = new Label(" "); + + mXYLabel = new Label(strprintf("%s (?,?)", _("Player Position:"))); + mTargetLabel = new Label(strprintf("%s ?", _("Target:"))); + mTargetIdLabel = new Label(strprintf("%s ? ", _("Target Id:"))); + mTargetLevelLabel = new Label(strprintf("%s ?", _("Target Level:"))); + mTargetPartyLabel = new Label(strprintf("%s ?", _("Target Party:"))); + mTargetGuildLabel = new Label(strprintf("%s ?", _("Target Guild:"))); + + place(0, 0, mFPSLabel, 3); + place(4, 0, mTileMouseLabel, 2); + place(0, 1, mMusicFileLabel, 3); + place(4, 1, mParticleCountLabel, 2); + place(4, 2, mMapActorCountLabel, 2); + place(0, 2, mMapLabel, 4); + place(0, 3, mMinimapLabel, 4); + place(0, 4, mXYLabel, 4); + place(4, 3, mPingLabel, 2); + place(4, 4, mInPackets1Label, 2); + place(4, 5, mOutPackets1Label, 2); + place(0, 5, mTargetLabel, 4); + place(0, 6, mTargetIdLabel, 4); + place(0, 7, mTargetLevelLabel, 4); + place(0, 8, mTargetPartyLabel, 4); + place(0, 9, mTargetGuildLabel, 4); + + loadWindowState(); +} + +void DebugWindow::logic() +{ + if (!isVisible()) + return; + + mFPSLabel->setCaption(strprintf(mFPSText.c_str(), fps)); + + if (player_node) + { + mXYLabel->setCaption(strprintf("%s (%d, %d)", _("Player Position:"), + player_node->getTileX(), player_node->getTileY())); + } + else + { + mXYLabel->setCaption(strprintf("%s (?, ?)", _("Player Position:"))); + } + + if (player_node && player_node->getTarget()) + { + Being *target = player_node->getTarget(); + + mTargetLabel->setCaption(strprintf("%s %s (%d, %d)", _("Target:"), + target->getName().c_str(), target->getTileX(), + target->getTileY())); + + mTargetIdLabel->setCaption(strprintf("%s %d", + _("Target Id:"), target->getId())); + if (target->getLevel()) + { + mTargetLevelLabel->setCaption(strprintf("%s %d", + _("Target Level:"), target->getLevel())); + } + else + { + mTargetLevelLabel->setCaption(strprintf("%s ?", + _("Target Level:"))); + } + + mTargetPartyLabel->setCaption(strprintf("%s %s", _("Target Party:"), + target->getPartyName().c_str())); + + mTargetGuildLabel->setCaption(strprintf("%s %s", _("Target Guild:"), + target->getGuildName().c_str())); + } + else + { + mTargetLabel->setCaption(strprintf("%s ?", _("Target:"))); + mTargetIdLabel->setCaption(strprintf("%s ?", _("Target Id:"))); + mTargetLevelLabel->setCaption(strprintf("%s ?", _("Target Level:"))); + mTargetPartyLabel->setCaption(strprintf("%s ?", _("Target Party:"))); + mTargetGuildLabel->setCaption(strprintf("%s ?", _("Target Guild:"))); + } + + const Map *map = Game::instance()->getCurrentMap(); + if (map && viewport) + { + // Get the current mouse position + int mouseTileX = (viewport->getMouseX() + viewport->getCameraX()) + / map->getTileWidth(); + int mouseTileY = (viewport->getMouseY() + viewport->getCameraY()) + / map->getTileHeight(); + mTileMouseLabel->setCaption(strprintf("%s (%d, %d)", + _("Cursor:"), mouseTileX, mouseTileY)); + + mMusicFileLabel->setCaption(strprintf("%s %s", _("Music:"), + map->getProperty("music").c_str())); + mMinimapLabel->setCaption(strprintf("%s %s", _("Minimap:"), + map->getProperty("minimap").c_str())); + mMapLabel->setCaption(strprintf("%s %s", _("Map:"), + map->getProperty("_filename").c_str())); + + + if (mUpdateTime != cur_time) + { + mUpdateTime = cur_time; + mParticleCountLabel->setCaption(strprintf(_("Particle count: %d"), + Particle::particleCount)); + + mMapActorCountLabel->setCaption( + strprintf("%s %d", _("Map actors count:"), + map->getActorsCount())); + } + } + else + { + mTileMouseLabel->setCaption(strprintf("%s (?, ?)", _("Cursor:"))); + + mMusicFileLabel->setCaption(strprintf("%s ?", _("Music:"))); + mMinimapLabel->setCaption(strprintf("%s ?", _("Minimap:"))); + mMapLabel->setCaption(strprintf("%s ?", _("Map:"))); + + mMapActorCountLabel->setCaption( + strprintf("%s ?", _("Map actors count:"))); + } + + mMapActorCountLabel->adjustSize(); + mParticleCountLabel->adjustSize(); + + if (player_node && player_node->getPingTime() != 0) + { + mPingLabel->setCaption(strprintf(_("Ping: %d ms"), + player_node->getPingTime())); + } + else + { + mPingLabel->setCaption(_("Ping: ? ms")); + } + + mInPackets1Label->setCaption(strprintf(_("In: %d bytes/s"), + PacketCounters::getInBytes())); + mOutPackets1Label->setCaption(strprintf(_("Out: %d bytes/s"), + PacketCounters::getOutBytes())); + + if (player_node) + player_node->tryPingRequest(); +} + +void DebugWindow::draw(gcn::Graphics *g) +{ + Window::draw(g); + + if (player_node) + { + Being *target = player_node->getTarget(); + if (target) + { + Graphics *g2 = static_cast(g); + target->draw(g2, -target->getPixelX() + 16 + getWidth() / 2, + -target->getPixelY() + 32 + getHeight() / 2); + } + } +} diff --git a/src/gui/debugwindow.h b/src/gui/debugwindow.h new file mode 100644 index 000000000..1c97f8ca9 --- /dev/null +++ b/src/gui/debugwindow.h @@ -0,0 +1,72 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DEBUGWINDOW_H +#define DEBUGWINDOW_H + +#include "gui/widgets/window.h" + +class Label; + +/** + * The debug window. + * + * \ingroup Interface + */ +class DebugWindow : public Window +{ + public: + /** + * Constructor. + */ + DebugWindow(); + + /** + * Logic (updates components' size and infos) + */ + void logic(); + + void draw(gcn::Graphics *g); + + void setPing(int pingTime); + + private: + Label *mMusicFileLabel, *mMapLabel, *mMinimapLabel; + Label *mTileMouseLabel, *mFPSLabel; + Label *mParticleCountLabel; + Label *mMapActorCountLabel; + Label *mXYLabel; + Label *mTargetLabel; + Label *mTargetIdLabel; + Label *mTargetLevelLabel; + Label *mTargetPartyLabel; + Label *mTargetGuildLabel; + Label *mPingLabel; + Label *mInPackets1Label; + Label *mOutPackets1Label; + + std::string mFPSText; + int mUpdateTime; +}; + +extern DebugWindow *debugWindow; + +#endif diff --git a/src/gui/editdialog.cpp b/src/gui/editdialog.cpp new file mode 100644 index 000000000..b8d999ce7 --- /dev/null +++ b/src/gui/editdialog.cpp @@ -0,0 +1,73 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/editdialog.h" + +#include "gui/gui.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/textfield.h" + +#include "utils/gettext.h" + +#include + +EditDialog::EditDialog(const std::string &title, const std::string &msg, + std::string eventOk, int width, + Window *parent, bool modal): + Window(title, modal, parent) +{ + mTextField = new TextField; + mTextField->setText(msg); + + mEventOk = eventOk; + + gcn::Button *okButton = new Button(_("OK"), mEventOk, this); + + const int numRows = 1; + const int fontHeight = getFont()->getHeight(); + const int height = numRows * fontHeight; + + setContentSize(width, height + fontHeight + okButton->getHeight()); + mTextField->setPosition(getPadding(), getPadding()); + mTextField->setWidth(width - (2 * getPadding())); + + okButton->setPosition((width - okButton->getWidth()) / 2, height + 8); + + add(mTextField); + add(okButton); + + center(); + setVisible(true); + okButton->requestFocus(); +} + +void EditDialog::action(const gcn::ActionEvent &event) +{ + // Proxy button events to our listeners + ActionListenerIterator i; + for (i = mActionListeners.begin(); i != mActionListeners.end(); ++i) + (*i)->action(event); + + if (event.getId() == mEventOk) + scheduleDelete(); +} diff --git a/src/gui/editdialog.h b/src/gui/editdialog.h new file mode 100644 index 000000000..55947b23d --- /dev/null +++ b/src/gui/editdialog.h @@ -0,0 +1,66 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef EDIT_DIALOG_H +#define EDIT_DIALOG_H + +#include "gui/widgets/window.h" +#include "gui/widgets/textfield.h" + +#include + +#define ACTION_EDIT_OK "edit ok" + +class TextField; + +/** + * An 'Ok' button dialog. + * + * \ingroup GUI + */ +class EditDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + EditDialog(const std::string &title, const std::string &msg, + std::string eventOk = ACTION_EDIT_OK, int width = 300, + Window *parent = NULL, bool modal = true); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + std::string getMsg() + { return mTextField->getText(); } + + private: + std::string mEventOk; + + TextField *mTextField; +}; + +#endif diff --git a/src/gui/emotepopup.cpp b/src/gui/emotepopup.cpp new file mode 100644 index 000000000..2a0d94aca --- /dev/null +++ b/src/gui/emotepopup.cpp @@ -0,0 +1,214 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/emotepopup.h" + +#include "animatedsprite.h" +#include "configuration.h" +#include "emoteshortcut.h" +#include "graphics.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "resources/emotedb.h" +#include "resources/image.h" +#include "resources/iteminfo.h" + +#include "utils/dtor.h" + +#include +#include + +const int EmotePopup::gridWidth = 34; // emote icon width + 4 +const int EmotePopup::gridHeight = 36; // emote icon height + 4 + +static const int MAX_COLUMNS = 6; + +EmotePopup::EmotePopup(): + mSelectedEmoteIndex(-1), + mHoveredEmoteIndex(-1), + mRowCount(1), + mColumnCount(1) +{ + // Setup emote sprites + for (int i = 0; i <= EmoteDB::getLast(); ++i) + { + const AnimatedSprite *sprite = EmoteDB::getAnimation(i, true); + if (sprite) + mEmotes.push_back(sprite); + } + + mSelectionImage = Theme::getImageFromTheme("selection.png"); + if (!mSelectionImage) + logger->log1("Error: Unable to load selection.png"); + + if (mSelectionImage) + mSelectionImage->setAlpha(Client::getGuiAlpha()); + + addMouseListener(this); + recalculateSize(); + setVisible(true); +} + +EmotePopup::~EmotePopup() +{ + if (mSelectionImage) + mSelectionImage->decRef(); +} + +void EmotePopup::draw(gcn::Graphics *graphics) +{ + Popup::draw(graphics); + + if (!mColumnCount) + return; + + const unsigned int emoteCount = static_cast(mEmotes.size()); + const unsigned int emotesLeft + = static_cast(mEmotes.size() % mColumnCount); + + for (unsigned int i = 0; i < emoteCount ; i++) + { + int row = i / mColumnCount; + int column = i % mColumnCount; + + unsigned int emoteX = 4 + column * gridWidth; + unsigned int emoteY = 4 + row * gridHeight; + + // Center the last row when there are less emotes than columns + if (emotesLeft > 0 && row == mRowCount - 1) + emoteX += (mColumnCount - emotesLeft) * gridWidth / 2; + + // Draw selection image below hovered item + if (mSelectionImage && static_cast(i) == mHoveredEmoteIndex) + { + static_cast(graphics)->drawImage( + mSelectionImage, emoteX, emoteY + 4); + } + + // Draw emote icon + if (mEmotes[i]) + mEmotes[i]->draw(static_cast(graphics), emoteX, emoteY); + } +} + +void EmotePopup::mousePressed(gcn::MouseEvent &event) +{ + if (event.getButton() != gcn::MouseEvent::LEFT) + return; + + const int index = getIndexAt(event.getX(), event.getY()); + if (index != -1) + { + setSelectedEmoteIndex(index); + if (emoteShortcut) + { + emoteShortcut->setEmoteSelected( + static_cast(index + 1)); + } + } +} + +void EmotePopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + mHoveredEmoteIndex = getIndexAt(event.getX(), event.getY()); +} + +int EmotePopup::getSelectedEmote() const +{ + return 1 + mSelectedEmoteIndex; +} + +void EmotePopup::setSelectedEmoteIndex(int index) +{ + if (index == mSelectedEmoteIndex) + return; + + mSelectedEmoteIndex = index; + distributeValueChangedEvent(); +} + +int EmotePopup::getIndexAt(int x, int y) const +{ + if (!gridWidth || !gridHeight) + return -1; + + const unsigned int emotesLeft + = static_cast(mEmotes.size() % mColumnCount); + const unsigned int row = y / gridHeight; + unsigned int column; + + // Take into account that the last row is centered + if (emotesLeft > 0 && static_cast(row) == mRowCount - 1) + { + int unsigned emotesMissing = mColumnCount - emotesLeft; + column = std::min((x - emotesMissing * gridWidth / 2) / gridWidth, + emotesLeft - 1); + } + else + { + column = std::min(x / gridWidth, mColumnCount - 1); + } + + int unsigned index = column + (row * mColumnCount); + + if (index < mEmotes.size()) + return index; + + return -1; +} + +void EmotePopup::recalculateSize() +{ + const unsigned emoteCount = static_cast(mEmotes.size()); + + mRowCount = emoteCount / MAX_COLUMNS; + if (emoteCount % MAX_COLUMNS > 0) + ++mRowCount; + + if (mRowCount) + mColumnCount = emoteCount / mRowCount; + else + mColumnCount = 1; + + if (emoteCount % mRowCount > 0) + ++mColumnCount; + + setContentSize(mColumnCount * gridWidth, mRowCount * gridHeight); +} + +void EmotePopup::distributeValueChangedEvent() +{ + gcn::SelectionEvent event(this); + Listeners::const_iterator i_end = mListeners.end(); + Listeners::const_iterator i; + + for (i = mListeners.begin(); i != i_end; ++i) + { + if (*i) + (*i)->valueChanged(event); + } +} diff --git a/src/gui/emotepopup.h b/src/gui/emotepopup.h new file mode 100644 index 000000000..c1026d0a5 --- /dev/null +++ b/src/gui/emotepopup.h @@ -0,0 +1,121 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EMOTEPOPUP_H +#define EMOTEPOPUP_H + +#include "gui/widgets/popup.h" + +#include + +#include +#include + +class AnimatedSprite; +class Image; + +namespace gcn +{ + class SelectionListener; +} + +/** + * An emote popup. Used to activate emotes and assign them to shortcuts. + * + * \ingroup GUI + */ +class EmotePopup : public Popup +{ + public: + /** + * Constructor. Initializes the graphic. + */ + EmotePopup(); + + virtual ~EmotePopup(); + + /** + * Draws the emotes. + */ + void draw(gcn::Graphics *graphics); + + void mousePressed(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + /** + * Returns the selected emote. + */ + int getSelectedEmote() const; + + /** + * Adds a listener to the list that's notified each time a change to + * the selection occurs. + */ + void addSelectionListener(gcn::SelectionListener *listener) + { mListeners.push_back(listener); } + + /** + * Removes a listener from the list that's notified each time a change + * to the selection occurs. + */ + void removeSelectionListener(gcn::SelectionListener *listener) + { mListeners.remove(listener); } + + private: + /** + * Sets the index of the currently selected emote. + */ + void setSelectedEmoteIndex(int index); + + /** + * Returns the index at the specified coordinates. Returns -1 when + * there is no valid index. + */ + int getIndexAt(int x, int y) const; + + /** + * Determine and set the size of the container. + */ + void recalculateSize(); + + /** + * Sends out selection events to the list of selection listeners. + */ + void distributeValueChangedEvent(); + + std::vector mEmotes; + Image *mSelectionImage; + int mSelectedEmoteIndex; + int mHoveredEmoteIndex; + + int mRowCount; + int mColumnCount; + + typedef std::list Listeners; + + Listeners mListeners; + + static const int gridWidth; + static const int gridHeight; +}; + +#endif diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp new file mode 100644 index 000000000..dc5dc8c04 --- /dev/null +++ b/src/gui/equipmentwindow.cpp @@ -0,0 +1,260 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/button.h" + +#include "equipment.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" + +#include "gui/equipmentwindow.h" +#include "gui/itempopup.h" +#include "gui/theme.h" +#include "gui/setup.h" +#include "gui/viewport.h" + +#include "gui/widgets/playerbox.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +static const int BOX_WIDTH = 36; +static const int BOX_HEIGHT = 36; + +// Positions of the boxes, 2nd dimension is X and Y respectively. +static const int boxPosition[][2] = +{ + { 90, 40 }, // EQUIP_TORSO_SLOT + { 8, 78 }, // EQUIP_GLOVES_SLOT + { 70, 0 }, // EQUIP_HEAD_SLOT + { 50, 253 }, // EQUIP_LEGS_SLOT + { 90, 253 }, // EQUIP_FEET_SLOT + { 8, 213 }, // EQUIP_RING1_SLOT + { 129, 213 }, // EQUIP_RING2_SLOT + { 50, 40 }, // EQUIP_NECK_SLOT + { 8, 168 }, // EQUIP_FIGHT1_SLOT + { 129, 168 }, // EQUIP_FIGHT2_SLOT + { 129, 78 }, // EQUIP_PROJECTILE_SLOT + { 8, 123 }, // EQUIP_EVOL_RING1_SLOT + { 129, 123 }, // EQUIP_EVOL_RING2_SLOT +}; + +EquipmentWindow::EquipmentWindow(Equipment *equipment): + Window(_("Equipment")), + mEquipment(equipment), + mSelected(-1) +{ + mItemPopup = new ItemPopup; + if (setupWindow) + setupWindow->registerWindowForReset(this); + + // Control that shows the Player + PlayerBox *playerBox = new PlayerBox; + playerBox->setDimension(gcn::Rectangle(50, 80, 74, 168)); + playerBox->setPlayer(player_node); + + setWindowName("Equipment"); + setCloseButton(true); + setSaveVisible(true); + setDefaultSize(180, 345, ImageRect::CENTER); + loadWindowState(); + + mUnequip = new Button(_("Unequip"), "unequip", this); + const gcn::Rectangle &area = getChildrenArea(); + mUnequip->setPosition(area.width - mUnequip->getWidth() - 5, + area.height - mUnequip->getHeight() - 5); + mUnequip->setEnabled(false); + + add(playerBox); + add(mUnequip); + + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + mEquipBox[i].posX = boxPosition[i][0] + getPadding(); + mEquipBox[i].posY = boxPosition[i][1] + getTitleBarHeight(); + } +} + +EquipmentWindow::~EquipmentWindow() +{ + delete mItemPopup; + mItemPopup = 0; +} + +void EquipmentWindow::draw(gcn::Graphics *graphics) +{ + // Draw window graphics + Window::draw(graphics); + + Graphics *g = static_cast(graphics); + + Window::drawChildren(graphics); + + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + if (i == mSelected) + { + const gcn::Color color = Theme::getThemeColor(Theme::HIGHLIGHT); + + // Set color to the highlight color + g->setColor(gcn::Color(color.r, color.g, color.b, getGuiAlpha())); + g->fillRectangle(gcn::Rectangle(mEquipBox[i].posX, + mEquipBox[i].posY, BOX_WIDTH, BOX_HEIGHT)); + } + + // Set color black + g->setColor(gcn::Color(0, 0, 0)); + // Draw box border + g->drawRectangle(gcn::Rectangle(mEquipBox[i].posX, mEquipBox[i].posY, + BOX_WIDTH, BOX_HEIGHT)); + + Item *item = mEquipment->getEquipment(i); + if (item) + { + // Draw Item. + Image *image = item->getImage(); + if (image) + { + image->setAlpha(1.0f); // Ensure the image is drawn + // with maximum opacity + g->drawImage(image, + mEquipBox[i].posX + 2, + mEquipBox[i].posY + 2); + if (i == EQUIP_PROJECTILE_SLOT) + { + g->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(toString(item->getQuantity()), + mEquipBox[i].posX + (BOX_WIDTH / 2), + mEquipBox[i].posY - getFont()->getHeight(), + gcn::Graphics::CENTER); + } + } + } + } +} + +void EquipmentWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "unequip" && mSelected > -1) + { + Item *item = mEquipment->getEquipment(mSelected); + Net::getInventoryHandler()->unequipItem(item); + setSelected(-1); + } +} + +Item *EquipmentWindow::getItem(int x, int y) const +{ + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY, + BOX_WIDTH, BOX_HEIGHT); + + if (tRect.isPointInRect(x, y)) + return mEquipment->getEquipment(i); + } + return NULL; +} + +void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent) +{ + Window::mousePressed(mouseEvent); + + const int x = mouseEvent.getX(); + const int y = mouseEvent.getY(); + + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + // Checks if any of the presses were in the equip boxes. + for (int i = 0; i < Equipment::EQUIP_VECTOREND; i++) + { + Item *item = mEquipment->getEquipment(i); + gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY, + BOX_WIDTH, BOX_HEIGHT); + + if (tRect.isPointInRect(x, y) && item) + setSelected(i); + } + } + else if (mouseEvent.getButton() == gcn::MouseEvent::RIGHT) + { + if (Item *item = getItem(x, y)) + { + /* Convert relative to the window coordinates to absolute screen + * coordinates. + */ + const int mx = x + getX(); + const int my = y + getY(); + if (viewport) + viewport->showPopup(this, mx, my, item, true); + } + } +} + +// Show ItemTooltip +void EquipmentWindow::mouseMoved(gcn::MouseEvent &event) +{ + if (!mItemPopup) + return; + + const int x = event.getX(); + const int y = event.getY(); + + Item *item = getItem(x, y); + + if (item) + { + int mouseX, mouseY; + SDL_GetMouseState(&mouseX, &mouseY); + + mItemPopup->setItem(item); + mItemPopup->position(x + getX(), y + getY()); + } + else + { + mItemPopup->setVisible(false); + } +} + +// Hide ItemTooltip +void EquipmentWindow::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mItemPopup) + mItemPopup->setVisible(false); +} + +void EquipmentWindow::setSelected(int index) +{ + mSelected = index; + if (mUnequip) + mUnequip->setEnabled(mSelected != -1); +} diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h new file mode 100644 index 000000000..d80535ed6 --- /dev/null +++ b/src/gui/equipmentwindow.h @@ -0,0 +1,98 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EQUIPMENTWINDOW_H +#define EQUIPMENTWINDOW_H + +#include "equipment.h" +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Inventory; +class Item; +class ItemPopup; + +/** + * Equipment dialog. + * + * \ingroup Interface + */ +class EquipmentWindow : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + EquipmentWindow(Equipment *equipment); + + /** + * Destructor. + */ + ~EquipmentWindow(); + + /** + * Draws the equipment window. + */ + void draw(gcn::Graphics *graphics); + + void action(const gcn::ActionEvent &event); + + void mousePressed(gcn::MouseEvent& mouseEvent); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + Item *getItem(int x, int y) const; + + void setSelected(int index); + + Equipment *mEquipment; + + /** + * Equipment box. + */ + struct EquipBox + { + int posX; + int posY; + }; + + EquipBox mEquipBox[Equipment::EQUIP_VECTOREND]; /**. + */ + +#include "focushandler.h" + +#include "gui/widgets/window.h" + +void FocusHandler::requestModalFocus(gcn::Widget *widget) +{ + /* If there is another widget with modal focus, remove its modal focus + * and put it on the modal widget stack. + */ + if (mModalFocusedWidget && mModalFocusedWidget != widget) + { + mModalStack.push_front(mModalFocusedWidget); + mModalFocusedWidget = NULL; + } + + gcn::FocusHandler::requestModalFocus(widget); +} + +void FocusHandler::releaseModalFocus(gcn::Widget *widget) +{ + mModalStack.remove(widget); + + if (mModalFocusedWidget == widget) + { + gcn::FocusHandler::releaseModalFocus(widget); + + /* Check if there were any previously modal widgets that'd still like + * to regain their modal focus. + */ + if (!mModalStack.empty()) + { + gcn::FocusHandler::requestModalFocus(mModalStack.front()); + mModalStack.pop_front(); + } + } +} + +void FocusHandler::remove(gcn::Widget *widget) +{ + releaseModalFocus(widget); + + gcn::FocusHandler::remove(widget); +} + +void FocusHandler::tabNext() +{ + gcn::FocusHandler::tabNext(); + + checkForWindow(); +} + +void FocusHandler::tabPrevious() +{ + gcn::FocusHandler::tabPrevious(); + + checkForWindow(); +} + +void FocusHandler::checkForWindow() +{ + if (mFocusedWidget) + { + gcn::Widget *widget = mFocusedWidget->getParent(); + + while (widget) + { + Window *window = dynamic_cast(widget); + + if (window) + { + window->requestMoveToTop(); + break; + } + + widget = widget->getParent(); + } + } +} diff --git a/src/gui/focushandler.h b/src/gui/focushandler.h new file mode 100644 index 000000000..f933323ae --- /dev/null +++ b/src/gui/focushandler.h @@ -0,0 +1,77 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FOCUSHANDLER_H +#define FOCUSHANDLER_H + +#include + +#include + +/** + * The focus handler. This focus handler does exactly the same as the Guichan + * focus handler, but keeps a stack of modal widgets to be able to handle + * multiple modal focus requests. + */ +class FocusHandler : public gcn::FocusHandler +{ + public: + /** + * Sets modal focus to a widget. When there is already a modal widget + * then that widget loses modal focus and will regain it after this + * widget releases his modal focus. + */ + void requestModalFocus(gcn::Widget *widget); + + /** + * Releases modal focus of a widget. When this widget had modal focus + * and there are other widgets that had also requested modal focus, + * then modal focus will be transfered to the last of those. + */ + void releaseModalFocus(gcn::Widget *widget); + + /** + * Removes a widget from the focus handler. Also makes sure no dangling + * pointers remain in modal focus stack. + */ + void remove(gcn::Widget *widget); + + /** + * Overloaded to allow windows to move to the top when one of their + * widgets is tabbed to when tabbing through focusable elements. + */ + void tabNext(); + void tabPrevious(); + + private: + /** + * Checks to see if the widget tabbed to is in a window, and if it is, + * it requests the window be moved to the top. + */ + void checkForWindow(); + + /** + * Stack of widgets that have requested modal forcus. + */ + std::list mModalStack; +}; + +#endif diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp new file mode 100644 index 000000000..57a94b3d1 --- /dev/null +++ b/src/gui/gui.cpp @@ -0,0 +1,310 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/gui.h" + +#include "gui/focushandler.h" +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" +#include "gui/truetypefont.h" + +#include "gui/widgets/window.h" +#include "gui/widgets/windowcontainer.h" + +#include "configlistener.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/imageloader.h" +#include "resources/resourcemanager.h" + +#include +#include + +// Guichan stuff +Gui *gui = 0; +SDLInput *guiInput = 0; + +// Bolded font +gcn::Font *boldFont = 0; + +class GuiConfigListener : public ConfigListener +{ + public: + GuiConfigListener(Gui *g): + mGui(g) + {} + + void optionChanged(const std::string &name) + { + if (name == "customcursor" && mGui) + { + bool bCustomCursor = config.getBoolValue("customcursor"); + mGui->setUseCustomCursor(bCustomCursor); + } + } + private: + Gui *mGui; +}; + +Gui::Gui(Graphics *graphics): + mCustomCursor(false), + mMouseCursors(NULL), + mMouseCursorAlpha(1.0f), + mMouseInactivityTimer(0), + mCursorType(CURSOR_POINTER) +{ + logger->log1("Initializing GUI..."); + // Set graphics + setGraphics(graphics); + + // Set image loader + static ImageLoader imageLoader; + gcn::Image::setImageLoader(&imageLoader); + + // Set input + guiInput = new SDLInput; + setInput(guiInput); + + // Set focus handler + delete mFocusHandler; + mFocusHandler = new FocusHandler; + + // Initialize top GUI widget + WindowContainer *guiTop = new WindowContainer; + guiTop->setFocusable(true); + guiTop->setDimension(gcn::Rectangle(0, 0, + graphics->getWidth(), graphics->getHeight())); + guiTop->setOpaque(false); + Window::setWindowContainer(guiTop); + setTop(guiTop); + + // Set global font + const int fontSize = config.getIntValue("fontSize"); + std::string fontFile = config.getValue("font", ""); +// may be here need get paths from paths.getValue? +// std::string path = resman->getPath(fontFile); + if (fontFile.empty()) + fontFile = branding.getStringValue("font"); + + try + { + mGuiFont = new TrueTypeFont(fontFile, fontSize); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + // Set particle font + fontFile = config.getValue("particleFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("particleFont"); + + try + { + mInfoParticleFont = new TrueTypeFont( + fontFile, fontSize, TTF_STYLE_BOLD); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + // Set bold font + fontFile = config.getValue("boldFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("boldFont"); + + try + { + boldFont = new TrueTypeFont(fontFile, fontSize); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + // Set help font + fontFile = config.getValue("helpFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("helpFont"); + + try + { + mHelpFont = new TrueTypeFont(fontFile, fontSize); + } + catch (gcn::Exception e) + { + logger->error(std::string("Unable to load '") + fontFile + + std::string("': ") + e.getMessage()); + } + + gcn::Widget::setGlobalFont(mGuiFont); + + // Initialize mouse cursor and listen for changes to the option + setUseCustomCursor(config.getBoolValue("customcursor")); + mConfigListener = new GuiConfigListener(this); + config.addListener("customcursor", mConfigListener); +} + +Gui::~Gui() +{ + config.removeListener("customcursor", mConfigListener); + delete mConfigListener; + mConfigListener = 0; + + if (mMouseCursors) + { + mMouseCursors->decRef(); + mMouseCursors = 0; + } + + delete mGuiFont; + mGuiFont = 0; + delete boldFont; + boldFont = 0; + delete mHelpFont; + mHelpFont = 0; + delete mInfoParticleFont; + mInfoParticleFont = 0; + delete getTop(); + + delete guiInput; + guiInput = 0; + + Theme::deleteInstance(); +} + +void Gui::logic() +{ + ResourceManager *resman = ResourceManager::getInstance(); + resman->clearScheduled(); + + // Fade out mouse cursor after extended inactivity + if (mMouseInactivityTimer < 100 * 15) + { + ++mMouseInactivityTimer; + mMouseCursorAlpha = std::min(1.0f, mMouseCursorAlpha + 0.05f); + } + else + { + mMouseCursorAlpha = std::max(0.0f, mMouseCursorAlpha - 0.005f); + } + + Palette::advanceGradients(); + + gcn::Gui::logic(); +} + +void Gui::draw() +{ + mGraphics->pushClipArea(getTop()->getDimension()); + getTop()->draw(mGraphics); + + int mouseX, mouseY; + Uint8 button = SDL_GetMouseState(&mouseX, &mouseY); + + if ((SDL_GetAppState() & SDL_APPMOUSEFOCUS || button & SDL_BUTTON(1)) + && mMouseCursors && mCustomCursor && mMouseCursorAlpha > 0.0f) + { + Image *mouseCursor = mMouseCursors->get(mCursorType); + if (mouseCursor) + { + mouseCursor->setAlpha(mMouseCursorAlpha); + + static_cast(mGraphics)->drawImage( + mouseCursor, + mouseX - 15, + mouseY - 17); + } + } + + mGraphics->popClipArea(); +} + +void Gui::setUseCustomCursor(bool customCursor) +{ + if (customCursor != mCustomCursor) + { + mCustomCursor = customCursor; + + if (mCustomCursor) + { + // Hide the SDL mouse cursor + SDL_ShowCursor(SDL_DISABLE); + + // Load the mouse cursor + mMouseCursors = Theme::getImageSetFromTheme("mouse.png", 40, 40); + + if (!mMouseCursors) + logger->log("Error: Unable to load mouse cursors."); + } + else + { + // Show the SDL mouse cursor + SDL_ShowCursor(SDL_ENABLE); + + // Unload the mouse cursor + if (mMouseCursors) + { + mMouseCursors->decRef(); + mMouseCursors = NULL; + } + } + } +} + +void Gui::handleMouseMoved(const gcn::MouseInput &mouseInput) +{ + gcn::Gui::handleMouseMoved(mouseInput); + mMouseInactivityTimer = 0; +} + +void Gui::updateFonts() +{ + const int fontSize = config.getIntValue("fontSize"); + std::string fontFile = config.getValue("font", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("font"); + + static_cast(mGuiFont)->loadFont(fontFile, fontSize); + + fontFile = config.getValue("particleFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("particleFont"); + + static_cast(mInfoParticleFont)->loadFont( + fontFile, fontSize, TTF_STYLE_BOLD); + + fontFile = config.getValue("boldFont", ""); + if (fontFile.empty()) + fontFile = branding.getStringValue("boldFont"); + + static_cast(boldFont)->loadFont(fontFile, fontSize); +} diff --git a/src/gui/gui.h b/src/gui/gui.h new file mode 100644 index 000000000..b4ddfc299 --- /dev/null +++ b/src/gui/gui.h @@ -0,0 +1,148 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_H +#define GUI_H + +#include "guichanfwd.h" + +#include + +class Graphics; +class GuiConfigListener; +class ImageSet; +class SDLInput; + +/** + * \defgroup GUI Core GUI related classes (widgets) + */ + +/** + * \defgroup Interface User interface related classes (windows, dialogs) + */ + +/** + * Main GUI class. + * + * \ingroup GUI + */ +class Gui : public gcn::Gui +{ + public: + /** + * Constructor. + */ + Gui(Graphics *screen); + + /** + * Destructor. + */ + ~Gui(); + + /** + * Performs logic of the GUI. Overridden to track mouse pointer + * activity. + */ + void logic(); + + /** + * Draws the whole Gui by calling draw functions down in the + * Gui hierarchy. It also draws the mouse pointer. + */ + void draw(); + + gcn::FocusHandler *getFocusHandler() const + { return mFocusHandler; } + + /** + * Return game font. + */ + gcn::Font *getFont() const + { return mGuiFont; } + + /** + * Return help font. + */ + gcn::Font *getHelpFont() const + { return mHelpFont; } + + /** + * Return the Font used for "Info Particles", i.e. ones showing, what + * you picked up, etc. + */ + gcn::Font *getInfoParticleFont() const + { return mInfoParticleFont; } + + /** + * Sets whether a custom cursor should be rendered. + */ + void setUseCustomCursor(bool customCursor); + + /** + * Sets which cursor should be used. + */ + void setCursorType(int index) + { mCursorType = index; } + + void updateFonts(); + + /** + * Cursors are in graphic order from left to right. + * CURSOR_POINTER should be left untouched. + * CURSOR_TOTAL should always be last. + */ + enum + { + CURSOR_POINTER = 0, + CURSOR_RESIZE_ACROSS, + CURSOR_RESIZE_DOWN, + CURSOR_RESIZE_DOWN_LEFT, + CURSOR_RESIZE_DOWN_RIGHT, + CURSOR_FIGHT, + CURSOR_PICKUP, + CURSOR_TALK, + CURSOR_TOTAL + }; + + protected: + void handleMouseMoved(const gcn::MouseInput &mouseInput); + + private: + GuiConfigListener *mConfigListener; + gcn::Font *mGuiFont; /**< The global GUI font */ + gcn::Font *mInfoParticleFont; /**< Font for Info Particles*/ + gcn::Font *mHelpFont; /**< Font for Help Window*/ + bool mCustomCursor; /**< Show custom cursor */ + ImageSet *mMouseCursors; /**< Mouse cursor images */ + float mMouseCursorAlpha; + int mMouseInactivityTimer; + int mCursorType; +}; + +extern Gui *gui; /**< The GUI system */ +extern SDLInput *guiInput; /**< GUI input */ + +/** + * Bolded text font + */ +extern gcn::Font *boldFont; + +#endif // GUI_H diff --git a/src/gui/help.cpp b/src/gui/help.cpp new file mode 100644 index 000000000..aa114b99f --- /dev/null +++ b/src/gui/help.cpp @@ -0,0 +1,106 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/help.h" + +#include "gui/gui.h" +#include "gui/setup.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" + +#include "resources/resourcemanager.h" +#include "configuration.h" + +#include "utils/gettext.h" + +HelpWindow::HelpWindow(): + Window(_("Help")) +{ + setMinWidth(300); + setMinHeight(250); + setContentSize(455, 350); + setWindowName("Help"); + setResizable(true); + setupWindow->registerWindowForReset(this); + + setDefaultSize(500, 400, ImageRect::CENTER); + + mBrowserBox = new BrowserBox; + mBrowserBox->setOpaque(false); + mScrollArea = new ScrollArea(mBrowserBox); + Button *okButton = new Button(_("Close"), "close", this); + + mScrollArea->setDimension(gcn::Rectangle(5, 5, 445, + 335 - okButton->getHeight())); + okButton->setPosition(450 - okButton->getWidth(), + 345 - okButton->getHeight()); + + mBrowserBox->setLinkHandler(this); + mBrowserBox->setFont(gui->getHelpFont()); + + place(0, 0, mScrollArea, 5, 3).setPadding(3); + place(4, 3, okButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + loadWindowState(); +} + +void HelpWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "close") + setVisible(false); +} + +void HelpWindow::handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_) +{ + std::string helpFile = link; + loadHelp(helpFile); +} + +void HelpWindow::loadHelp(const std::string &helpFile) +{ + mBrowserBox->clearRows(); + + loadFile("header"); + loadFile(helpFile); + + mScrollArea->setVerticalScrollAmount(0); + setVisible(true); +} + +void HelpWindow::loadFile(const std::string &file) +{ + ResourceManager *resman = ResourceManager::getInstance(); + std::string helpPath = branding.getStringValue("helpPath"); + if (helpPath.empty()) + helpPath = paths.getStringValue("help"); + std::vector lines = + resman->loadTextFile(helpPath + file + ".txt"); + + for (unsigned int i = 0; i < lines.size(); ++i) + mBrowserBox->addRow(lines[i]); +} diff --git a/src/gui/help.h b/src/gui/help.h new file mode 100644 index 000000000..fe5cb9fe7 --- /dev/null +++ b/src/gui/help.h @@ -0,0 +1,76 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef HELP_H +#define HELP_H + +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/window.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class BrowserBox; +class LinkHandler; + +/** + * The help dialog. + */ +class HelpWindow : public Window, public LinkHandler, + public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + HelpWindow(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Handles link action. + */ + void handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_); + + /** + * Loads help in the dialog. + */ + void loadHelp(const std::string &helpFile); + + private: + void loadFile(const std::string &file); + + BrowserBox *mBrowserBox; + gcn::ScrollArea *mScrollArea; +}; + +extern HelpWindow *helpWindow; + +#endif diff --git a/src/gui/inventorywindow.cpp b/src/gui/inventorywindow.cpp new file mode 100644 index 000000000..abb702005 --- /dev/null +++ b/src/gui/inventorywindow.cpp @@ -0,0 +1,503 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/inventorywindow.h" + +#include "inventory.h" +#include "item.h" +#include "units.h" +#include "keyboardconfig.h" +#include "playerinfo.h" + +#include "gui/itemamount.h" +#include "gui/setup.h" +#include "gui/sdlinput.h" +#include "gui/shopwindow.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/itemcontainer.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/iteminfo.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +#include + +InventoryWindow::WindowList InventoryWindow::instances; + +InventoryWindow::InventoryWindow(Inventory *inventory): + Window(inventory ? (inventory->isMainInventory() + ? _("Inventory") : _("Storage")) : _("Inventory")), + mInventory(inventory), + mDropButton(0), + mSplit(false) +{ + listen(CHANNEL_ATTRIBUTES); + + setWindowName(isMainInventory() ? "Inventory" : "Storage"); + + if (setupWindow) + setupWindow->registerWindowForReset(this); + + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + + setDefaultSize(387, 307, ImageRect::CENTER); + setMinWidth(316); + setMinHeight(179); + addKeyListener(this); + + mItems = new ItemContainer(mInventory); + mItems->addSelectionListener(this); + + gcn::ScrollArea *invenScroll = new ScrollArea(mItems); + invenScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mSlotsLabel = new Label(_("Slots:")); + mSlotsBar = new ProgressBar(0.0f, 100, 20, Theme::PROG_INVY_SLOTS); + + if (isMainInventory()) + { + std::string equip = _("Equip"); + std::string use = _("Use"); + std::string unequip = _("Unequip"); + + std::string longestUseString = getFont()->getWidth(equip) > + getFont()->getWidth(use) ? equip : use; + + if (getFont()->getWidth(longestUseString) < + getFont()->getWidth(unequip)) + { + longestUseString = unequip; + } + + mUseButton = new Button(longestUseString, "use", this); + mUseButton2 = new Button(longestUseString, "equip", this); + mDropButton = new Button(_("Drop..."), "drop", this); + mSplitButton = new Button(_("Split"), "split", this); + mOutfitButton = new Button(_("Outfits"), "outfit", this); + mShopButton = new Button(_("Shop"), "shop", this); + + mWeightLabel = new Label(_("Weight:")); + mWeightBar = new ProgressBar(0.0f, 100, 20, Theme::PROG_WEIGHT); + + place(0, 0, mWeightLabel).setPadding(3); + place(1, 0, mWeightBar, 3); + place(4, 0, mSlotsLabel).setPadding(3); + place(5, 0, mSlotsBar, 2); + place(0, 1, invenScroll, 7).setPadding(3); + place(0, 2, mUseButton); + place(1, 2, mUseButton2); + place(2, 2, mDropButton); + place(4, 2, mSplitButton); + place(5, 2, mShopButton); + place(6, 2, mOutfitButton); + + updateWeight(); + } + else + { + mStoreButton = new Button(_("Store"), "store", this); + mRetrieveButton = new Button(_("Retrieve"), "retrieve", this); + mCloseButton = new Button(_("Close"), "close", this); + + place(0, 0, mSlotsLabel).setPadding(3); + place(1, 0, mSlotsBar, 3); + place(0, 1, invenScroll, 4, 4); + place(0, 5, mStoreButton); + place(1, 5, mRetrieveButton); + place(3, 5, mCloseButton); + } + + Layout &layout = getLayout(); + layout.setRowHeight(1, Layout::AUTO_SET); + + mInventory->addInventoyListener(this); + + instances.push_back(this); + + if (inventory->isMainInventory()) + { + updateDropButton(); + } + else + { + if (!instances.empty()) + instances.front()->updateDropButton(); + } + + loadWindowState(); + slotsChanged(mInventory); + + if (!isMainInventory()) + setVisible(true); +} + +InventoryWindow::~InventoryWindow() +{ + instances.remove(this); + mInventory->removeInventoyListener(this); + if (!instances.empty()) + instances.front()->updateDropButton(); +} + +void InventoryWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "outfit") + { + extern Window *outfitWindow; + if (outfitWindow) + { + outfitWindow->setVisible(!outfitWindow->isVisible()); + if (outfitWindow->isVisible()) + outfitWindow->requestMoveToTop(); + } + } + + if (event.getId() == "shop") + { + if (shopWindow) + { + shopWindow->setVisible(!shopWindow->isVisible()); + if (shopWindow->isVisible()) + shopWindow->requestMoveToTop(); + } + } + else if (event.getId() == "close") + { + close(); + } + else if (event.getId() == "store") + { + if (!inventoryWindow || !inventoryWindow->isVisible()) + return; + + Item *item = inventoryWindow->getSelectedItem(); + + if (!item) + return; + + ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, this, item); + } + + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + if (event.getId() == "use") + { + if (item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + Net::getInventoryHandler()->useItem(item); + } + if (event.getId() == "equip") + { + if (!item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + Net::getInventoryHandler()->useItem(item); + } + else if (event.getId() == "drop") + { + if (isStorageActive()) + { + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + item->getInvIndex(), item->getQuantity(), + Inventory::STORAGE); + } + else + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop, + this, item); + } + } + else if (event.getId() == "split") + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item, + (item->getQuantity() - 1)); + } + else if (event.getId() == "retrieve") + { + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, this, + item); + } +} + +Item *InventoryWindow::getSelectedItem() const +{ + return mItems->getSelectedItem(); +} + +void InventoryWindow::mouseClicked(gcn::MouseEvent &event) +{ + Window::mouseClicked(event); + + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + Item *item = mItems->getSelectedItem(); + + if (!item) + return; + + /* Convert relative to the window coordinates to absolute screen + * coordinates. + */ + const int mx = event.getX() + getX(); + const int my = event.getY() + getY(); + + if (viewport) + viewport->showPopup(this, mx, my, item, isMainInventory()); + } + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (isStorageActive() && keyboard.isKeyActive(keyboard.KEY_EMOTE)) + { + Item *item = mItems->getSelectedItem(); + + if (!item || !mInventory) + return; + + if (mInventory->isMainInventory()) + { + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + item->getInvIndex(), item->getQuantity(), + Inventory::STORAGE); + } + else + { + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + item->getInvIndex(), item->getQuantity(), + Inventory::INVENTORY); + } + } + } +} + +void InventoryWindow::keyPressed(gcn::KeyEvent &event) +{ + switch (event.getKey().getValue()) + { + case Key::LEFT_SHIFT: + case Key::RIGHT_SHIFT: + mSplit = true; + break; + default: + break; + } +} + +void InventoryWindow::keyReleased(gcn::KeyEvent &event) +{ + switch (event.getKey().getValue()) + { + case Key::LEFT_SHIFT: + case Key::RIGHT_SHIFT: + mSplit = false; + break; + default: + break; + } +} + +void InventoryWindow::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + if (!mInventory || !mInventory->isMainInventory()) + return; + + Item *item = mItems->getSelectedItem(); + + if (mSplit && item && Net::getInventoryHandler()-> + canSplit(mItems->getSelectedItem())) + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item, + (item->getQuantity() - 1)); + } + + if (!item || item->getQuantity() == 0) + { + if (mUseButton) + mUseButton->setEnabled(true); + if (mUseButton2) + mUseButton2->setEnabled(true); + if (mDropButton) + mDropButton->setEnabled(true); + return; + } + + if (mUseButton) + mUseButton->setEnabled(true); + if (mUseButton2) + mUseButton2->setEnabled(true); + if (mDropButton) + mDropButton->setEnabled(true); + + if (mUseButton && item && item->isEquipment()) + { + if (item->isEquipped()) + mUseButton->setCaption(_("Unequip")); + else + mUseButton->setCaption(_("Equip")); + mUseButton2->setCaption(_("Use")); + } + else if (mUseButton2) + { + mUseButton->setCaption(_("Use")); + if (item->isEquipped()) + mUseButton2->setCaption(_("Unequip")); + else + mUseButton2->setCaption(_("Equip")); + } + + updateDropButton(); + + if (mSplitButton) + { + if (Net::getInventoryHandler()->canSplit(item)) + mSplitButton->setEnabled(true); + else + mSplitButton->setEnabled(false); + } +} + + +void InventoryWindow::setSplitAllowed(bool allowed) +{ + mSplitButton->setVisible(allowed); +} + +void InventoryWindow::close() +{ + if (this == inventoryWindow) + { + setVisible(false); + } + else + { + Net::getInventoryHandler()->closeStorage(Inventory::STORAGE); + scheduleDelete(); + } +} + +void InventoryWindow::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + int id = event.getInt("id"); + if (id == TOTAL_WEIGHT || id == MAX_WEIGHT) + updateWeight(); + } +} + +void InventoryWindow::updateWeight() +{ + if (!isMainInventory()) + return; + + int total = PlayerInfo::getAttribute(TOTAL_WEIGHT); + int max = PlayerInfo::getAttribute(MAX_WEIGHT); + + if (max <= 0) + return; + + // Adjust progress bar + mWeightBar->setProgress(static_cast(total) + / static_cast(max)); + mWeightBar->setText(strprintf("%s/%s", Units::formatWeight(total).c_str(), + Units::formatWeight(max).c_str())); +} + +void InventoryWindow::slotsChanged(Inventory* inventory) +{ + if (inventory == mInventory) + { + const int usedSlots = mInventory->getNumberOfSlotsUsed(); + const int maxSlots = mInventory->getSize(); + + if (maxSlots) + { + mSlotsBar->setProgress(static_cast(usedSlots) + / static_cast(maxSlots)); + } + + mSlotsBar->setText(strprintf("%d/%d", usedSlots, maxSlots)); + } +} + +void InventoryWindow::updateDropButton() +{ + if (!mDropButton) + return; + + if (isStorageActive()) + { + mDropButton->setCaption(_("Store")); + } + else + { + Item *item = 0; + if (mItems) + item = mItems->getSelectedItem(); + + if (item && item->getQuantity() > 1) + mDropButton->setCaption(_("Drop...")); + else + mDropButton->setCaption(_("Drop")); + } +} diff --git a/src/gui/inventorywindow.h b/src/gui/inventorywindow.h new file mode 100644 index 000000000..87f57eb9d --- /dev/null +++ b/src/gui/inventorywindow.h @@ -0,0 +1,155 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INVENTORYWINDOW_H +#define INVENTORYWINDOW_H + +#include "inventory.h" +#include "listener.h" + +#include "gui/widgets/window.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Item; +class ItemContainer; +class ProgressBar; +class TextBox; + +/** + * Inventory dialog. + * + * \ingroup Interface + */ +class InventoryWindow : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public gcn::SelectionListener, + public InventoryListener, + public Mana::Listener +{ + public: + /** + * Constructor. + */ + InventoryWindow(Inventory *inventory); + + /** + * Destructor. + */ + ~InventoryWindow(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Returns the selected item. + */ + Item* getSelectedItem() const; + + /** + * Handles the mouse clicks. + */ + void mouseClicked(gcn::MouseEvent &event); + + /** + * Handles the key presses. + */ + void keyPressed(gcn::KeyEvent &event); + + /** + * Handles the key releases. + */ + void keyReleased(gcn::KeyEvent &event); + + /** + * Updates labels to currently selected item. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Sets whether the split button should be shown. + */ + void setSplitAllowed(bool allowed); + + /** + * Closes the Storage Window, as well as telling the server that the + * window has been closed. + */ + void close(); + + void slotsChanged(Inventory* inventory); + + bool isMainInventory() + { return mInventory->isMainInventory(); } + + /** + * Returns true if any instances exist. + */ + static bool isStorageActive() + { return instances.size() > 1; } + + void updateDropButton(); + + void event(Channels channel, const Mana::Event &event); + + private: + /** + * Updates the weight bar. + */ + void updateWeight(); + + + typedef std::list WindowList; + static WindowList instances; + + Inventory *mInventory; + ItemContainer *mItems; + + std::string mWeight, mSlots; + + gcn::Button *mUseButton, *mUseButton2, *mDropButton, + *mSplitButton, *mOutfitButton, *mShopButton, + *mStoreButton, *mRetrieveButton, *mCloseButton; + + gcn::Label *mWeightLabel, *mSlotsLabel; + + ProgressBar *mWeightBar, *mSlotsBar; + + bool mSplit; +}; + +extern InventoryWindow *inventoryWindow; + +#endif diff --git a/src/gui/itemamount.cpp b/src/gui/itemamount.cpp new file mode 100644 index 000000000..25356823f --- /dev/null +++ b/src/gui/itemamount.cpp @@ -0,0 +1,432 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/itemamount.h" + +#include "item.h" +#include "keyboardconfig.h" + +#include "gui/trade.h" +#include "net/inventoryhandler.h" +#include "gui/itempopup.h" +#include "net/net.h" +#include "gui/shopwindow.h" +#include "gui/viewport.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/icon.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/label.h" +#include "gui/widgets/slider.h" + +#include "resources/itemdb.h" + +#include "utils/gettext.h" + +#include + +class ItemsModal : public gcn::ListModel +{ +public: + ItemsModal() + { + std::map info = ItemDB::getItemInfos(); + std::list tempStrings; + + for (std::map::const_iterator + i = info.begin(), i_end = info.end(); + i != i_end; ++i) + { + if (i->first < 0) + continue; + + ItemInfo info = (*i->second); + std::string name = info.getName(); + if (name != "unnamed" && !info.getName().empty() + && info.getName() != "unnamed") + { + tempStrings.push_back(name); + } + } + tempStrings.sort(); + for (std::list::const_iterator i = tempStrings.begin(), + i_end = tempStrings.end(); i != i_end; ++i) + { + mStrings.push_back(*i); + } + } + + virtual ~ItemsModal() + { } + + virtual int getNumberOfElements() + { + return static_cast(mStrings.size()); + } + + virtual std::string getElementAt(int i) + { + if (i < 0 || i >= getNumberOfElements()) + return _("???"); + + return mStrings.at(i); + } +private: + std::vector mStrings; +}; + +void ItemAmountWindow::finish(Item *item, int amount, int price, Usage usage) +{ + switch (usage) + { + case TradeAdd: + if (tradeWindow) + tradeWindow->tradeItem(item, amount); + break; + case ItemDrop: + Net::getInventoryHandler()->dropItem(item, amount); + break; + case ItemSplit: + Net::getInventoryHandler()->splitItem(item, amount); + break; + case StoreAdd: + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + item->getInvIndex(), amount, + Inventory::STORAGE); + break; + case StoreRemove: + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + item->getInvIndex(), amount, + Inventory::INVENTORY); + break; + case ShopBuyAdd: + if (shopWindow) + shopWindow->addBuyItem(item, amount, price); + break; + case ShopSellAdd: + if (shopWindow) + shopWindow->addSellItem(item, amount, price); + break; + default: + break; + } +} + +ItemAmountWindow::ItemAmountWindow(Usage usage, Window *parent, Item *item, + int maxRange): + Window("", true, parent), + mItemPriceTextField(0), + mGPLabel(0), + mItem(item), + mMax(maxRange), + mUsage(usage), + mItemPriceSlide(0), + mItemsModal(0), + mPrice(0) +{ + if (!mItem) + { + setVisible(false); + return; + } + if (usage == ShopBuyAdd) + mMax = 10000; + else if (!mMax) + mMax = mItem->getQuantity(); + + // Save keyboard state + mEnabledKeyboard = keyboard.isEnabled(); + keyboard.setEnabled(false); + + // Integer field + mItemAmountTextField = new IntTextField(1); + mItemAmountTextField->setRange(1, mMax); + mItemAmountTextField->setWidth(35); + mItemAmountTextField->addKeyListener(this); + + // Slider + mItemAmountSlide = new Slider(1.0, mMax); + mItemAmountSlide->setHeight(10); + mItemAmountSlide->setActionEventId("slide"); + mItemAmountSlide->addActionListener(this); + + if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd) + { + // Integer field + mItemPriceTextField = new IntTextField(1); + mItemPriceTextField->setRange(1, 10000000); + mItemPriceTextField->setWidth(35); + mItemPriceTextField->addKeyListener(this); + + // Slider + mItemPriceSlide = new Slider(1.0, 10000000); + mItemPriceSlide->setHeight(10); + mItemPriceSlide->setActionEventId("slidePrice"); + mItemPriceSlide->addActionListener(this); + + mGPLabel = new Label(" GP"); + } + + if (mUsage == ShopBuyAdd) + { + mItemsModal = new ItemsModal; + mItemDropDown = new DropDown(mItemsModal); + mItemDropDown->setActionEventId("itemType"); + mItemDropDown->addActionListener(this); + } + + //Item icon + Image *image = item->getImage(); + mItemIcon = new Icon(image); + + // Buttons + Button *minusAmountButton = new Button(_("-"), "dec", this); + Button *plusAmountButton = new Button(_("+"), "inc", this); + Button *okButton = new Button(_("OK"), "ok", this); + Button *cancelButton = new Button(_("Cancel"), "cancel", this); + Button *addAllButton = new Button(_("All"), "all", this); + + minusAmountButton->adjustSize(); + minusAmountButton->setWidth(plusAmountButton->getWidth()); + + // Set positions + ContainerPlacer place; + place = getPlacer(0, 0); + int n = 0; + if (mUsage == ShopBuyAdd) + { + place(0, n, mItemDropDown, 8); + n++; + } + place(1, n, minusAmountButton); + place(2, n, mItemAmountTextField, 3); + place(5, n, plusAmountButton); + place(6, n, addAllButton); + + place(0, n, mItemIcon, 1, 3); + place(1, n + 1, mItemAmountSlide, 7); + + if (mUsage == ShopBuyAdd || mUsage == ShopSellAdd) + { + Button *minusPriceButton = new Button(_("-"), "decPrice", this); + Button *plusPriceButton = new Button(_("+"), "incPrice", this); + minusPriceButton->adjustSize(); + minusPriceButton->setWidth(plusPriceButton->getWidth()); + + place(1, n + 2, minusPriceButton); + place(2, n + 2, mItemPriceTextField, 3); + place(5, n + 2, plusPriceButton); + place(6, n + 2, mGPLabel); + + place(1, n + 3, mItemPriceSlide, 7); + place(4, n + 5, cancelButton); + place(5, n + 5, okButton); + } + else + { + place(4, n + 2, cancelButton); + place(5, n + 2, okButton); + } + + reflowLayout(225, 0); + + resetAmount(); + + switch (usage) + { + case TradeAdd: + setCaption(_("Select amount of items to trade.")); + break; + case ItemDrop: + setCaption(_("Select amount of items to drop.")); + break; + case StoreAdd: + setCaption(_("Select amount of items to store.")); + break; + case StoreRemove: + setCaption(_("Select amount of items to retrieve.")); + break; + case ItemSplit: + setCaption(_("Select amount of items to split.")); + break; + case ShopBuyAdd: + setCaption(_("Add to buy shop.")); + break; + case ShopSellAdd: + setCaption(_("Add to sell shop.")); + break; + default: + setCaption(_("Unknown.")); + break; + } + + setLocationRelativeTo(getParentWindow()); + setVisible(true); + + mItemPopup = new ItemPopup; + mItemIcon->addMouseListener(this); +} + +ItemAmountWindow::~ItemAmountWindow() +{ + delete mItemPopup; + mItemPopup = 0; +} + +// Show ItemTooltip +void ItemAmountWindow::mouseMoved(gcn::MouseEvent &event) +{ + if (!viewport || !mItemPopup) + return; + + if (event.getSource() == mItemIcon) + { + mItemPopup->setItem(mItem); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } +} + +// Hide ItemTooltip +void ItemAmountWindow::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mItemPopup) + mItemPopup->setVisible(false); +} + +void ItemAmountWindow::resetAmount() +{ + mItemAmountTextField->setValue(1); +} + +void ItemAmountWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + close(); + return; + } + else if (event.getId() == "ok") + { + if (mItemPriceTextField) + { + finish(mItem, mItemAmountTextField->getValue(), + mItemPriceTextField->getValue(), mUsage); + } + else + { + finish(mItem, mItemAmountTextField->getValue(), + 0, mUsage); + } + close(); + return; + } + else if (event.getId() == "itemType") + { + if (!mItemDropDown || !mItemsModal) + return; + + std::string str = mItemsModal->getElementAt( + mItemDropDown->getSelected()); + int id = ItemDB::get(str).getId(); + + mItem = new Item(id, 10000); + + if (mUsage == ShopBuyAdd) + mMax = 10000; + else if (!mMax) + mMax = mItem->getQuantity(); + + Image *image = mItem->getImage(); + mItemIcon->setImage(image); + } + + int amount = mItemAmountTextField->getValue(); + + if (event.getId() == "inc" && amount < mMax) + amount++; + else if (event.getId() == "dec" && amount > 1) + amount--; + else if (event.getId() == "all") + amount = mMax; + else if (event.getId() == "slide") + amount = static_cast(mItemAmountSlide->getValue()); + mItemAmountTextField->setValue(amount); + mItemAmountSlide->setValue(amount); + + if (mItemPriceTextField && mItemPriceSlide) + { + int price = 0; + + if (mPrice > 7) + mPrice = 7; + else if (mPrice < 0) + mPrice = 0; + + if (event.getId() == "incPrice") + { + mPrice++; + price = static_cast(pow(10, mPrice)); + } + else if (event.getId() == "decPrice") + { + mPrice--; + price = static_cast(pow(10, mPrice)); + } + else if (event.getId() == "slidePrice") + { + price = static_cast(mItemPriceSlide->getValue()); + if (price) + mPrice = static_cast(log(price)); + else + mPrice = 0; + } + mItemPriceTextField->setValue(price); + mItemPriceSlide->setValue(price); + } +} + +void ItemAmountWindow::close() +{ + keyboard.setEnabled(mEnabledKeyboard); + scheduleDelete(); +} + +void ItemAmountWindow::keyReleased(gcn::KeyEvent &keyEvent _UNUSED_) +{ + mItemAmountSlide->setValue(mItemAmountTextField->getValue()); +} + +void ItemAmountWindow::showWindow(Usage usage, Window *parent, Item *item, + int maxRange) +{ + if (!item) + return; + + if (!maxRange) + maxRange = item->getQuantity(); + + if (usage != ShopBuyAdd && usage != ShopSellAdd && maxRange <= 1) + finish(item, maxRange, 0, usage); + else + new ItemAmountWindow(usage, parent, item, maxRange); +} diff --git a/src/gui/itemamount.h b/src/gui/itemamount.h new file mode 100644 index 000000000..f28581294 --- /dev/null +++ b/src/gui/itemamount.h @@ -0,0 +1,124 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEM_AMOUNT_WINDOW_H +#define ITEM_AMOUNT_WINDOW_H + +#include "gui/widgets/window.h" + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Icon; +class IntTextField; +class Item; +class ItemsModal; +class ItemPopup; +class Label; + +/** + * Window used for selecting the amount of items to drop, trade or split. + * + * \ingroup Interface + */ +class ItemAmountWindow : public Window, + public gcn::ActionListener, + public gcn::KeyListener +{ + public: + enum Usage + { + TradeAdd = 0, + ItemDrop, + StoreAdd, + StoreRemove, + ItemSplit, + ShopBuyAdd, + ShopSellAdd + }; + + /** + * Called when receiving actions from widget. + */ + void action(const gcn::ActionEvent &event); + + /** + * Sets default amount value. + */ + void resetAmount(); + + // MouseListener + void mouseMoved(gcn::MouseEvent &event); + void mouseExited(gcn::MouseEvent &event); + + /** + * Schedules the Item Amount window for deletion. + */ + void close(); + + void keyReleased(gcn::KeyEvent &keyEvent); + + /** + * Creates the dialog, or bypass it if there aren't enough items. + */ + static void showWindow(Usage usage, Window *parent, Item *item, + int maxRange = 0); + + ~ItemAmountWindow(); + + private: + static void finish(Item *item, int amount, int price, Usage usage); + + ItemAmountWindow(Usage usage, Window *parent, Item *item, + int maxRange = 0); + + IntTextField *mItemAmountTextField; /**< Item amount caption. */ + IntTextField *mItemPriceTextField; /**< Item price caption. */ + Label *mGPLabel; + Item *mItem; + Icon *mItemIcon; + + int mMax; + Usage mUsage; + ItemPopup *mItemPopup; + + /** + * Item Amount buttons. + */ + gcn::Slider *mItemAmountSlide; + + gcn::Slider *mItemPriceSlide; + + gcn::DropDown *mItemDropDown; + + ItemsModal *mItemsModal; + + bool mEnabledKeyboard; + int mPrice; +}; + +#endif // ITEM_AMOUNT_WINDOW_H diff --git a/src/gui/itempopup.cpp b/src/gui/itempopup.cpp new file mode 100644 index 000000000..15a0be6bc --- /dev/null +++ b/src/gui/itempopup.cpp @@ -0,0 +1,238 @@ +/* + * The Mana Client + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/itempopup.h" + +#include "graphics.h" +#include "item.h" +#include "units.h" + +#include "gui/gui.h" +#include "gui/theme.h" + +#include "gui/widgets/icon.h" +#include "gui/widgets/textbox.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include + +#include +#include + +ItemPopup::ItemPopup(): + Popup("ItemPopup"), + mIcon(0) +{ + // Item Name + mItemName = new gcn::Label; + mItemName->setFont(boldFont); + mItemName->setPosition(getPadding(), getPadding()); + + const int fontHeight = getFont()->getHeight(); + + // Item Description + mItemDesc = new TextBox; + mItemDesc->setEditable(false); + mItemDesc->setPosition(getPadding(), fontHeight); + + // Item Effect + mItemEffect = new TextBox; + mItemEffect->setEditable(false); + mItemEffect->setPosition(getPadding(), 2 * fontHeight + 2 * getPadding()); + + // Item Weight + mItemWeight = new TextBox; + mItemWeight->setEditable(false); + mItemWeight->setPosition(getPadding(), 3 * fontHeight + 4 * getPadding()); + + mIcon = new Icon(0); + + add(mItemName); + add(mItemDesc); + add(mItemEffect); + add(mItemWeight); + add(mIcon); + + addMouseListener(this); +} + +ItemPopup::~ItemPopup() +{ + if (mIcon) + { + Image *image = mIcon->getImage(); + if (image) + image->decRef(); + } +} + +void ItemPopup::setItem(const Item *item, bool showImage) +{ + if (!item) + return; + + const ItemInfo &ii = item->getInfo(); + setItem(ii, showImage); + if (item->getRefine() > 0) + { + mItemName->setCaption(strprintf("%s (+%d), %d", ii.getName().c_str(), + item->getRefine(), ii.getId())); + mItemName->adjustSize(); + int minWidth = mItemName->getWidth() + 8; + if (getWidth() < minWidth) + setWidth(minWidth); + } +} + +void ItemPopup::setItem(const ItemInfo &item, bool showImage) +{ + if (!mIcon || item.getName() == mItemName->getCaption()) + return; + + int space = 0; + + Image *oldImage = mIcon->getImage(); + if (oldImage) + oldImage->decRef(); + + if (showImage) + { + ResourceManager *resman = ResourceManager::getInstance(); + Image *image = resman->getImage( + paths.getStringValue("itemIcons") + + item.getDisplay().image); + + mIcon->setImage(image); + if (image) + { + int x = getPadding(); + int y = getPadding(); + mIcon->setPosition(x, y); + space = mIcon->getWidth(); + } + } + else + { + mIcon->setImage(0); + } + + mItemType = item.getType(); + + mItemName->setCaption(item.getName() + _(", ") + toString(item.getId())); + mItemName->adjustSize(); + mItemName->setForegroundColor(getColor(mItemType)); + mItemName->setPosition(getPadding() + space, getPadding()); + + mItemDesc->setTextWrapped(item.getDescription(), 196); + mItemEffect->setTextWrapped(item.getEffect(), 196); + mItemWeight->setTextWrapped(strprintf(_("Weight: %s"), + Units::formatWeight(item.getWeight()).c_str()), + 196); + + int minWidth = mItemName->getWidth() + space; + + if (mItemName->getWidth() + space > minWidth) + minWidth = mItemName->getWidth() + space; + if (mItemDesc->getMinWidth() > minWidth) + minWidth = mItemDesc->getMinWidth(); + if (mItemEffect->getMinWidth() > minWidth) + minWidth = mItemEffect->getMinWidth(); + if (mItemWeight->getMinWidth() > minWidth) + minWidth = mItemWeight->getMinWidth(); + + minWidth += 8; + setWidth(minWidth); + + const int numRowsDesc = mItemDesc->getNumberOfRows(); + const int numRowsEffect = mItemEffect->getNumberOfRows(); + const int numRowsWeight = mItemWeight->getNumberOfRows(); + const int height = getFont()->getHeight(); + + if (item.getEffect().empty()) + { + setContentSize(minWidth, (numRowsDesc + numRowsWeight + getPadding()) * + height); + + mItemWeight->setPosition(getPadding(), (numRowsDesc + getPadding()) * + height); + } + else + { + setContentSize(minWidth, (numRowsDesc + numRowsEffect + numRowsWeight + + getPadding()) * height); + + mItemWeight->setPosition(getPadding(), (numRowsDesc + numRowsEffect + + getPadding()) * height); + } + + mItemDesc->setPosition(getPadding(), 2 * height); + mItemEffect->setPosition(getPadding(), + (numRowsDesc + getPadding()) * height); +} + +gcn::Color ItemPopup::getColor(ItemType type) +{ + switch (type) + { + case ITEM_UNUSABLE: + return Theme::getThemeColor(Theme::GENERIC); + case ITEM_USABLE: + return Theme::getThemeColor(Theme::USABLE); + case ITEM_EQUIPMENT_ONE_HAND_WEAPON: + return Theme::getThemeColor(Theme::ONEHAND); + case ITEM_EQUIPMENT_TWO_HANDS_WEAPON: + return Theme::getThemeColor(Theme::TWOHAND); + case ITEM_EQUIPMENT_TORSO: + return Theme::getThemeColor(Theme::TORSO); + case ITEM_EQUIPMENT_ARMS: + return Theme::getThemeColor(Theme::ARMS); + case ITEM_EQUIPMENT_HEAD: + return Theme::getThemeColor(Theme::HEAD); + case ITEM_EQUIPMENT_LEGS: + return Theme::getThemeColor(Theme::LEGS); + case ITEM_EQUIPMENT_SHIELD: + return Theme::getThemeColor(Theme::SHIELD); + case ITEM_EQUIPMENT_RING: + return Theme::getThemeColor(Theme::RING); + case ITEM_EQUIPMENT_NECKLACE: + return Theme::getThemeColor(Theme::NECKLACE); + case ITEM_EQUIPMENT_FEET: + return Theme::getThemeColor(Theme::FEET); + case ITEM_EQUIPMENT_AMMO: + return Theme::getThemeColor(Theme::AMMO); + default: + return Theme::getThemeColor(Theme::UNKNOWN_ITEM); + } +} + +void ItemPopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + // When the mouse moved on top of the popup, hide it + setVisible(false); +} \ No newline at end of file diff --git a/src/gui/itempopup.h b/src/gui/itempopup.h new file mode 100644 index 000000000..0baf8fb79 --- /dev/null +++ b/src/gui/itempopup.h @@ -0,0 +1,71 @@ +/* + * The Mana Client + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEMPOPUP_H +#define ITEMPOPUP_H + +#include "gui/widgets/popup.h" + +#include "resources/iteminfo.h" + +#include + +class Icon; +class TextBox; + +/** + * A popup that displays information about an item. + */ +class ItemPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + ItemPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~ItemPopup(); + + /** + * Sets the info to be displayed given a particular item. + */ + void setItem(const ItemInfo &item, bool showImage = false); + + void setItem(const Item *item, bool showImage = false); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + + private: + gcn::Label *mItemName; + TextBox *mItemDesc; + TextBox *mItemEffect; + TextBox *mItemWeight; + ItemType mItemType; + Icon *mIcon; + + static gcn::Color getColor(ItemType type); +}; + +#endif // ITEMPOPUP_H diff --git a/src/gui/killstats.cpp b/src/gui/killstats.cpp new file mode 100644 index 000000000..8ce38c719 --- /dev/null +++ b/src/gui/killstats.cpp @@ -0,0 +1,424 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "killstats.h" + +#include +#include + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/chattab.h" +#include "gui/chat.h" + +#include "actorspritemanager.h" +#include "event.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +KillStats::KillStats(): + Window(_("Kill stats")), mKillCounter(0), mExpCounter(0), + mKillTCounter(0), mExpTCounter(0), mKillTimer(0), + m1minExpTime(0), m1minExpNum(0), m1minSpeed(0), + m5minExpTime(0), m5minExpNum(0), m5minSpeed(0), + m15minExpTime(0), m15minExpNum(0), m15minSpeed(0), + mJackoSpawnTime(0), mValidateJackoTime(0), mJackoId(0), + mIsJackoAlive(false), mIsJackoMustSpawn(true), + mIsJackoSpawnTimeUnknown(true) +{ + setWindowName("Kill stats"); + setCloseButton(true); + setResizable(true); + setDefaultSize(250, 250, 350, 300); + + listen(CHANNEL_ATTRIBUTES); + int xp(PlayerInfo::getAttribute(EXP)); + int xpNextLevel(PlayerInfo::getAttribute(EXP_NEEDED)); + + mResetButton = new Button(_("Reset stats"), "reset", this); + mTimerButton = new Button(_("Reset timer"), "timer", this); + if (!xpNextLevel) + xpNextLevel = 1; + + mLine1 = new Label(_("Level: ") + toString(player_node->getLevel()) + + " at " + toString(static_cast(xp) + / static_cast(xpNextLevel) * 100.0f) + "%"); + + mLine2 = new Label(_("Exp: ") + toString(xp) + "/" + + toString(xpNextLevel) + _(" Left: ") + + toString(xpNextLevel-xp)); + mLine3 = new Label("1% = " + toString(xpNextLevel / 100) + + _(" exp, Avg Mob for 1%: ?")); + mLine4 = new Label(_("Kills: ?, Total Exp: ?")); + mLine5 = new Label(_("Avg Exp: ?, No. of Avg mob to next level: ?")); + mLine6 = new Label(_("Kills/Min: ?, Exp/Min: ?")); + + mExpSpeed1Label = new Label(_("Exp speed per 1 min: ?")); + mExpTime1Label = new Label(_("Time for next level per 1 min: ?")); + mExpSpeed5Label = new Label(_("Exp speed per 5 min: ?")); + mExpTime5Label = new Label(_("Time for next level per 5 min: ?")); + mExpSpeed15Label = new Label(_("Exp speed per 15 min: ?")); + mExpTime15Label = new Label(_("Time for Next level per 15 min: ?")); + + mLastKillExpLabel = new Label(_("Last kill exp: ?")); + mTimeBeforeJackoLabel = new Label(_("Time before jacko spawn: ?")); + + place(0, 0, mLine1, 6).setPadding(0); + place(0, 1, mLine2, 6).setPadding(0); + place(0, 2, mLine3, 6).setPadding(0); + place(0, 3, mLine4, 6).setPadding(0); + place(0, 4, mLine5, 6).setPadding(0); + place(0, 5, mLine6, 6).setPadding(0); + + place(0, 6, mLastKillExpLabel, 6).setPadding(0); + place(0, 7, mTimeBeforeJackoLabel, 6).setPadding(0); + place(0, 8, mExpSpeed1Label, 6).setPadding(0); + place(0, 9, mExpTime1Label, 6).setPadding(0); + place(0, 10, mExpSpeed5Label, 6).setPadding(0); + place(0, 11, mExpTime5Label, 6).setPadding(0); + place(0, 12, mExpSpeed15Label, 6).setPadding(0); + place(0, 13, mExpTime15Label, 6).setPadding(0); + + place(5, 12, mTimerButton).setPadding(0); + place(5, 13, mResetButton).setPadding(0); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + loadWindowState(); + +} + +KillStats::~KillStats() +{ +} + +void KillStats::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "reset") + { + mKillCounter = 0; + mExpCounter = 0; + mLine3->setCaption("1% = " + toString( + PlayerInfo::getAttribute(EXP_NEEDED) / 100) + + " exp, Avg Mob for 1%: ?"); + mLine4->setCaption(_("Kills: ?, Total Exp: ?")); + mLine5->setCaption(_("Avg Exp: ?, No. of Avg mob to next level: ?")); + + m1minExpTime = 0; + m1minExpNum = 0; + m1minSpeed = 0; + m5minExpTime = 0; + m5minExpNum = 0; + m5minSpeed = 0; + m15minExpTime = 0; + m15minExpNum = 0; + m15minSpeed = 0; + } + else if (event.getId() == "timer") + { + mKillTimer = 0; + mKillTCounter = 0; + mExpTCounter = 0; + mLine6->setCaption(_("Kills/Min: ?, Exp/Min: ?")); + + m1minExpTime = 0; + m1minExpNum = 0; + m1minSpeed = 0; + m5minExpTime = 0; + m5minExpNum = 0; + m5minSpeed = 0; + m15minExpTime = 0; + m15minExpNum = 0; + m15minSpeed = 0; + } +} + +void KillStats::gainXp(int xp) +{ + if (xp == PlayerInfo::getAttribute(EXP_NEEDED)) + xp = 0; + else if (!xp) + return; + + mKillCounter++; + mKillTCounter++; + + mExpCounter = mExpCounter + xp; + mExpTCounter = mExpTCounter + xp; + if (!mKillCounter) + mKillCounter = 1; + + float AvgExp = static_cast(mExpCounter / mKillCounter); + int xpNextLevel(PlayerInfo::getAttribute(EXP_NEEDED)); + + if (mKillTimer == 0) + mKillTimer = cur_time; + + if (!xpNextLevel) + xpNextLevel = 1; + + double timeDiff = difftime(cur_time, mKillTimer) / 60; + + if (timeDiff <= 0.001) + timeDiff = 1; + + mLine1->setCaption("Level: " + toString(player_node->getLevel()) + " at " + + toString(static_cast(PlayerInfo::getAttribute(EXP)) + / static_cast(xpNextLevel) * 100.0f) + "%"); + + mLine2->setCaption("Exp: " + toString(PlayerInfo::getAttribute(EXP)) + "/" + + toString(xpNextLevel) + " Left: " + + toString(xpNextLevel - PlayerInfo::getAttribute(EXP))); + if (AvgExp >= 0.001 && AvgExp <= 0.001) + { + mLine3->setCaption("1% = " + toString(xpNextLevel / 100) + + " exp, Avg Mob for 1%: ?"); + + mLine5->setCaption("Avg Exp: " + toString(AvgExp) + + ", No. of Avg mob to next level: ?"); + } + else + { + mLine3->setCaption("1% = " + toString(xpNextLevel / 100) + + " exp, Avg Mob for 1%: " + + toString((static_cast(xpNextLevel) / 100) / AvgExp)); + + mLine5->setCaption("Avg Exp: " + toString(AvgExp) + + ", No. of Avg mob to next level: " + + toString(static_cast(xpNextLevel - + PlayerInfo::getAttribute(EXP)) / AvgExp)); + } + mLine4->setCaption("Kills: " + toString(mKillCounter) + + ", Total Exp: " + toString(mExpCounter)); + + mLine6->setCaption("Kills/Min: " + toString(mKillTCounter / timeDiff) + + ", Exp/Min: " + toString(mExpTCounter / timeDiff)); + + mLastKillExpLabel->setCaption("Last Kill Exp: " + toString(xp)); + + recalcStats(); + update(); +} + +void KillStats::recalcStats() +{ + int curTime = cur_time; + + // Need Update Exp Counter + if (curTime - m1minExpTime > 60) + { + int newExp = PlayerInfo::getAttribute(EXP); + if (m1minExpTime != 0) + m1minSpeed = newExp - m1minExpNum; + else + m1minSpeed = 0; + m1minExpTime = curTime; + m1minExpNum = newExp; + } + + if (curTime - m5minExpTime > 60*5) + { + int newExp = PlayerInfo::getAttribute(EXP); + if (m5minExpTime != 0) + m5minSpeed = newExp - m5minExpNum; + else + m5minSpeed = 0; + m5minExpTime = curTime; + m5minExpNum = newExp; + } + + if (curTime - m15minExpTime > 60*15) + { + int newExp = PlayerInfo::getAttribute(EXP); + if (m15minExpTime != 0) + m15minSpeed = newExp - m15minExpNum; + else + m15minSpeed = 0; + m15minExpTime = curTime; + m15minExpNum = newExp; + } + validateJacko(); +} + +void KillStats::update() +{ + mExpSpeed1Label->setCaption( + strprintf(_("Exp Speed per 1 min: %d"), m1minSpeed)); + mExpSpeed1Label->adjustSize(); + + if (m1minSpeed != 0) + { + mExpTime1Label->setCaption(strprintf(_(" Time For Next Level: %f"), + static_cast((PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP)) / m1minSpeed))); + } + else + { + mExpTime1Label->setCaption(_(" Time For Next Level: ?")); + } + mExpTime1Label->adjustSize(); + + mExpSpeed5Label->setCaption( + strprintf(_("Exp Speed per 5 min: %d"), m5minSpeed / 5)); + mExpSpeed5Label->adjustSize(); + + if (m5minSpeed != 0) + { + mExpTime5Label->setCaption(strprintf(_(" Time For Next Level: %f"), + static_cast((PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP)) / m5minSpeed * 5))); + } + else + { + mExpTime5Label->setCaption(_(" Time For Next Level: ?")); + } + mExpTime5Label->adjustSize(); + + mExpSpeed15Label->setCaption( + strprintf(_("Exp Speed per 15 min: %d"), m15minSpeed / 15)); + mExpSpeed15Label->adjustSize(); + + if (m15minSpeed != 0) + { + mExpTime15Label->setCaption(strprintf(_(" Time For Next Level: %f"), + static_cast((PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP)) / m15minSpeed * 15))); + } + else + { + mExpTime15Label->setCaption(_(" Time For Next Level: ?")); + } + + validateJacko(); + updateJackoLabel(); +} +void KillStats::draw(gcn::Graphics *g) +{ + update(); + + Window::draw(g); +} + +void KillStats::updateJackoLabel() +{ + if (mIsJackoAlive) + { + mTimeBeforeJackoLabel->setCaption( + _("Time before jacko spawn: jacko alive")); + } + else if (mIsJackoSpawnTimeUnknown && mJackoSpawnTime != 0) + { + mTimeBeforeJackoLabel->setCaption(_("Time before jacko spawn: ") + + toString(mJackoSpawnTime - cur_time) + _("?")); + } + else if (mIsJackoMustSpawn) + { + mTimeBeforeJackoLabel->setCaption( + _("Time before jacko spawn: jacko spawning")); + } + else + { + mTimeBeforeJackoLabel->setCaption(_("Time before jacko spawn: ") + + toString(mJackoSpawnTime - cur_time)); + } +} + +void KillStats::jackoDead(int id) +{ + if (id == mJackoId && mIsJackoAlive) + { + mIsJackoAlive = false; + mJackoSpawnTime = cur_time + 60*4; + mIsJackoSpawnTimeUnknown = false; + updateJackoLabel(); + } +} + +void KillStats::addLog(std::string str) +{ + if (debugChatTab) + debugChatTab->chatLog(str, BY_SERVER); +} + +void KillStats::jackoAlive(int id) +{ + if (!mIsJackoAlive) + { + mJackoId = id; + mIsJackoAlive = true; + mIsJackoMustSpawn = false; + mJackoSpawnTime = 0; + mIsJackoSpawnTimeUnknown = false; + updateJackoLabel(); + } +} + +void KillStats::validateJacko() +{ + if (!actorSpriteManager || !player_node) + return; + + Map *currentMap = Game::instance()->getCurrentMap(); + if (currentMap) + { + if (currentMap->getProperty("_filename") == "018-1" + || currentMap->getProperty("_filename") == "maps/018-1.tmx") + { + if (player_node->getTileX() >= 167 + && player_node->getTileX() <= 175 + && player_node->getTileY() >= 21 + && player_node->getTileY() <= 46) + { + Being *dstBeing = actorSpriteManager->findBeingByName( + "Jack O", Being::MONSTER); + if (mIsJackoAlive && !dstBeing) + { + mIsJackoAlive = false; + mJackoSpawnTime = cur_time + 60*4; + mIsJackoSpawnTimeUnknown = true; + } + } + } + + if (!mIsJackoAlive && cur_time > mJackoSpawnTime + 15) + mIsJackoMustSpawn = true; + } +} + +void KillStats::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + int id = event.getInt("id"); + if (id == EXP || id == EXP_NEEDED) + { + gainXp(event.getInt("newValue") - event.getInt("oldValue")); +// update(); + } + } +} diff --git a/src/gui/killstats.h b/src/gui/killstats.h new file mode 100644 index 000000000..df53aa319 --- /dev/null +++ b/src/gui/killstats.h @@ -0,0 +1,132 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef KILLSTATS_H +#define KILLSTATS_H + +#include + +#include "listener.h" + +#include "gui/widgets/window.h" + +class Label; +class Button; + +class KillStats : public Window, gcn::ActionListener, public Mana::Listener +{ + public: + /** + * Constructor. + */ + KillStats(); + + /** + * Destructor. + */ + ~KillStats(); + + /** + * Stuff. + */ + void action(const gcn::ActionEvent &event); + void gainXp(int Xp); + + /** + * Recalc stats if needed + */ + void recalcStats(); + + /** + * Draw this window + */ + void draw(gcn::Graphics *graphics); + + /** + * Updates this dialog + */ + void update(); + + /** + * Updates jacko info + */ + void updateJackoLabel(); + + void jackoDead(int id); + + void jackoAlive(int id); + + void addLog(std::string str); + + void event(Channels channel _UNUSED_, + const Mana::Event &event); + + private: + void validateJacko(); + + int mKillCounter; /**< Session Kill counter. */ + int mExpCounter; /**< Session Exp counter. */ + int mKillTCounter; /**< Timer Kill counter. */ + int mExpTCounter; /**< Timer Exp counter. */ + time_t mKillTimer; /**< Timer for kill stats. */ + Button *mResetButton; + Button *mTimerButton; + Label *mLine1; + Label *mLine2; + Label *mLine3; + Label *mLine4; + Label *mLine5; + Label *mLine6; + + gcn::Label *mExpSpeed1Label; + gcn::Label *mExpTime1Label; + gcn::Label *mExpSpeed5Label; + gcn::Label *mExpTime5Label; + gcn::Label *mExpSpeed15Label; + gcn::Label *mExpTime15Label; + + gcn::Label *mLastKillExpLabel; + gcn::Label *mTimeBeforeJackoLabel; + + int m1minExpTime; + int m1minExpNum; + int m1minSpeed; + + int m5minExpTime; + int m5minExpNum; + int m5minSpeed; + + int m15minExpTime; + int m15minExpNum; + int m15minSpeed; + + int mJackoSpawnTime; + int mValidateJackoTime; + int mJackoId; + bool mIsJackoAlive; + bool mIsJackoMustSpawn; + bool mIsJackoSpawnTimeUnknown; +}; + +extern KillStats *killStats; + +#endif diff --git a/src/gui/login.cpp b/src/gui/login.cpp new file mode 100644 index 000000000..997895c95 --- /dev/null +++ b/src/gui/login.cpp @@ -0,0 +1,231 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/login.h" + +#include "client.h" +#include "configuration.h" + +#include "gui/okdialog.h" +#include "gui/sdlinput.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +static const int MAX_SERVER_LIST_SIZE = 15; +static const int LOGIN_DIALOG_WIDTH = 300; +static const int LOGIN_DIALOG_HEIGHT = 140; +static const int FIELD_WIDTH = LOGIN_DIALOG_WIDTH - 70; + +std::string LoginDialog::savedPassword = ""; +std::string LoginDialog::savedPasswordKey = ""; + + +const char *UPDATE_TYPE_TEXT[3] = +{ + _("Normal"), + _("Auto Close"), + _("Skip"), +}; + +class UpdateTypeModel : public gcn::ListModel +{ +public: + virtual ~UpdateTypeModel() + { } + + virtual int getNumberOfElements() + { + return 3; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return UPDATE_TYPE_TEXT[i]; + } +}; + +LoginDialog::LoginDialog(LoginData *loginData, std::string serverName, + std::string *updateHost): + Window(_("Login")), + mLoginData(loginData), + mUpdateHost(updateHost) +{ + gcn::Label *serverLabel1 = new Label(_("Server:")); + gcn::Label *serverLabel2 = new Label(serverName); + serverLabel2->adjustSize(); + gcn::Label *userLabel = new Label(_("Name:")); + gcn::Label *passLabel = new Label(_("Password:")); + mCustomUpdateHost = new CheckBox(_("Custom update host"), + loginData->updateType & LoginData::Upd_Custom, this, "customhost"); + + mUpdateHostText = new TextField(serverConfig.getValue( + "customUpdateHost", "")); + + mUpdateHostText->adjustSize(); + + mUserField = new TextField(mLoginData->username); + mPassField = new PasswordField(mLoginData->password); + + if (mPassField->getText().empty() && LoginDialog::savedPassword != "") + mPassField->setText(LoginDialog::savedPassword); + + mKeepCheck = new CheckBox(_("Remember username"), mLoginData->remember); + mUpdateTypeLabel = new Label(_("Update:")); + mUpdateTypeDropDown = new DropDown(new UpdateTypeModel()); + mUpdateTypeDropDown->setActionEventId("updatetype"); + mUpdateTypeDropDown->setSelected((loginData->updateType + | LoginData::Upd_Custom) ^ LoginData::Upd_Custom); + + if (!mCustomUpdateHost->isSelected()) + mUpdateHostText->setVisible(false); + + mRegisterButton = new Button(_("Register"), "register", this); + mServerButton = new Button(_("Change Server"), "server", this); + mLoginButton = new Button(_("Login"), "login", this); + + mUserField->setActionEventId("login"); + mPassField->setActionEventId("login"); + + mUserField->addKeyListener(this); + mPassField->addKeyListener(this); + mUserField->addActionListener(this); + mPassField->addActionListener(this); + + place(0, 0, serverLabel1); + place(1, 0, serverLabel2, 8).setPadding(1); + + place(0, 1, userLabel); + place(0, 2, passLabel); + place(1, 1, mUserField, 8).setPadding(1); + place(1, 2, mPassField, 8).setPadding(1); + place(0, 6, mUpdateTypeLabel, 1); + place(1, 6, mUpdateTypeDropDown, 8); + place(0, 7, mCustomUpdateHost, 9); + place(0, 8, mUpdateHostText, 9); + place(0, 9, mKeepCheck, 9); + place(0, 10, mRegisterButton).setHAlign(LayoutCell::LEFT); + place(2, 10, mServerButton); + place(3, 10, mLoginButton); + + reflowLayout(); + + addKeyListener(this); + + center(); + setVisible(true); + + if (mUserField->getText().empty()) + mUserField->requestFocus(); + else + mPassField->requestFocus(); + + mLoginButton->setEnabled(canSubmit()); + mRegisterButton->setEnabled(Net::getLoginHandler() + ->isRegistrationEnabled()); +} + +LoginDialog::~LoginDialog() +{ +} + +void LoginDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "login" && canSubmit()) + { + mLoginData->username = mUserField->getText(); + mLoginData->password = mPassField->getText(); + mLoginData->remember = mKeepCheck->isSelected(); + int updateType = mUpdateTypeDropDown->getSelected(); + + if (mCustomUpdateHost->isSelected()) + { + updateType |= LoginData::Upd_Custom; + serverConfig.setValue("customUpdateHost", + mUpdateHostText->getText()); + + mLoginData->updateHost = mUpdateHostText->getText(); + *mUpdateHost = mUpdateHostText->getText(); + } + mLoginData->updateType = updateType; + serverConfig.setValue("updateType", updateType); + + mLoginData->registerLogin = false; + + mRegisterButton->setEnabled(false); + mServerButton->setEnabled(false); + mLoginButton->setEnabled(false); + + if (mLoginData->remember) + LoginDialog::savedPassword = mPassField->getText(); + + Client::setState(STATE_LOGIN_ATTEMPT); + } + else if (event.getId() == "server") + { + Client::setState(STATE_SWITCH_SERVER); + } + else if (event.getId() == "register") + { + mLoginData->username = mUserField->getText(); + mLoginData->password = mPassField->getText(); + + Client::setState(STATE_REGISTER_PREP); + } + else if (event.getId() == "customhost") + { + mUpdateHostText->setVisible(mCustomUpdateHost->isSelected()); + } +} + +void LoginDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + action(gcn::ActionEvent(NULL, mServerButton->getActionEventId())); + else if (key.getValue() == Key::ENTER) + action(gcn::ActionEvent(NULL, mLoginButton->getActionEventId())); + else + mLoginButton->setEnabled(canSubmit()); +} + +bool LoginDialog::canSubmit() const +{ + return !mUserField->getText().empty() && + !mPassField->getText().empty() && + Client::getState() == STATE_LOGIN; +} diff --git a/src/gui/login.h b/src/gui/login.h new file mode 100644 index 000000000..f20637032 --- /dev/null +++ b/src/gui/login.h @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LOGIN_H +#define LOGIN_H + +#include "gui/widgets/window.h" + +#include +#include +#include + +#include +#include + +class LoginData; + +/** + * The login dialog. + * + * \ingroup Interface + */ +class LoginDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + LoginDialog(LoginData *loginData, std::string serverName, + std::string *updateHost); + + ~LoginDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Called when a key is pressed in one of the text fields. + */ + void keyPressed(gcn::KeyEvent &keyEvent); + + static std::string savedPasswordKey; + static std::string savedPassword; + + private: + /** + * Returns whether submit can be enabled. This is true in the login + * state, when all necessary fields have some text. + */ + bool canSubmit() const; + + gcn::TextField *mUserField; + gcn::TextField *mPassField; + gcn::CheckBox *mKeepCheck; + gcn::Label *mUpdateTypeLabel; + gcn::DropDown *mUpdateTypeDropDown; + gcn::Button *mServerButton; + gcn::Button *mLoginButton; + gcn::Button *mRegisterButton; + gcn::CheckBox *mCustomUpdateHost; + gcn::TextField *mUpdateHostText; + + LoginData *mLoginData; + std::string *mUpdateHost; +}; + +#endif diff --git a/src/gui/minimap.cpp b/src/gui/minimap.cpp new file mode 100644 index 000000000..6edefcfdd --- /dev/null +++ b/src/gui/minimap.cpp @@ -0,0 +1,292 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/minimap.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "configuration.h" +#include "graphics.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" + +#include "gui/userpalette.h" +#include "gui/setup.h" +#include "gui/viewport.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" + +#include + +bool Minimap::mShow = true; + +Minimap::Minimap(): + Window(_("Map")), + mMapImage(0), + mWidthProportion(0.5), + mHeightProportion(0.5) +{ + setWindowName("Minimap"); + mShow = config.getValueBool(getWindowName() + "Show", true); + setDefaultSize(5, 25, 100, 100); + // set this to false as the minimap window size is changed + //depending on the map size + setResizable(false); + setupWindow->registerWindowForReset(this); + + setDefaultVisible(true); + setSaveVisible(true); + + setStickyButton(true); + setSticky(false); + + loadWindowState(); + setVisible(mShow, isSticky()); +} + +Minimap::~Minimap() +{ + config.setValue(getWindowName() + "Show", mShow); + + if (mMapImage) + mMapImage->decRef(); +} + +void Minimap::setMap(Map *map) +{ + std::string caption = ""; + std::string minimapName; + + if (map) + caption = map->getName(); + + if (caption.empty()) + caption = _("Map"); + + setCaption(caption); + + // Adapt the image + if (mMapImage) + { + mMapImage->decRef(); + mMapImage = 0; + } + + if (map) + { + std::string tempname = + "graphics/minimaps/" + map->getFilename() + ".png"; + ResourceManager *resman = ResourceManager::getInstance(); + + minimapName = map->getProperty("minimap"); + + if (minimapName.empty() && resman->exists(tempname)) + minimapName = tempname; + + mMapImage = resman->getImage(minimapName); + } + + if (mMapImage && map) + { + const int offsetX = 2 * getPadding(); + const int offsetY = getTitleBarHeight() + getPadding(); + const int titleWidth = getFont()->getWidth(getCaption()) + 15; + const int mapWidth = mMapImage->getWidth() < 100 ? + mMapImage->getWidth() + offsetX : 100; + const int mapHeight = mMapImage->getHeight() < 100 ? + mMapImage->getHeight() + offsetY : 100; + + setMinWidth(mapWidth > titleWidth ? mapWidth : titleWidth); + setMinHeight(mapHeight); + + mWidthProportion = static_cast( + mMapImage->getWidth()) / static_cast(map->getWidth()); + mHeightProportion = static_cast( + mMapImage->getHeight()) / static_cast(map->getHeight()); + + setMaxWidth(mMapImage->getWidth() > titleWidth ? + mMapImage->getWidth() + offsetX : titleWidth); + setMaxHeight(mMapImage->getHeight() + offsetY); + + setDefaultSize(getX(), getY(), getWidth(), getHeight()); + resetToDefaultSize(); + + if (mShow) + setVisible(true); + } + else + { + if (!isSticky()) + setVisible(false); + } +} + +void Minimap::toggle() +{ + setVisible(!isVisible(), isSticky()); + mShow = isVisible(); +} + +void Minimap::draw(gcn::Graphics *graphics) +{ + Window::draw(graphics); + + if (!userPalette || !player_node || !viewport) + return; + + Graphics *graph = static_cast(graphics); + + const gcn::Rectangle a = getChildrenArea(); + + graphics->pushClipArea(a); + + int mapOriginX = 0; + int mapOriginY = 0; + + if (mMapImage) + { + if (mMapImage->getWidth() > a.width || + mMapImage->getHeight() > a.height) + { + const Vector &p = player_node->getPosition(); + mapOriginX = ((a.width) / 2) - static_cast((p.x + + viewport->getCameraRelativeX()) * mWidthProportion) / 32; + mapOriginY = ((a.height) / 2) - static_cast((p.y + + viewport->getCameraRelativeX()) * mHeightProportion) / 32; + + const int minOriginX = a.width - mMapImage->getWidth(); + const int minOriginY = a.height - mMapImage->getHeight(); + + if (mapOriginX < minOriginX) + mapOriginX = minOriginX; + if (mapOriginY < minOriginY) + mapOriginY = minOriginY; + if (mapOriginX > 0) + mapOriginX = 0; + if (mapOriginY > 0) + mapOriginY = 0; + } + + graph->drawImage(mMapImage, mapOriginX, mapOriginY); + } + + if (!actorSpriteManager) + return; + + const ActorSprites &actors = actorSpriteManager->getAll(); + + for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); + it != it_end; it++) + { + if (!(*it) || (*it)->getType() == ActorSprite::FLOOR_ITEM) + continue; + + const Being *being = static_cast(*it); + if (!being) + continue; + + int dotSize = 2; + + int type = UserPalette::PC; + + if (being == player_node) + { + type = UserPalette::SELF; + dotSize = 3; + } + else if (being->isGM()) + { + type = UserPalette::GM; + } + else if (being->isInParty()) + { + type = UserPalette::PARTY; + } + else if (being) + { + switch (being->getType()) + { + case ActorSprite::MONSTER: + type = UserPalette::MONSTER; + break; + + case ActorSprite::NPC: + type = UserPalette::NPC; + break; + + default: + continue; + } + } + + if (userPalette) + graphics->setColor(userPalette->getColor(type)); + + const int offsetHeight = static_cast(static_cast( + dotSize - 1) * mHeightProportion); + const int offsetWidth = static_cast(static_cast( + dotSize - 1) * mWidthProportion); + const Vector &pos = being->getPosition(); + + graphics->fillRectangle(gcn::Rectangle( + static_cast(pos.x * mWidthProportion) / 32 + + mapOriginX - offsetWidth, + static_cast(pos.y * mHeightProportion) / 32 + + mapOriginY - offsetHeight, + dotSize, dotSize)); + } + + const Vector &pos = player_node->getPosition(); +// logger->log("width:" + toString(graph->getWidth())); + + int x = static_cast((pos.x - (graph->getWidth() / 2) + + viewport->getCameraRelativeX()) + * mWidthProportion) / 32 + mapOriginX; + int y = static_cast((pos.y - (graph->getHeight() / 2) + + viewport->getCameraRelativeY()) + * mHeightProportion) / 32 + mapOriginY; + + const int w = graph->getWidth() * mWidthProportion / 32; + const int h = graph->getHeight() * mHeightProportion / 32; + + if (w <= a.width) + { + if (x < 0 && w) + x = 0; + if (x + w > a.width) + x = a.width - w; + } + if (h <= a.height) + { + if (y < 0 && h) + y = 0; + if (y + h > a.height) + y = a.height - h; + } + + graphics->setColor(userPalette->getColor(UserPalette::PC)); + graphics->drawRectangle(gcn::Rectangle(x, y, w, h)); + graphics->popClipArea(); +} diff --git a/src/gui/minimap.h b/src/gui/minimap.h new file mode 100644 index 000000000..a376a15c1 --- /dev/null +++ b/src/gui/minimap.h @@ -0,0 +1,69 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MINIMAP_H +#define MINIMAP_H + +#include "gui/widgets/window.h" + +class Image; +class Map; + +/** + * Minimap window. Shows a minimap image and the name of the current map. + * + * The name of the map is defined by the map property "name". The minimap image + * is defined by the map property "minimap". The path to the image should be + * given relative to the root of the client data. + * + * \ingroup Interface + */ +class Minimap : public Window +{ + public: + Minimap(); + ~Minimap(); + + /** + * Sets the map image that should be displayed. + */ + void setMap(Map *map); + + /** + * Toggles the displaying of the minimap. + */ + void toggle(); + + /** + * Draws the minimap. + */ + void draw(gcn::Graphics *graphics); + + private: + Image *mMapImage; + float mWidthProportion; + float mHeightProportion; + static bool mShow; +}; + +extern Minimap *minimap; + +#endif diff --git a/src/gui/ministatus.cpp b/src/gui/ministatus.cpp new file mode 100644 index 000000000..406ad7ddd --- /dev/null +++ b/src/gui/ministatus.cpp @@ -0,0 +1,228 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/ministatus.h" + +#include "animatedsprite.h" +#include "configuration.h" +#include "graphics.h" +#include "playerinfo.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/statuswindow.h" +#include "gui/statuspopup.h" +#include "gui/textpopup.h" +#include "gui/theme.h" + +#include "gui/widgets/chattab.h" +#include "gui/widgets/label.h" +#include "gui/widgets/progressbar.h" + +#include "net/net.h" +#include "net/playerhandler.h" +#include "net/gamehandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern volatile int tick_time; + +MiniStatusWindow::MiniStatusWindow(): + Popup("MiniStatus") +{ + listen(CHANNEL_ATTRIBUTES); + + mHpBar = new ProgressBar(0, 100, 20, Theme::PROG_HP); + StatusWindow::updateHPBar(mHpBar); + + if (Net::getGameHandler()->canUseMagicBar()) + { + mMpBar = new ProgressBar(0, 100, 20, + Net::getPlayerHandler()->canUseMagic() + ? Theme::PROG_MP : Theme::PROG_NO_MP); + + StatusWindow::updateMPBar(mMpBar); + } + else + { + mMpBar = 0; + } + + mXpBar = new ProgressBar(0, 100, 20, Theme::PROG_EXP); + StatusWindow::updateXPBar(mXpBar); + + mStatusBar = new ProgressBar(100, 150, 20, Theme::PROG_EXP); + + mHpBar->setPosition(0, 3); + if (mMpBar) + mMpBar->setPosition(mHpBar->getWidth() + 3, 3); + mXpBar->setPosition(mMpBar ? mMpBar->getX() + mMpBar->getWidth() + 3 : + mHpBar->getX() + mHpBar->getWidth() + 3, 3); + mStatusBar->setPosition(mXpBar->getX() + mXpBar->getWidth() + 3, 3); + + add(mHpBar); + if (mMpBar) + add(mMpBar); + add(mXpBar); + add(mStatusBar); + + setContentSize(mStatusBar->getX() + mStatusBar->getWidth(), + mXpBar->getY() + mXpBar->getHeight()); + + setVisible(config.getValueBool(getPopupName() + "Visible", true)); + + mStatusPopup = new StatusPopup(); + mTextPopup = new TextPopup(); + + addMouseListener(this); + updateStatus(); +} + +MiniStatusWindow::~MiniStatusWindow() +{ + delete mTextPopup; + mTextPopup = 0; + delete mStatusPopup; + mStatusPopup = 0; +} + +void MiniStatusWindow::setIcon(int index, AnimatedSprite *sprite) +{ + if (index >= static_cast(mIcons.size())) + mIcons.resize(index + 1, 0); + + delete mIcons[index]; + + mIcons[index] = sprite; +} + +void MiniStatusWindow::eraseIcon(int index) +{ + mIcons.erase(mIcons.begin() + index); +} + +void MiniStatusWindow::drawIcons(Graphics *graphics) +{ + // Draw icons + int icon_x = mStatusBar->getX() + mStatusBar->getWidth() + 4; + for (unsigned int i = 0; i < mIcons.size(); i++) + { + if (mIcons[i]) + { + mIcons[i]->draw(graphics, icon_x, 3); + icon_x += 2 + mIcons[i]->getWidth(); + } + } +} + +void MiniStatusWindow::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + int id = event.getInt("id"); + if (id == HP || id == MAX_HP) + StatusWindow::updateHPBar(mHpBar); + else if (id == MP || id == MAX_MP) + StatusWindow::updateMPBar(mMpBar); + else if (id == EXP || id == EXP_NEEDED) + StatusWindow::updateXPBar(mXpBar); + } + else if (event.getName() == EVENT_UPDATESTAT) + { + StatusWindow::updateMPBar(mMpBar); + } +} + +void MiniStatusWindow::updateStatus() +{ + StatusWindow::updateStatusBar(mStatusBar); + if (mStatusPopup && mStatusPopup->isVisible()) + mStatusPopup->update(); +} + +void MiniStatusWindow::logic() +{ + Popup::logic(); + + for (unsigned int i = 0; i < mIcons.size(); i++) + { + if (mIcons[i]) + mIcons[i]->update(tick_time * 10); + } +} + +void MiniStatusWindow::draw(gcn::Graphics *graphics) +{ + drawChildren(graphics); +} + +void MiniStatusWindow::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + const int x = event.getX(); + const int y = event.getY(); + + if (event.getSource() == mStatusBar) + { + mStatusPopup->view(x + getX(), y + getY()); + mTextPopup->hide(); + } + else if (event.getSource() == mXpBar) + { + mTextPopup->show(x + getX(), y + getY(), + strprintf("%u/%u", PlayerInfo::getAttribute(EXP), + PlayerInfo::getAttribute(EXP_NEEDED)), + strprintf("%s: %u", _("Need"), + PlayerInfo::getAttribute(EXP_NEEDED) + - PlayerInfo::getAttribute(EXP))); + mStatusPopup->hide(); + } + else if (event.getSource() == mHpBar) + { + mTextPopup->show(x + getX(), y + getY(), + strprintf("%u/%u", PlayerInfo::getAttribute(HP), + PlayerInfo::getAttribute(MAX_HP))); + mStatusPopup->hide(); + } + else if (event.getSource() == mMpBar) + { + mTextPopup->show(x + getX(), y + getY(), + strprintf("%u/%u", PlayerInfo::getAttribute(MP), + PlayerInfo::getAttribute(MAX_MP))); + mStatusPopup->hide(); + } + else + { + mTextPopup->hide(); + mStatusPopup->hide(); + } +} + +void MiniStatusWindow::mouseExited(gcn::MouseEvent &event) +{ + Popup::mouseExited(event); + + mTextPopup->hide(); + mStatusPopup->hide(); +} diff --git a/src/gui/ministatus.h b/src/gui/ministatus.h new file mode 100644 index 000000000..080beece3 --- /dev/null +++ b/src/gui/ministatus.h @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MINISTATUS_H +#define MINISTATUS_H + +#include "listener.h" + +#include "gui/widgets/popup.h" +#include "gui/widgets/window.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class AnimatedSprite; +class Graphics; +class ProgressBar; +class StatusPopup; +class TextPopup; + +/** + * The player mini-status dialog. + * + * \ingroup Interface + */ +class MiniStatusWindow : public Popup, public Mana::Listener +{ + public: + MiniStatusWindow(); + + ~MiniStatusWindow(); + + /** + * Sets one of the icons. + */ + void setIcon(int index, AnimatedSprite *sprite); + + void eraseIcon(int index); + + void drawIcons(Graphics *graphics); + + void event(Channels channel, const Mana::Event &event); + + void updateStatus(); + + void logic(); // Updates icons + + void draw(gcn::Graphics *graphics); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + void mouseExited(gcn::MouseEvent &event); + + private: + bool isInBar(ProgressBar *bar, int x, int y) const; + + /* + * Mini Status Bars + */ + ProgressBar *mHpBar; + ProgressBar *mMpBar; + ProgressBar *mXpBar; + ProgressBar *mStatusBar; + TextPopup *mTextPopup; + StatusPopup *mStatusPopup; + + std::vector mIcons; +}; + +extern MiniStatusWindow *miniStatusWindow; + +#endif diff --git a/src/gui/npcdialog.cpp b/src/gui/npcdialog.cpp new file mode 100644 index 000000000..6414da6ed --- /dev/null +++ b/src/gui/npcdialog.cpp @@ -0,0 +1,483 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/npcdialog.h" + +#include "configuration.h" +#include "client.h" + +#include "gui/setup.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/textbox.h" +#include "gui/widgets/textfield.h" + +#include "net/net.h" +#include "net/npchandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#define CAPTION_WAITING _("Waiting for server") +#define CAPTION_NEXT _("Next") +#define CAPTION_CLOSE _("Close") +#define CAPTION_SUBMIT _("Submit") + +NpcDialog::DialogList NpcDialog::instances; + +NpcDialog::NpcDialog(int npcId) + : Window(_("NPC")), + mNpcId(npcId), + mLogInteraction(config.getBoolValue("logNpcInGui")), + mDefaultInt(0), + mInputState(NPC_INPUT_NONE), + mActionState(NPC_ACTION_WAIT), + mLastNextTime(0) +{ + // Basic Window Setup + setWindowName("NpcText"); + setResizable(true); + //setupWindow->registerWindowForReset(this); + setFocusable(true); + + setMinWidth(200); + setMinHeight(150); + + setDefaultSize(260, 200, ImageRect::CENTER); + + // Setup output text box + mTextBox = new TextBox; + mTextBox->setEditable(false); + mTextBox->setOpaque(false); + + mScrollArea = new ScrollArea(mTextBox); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mScrollArea->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + + // Setup listbox + mItemList = new ListBox(this); + mItemList->setWrappingEnabled(true); + setContentSize(260, 175); + + mListScrollArea = new ScrollArea(mItemList); + mListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mItemList->setVisible(true); + + // Setup string input box + mTextField = new TextField(""); + mTextField->setVisible(true); + + // Setup int input box + mIntField = new IntTextField; + mIntField->setVisible(true); + + mClearButton = new Button(_("Clear"), "clear", this); + + // Setup button + mButton = new Button("", "ok", this); + + //Setup more and less buttons (int input) + mPlusButton = new Button(_("+"), "inc", this); + mMinusButton = new Button(_("-"), "dec", this); + + int width = std::max(mButton->getFont()->getWidth(CAPTION_WAITING), + mButton->getFont()->getWidth(CAPTION_NEXT)); + width = std::max(width, mButton->getFont()->getWidth(CAPTION_CLOSE)); + width = std::max(width, mButton->getFont()->getWidth(CAPTION_SUBMIT)); + + mButton->setWidth(8 + width); + + mResetButton = new Button(_("Reset"), "reset", this); + + // Place widgets + buildLayout(); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); + requestFocus(); + + config.addListener("logNpcInGui", this); +} + +NpcDialog::~NpcDialog() +{ + config.removeListener("logNpcInGui", this); + + // These might not actually be in the layout, so lets be safe + delete mScrollArea; + mScrollArea = 0; + delete mItemList; + mItemList = 0; + delete mTextField; + mTextField = 0; + delete mIntField; + mIntField = 0; + delete mResetButton; + mResetButton = 0; + delete mPlusButton; + mPlusButton = 0; + delete mMinusButton; + mMinusButton = 0; + + instances.remove(this); + +} + +void NpcDialog::setText(const std::string &text) +{ + mText = text; + + mTextBox->setTextWrapped(mText, mScrollArea->getWidth() - 15); +} + +void NpcDialog::addText(const std::string &text, bool save) +{ + if (save || mLogInteraction) + { + if (mText.size() > 5000) + mText = ""; + + mNewText += text + "\n"; + setText(mText + text + "\n"); + } + mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll()); + mActionState = NPC_ACTION_WAIT; + buildLayout(); +} + +void NpcDialog::showNextButton() +{ + mActionState = NPC_ACTION_NEXT; + buildLayout(); +} + +void NpcDialog::showCloseButton() +{ + mActionState = NPC_ACTION_CLOSE; + buildLayout(); +} + +void NpcDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + { + if (mActionState == NPC_ACTION_NEXT) + { + if (!Client::limitPackets(PACKET_NPC_NEXT)) + return; + + nextDialog(); + // TRANSLATORS: Please leave the \n sequences intact. + addText(_("\n> Next\n"), false); + } + else if (mActionState == NPC_ACTION_CLOSE) + { + closeDialog(); + } + else if (mActionState == NPC_ACTION_INPUT) + { + std::string printText = ""; // Text that will get printed + // in the textbox + + if (mInputState == NPC_INPUT_LIST) + { + unsigned char choice = 0; + int selectedIndex = mItemList->getSelected(); + + if (selectedIndex >= static_cast(mItems.size()) + || selectedIndex < 0 + || !Client::limitPackets(PACKET_NPC_INPUT)) + { + return; + } + choice = static_cast(selectedIndex + 1); + printText = mItems[selectedIndex]; + + Net::getNpcHandler()->listInput(mNpcId, choice); + } + else if (mInputState == NPC_INPUT_STRING) + { + if (!Client::limitPackets(PACKET_NPC_INPUT)) + return; + printText = mTextField->getText(); + Net::getNpcHandler()->stringInput(mNpcId, printText); + } + else if (mInputState == NPC_INPUT_INTEGER) + { + if (!Client::limitPackets(PACKET_NPC_INPUT)) + return; + printText = strprintf("%d", mIntField->getValue()); + Net::getNpcHandler()->integerInput( + mNpcId, mIntField->getValue()); + } + // addText will auto remove the input layout + addText(strprintf("\n> \"%s\"\n", printText.c_str()), false); + + mNewText.clear(); + } + + if (!mLogInteraction) + setText(""); + } + else if (event.getId() == "reset") + { + if (mInputState == NPC_INPUT_STRING) + mTextField->setText(mDefaultString); + else if (mInputState == NPC_INPUT_INTEGER) + mIntField->setValue(mDefaultInt); + } + else if (event.getId() == "inc") + { + mIntField->setValue(mIntField->getValue() + 1); + } + else if (event.getId() == "dec") + { + mIntField->setValue(mIntField->getValue() - 1); + } + else if (event.getId() == "clear") + { + setText(mNewText); + } +} + +void NpcDialog::nextDialog() +{ + Net::getNpcHandler()->nextDialog(mNpcId); +} + +void NpcDialog::closeDialog() +{ + Net::getNpcHandler()->closeDialog(mNpcId); +} + +int NpcDialog::getNumberOfElements() +{ + return static_cast(mItems.size()); +} + +std::string NpcDialog::getElementAt(int i) +{ + return mItems[i]; +} + +void NpcDialog::choiceRequest() +{ + mItems.clear(); + mActionState = NPC_ACTION_INPUT; + mInputState = NPC_INPUT_LIST; + buildLayout(); +} + +void NpcDialog::addChoice(const std::string &choice) +{ + mItems.push_back(choice); +} + +void NpcDialog::parseListItems(const std::string &itemString) +{ + std::istringstream iss(itemString); + + std::string tmp; + while (getline(iss, tmp, ':')) + mItems.push_back(tmp); +} + +void NpcDialog::textRequest(const std::string &defaultText) +{ + mActionState = NPC_ACTION_INPUT; + mInputState = NPC_INPUT_STRING; + mDefaultString = defaultText; + mTextField->setText(defaultText); + buildLayout(); +} + +bool NpcDialog::isTextInputFocused() const +{ + return mTextField->isFocused(); +} + +bool NpcDialog::isInputFocused() const +{ + return mTextField->isFocused() || mIntField->isFocused(); +} + +bool NpcDialog::isAnyInputFocused() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if ((*it)->isInputFocused()) + return true; + } + + return false; +} + +void NpcDialog::integerRequest(int defaultValue, int min, int max) +{ + mActionState = NPC_ACTION_INPUT; + mInputState = NPC_INPUT_INTEGER; + mDefaultInt = defaultValue; + mIntField->setRange(min, max); + mIntField->setValue(defaultValue); + buildLayout(); +} + +void NpcDialog::move(int amount) +{ + if (mActionState != NPC_ACTION_INPUT) + return; + + switch (mInputState) + { + case NPC_INPUT_INTEGER: + mIntField->setValue(mIntField->getValue() + amount); + break; + case NPC_INPUT_LIST: + mItemList->setSelected(mItemList->getSelected() - amount); + break; + case NPC_INPUT_NONE: + case NPC_INPUT_STRING: + default: + break; + } +} + +void NpcDialog::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + + setText(mText); +} + +void NpcDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (!visible) + scheduleDelete(); +} + +void NpcDialog::optionChanged(const std::string &name) +{ + if (name == "logNpcInGui") + mLogInteraction = config.getBoolValue("logNpcInGui"); +} + +NpcDialog *NpcDialog::getActive() +{ + if (instances.size() == 1) + return instances.front(); + + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + { + if ((*it)->isFocused()) + return (*it); + } + + return 0; +} + +void NpcDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + (*it)->close(); +} + +void NpcDialog::buildLayout() +{ + clearLayout(); + + if (mActionState != NPC_ACTION_INPUT) + { + if (mActionState == NPC_ACTION_WAIT) + mButton->setCaption(CAPTION_WAITING); + else if (mActionState == NPC_ACTION_NEXT) + mButton->setCaption(CAPTION_NEXT); + else if (mActionState == NPC_ACTION_CLOSE) + mButton->setCaption(CAPTION_CLOSE); + place(0, 0, mScrollArea, 5, 3); + place(3, 3, mClearButton); + place(4, 3, mButton); + } + else if (mInputState != NPC_INPUT_NONE) + { + if (!mLogInteraction) + setText(mNewText); + + mButton->setCaption(CAPTION_SUBMIT); + if (mInputState == NPC_INPUT_LIST) + { + place(0, 0, mScrollArea, 6, 3); + place(0, 3, mListScrollArea, 6, 3); + place(2, 6, mClearButton, 2); + place(4, 6, mButton, 2); + + mItemList->setSelected(-1); + } + else if (mInputState == NPC_INPUT_STRING) + { + place(0, 0, mScrollArea, 6, 3); + place(0, 3, mTextField, 6); + place(0, 4, mResetButton, 2); + place(2, 4, mClearButton, 2); + place(4, 4, mButton, 2); + } + else if (mInputState == NPC_INPUT_INTEGER) + { + place(0, 0, mScrollArea, 6, 3); + place(0, 3, mMinusButton, 1); + place(1, 3, mIntField, 4); + place(5, 3, mPlusButton, 1); + place(0, 4, mResetButton, 2); + place(2, 4, mClearButton, 2); + place(4, 4, mButton, 2); + } + } + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + mButton->setEnabled(mActionState != NPC_ACTION_WAIT); + + redraw(); + + mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll()); +} \ No newline at end of file diff --git a/src/gui/npcdialog.h b/src/gui/npcdialog.h new file mode 100644 index 000000000..eee4b32e2 --- /dev/null +++ b/src/gui/npcdialog.h @@ -0,0 +1,232 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NPCDIALOG_H +#define NPCDIALOG_H + +#include "configlistener.h" + +#include "gui/widgets/window.h" + +#include +#include + +#include +#include +#include + +class TextBox; +class ListBox; +class TextField; +class IntTextField; +class Button; + +/** + * The npc dialog. + * + * \ingroup Interface + */ +class NpcDialog : public Window, public gcn::ActionListener, + public gcn::ListModel, public ConfigListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + NpcDialog(int npcId); + + ~NpcDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Sets the text shows in the dialog. + * + * @param string The new text. + */ + void setText(const std::string &string); + + /** + * Adds the text to the text shows in the dialog. Also adds a newline + * to the end. + * + * @param string The text to add. + */ + void addText(const std::string &string, bool save = true); + + /** + * When called, the widget will show a "Next" button. + */ + void showNextButton(); + + /** + * When called, the widget will show a "Close" button and will close + * the dialog when clicked. + */ + void showCloseButton(); + + /** + * Notifies the server that client has performed a next action. + */ + void nextDialog(); + + /** + * Notifies the server that the client has performed a close action. + */ + void closeDialog(); + + /** + * Returns the number of items in the choices list. + */ + int getNumberOfElements(); + + /** + * Returns the name of item number i of the choices list. + */ + std::string getElementAt(int i); + + /** + * Makes this dialog request a choice selection from the user. + */ + void choiceRequest(); + + /** + * Adds a choice to the list box. + */ + void addChoice(const std::string &); + + /** + * Fills the options list for an NPC dialog. + * + * @param itemString A string with the options separated with colons. + */ + void parseListItems(const std::string &itemString); + + /** + * Requests a text string from the user. + */ + void textRequest(const std::string &defaultText = ""); + + bool isInputFocused() const; + + bool isTextInputFocused() const; + + static bool isAnyInputFocused(); + + /** + * Requests a interger from the user. + */ + void integerRequest(int defaultValue = 0, int min = 0, + int max = 2147483647); + + void move(int amount); + + /** + * Called when resizing the window. + * + * @param event The calling event + */ + void widgetResized(const gcn::Event &event); + + void setVisible(bool visible); + + void optionChanged(const std::string &name); + + /** + * Returns true if any instances exist. + */ + static bool isActive() { return !instances.empty(); } + + /** + * Returns the first active instance. Useful for pushing user + * interaction. + */ + static NpcDialog *getActive(); + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list DialogList; + static DialogList instances; + + void buildLayout(); + + int mNpcId; + bool mLogInteraction; + + int mDefaultInt; + std::string mDefaultString; + + // Used for the main input area + gcn::ScrollArea *mScrollArea; + TextBox *mTextBox; + std::string mText; + std::string mNewText; + + // Used for choice input + ListBox *mItemList; + gcn::ScrollArea *mListScrollArea; + std::vector mItems; + + // Used for string and integer input + TextField *mTextField; + IntTextField *mIntField; + Button *mPlusButton; + Button *mMinusButton; + + Button *mClearButton; + + // Used for the button + Button *mButton; + + // Will reset the text and integer input to the provided default + Button *mResetButton; + + enum NpcInputState + { + NPC_INPUT_NONE = 0, + NPC_INPUT_LIST, + NPC_INPUT_STRING, + NPC_INPUT_INTEGER + }; + + enum NpcActionState + { + NPC_ACTION_WAIT = 0, + NPC_ACTION_NEXT, + NPC_ACTION_INPUT, + NPC_ACTION_CLOSE + }; + + NpcInputState mInputState; + NpcActionState mActionState; + int mLastNextTime; +}; + +#endif // NPCDIALOG_H diff --git a/src/gui/npcpostdialog.cpp b/src/gui/npcpostdialog.cpp new file mode 100644 index 000000000..00bf55028 --- /dev/null +++ b/src/gui/npcpostdialog.cpp @@ -0,0 +1,128 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/npcpostdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/label.h" +#include "gui/widgets/textbox.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/scrollarea.h" + +#include "net/net.h" +#include "net/npchandler.h" + +#include "utils/gettext.h" + +NpcPostDialog::DialogList NpcPostDialog::instances; + +NpcPostDialog::NpcPostDialog(int npcId): + Window(_("NPC")), + mNpcId(npcId) +{ + setContentSize(400, 180); + + // create text field for receiver + gcn::Label *senderText = new Label(_("To:")); + senderText->setPosition(5, 5); + mSender = new TextField; + mSender->setPosition(senderText->getWidth() + 5, 5); + mSender->setWidth(65); + + // create button for sending + Button *sendButton = new Button(_("Send"), "send", this); + sendButton->setPosition(400 - sendButton->getWidth(), + 170 - sendButton->getHeight()); + Button *cancelButton = new Button(_("Cancel"), "cancel", this); + cancelButton->setPosition(sendButton->getX() + - (cancelButton->getWidth() + 2), sendButton->getY()); + + // create textfield for letter + mText = new TextBox; + mText->setHeight(400 - (mSender->getHeight() + sendButton->getHeight())); + mText->setEditable(true); + + // create scroll box for letter text + ScrollArea *scrollArea = new ScrollArea(mText); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + scrollArea->setDimension(gcn::Rectangle( + 5, mSender->getHeight() + 5, + 380, 140 - (mSender->getHeight() + sendButton->getHeight()))); + + add(senderText); + add(mSender); + add(scrollArea); + add(sendButton); + add(cancelButton); + + setLocationRelativeTo(getParent()); + + instances.push_back(this); + setVisible(true); +} + +NpcPostDialog::~NpcPostDialog() +{ + instances.remove(this); +} + +void NpcPostDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "send") + { + if (mSender->getText().empty() || mText->getText().empty()) + { + if (localChatTab) + { + localChatTab->chatLog(_("Failed to send as sender or letter " + "invalid.")); + } + } + else + { + Net::getNpcHandler()->sendLetter(mNpcId, mSender->getText(), + mText->getText()); + } + setVisible(false); + } + else if (event.getId() == "cancel") + { + setVisible(false); + } +} + +void NpcPostDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (!visible) + scheduleDelete(); +} + +void NpcPostDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + (*it)->close(); +} \ No newline at end of file diff --git a/src/gui/npcpostdialog.h b/src/gui/npcpostdialog.h new file mode 100644 index 000000000..6cc64d1e2 --- /dev/null +++ b/src/gui/npcpostdialog.h @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_NPCPOSTDIALOG_H +#define GUI_NPCPOSTDIALOG_H + +#include "gui/widgets/window.h" + +#include + +class TextBox; +class TextField; + +class NpcPostDialog : public Window, public gcn::ActionListener +{ +public: + /** + * Constructor + */ + NpcPostDialog(int npcId); + + ~NpcPostDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void setVisible(bool visible); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + +private: + typedef std::list DialogList; + static DialogList instances; + + int mNpcId; + + TextBox *mText; + TextField *mSender; +}; + +#endif diff --git a/src/gui/okdialog.cpp b/src/gui/okdialog.cpp new file mode 100644 index 000000000..9812a171f --- /dev/null +++ b/src/gui/okdialog.cpp @@ -0,0 +1,81 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/okdialog.h" + +#include "gui/gui.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/textbox.h" + +#include "utils/gettext.h" + +#include + +OkDialog::OkDialog(const std::string &title, const std::string &msg, + bool modal, bool showCenter, Window *parent): + Window(title, modal, parent) +{ + mTextBox = new TextBox; + mTextBox->setEditable(false); + mTextBox->setOpaque(false); + mTextBox->setTextWrapped(msg, 260); + + gcn::Button *okButton = new Button(_("OK"), "ok", this); + + const int numRows = mTextBox->getNumberOfRows(); + const int fontHeight = getFont()->getHeight(); + const int height = numRows * fontHeight; + int width = getFont()->getWidth(title); + + if (width < mTextBox->getMinWidth()) + width = mTextBox->getMinWidth(); + if (width < okButton->getWidth()) + width = okButton->getWidth(); + + setContentSize(mTextBox->getMinWidth() + fontHeight, height + + fontHeight + okButton->getHeight()); + mTextBox->setPosition(getPadding(), getPadding()); + + // 8 is the padding that GUIChan adds to button widgets + // (top and bottom combined) + okButton->setPosition((width - okButton->getWidth()) / 2, height + 8); + + add(mTextBox); + add(okButton); + + if (showCenter) + center(); + else + centerHorisontally(); + setVisible(true); + okButton->requestFocus(); +} + +void OkDialog::action(const gcn::ActionEvent &event) +{ + setActionEventId(event.getId()); + distributeActionEvent(); + + // Can we receive anything else anyway? + if (event.getId() == "ok") + scheduleDelete(); +} diff --git a/src/gui/okdialog.h b/src/gui/okdialog.h new file mode 100644 index 000000000..b7db20d79 --- /dev/null +++ b/src/gui/okdialog.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OK_DIALOG_H +#define OK_DIALOG_H + +#include "gui/widgets/window.h" + +#include + +class TextBox; + +/** + * An 'Ok' button dialog. + * + * \ingroup GUI + */ +class OkDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + OkDialog(const std::string &title, const std::string &msg, + bool modal = true, bool showCenter = true, + Window *parent = NULL); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + TextBox *mTextBox; +}; + +#endif diff --git a/src/gui/outfitwindow.cpp b/src/gui/outfitwindow.cpp new file mode 100644 index 000000000..770c77572 --- /dev/null +++ b/src/gui/outfitwindow.cpp @@ -0,0 +1,914 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/outfitwindow.h" + +#include "configuration.h" +#include "equipment.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "log.h" +#include "playerinfo.h" + +#include "gui/chat.h" +#include "gui/viewport.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +float OutfitWindow::mAlpha = 1.0; + +OutfitWindow::OutfitWindow(): + Window(_("Outfits")), + mBoxWidth(33), + mBoxHeight(33), + mGridWidth(4), + mGridHeight(3), + mItemClicked(false), + mItemMoved(NULL), + mItemSelected(-1), + mCurrentOutfit(0), + mAwayOutfit(0) +{ + setWindowName("Outfits"); + setResizable(true); + setCloseButton(true); + setDefaultSize(250, 400, 150, 230); + setMinWidth(145); + setMinHeight(220); + + addMouseListener(this); + + mPreviousButton = new Button(_("<"), "previous", this); + mNextButton = new Button(_(">"), "next", this); + mCurrentLabel = new Label(strprintf(_("Outfit: %d"), 1)); + mCurrentLabel->setAlignment(gcn::Graphics::CENTER); + mKeyLabel = new Label(strprintf(_("Key: %s"), + keyName(mCurrentOutfit).c_str())); + mKeyLabel->setAlignment(gcn::Graphics::CENTER); + mUnequipCheck = new CheckBox(_("Unequip first"), + serverConfig.getValueBool("OutfitUnequip0", true)); + + mAwayOutfitCheck = new CheckBox(_("Away outfit"), + serverConfig.getValue("OutfitAwayIndex", OUTFITS_COUNT - 1)); + + mUnequipCheck->setActionEventId("unequip"); + mUnequipCheck->addActionListener(this); + + mAwayOutfitCheck->setActionEventId("away"); + mAwayOutfitCheck->addActionListener(this); + + place(0, 3, mKeyLabel, 4); + place(0, 4, mPreviousButton, 1); + place(1, 4, mCurrentLabel, 2); + place(3, 4, mNextButton, 1); + place(0, 5, mUnequipCheck, 4); + place(0, 6, mAwayOutfitCheck, 4); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + layout.setColWidth(4, Layout::CENTER); + + loadWindowState(); + + load(); +} + +OutfitWindow::~OutfitWindow() +{ + save(); +} + +void OutfitWindow::load(bool oldConfig) +{ + Configuration *cfg; + if (oldConfig) + cfg = &config; + else + cfg = &serverConfig; + + memset(mItems, -1, sizeof(mItems)); + + for (int o = 0; o < OUTFITS_COUNT; o++) + { + std::string outfit = cfg->getValue("Outfit" + toString(o), "-1"); + std::string buf; + std::stringstream ss(outfit); + + std::vector tokens; + + while (ss >> buf) + tokens.push_back(atoi(buf.c_str())); + + for (int i = 0; i < static_cast(tokens.size()) + && i < OUTFIT_ITEM_COUNT; i++) + { + mItems[o][i] = tokens[i]; + } + + mItemsUnequip[o] = cfg->getValueBool("OutfitUnequip" + toString(o), + true); + } + mAwayOutfit = cfg->getValue("OutfitAwayIndex", OUTFITS_COUNT - 1); + if (mAwayOutfit >= OUTFITS_COUNT) + mAwayOutfit = OUTFITS_COUNT - 1; + + if (mAwayOutfitCheck) + mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit); +} + +void OutfitWindow::save() +{ + std::string outfitStr; + for (int o = 0; o < OUTFITS_COUNT; o++) + { + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + outfitStr += mItems[o][i] ? toString(mItems[o][i]) : toString(-1); + if (i < OUTFIT_ITEM_COUNT - 1) + outfitStr += " "; + } + serverConfig.setValue("Outfit" + toString(o), outfitStr); + serverConfig.setValue("OutfitUnequip" + toString(o), mItemsUnequip[o]); + outfitStr = ""; + } + serverConfig.setValue("OutfitAwayIndex", mAwayOutfit); +} + +void OutfitWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "next") + { + next(); + } + else if (event.getId() == "previous") + { + previous(); + } + else if (event.getId() == "unequip") + { + if (mCurrentOutfit < OUTFITS_COUNT) + mItemsUnequip[mCurrentOutfit] = mUnequipCheck->isSelected(); + } + else if (event.getId() == "away") + { + mAwayOutfit = mCurrentOutfit; + if (!mAwayOutfitCheck->isSelected()) + mAwayOutfitCheck->setSelected(true); + } +} + +void OutfitWindow::wearOutfit(int outfit, bool unwearEmpty, bool select) +{ + bool isEmpty = true; + + Item *item; + if (outfit < 0 || outfit > OUTFITS_COUNT) + return; + + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + item = PlayerInfo::getInventory()->findItem(mItems[outfit][i]); + if (item && !item->isEquipped() && item->getQuantity()) + { + if (item->isEquipment()) + { + Net::getInventoryHandler()->equipItem(item); + isEmpty = false; + } + } + } + + if ((!isEmpty || unwearEmpty) && outfit < OUTFITS_COUNT + && mItemsUnequip[outfit]) + { + unequipNotInOutfit(outfit); + } + if (select) + { + mCurrentOutfit = outfit; + showCurrentOutfit(); + } +} + +void OutfitWindow::copyOutfit(int outfit) +{ + copyOutfit(outfit, mCurrentOutfit); +} + +void OutfitWindow::copyOutfit(int src, int dst) +{ + if (src < 0 || src > OUTFITS_COUNT + || dst < 0 || dst > OUTFITS_COUNT) + { + return; + } + + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + mItems[dst][i] = mItems[src][i]; + } +} + +void OutfitWindow::draw(gcn::Graphics *graphics) +{ + Window::draw(graphics); + Graphics *g = static_cast(graphics); + + for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + { + const int itemX = 10 + ((i % mGridWidth) * mBoxWidth); + const int itemY = 25 + ((i / mGridWidth) * mBoxHeight); + + graphics->setColor(gcn::Color(0, 0, 0, 64)); + graphics->drawRectangle(gcn::Rectangle(itemX, itemY, 32, 32)); + graphics->setColor(gcn::Color(255, 255, 255, 32)); + graphics->fillRectangle(gcn::Rectangle(itemX, itemY, 32, 32)); + + if (mItems[mCurrentOutfit][i] < 0) + continue; + + bool foundItem = false; + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(mItems[mCurrentOutfit][i]); + if (item) + { + // Draw item icon. + Image* image = item->getImage(); + if (image) + { + g->drawImage(image, itemX, itemY); + foundItem = true; + } + } + } + if (!foundItem) + { + Image *image = Item::getImage(mItems[mCurrentOutfit][i]); + if (image) + g->drawImage(image, itemX, itemY); + } + } + if (mItemMoved) + { + // Draw the item image being dragged by the cursor. + Image* image = mItemMoved->getImage(); + if (image) + { + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); + + g->drawImage(image, tPosX, tPosY); + } + } +} + + +void OutfitWindow::mouseDragged(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mItemMoved && mItemClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + Window::mouseDragged(event); + return; + } + const int itemId = mItems[mCurrentOutfit][index]; + if (itemId < 0) + { + Window::mouseDragged(event); + return; + } + mMoved = false; + event.consume(); + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(itemId); + if (item) + mItemMoved = item; + else + mItemMoved = 0; + mItems[mCurrentOutfit][index] = -1; + } + } + if (mItemMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } + Window::mouseDragged(event); +} + +void OutfitWindow::mousePressed(gcn::MouseEvent &event) +{ + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + if (event.getButton() == gcn::MouseEvent::RIGHT && viewport) + viewport->showOutfitsPopup(); + Window::mousePressed(event); + return; + } + mMoved = false; + event.consume(); + + // Stores the selected item if there is one. + if (isItemSelected()) + { + mItems[mCurrentOutfit][index] = mItemSelected; + mItemSelected = -1; + } + else if (mItems[mCurrentOutfit][index]) + { + mItemClicked = true; + } + Window::mousePressed(event); +} + +void OutfitWindow::mouseReleased(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (isItemSelected()) + mItemSelected = -1; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + mItemMoved = NULL; + Window::mouseReleased(event); + return; + } + mMoved = false; + event.consume(); + if (mItemMoved) + { + mItems[mCurrentOutfit][index] = mItemMoved->getId(); + mItemMoved = NULL; + } + if (mItemClicked) + mItemClicked = false; + } + Window::mouseReleased(event); +} + +int OutfitWindow::getIndexFromGrid(int pointX, int pointY) const +{ + const gcn::Rectangle tRect = gcn::Rectangle( + 10, 25, mGridWidth * mBoxWidth, mGridHeight * mBoxHeight); + if (!tRect.isPointInRect(pointX, pointY)) + return -1; + const int index = (((pointY - 25) / mBoxHeight) * mGridWidth) + + (pointX - 10) / mBoxWidth; + if (index >= OUTFIT_ITEM_COUNT || index < 0) + return -1; + return index; +} + +void OutfitWindow::unequipNotInOutfit(int outfit) +{ + // here we think that outfit is correct index + + Inventory *inventory = PlayerInfo::getInventory(); + if (!inventory) + return; + + for (unsigned i = 0; i < inventory->getSize(); i++) + { + if (inventory->getItem(i) && inventory->getItem(i)->isEquipped()) + { + bool found = false; + for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++) + { + if (inventory->getItem(i)->getId() == mItems[outfit][f]) + { + found = true; + break; + } + } + if (!found) + Net::getInventoryHandler()->unequipItem(inventory->getItem(i)); + } + } +} + +int OutfitWindow::keyToNumber(SDLKey key) +{ + int outfitNum = -1; + switch (key) + { + case SDLK_1: + case SDLK_2: + case SDLK_3: + case SDLK_4: + case SDLK_5: + case SDLK_6: + case SDLK_7: + case SDLK_8: + case SDLK_9: + outfitNum = key - SDLK_1; + break; + + case SDLK_0: + outfitNum = 9; + break; + + case SDLK_MINUS: + outfitNum = 10; + break; + + case SDLK_EQUALS: + outfitNum = 11; + break; + + case SDLK_BACKSPACE: + outfitNum = 12; + break; + + case SDLK_INSERT: + outfitNum = 13; + break; + + case SDLK_HOME: + outfitNum = 14; + break; + + case SDLK_q: + outfitNum = 15; + break; + + case SDLK_w: + outfitNum = 16; + break; + + case SDLK_e: + outfitNum = 17; + break; + + case SDLK_r: + outfitNum = 18; + break; + + case SDLK_t: + outfitNum = 19; + break; + + case SDLK_y: + outfitNum = 20; + break; + + case SDLK_u: + outfitNum = 21; + break; + + case SDLK_i: + outfitNum = 22; + break; + + case SDLK_o: + outfitNum = 23; + break; + + case SDLK_p: + outfitNum = 24; + break; + + case SDLK_LEFTBRACKET: + outfitNum = 25; + break; + + case SDLK_RIGHTBRACKET: + outfitNum = 26; + break; + + case SDLK_BACKSLASH: + outfitNum = 27; + break; + + case SDLK_a: + outfitNum = 28; + break; + + case SDLK_s: + outfitNum = 29; + break; + + case SDLK_d: + outfitNum = 30; + break; + + case SDLK_f: + outfitNum = 31; + break; + + case SDLK_g: + outfitNum = 32; + break; + + case SDLK_h: + outfitNum = 33; + break; + + case SDLK_j: + outfitNum = 34; + break; + + case SDLK_k: + outfitNum = 35; + break; + + case SDLK_l: + outfitNum = 36; + break; + + case SDLK_SEMICOLON: + outfitNum = 37; + break; + + case SDLK_QUOTE: + outfitNum = 38; + break; + + case SDLK_z: + outfitNum = 39; + break; + + + case SDLK_x: + outfitNum = 40; + break; + + case SDLK_c: + outfitNum = 41; + break; + + case SDLK_v: + outfitNum = 42; + break; + + case SDLK_b: + outfitNum = 43; + break; + + case SDLK_n: + outfitNum = 44; + break; + + case SDLK_m: + outfitNum = 45; + break; + + case SDLK_COMMA: + outfitNum = 46; + break; + + case SDLK_PERIOD: + outfitNum = 47; + break; + + case SDLK_SLASH: + outfitNum = 48; + break; + + default: + break; + } + + return outfitNum; +} + +SDLKey OutfitWindow::numberToKey(int number) +{ + SDLKey key = SDLK_UNKNOWN; + switch (number) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + key = static_cast( + static_cast(SDLK_1) + number); + break; + + case 9: + key = SDLK_0; + break; + + case 10: + key = SDLK_MINUS; + break; + + case 11: + key = SDLK_EQUALS; + break; + + case 12: + key = SDLK_BACKSPACE; + break; + + case 13: + key = SDLK_INSERT; + break; + + case 14: + key = SDLK_HOME; + break; + + case 15: + key = SDLK_q; + break; + + case 16: + key = SDLK_w; + break; + + case 17: + key = SDLK_e; + break; + + case 18: + key = SDLK_r; + break; + + case 19: + key = SDLK_t; + break; + + case 20: + key = SDLK_y; + break; + + case 21: + key = SDLK_u; + break; + + case 22: + key = SDLK_i; + break; + + case 23: + key = SDLK_o; + break; + + case 24: + key = SDLK_p; + break; + + case 25: + key = SDLK_LEFTBRACKET; + break; + + case 26: + key = SDLK_RIGHTBRACKET; + break; + + case 27: + key = SDLK_BACKSLASH; + break; + + case 28: + key = SDLK_a; + break; + + case 29: + key = SDLK_s; + break; + + case 30: + key = SDLK_d; + break; + + case 31: + key = SDLK_f; + break; + + case 32: + key = SDLK_g; + break; + + case 33: + key = SDLK_h; + break; + + case 34: + key = SDLK_j; + break; + + case 35: + key = SDLK_k; + break; + + case 36: + key = SDLK_l; + break; + + case 37: + key = SDLK_SEMICOLON; + break; + + case 38: + key = SDLK_QUOTE; + break; + + case 39: + key = SDLK_z; + break; + + + case 40: + key = SDLK_x; + break; + + case 41: + key = SDLK_c; + break; + + case 42: + key = SDLK_v; + break; + + case 43: + key = SDLK_b; + break; + + case 44: + key = SDLK_n; + break; + + case 45: + key = SDLK_m; + break; + + case 46: + key = SDLK_COMMA; + break; + + case 47: + key = SDLK_PERIOD; + break; + + case 48: + key = SDLK_SLASH; + break; + + default: + break; + } + + return key; +} + +std::string OutfitWindow::keyName(int number) +{ + return SDL_GetKeyName(numberToKey(number)); +} + +void OutfitWindow::next() +{ + if (mCurrentOutfit < (OUTFITS_COUNT - 1)) + mCurrentOutfit++; + else + mCurrentOutfit = 0; + showCurrentOutfit(); +} + +void OutfitWindow::previous() +{ + if (mCurrentOutfit > 0) + mCurrentOutfit--; + else + mCurrentOutfit = OUTFITS_COUNT - 1; + showCurrentOutfit(); +} + +void OutfitWindow::showCurrentOutfit() +{ + mCurrentLabel->setCaption(strprintf(_("Outfit: %d"), mCurrentOutfit + 1)); + mUnequipCheck->setSelected(mItemsUnequip[mCurrentOutfit]); + mKeyLabel->setCaption(strprintf(_("Key: %s"), + keyName(mCurrentOutfit).c_str())); + mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit); +} + +void OutfitWindow::wearNextOutfit(bool all) +{ + bool fromStart = false; + next(); + if (!all && mCurrentOutfit < OUTFITS_COUNT) + { + while (!mItemsUnequip[mCurrentOutfit]) + { + next(); + if (mCurrentOutfit == 0) + { + if (!fromStart) + fromStart = true; + else + return; + } + } + } + wearOutfit(mCurrentOutfit); +} + +void OutfitWindow::wearPreviousOutfit(bool all) +{ + bool fromStart = false; + previous(); + if (!all && mCurrentOutfit < OUTFITS_COUNT) + { + while (!mItemsUnequip[mCurrentOutfit]) + { + previous(); + if (mCurrentOutfit == 0) + { + if (!fromStart) + fromStart = true; + else + return; + } + } + } + wearOutfit(mCurrentOutfit); +} + +void OutfitWindow::copyFromEquiped() +{ + copyFromEquiped(mCurrentOutfit); +} + +void OutfitWindow::copyFromEquiped(int dst) +{ + Inventory *inventory = PlayerInfo::getInventory(); + if (!inventory) + return; + + int outfitCell = 0; + + for (unsigned i = 0; i < inventory->getSize(); i++) + { + if (inventory->getItem(i) && inventory->getItem(i)->isEquipped()) + { + mItems[dst][outfitCell++] = inventory->getItem(i)->getId(); + if (outfitCell > 8) + break; + } + } +} + +void OutfitWindow::wearAwayOutfit() +{ + copyFromEquiped(OUTFITS_COUNT); + wearOutfit(mAwayOutfit, false); +} + +void OutfitWindow::unwearAwayOutfit() +{ + wearOutfit(OUTFITS_COUNT); +} diff --git a/src/gui/outfitwindow.h b/src/gui/outfitwindow.h new file mode 100644 index 000000000..684d1c6f1 --- /dev/null +++ b/src/gui/outfitwindow.h @@ -0,0 +1,135 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OUTFITWINDOW_H +#define OUTFITWINDOW_H + +#include "gui/widgets/window.h" + +#include +#include + +#define OUTFITS_COUNT 100 +#define OUTFIT_ITEM_COUNT 12 + +class Button; +class CheckBox; +class Item; +class Label; + +class OutfitWindow : public Window, gcn::ActionListener +{ + public: + /** + * Constructor. + */ + OutfitWindow(); + + /** + * Destructor. + */ + ~OutfitWindow(); + + void action(const gcn::ActionEvent &event); + + void draw(gcn::Graphics *graphics); + + void mousePressed(gcn::MouseEvent &event); + + void mouseDragged(gcn::MouseEvent &event); + + void mouseReleased(gcn::MouseEvent &event); + + void load(bool oldConfig = false); + + void setItemSelected(int itemId) + { mItemSelected = itemId; } + + bool isItemSelected() + { return mItemSelected > 0; } + + void wearOutfit(int outfit, bool unwearEmpty = true, + bool select = false); + + void copyOutfit(int outfit); + + void copyOutfit(int src, int dst); + + void copyFromEquiped(); + + void copyFromEquiped(int dst); + + void unequipNotInOutfit(int outfit); + + int keyToNumber(SDLKey key); + + SDLKey numberToKey(int number); + + void next(); + + void previous(); + + void wearNextOutfit(bool all = false); + + void wearPreviousOutfit(bool all = false); + + void wearAwayOutfit(); + + void unwearAwayOutfit(); + + void showCurrentOutfit(); + + std::string keyName(int number); + + private: + Button *mPreviousButton; + Button *mNextButton; + Label *mCurrentLabel; + CheckBox *mUnequipCheck; + CheckBox *mAwayOutfitCheck; + Label *mKeyLabel; + + int getIndexFromGrid(int pointX, int pointY) const; + + int mBoxWidth; + int mBoxHeight; + int mCursorPosX, mCursorPosY; + int mGridWidth, mGridHeight; + bool mItemClicked; + Item *mItemMoved; + + void save(); + + int mItems[OUTFITS_COUNT + 1][OUTFIT_ITEM_COUNT]; + bool mItemsUnequip[OUTFITS_COUNT]; + int mItemSelected; + + int mCurrentOutfit; + int mAwayOutfit; + + Image *mBackgroundImg; + + static float mAlpha; +}; + +extern OutfitWindow *outfitWindow; + +#endif diff --git a/src/gui/palette.cpp b/src/gui/palette.cpp new file mode 100644 index 000000000..72c490912 --- /dev/null +++ b/src/gui/palette.cpp @@ -0,0 +1,274 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "palette.h" + +#include "configuration.h" +#include "client.h" + +#include "gui/gui.h" + +#include "utils/gettext.h" +#include "utils/mathutils.h" +#include "utils/stringutils.h" + +#include + +const gcn::Color Palette::BLACK = gcn::Color(0, 0, 0); +Palette::Palettes Palette::mInstances; + +const gcn::Color Palette::RAINBOW_COLORS[7] = { + gcn::Color(255, 0, 0), + gcn::Color(255, 153, 0), + gcn::Color(255, 255, 0), + gcn::Color(0, 153, 0), + gcn::Color(0, 204, 204), + gcn::Color(51, 0, 153), + gcn::Color(153, 0, 153) +}; +/** Number of Elemets of RAINBOW_COLORS */ +const int Palette::RAINBOW_COLOR_COUNT = 7; + +Palette::Palette(int size) : + mRainbowTime(tick_time), + mColors(Colors(size)) +{ + mInstances.insert(this); +} + +Palette::~Palette() +{ + mInstances.erase(this); +} + +const gcn::Color& Palette::getColor(char c, bool &valid) +{ + for (Colors::const_iterator col = mColors.begin(), + colEnd = mColors.end(); col != colEnd; ++col) + { + if (col->ch == c) + { + valid = true; + return col->color; + } + } + valid = false; + return BLACK; +} + +void Palette::advanceGradients() +{ + Palettes::iterator it = mInstances.begin(); + Palettes::iterator it_end = mInstances.end(); + + for (; it != it_end; it++) + (*it)->advanceGradient(); +} + +void Palette::advanceGradient() +{ + if (get_elapsed_time(mRainbowTime) > 5) + { + int pos, colIndex, colVal, delay, numOfColors; + // For slower systems, advance can be greater than one (advance > 1 + // skips advance-1 steps). Should make gradient look the same + // independent of the framerate. + int advance = get_elapsed_time(mRainbowTime) / 5; + double startColVal, destColVal; + + for (size_t i = 0; i < mGradVector.size(); i++) + { + if (!mGradVector[i]) + continue; + + delay = mGradVector[i]->delay; + + if (mGradVector[i]->grad == PULSE) + delay = delay / 20; + + numOfColors = (mGradVector[i]->grad == SPECTRUM ? 6 : + mGradVector[i]->grad == PULSE ? 127 : + RAINBOW_COLOR_COUNT); + + mGradVector[i]->gradientIndex = + (mGradVector[i]->gradientIndex + advance) % + (delay * numOfColors); + + pos = mGradVector[i]->gradientIndex % delay; + if (delay) + colIndex = mGradVector[i]->gradientIndex / delay; + else + colIndex = mGradVector[i]->gradientIndex; + + if (mGradVector[i]->grad == PULSE) + { + colVal = static_cast(255.0 * + sin(M_PI * colIndex / numOfColors)); + + const gcn::Color &col = mGradVector[i]->testColor; + + mGradVector[i]->color.r = + ((colVal * col.r) / 255) % (col.r + 1); + mGradVector[i]->color.g = + ((colVal * col.g) / 255) % (col.g + 1); + mGradVector[i]->color.b = + ((colVal * col.b) / 255) % (col.b + 1); + } + if (mGradVector[i]->grad == SPECTRUM) + { + if (colIndex % 2) + { // falling curve + if (delay) + { + colVal = static_cast(255.0 * + (cos(M_PI * pos / delay) + 1) / 2); + } + else + { + colVal = static_cast(255.0 * + (cos(M_PI * pos) + 1) / 2); + } + } + else + { // ascending curve + if (delay) + { + colVal = static_cast(255.0 * (cos(M_PI * + (delay - pos) / delay) + 1) / 2); + } + else + { + colVal = static_cast(255.0 * (cos(M_PI * + (delay - pos)) + 1) / 2); + } + } + + mGradVector[i]->color.r = + (colIndex == 0 || colIndex == 5) ? 255 : + (colIndex == 1 || colIndex == 4) ? colVal : 0; + mGradVector[i]->color.g = + (colIndex == 1 || colIndex == 2) ? 255 : + (colIndex == 0 || colIndex == 3) ? colVal : 0; + mGradVector[i]->color.b = + (colIndex == 3 || colIndex == 4) ? 255 : + (colIndex == 2 || colIndex == 5) ? colVal : 0; + } + else if (mGradVector[i]->grad == RAINBOW) + { + const gcn::Color &startCol = RAINBOW_COLORS[colIndex]; + const gcn::Color &destCol = + RAINBOW_COLORS[(colIndex + 1) % numOfColors]; + + if (delay) + startColVal = (cos(M_PI * pos / delay) + 1) / 2; + else + startColVal = 0; + + destColVal = 1 - startColVal; + + mGradVector[i]->color.r = static_cast(startColVal + * startCol.r + destColVal * destCol.r); + + mGradVector[i]->color.g = static_cast(startColVal + * startCol.g + destColVal * destCol.g); + + mGradVector[i]->color.b = static_cast(startColVal + * startCol.b + destColVal * destCol.b); + } + } + + if (advance) + mRainbowTime = tick_time; + } +} + +/* +gcn::Color Palette::produceHPColor(int hp, int maxHp, int alpha) +{ + float r1 = 255; + float g1 = 255; + float b1 = 255; + float r2 = 255; + float g2 = 255; + float b2 = 255; + + float weight = 1.0f; + + int thresholdLevel = maxHp / 4; + int thresholdProgress = hp % thresholdLevel; + + if (thresholdLevel) + weight = 1 - ((float)thresholdProgress) / ((float)thresholdLevel); + else + weight = 0; + + if (hp < (thresholdLevel)) + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_ONE_HALF); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_ONE_QUARTER); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + else if (hp < (thresholdLevel*2)) + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_THREE_QUARTERS); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_ONE_HALF); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + else if (hp < thresholdLevel*3) + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_FULL); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_THREE_QUARTERS); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + else + { + gcn::Color color1 = guiPalette->getColor(Palette::HPBAR_FULL); + gcn::Color color2 = guiPalette->getColor(Palette::HPBAR_FULL); + r1 = color1.r; r2 = color2.r; + g1 = color1.g; g2 = color2.g; + b1 = color1.b; b2 = color2.b; + } + + // Safety checks + if (weight > 1.0f) weight = 1.0f; + if (weight < 0.0f) weight = 0.0f; + + // Do the color blend + r1 = (int) weightedAverage(r1, r2,weight); + g1 = (int) weightedAverage(g1, g2, weight); + b1 = (int) weightedAverage(b1, b2, weight); + + // More safety checks + if (r1 > 255) r1 = 255; + if (g1 > 255) g1 = 255; + if (b1 > 255) b1 = 255; + + return gcn::Color(r1, g1, b1, alpha); +} +*/ + diff --git a/src/gui/palette.h b/src/gui/palette.h new file mode 100644 index 000000000..0c8af1d7f --- /dev/null +++ b/src/gui/palette.h @@ -0,0 +1,191 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PALETTE_H +#define PALETTE_H + +#include "log.h" +#include "utils/stringutils.h" + +#include + +#include +#include +#include +#include + +// Default Gradient Delay +#define GRADIENT_DELAY 40 + +/** + * Class controlling the game's color palette. + */ +class Palette +{ + public: + /** Black Color Constant */ + static const gcn::Color BLACK; + + /** Colors can be static or can alter over time. */ + enum GradientType + { + STATIC = 0, + PULSE, + SPECTRUM, + RAINBOW + }; + + /** + * Returns the color associated with a character, if it exists. Returns + * Palette::BLACK if the character is not found. + * + * @param c character requested + * @param valid indicate whether character is known + * + * @return the requested color or Palette::BLACK + */ + const gcn::Color &getColor(char c, bool &valid); + + /** + * Gets the color associated with the type. Sets the alpha channel + * before returning. + * + * @param type the color type requested + * @param alpha alpha channel to use + * + * @return the requested color + */ + inline const gcn::Color &getColor(int type, int alpha = 255) + { + gcn::Color* col = &mColors[type].color; + col->a = alpha; + return *col; + } + + inline const gcn::Color &getColorWithAlpha(int type) + { + gcn::Color* col = &mColors[type].color; + col->a = mColors[type].delay; + return *col; + } + + /** + * Gets the GradientType associated with the specified type. + * + * @param type the color type of the color + * + * @return the gradient type of the color with the given index + */ + inline GradientType getGradientType(int type) const + { + return mColors[type].grad; + } + + /** + * Get the character used by the specified color. + * + * @param type the color type of the color + * + * @return the color char of the color with the given index + */ + inline char getColorChar(int type) const + { + return mColors[type].ch; + } + + /** + * Gets the gradient delay for the specified type. + * + * @param type the color type of the color + * + * @return the gradient delay of the color with the given index + */ + inline int getGradientDelay(int type) const + { return mColors[type].delay; } + + /** + * Updates all colors, that are non-static. + */ + static void advanceGradients(); + + gcn::Color static produceHPColor(int hp, int maxHp, int alpha = 255); + + protected: + /** Colors used for the rainbow gradient */ + static const gcn::Color RAINBOW_COLORS[]; + static const int RAINBOW_COLOR_COUNT; + + /** Time tick, that gradient-type colors were updated the last time. */ + int mRainbowTime; + + typedef std::set Palettes; + static Palettes mInstances; + + /** + * Constructor + */ + Palette(int size); + + /** + * Destructor + */ + ~Palette(); + + void advanceGradient(); + + struct ColorElem + { + int type; + gcn::Color color; + gcn::Color testColor; + gcn::Color committedColor; + std::string text; + char ch; + GradientType grad; + GradientType committedGrad; + int gradientIndex; + int delay; + int committedDelay; + + void set(int type, gcn::Color& color, GradientType grad, int delay) + { + ColorElem::type = type; + ColorElem::color = color; + ColorElem::testColor = color; + ColorElem::grad = grad; + ColorElem::delay = delay; + ColorElem::gradientIndex = rand(); + } + + inline int getRGB() const + { + return (committedColor.r << 16) | (committedColor.g << 8) | + committedColor.b; + } + }; + typedef std::vector Colors; + /** Vector containing the colors. */ + Colors mColors; + std::vector mGradVector; +}; + +#endif diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp new file mode 100644 index 000000000..662b184b9 --- /dev/null +++ b/src/gui/popupmenu.cpp @@ -0,0 +1,1286 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/popupmenu.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "dropshortcut.h" +#include "guild.h" +#include "flooritem.h" +#include "graphics.h" +#include "item.h" +#include "itemshortcut.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" +#include "party.h" +#include "playerinfo.h" +#include "playerrelations.h" +#include "spellmanager.h" + +#include "gui/buy.h" +#include "gui/chat.h" +#include "gui/inventorywindow.h" +#include "gui/itemamount.h" +#include "gui/outfitwindow.h" +#include "gui/sell.h" +#include "gui/socialwindow.h" +#include "gui/textcommandeditor.h" +#include "gui/textdialog.h" +#include "gui/trade.h" +#include "gui/viewport.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/whispertab.h" + +#include "net/adminhandler.h" +#include "net/beinghandler.h" +#include "net/buysellhandler.h" +#include "net/guildhandler.h" +#include "net/inventoryhandler.h" +#include "net/net.h" +#include "net/npchandler.h" +#include "net/partyhandler.h" +#include "gui/shortcutwindow.h" +#include "net/tradehandler.h" + +#include "resources/itemdb.h" +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +std::string tradePartnerName(""); + +PopupMenu::PopupMenu(): + Popup("PopupMenu"), + mBeingId(0), + mFloorItem(0), + mItem(0), + mItemId(0), + mMapItem(0), + mTab(0), + mSpell(0), + mDialog(0), + mNick("") +{ + mBrowserBox = new BrowserBox; + mBrowserBox->setPosition(4, 4); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mBrowserBox->setOpaque(false); + mBrowserBox->setLinkHandler(this); + add(mBrowserBox); +} + +void PopupMenu::showPopup(int x, int y, Being *being) +{ + if (!being || !player_node) + return; + + mBeingId = being->getId(); + mNick = being->getName(); + mBrowserBox->clearRows(); + + const std::string &name = mNick; + + mBrowserBox->addRow(name); + + switch (being->getType()) + { + case ActorSprite::PLAYER: + { + // Players can be traded with. + mBrowserBox->addRow(_("@@trade|Trade@@")); + // TRANSLATORS: Attacking a player. + mBrowserBox->addRow(_("@@attack|Attack@@")); + // TRANSLATORS: Whispering a player. + mBrowserBox->addRow(_("@@whisper|Whisper@@")); + + mBrowserBox->addRow("##3---"); + + mBrowserBox->addRow(_("@@heal|Heal@@")); + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) + { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow(_("@@friend|Befriend@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::FRIEND: + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::ERASED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + break; + + default: + break; + } + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@follow|Follow@@")); + mBrowserBox->addRow(_("@@imitation|Imitation@@")); + + if (player_node->isInParty()) + { + if (player_node->getParty()) + { + if (player_node->getParty()->getName() + != being->getPartyName()) + { + mBrowserBox->addRow( + _("@@party|Invite to party@@")); + } + else + { + mBrowserBox->addRow( + _("@@kick party|Kick from party@@")); + } + mBrowserBox->addRow("##3---"); + } + } + + Guild *guild1 = being->getGuild(); + Guild *guild2 = player_node->getGuild(); + if (guild2) + { + if (guild1) + { + if (guild1->getId() == guild2->getId()) + { + mBrowserBox->addRow( + _("@@guild-kick|Kick from guild@@")); + mBrowserBox->addRow( + _("@@guild-pos|Change pos in guild >@@")); + } + } + else + { + mBrowserBox->addRow(_("@@guild|Invite to guild@@")); + } + } + + if (player_node->isGM()) + { + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@admin-kick|Kick player@@")); + } + mBrowserBox->addRow(_("@@nuke|Nuke@@")); + mBrowserBox->addRow(_("@@move|Move@@")); + mBrowserBox->addRow(_("@@undress|Undress@@")); + + if (player_relations.getDefault() & PlayerRelation::TRADE) + { + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + } + } + break; + + case ActorSprite::NPC: + // NPCs can be talked to (single option, candidate for removal + // unless more options would be added) + mBrowserBox->addRow(_("@@talk|Talk@@")); + + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@move|Move@@")); + break; + + case ActorSprite::MONSTER: + { + // Monsters can be attacked + mBrowserBox->addRow(_("@@attack|Attack@@")); + + if (player_node->isGM()) + mBrowserBox->addRow(_("@@admin-kick|Kick@@")); + } + break; + + default: + /* Other beings aren't interesting... */ + return; + } + mBrowserBox->addRow(_("@@name|Add name to chat@@")); + + //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showPopup(int x, int y, std::list &beings) +{ + mBrowserBox->clearRows(); + mBrowserBox->addRow("Players"); + std::list::iterator it, it_end; + for (it = beings.begin(), it_end = beings.end(); it != it_end; it++) + { + Being *being = *it; + if (!being->getName().empty()) + { + mBrowserBox->addRow(strprintf(_("@@player_%u|%s >@@"), + being->getId(), being->getName().c_str())); + } + } + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + showPopup(x, y); +} + +void PopupMenu::showPlayerPopup(int x, int y, std::string nick) +{ + if (nick.empty() || !player_node) + return; + + mNick = nick; + mBeingId = 0; + mBrowserBox->clearRows(); + + const std::string &name = mNick; + + mBrowserBox->addRow(name); + + mBrowserBox->addRow(_("@@whisper|Whisper@@")); + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) + { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow(_("@@friend|Befriend@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::FRIEND: + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::ERASED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + break; + + default: + break; + } + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@follow|Follow@@")); + mBrowserBox->addRow(_("@@imitation|Imitation@@")); + + Guild *guild2 = player_node->getGuild(); + if (guild2) + { + if (guild2->getMember(mNick)) + { + mBrowserBox->addRow(_("@@guild-kick|Kick from guild@@")); + mBrowserBox->addRow(_("@@guild-pos|Change pos in guild >@@")); + } + else + { + mBrowserBox->addRow(_("@@guild|Invite to guild@@")); + } + } + + mBrowserBox->addRow("##3---"); + if (player_relations.getDefault() & PlayerRelation::TRADE) + { + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + } + + mBrowserBox->addRow(_("@@name|Add name to chat@@")); + + //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); + +} + +void PopupMenu::showPopup(int x, int y, FloorItem *floorItem) +{ + if (!floorItem) + return; + + mFloorItem = floorItem; + ItemInfo info = floorItem->getInfo(); + mBrowserBox->clearRows(); + + // Floor item can be picked up (single option, candidate for removal) + std::string name = info.getName(); + mBrowserBox->addRow(name); + mBrowserBox->addRow(_("@@pickup|Pick up@@")); + mBrowserBox->addRow(_("@@chat|Add to chat@@")); + + //mBrowserBox->addRow(strprintf("@@look|%s@@", _("Look To"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showPopup(int x, int y, MapItem *mapItem) +{ + if (!mapItem) + return; + + mMapItem = mapItem; + + mBrowserBox->clearRows(); + + mBrowserBox->addRow(_("Map Item")); + mBrowserBox->addRow(_("@@rename map|Rename@@")); + mBrowserBox->addRow(_("@@remove map|Remove@@")); + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showOutfitsPopup(int x, int y) +{ + mBrowserBox->clearRows(); + + mBrowserBox->addRow(_("Outfits")); + mBrowserBox->addRow(_("@@load old outfits|Load old outfits@@")); + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showSpellPopup(int x, int y, TextCommand *cmd) +{ + if (!cmd) + return; + + mBrowserBox->clearRows(); + + mSpell = cmd; + mBrowserBox->addRow(_("Spells")); + mBrowserBox->addRow(_("@@load old spells|Load old spells@@")); + mBrowserBox->addRow(_("@@edit spell|Edit spell@@")); + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@cancel|Cancel@@")); + + showPopup(x, y); +} + +void PopupMenu::showChatPopup(int x, int y, ChatTab *tab) +{ + if (!tab || !actorSpriteManager || !player_node) + return; + + mTab = tab; + + mBrowserBox->clearRows(); + + if (tab->getType() == ChatTab::TAB_WHISPER) + mBrowserBox->addRow(_("@@chat close|Close@@")); + + mBrowserBox->addRow(strprintf("@@chat clear|%s@@", _("Clear"))); + mBrowserBox->addRow("##3---"); + + if (tab->getAllowHighlight()) + { + mBrowserBox->addRow(strprintf("@@disable highlight|%s@@", + _("Disable highlight"))); + mBrowserBox->addRow("##3---"); + } + else + { + mBrowserBox->addRow(strprintf("@@enable highlight|%s@@", + _("Enable highlight"))); + mBrowserBox->addRow("##3---"); + } + + if (tab->getType() == ChatTab::TAB_PARTY) + { + mBrowserBox->addRow(_("@@leave party|Leave@@")); + mBrowserBox->addRow("##3---"); + } + + if (tab->getType() == ChatTab::TAB_WHISPER) + { + WhisperTab *wTab = static_cast(tab); + std::string name = wTab->getNick(); + + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + + if (being) + { + mBeingId = being->getId(); + mNick = being->getName(); + + mBrowserBox->addRow(_("@@trade|Trade@@")); + mBrowserBox->addRow(_("@@attack|Attack@@")); + + mBrowserBox->addRow("##3---"); + + mBrowserBox->addRow(_("@@heal|Heal@@")); + mBrowserBox->addRow("##3---"); + + switch (player_relations.getRelation(name)) + { + case PlayerRelation::NEUTRAL: + mBrowserBox->addRow(_("@@friend|Befriend@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::FRIEND: + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::DISREGARDED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::IGNORED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@erase|Erase@@")); + break; + + case PlayerRelation::ERASED: + mBrowserBox->addRow(_("@@unignore|Unignore@@")); + mBrowserBox->addRow(_("@@disregard|Disregard@@")); + mBrowserBox->addRow(_("@@ignore|Completely ignore@@")); + break; + + default: + break; + } + + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@follow|Follow@@")); + mBrowserBox->addRow(_("@@imitation|Imitation@@")); + mBrowserBox->addRow(_("@@move|Move@@")); + mBrowserBox->addRow(_("@@undress|Undress@@")); + + if (player_relations.getDefault() & PlayerRelation::TRADE) + { + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(_("@@buy|Buy@@")); + mBrowserBox->addRow(_("@@sell|Sell@@")); + } + + mBrowserBox->addRow("##3---"); + + if (player_node->isInParty()) + { + if (player_node->getParty()) + { + if (!player_node->getParty()->isMember(wTab->getNick())) + { + mBrowserBox->addRow(_("@@party|Invite to party@@")); + } + else + { + mBrowserBox->addRow( + _("@@kick party|Kick from party@@")); + } + mBrowserBox->addRow("##3---"); + } + } + Guild *guild1 = being->getGuild(); + Guild *guild2 = player_node->getGuild(); + if (guild2) + { + if (guild1) + { + if (guild1->getId() == guild2->getId()) + { + mBrowserBox->addRow( + _("@@guild-kick|Kick from guild@@")); + mBrowserBox->addRow( + _("@@guild-pos|Change pos in guild >@@")); + } + } + else + { + mBrowserBox->addRow(_("@@guild|Invite to guild@@")); + } + } + } + } + mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@"))); + + showPopup(x, y); +} + +void PopupMenu::showChangePos(int x, int y) +{ + mBrowserBox->clearRows(); + mBrowserBox->addRow(_("Change guild position")); + + if (!player_node) + return; + + const Guild *guild = player_node->getGuild(); + if (guild) + { + PositionsMap map = guild->getPositions(); + PositionsMap::iterator itr = map.begin(); + PositionsMap::iterator itr_end = map.end(); + for (; itr != itr_end; ++itr) + { + mBrowserBox->addRow(strprintf(_("@@guild-pos-%d|%s@@"), + itr->first, itr->second.c_str())); + } + mBrowserBox->addRow(strprintf(_("@@cancel|Cancel@@"))); + + showPopup(x, y); + } + else + { + mBeingId = 0; + mFloorItem = 0; + mItem = 0; + mMapItem = 0; + mNick = ""; + setVisible(false); + } +} + +void PopupMenu::handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_) +{ + if (!actorSpriteManager) + return; + + Being *being = actorSpriteManager->findBeing(mBeingId); + + // Talk To action + if (link == "talk" && being && being->canTalk()) + { + being->talkTo(); + } + // Trade action + else if (link == "trade" && being && + being->getType() == ActorSprite::PLAYER) + { + Net::getTradeHandler()->request(being); + tradePartnerName = being->getName(); + if (tradeWindow) + tradeWindow->clear(); + } + else if (link == "buy" && being && mBeingId != 0) + { + if (being->getType() == Being::NPC) + Net::getNpcHandler()->buy(mBeingId); + else if (being->getType() == Being::PLAYER) + Net::getBuySellHandler()->requestSellList(being->getName()); + } + else if (link == "buy" && !mNick.empty()) + { + Net::getBuySellHandler()->requestSellList(mNick); + } + else if (link == "sell" && being && mBeingId != 0) + { + if (being->getType() == Being::NPC) + Net::getNpcHandler()->sell(mBeingId); + else if (being->getType() == Being::PLAYER) + Net::getBuySellHandler()->requestBuyList(being->getName()); + } + else if (link == "sell" && !mNick.empty()) + { + Net::getBuySellHandler()->requestBuyList(mNick); + } + // Attack action + else if (link == "attack" && being) + { + if (player_node) + player_node->attack(being, true); + } + else if (link == "heal" && being && being->getType() != Being::MONSTER) + { + actorSpriteManager->heal(player_node, being); + } + else if (link == "unignore" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), + PlayerRelation::NEUTRAL); + } + else if (link == "unignore" && !mNick.empty()) + { + player_relations.setRelation(mNick, + PlayerRelation::NEUTRAL); + } + else if (link == "ignore" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), + PlayerRelation::IGNORED); + } + else if (link == "ignore" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::IGNORED); + } + else if (link == "erase" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), PlayerRelation::ERASED); + } + else if (link == "erase" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::ERASED); + } + else if (link == "disregard" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), + PlayerRelation::DISREGARDED); + } + else if (link == "disregard" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::DISREGARDED); + } + else if (link == "friend" && being && + being->getType() == ActorSprite::PLAYER) + { + player_relations.setRelation(being->getName(), PlayerRelation::FRIEND); + } + else if (link == "friend" && !mNick.empty()) + { + player_relations.setRelation(mNick, PlayerRelation::FRIEND); + } + // Guild action + else if (link == "guild" && !mNick.empty()) + { + if (player_node) + { + const Guild *guild = player_node->getGuild(); + if (guild) + Net::getGuildHandler()->invite(guild->getId(), mNick); + } + } + else if (link == "nuke" && being) + { + actorSpriteManager->addBlock(being->getId()); + actorSpriteManager->destroy(being); + } + // Follow Player action + else if (link == "follow" && !mNick.empty()) + { + if (player_node) + player_node->setFollow(mNick); + } + else if (link == "imitation" && !mNick.empty()) + { + if (player_node) + player_node->setImitate(mNick); + } + // Pick Up Floor Item action + else if ((link == "pickup") && mFloorItem) + { + if (player_node) + player_node->pickUp(mFloorItem); + } + // Look To action + else if (link == "look") + { + } + else if (link == "use" && mItem) + { + if (mItem->isEquipment()) + { + if (mItem->isEquipped()) + Net::getInventoryHandler()->unequipItem(mItem); + else + Net::getInventoryHandler()->equipItem(mItem); + } + else + { + Net::getInventoryHandler()->useItem(mItem); + } + } + else if (link == "use" && mItemId) + { + if (mItemId < SPELL_MIN_ID) + { + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(mItemId); + if (item) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + { + Net::getInventoryHandler()->useItem(item); + } + } + } + } + else if (spellManager) + { + spellManager->useItem(mItemId); + } + } + else if (link == "chat") + { + if (chatWindow) + { + if (mItem) + chatWindow->addItemText(mItem->getInfo().getName()); + else if (mFloorItem) + chatWindow->addItemText(mFloorItem->getInfo().getName()); + } + } + else if (link == "whisper" && !mNick.empty() && chatWindow) + { + if (chatWindow) + { + if (config.getBoolValue("whispertab")) + chatWindow->localChatInput("/q " + mNick); + else + chatWindow->addInputText("/w \"" + mNick + "\" "); + } + } + else if (link == "move" && being) + { + if (player_node) + player_node->navigateTo(being->getTileX(), being->getTileY()); + } + else if (link == "split" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, + inventoryWindow, mItem); + } + else if (link == "drop" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop, + inventoryWindow, mItem); + } + else if (link == "store" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, + inventoryWindow, mItem); + } + else if (link == "store 10" && mItem) + { + int cnt = 10; + if (cnt > mItem->getQuantity()) + cnt = mItem->getQuantity(); + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + mItem->getInvIndex(), cnt, + Inventory::STORAGE); + } + else if (link == "store half" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + mItem->getInvIndex(), mItem->getQuantity() / 2, + Inventory::STORAGE); + } + else if (link == "store all" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::INVENTORY, + mItem->getInvIndex(), mItem->getQuantity(), + Inventory::STORAGE); + } + else if (link == "retrieve" && mItem) + { + ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, mWindow, + mItem); + } + else if (link == "retrieve 10" && mItem) + { + int cnt = 10; + if (cnt > mItem->getQuantity()) + cnt = mItem->getQuantity(); + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + mItem->getInvIndex(), cnt, + Inventory::INVENTORY); + } + else if (link == "retrieve half" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + mItem->getInvIndex(), mItem->getQuantity() / 2, + Inventory::INVENTORY); + } + else if (link == "retrieve all" && mItem) + { + Net::getInventoryHandler()->moveItem(Inventory::STORAGE, + mItem->getInvIndex(), mItem->getQuantity(), + Inventory::INVENTORY); + } + else if (link == "party" && being && + being->getType() == ActorSprite::PLAYER) + { + Net::getPartyHandler()->invite(being); + } + else if (link == "kick party" && being + && being->getType() == Being::PLAYER) + { + Net::getPartyHandler()->kick(being); + } + else if (link == "name" && !mNick.empty()) + { + const std::string &name = mNick; + if (chatWindow) + chatWindow->addInputText(name); + } + else if (link == "admin-kick" && being && + (being->getType() == ActorSprite::PLAYER || + being->getType() == ActorSprite::MONSTER)) + { + Net::getAdminHandler()->kick(being->getId()); + } + else if (link == "chat close" && mTab) + { + mTab->handleCommand("close", ""); + } + else if (link == "leave party" && mTab) + { + Net::getPartyHandler()->leave(); + } + else if (link == "chat clear" && mTab) + { + if (chatWindow) + chatWindow->clearTab(); + } + else if (link == "remove map" && mMapItem) + { + if (viewport) + { + Map *map = viewport->getCurrentMap(); + if (map) + { + SpecialLayer *specialLayer = map->getSpecialLayer(); + if (specialLayer) + { + const int x = mMapItem->getX(); + const int y = mMapItem->getY(); + specialLayer->setTile(x, y, MapItem::EMPTY); + if (socialWindow) + socialWindow->removePortal(x, y); + } + } + } + } + else if (link == "rename map" && mMapItem) + { + mRenameListener.setMapItem(mMapItem); + mDialog = new TextDialog(_("Rename map sign "), + _("Name: ")); + mRenameListener.setDialog(mDialog); + mDialog->setText(mMapItem->getComment()); + mDialog->setActionEventId("ok"); + mDialog->addActionListener(&mRenameListener); + } + else if (link == "load old outfits") + { + if (outfitWindow) + outfitWindow->load(true); + } + else if (link == "load old spells") + { + if (spellManager) + { + spellManager->load(true); + spellManager->save(); + } + } + else if (link == "load old item shortcuts") + { + if (itemShortcutWindow) + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num]) + { + itemShortcut[num]->load(true); + itemShortcut[num]->save(); + } + } + } + else if (link == "load old drop shortcuts") + { + if (dropShortcut) + { + dropShortcut->load(true); + dropShortcut->save(); + } + } + else if (link == "edit spell" && mSpell) + { + new TextCommandEditor(mSpell); + } + else if (link == "undress" && being) + { + Net::getBeingHandler()->undress(being); + } + else if (link == "guild-kick" && !mNick.empty()) + { + if (player_node) + { + const Guild *guild = player_node->getGuild(); + if (guild) + Net::getGuildHandler()->kick(guild->getMember(mNick)); + } + } + else if (link == "enable highlight" && mTab) + { + mTab->setAllowHighlight(true); + } + else if (link == "disable highlight" && mTab) + { + mTab->setAllowHighlight(false); + } + else if (link == "guild-pos" && !mNick.empty()) + { + showChangePos(getX(), getY()); + return; + } + else if (!link.find("guild-pos-")) + { + if (player_node) + { + int num = atoi(link.substr(10).c_str()); + const Guild *guild = player_node->getGuild(); + if (guild) + { + Net::getGuildHandler()->changeMemberPostion( + guild->getMember(mNick), num); + } + } + } + else if (!link.find("player_")) + { + mBeingId = atoi(link.substr(7).c_str()); + Being *being = actorSpriteManager->findBeing(mBeingId); + if (being) + { + showPopup(getX(), getY(), being); + return; + } + } + // Unknown actions + else if (link != "cancel") + { + logger->log("PopupMenu: Warning, unknown action '%s'", link.c_str()); + } + + setVisible(false); + + mBeingId = 0; + mFloorItem = 0; + mItem = 0; + mItemId = 0; + mMapItem = 0; + mNick = ""; +} + +void PopupMenu::showPopup(Window *parent, int x, int y, Item *item, + bool isInventory) +{ + if (!item) + return; + + mItem = item; + mWindow = parent; + mBrowserBox->clearRows(); + + int cnt = item->getQuantity(); + + if (isInventory) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip"))); + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip"))); + } + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + + if (cnt > 1) + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop..."))); + else + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); + + if (Net::getInventoryHandler()->canSplit(item)) + mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); + + if (InventoryWindow::isStorageActive()) + { + mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store"))); + if (cnt > 1) + { + if (cnt > 10) + { + mBrowserBox->addRow(strprintf("@@store 10|%s@@", + _("Store 10"))); + } + mBrowserBox->addRow(strprintf("@@store half|%s@@", + _("Store half"))); + mBrowserBox->addRow(strprintf("@@store all|%s@@", + _("Store all"))); + } + } + } + // Assume in storage for now + // TODO: make this whole system more flexible, if needed + else + { + mBrowserBox->addRow(strprintf("@@retrieve|%s@@", _("Retrieve"))); + if (cnt > 1) + { + if (cnt > 10) + { + mBrowserBox->addRow(strprintf("@@retrieve 10|%s@@", + _("Retrieve 10"))); + } + mBrowserBox->addRow(strprintf("@@retrieve half|%s@@", + _("Retrieve half"))); + mBrowserBox->addRow(strprintf("@@retrieve all|%s@@", + _("Retrieve all"))); + } + } + mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); +} + +void PopupMenu::showItemPopup(int x, int y, int itemId) +{ + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(itemId); + if (item) + { + showItemPopup(x, y, item); + } + else + { + mItem = 0; + mItemId = itemId; + mBrowserBox->clearRows(); + + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@", + _("Load old item shortcuts"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); + } +} + +void PopupMenu::showItemPopup(int x, int y, Item *item) +{ + mItem = item; + if (item) + mItemId = item->getId(); + else + mItemId = 0; + mBrowserBox->clearRows(); + + if (item) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip"))); + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip"))); + } + else + { + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + } + + if (item->getQuantity() > 1) + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop..."))); + else + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); + + if (Net::getInventoryHandler()->canSplit(item)) + mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); + + if (InventoryWindow::isStorageActive()) + mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store"))); + mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat"))); + mBrowserBox->addRow("##3---"); + } + mBrowserBox->addRow(strprintf("@@load old item shortcuts|%s@@", + _("Load old item shortcuts"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); +} + +void PopupMenu::showDropPopup(int x, int y, Item *item) +{ + mItem = item; + mBrowserBox->clearRows(); + + if (item) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Unequip"))); + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Equip"))); + } + else + mBrowserBox->addRow(strprintf("@@use|%s@@", _("Use"))); + + if (item->getQuantity() > 1) + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop..."))); + else + mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); + + if (Net::getInventoryHandler()->canSplit(item)) + mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); + + if (InventoryWindow::isStorageActive()) + mBrowserBox->addRow(strprintf("@@store|%s@@", _("Store"))); + mBrowserBox->addRow(strprintf("@@chat|%s@@", _("Add to chat"))); + mBrowserBox->addRow("##3---"); + } + mBrowserBox->addRow(strprintf("@@load old drop shortcuts|%s@@", + _("Load old drop shortcuts"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + showPopup(x, y); +} + +void PopupMenu::showPopup(int x _UNUSED_, int y _UNUSED_, Button *button) +{ + if (!button) + return; + +} + +void PopupMenu::showPopup(int x, int y) +{ + setContentSize(mBrowserBox->getWidth() + 8, mBrowserBox->getHeight() + 8); + if (graphics->getWidth() < (x + getWidth() + 5)) + x = graphics->getWidth() - getWidth(); + if (graphics->getHeight() < (y + getHeight() + 5)) + y = graphics->getHeight() - getHeight(); + setPosition(x, y); + setVisible(true); + requestMoveToTop(); +} + + +void RenameListener::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok" && mMapItem && viewport && mDialog) + { + Map *map = viewport->getMap(); + if (!map) + return; + + SpecialLayer *sl = map->getSpecialLayer(); + MapItem *item = 0; + if (sl) + { + item = sl->getTile(mMapItem->getX(), mMapItem->getY()); + if (!item) + { + sl->setTile(mMapItem->getX(), mMapItem->getY(), + mMapItem->getType()); + item = sl->getTile(mMapItem->getX(), mMapItem->getY()); + } + item->setComment(mDialog->getText()); + } + item = map->findPortalXY(mMapItem->getX(), mMapItem->getY()); + if (item) + item->setComment(mDialog->getText()); + + if (socialWindow) + socialWindow->updatePortalNames(); + } + mDialog = 0; +} diff --git a/src/gui/popupmenu.h b/src/gui/popupmenu.h new file mode 100644 index 000000000..d6c66aa81 --- /dev/null +++ b/src/gui/popupmenu.h @@ -0,0 +1,149 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef POPUP_MENU_H +#define POPUP_MENU_H + +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/popup.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Being; +class BrowserBox; +class Button; +class ChatTab; +class FloorItem; +class Item; +class ItemShortcut; +class MapItem; +class TextCommand; +class TextDialog; +class Window; + +class RenameListener : public gcn::ActionListener +{ + public: + void action(const gcn::ActionEvent &event); + + void setMapItem(MapItem* mapItem) + { mMapItem = mapItem; } + + void setDialog(TextDialog *dialog) + { mDialog = dialog; } + + private: + MapItem *mMapItem; + TextDialog *mDialog; +}; + +/** + * Window showing popup menu. + */ +class PopupMenu : public Popup, public LinkHandler +{ + public: + /** + * Constructor. + */ + PopupMenu(); + + /** + * Shows the being related popup menu at the specified mouse coords. + */ + void showPopup(int x, int y, Being *being); + + /** + * Shows the beings related popup menu at the specified mouse coords. + */ + void showPopup(int x, int y, std::list &beings); + + void showPlayerPopup(int x, int y, std::string nick); + + /** + * Shows the floor item related popup menu at the specified + * mouse coords. + */ + void showPopup(int x, int y, FloorItem *floorItem); + + /** + * Shows the related popup menu when right click on the inventory + * at the specified mouse coordinates. + */ + void showPopup(Window *parent, int x, int y, Item *item, + bool isInventory); + + void showPopup(int x, int y, Button *button); + + void showPopup(int x, int y, MapItem *mapItem); + + void showItemPopup(int x, int y, Item *item); + + void showItemPopup(int x, int y, int itemId); + + void showDropPopup(int x, int y, Item *item); + + void showOutfitsPopup(int x, int y); + + void showSpellPopup(int x, int y, TextCommand *cmd); + + /** + * Shows the related popup menu when right click on the chat + * at the specified mouse coordinates. + */ + void showChatPopup(int x, int y, ChatTab *tab); + + void showChangePos(int x, int y); + + /** + * Handles link action. + */ + void handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_); + + private: + BrowserBox* mBrowserBox; + + int mBeingId; + FloorItem* mFloorItem; + Item *mItem; + int mItemId; + MapItem *mMapItem; + ChatTab *mTab; + TextCommand *mSpell; + Window *mWindow; + RenameListener mRenameListener; + TextDialog *mDialog; + std::string mNick; + + /** + * Shared code for the various showPopup functions. + */ + void showPopup(int x, int y); +}; + +#endif diff --git a/src/gui/quitdialog.cpp b/src/gui/quitdialog.cpp new file mode 100644 index 000000000..7b587dccc --- /dev/null +++ b/src/gui/quitdialog.cpp @@ -0,0 +1,204 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/quitdialog.h" + +#include "client.h" + +#include "gui/chat.h" +#include "gui/sdlinput.h" +#include "gui/viewport.h" + +#include "gui/widgets/checkbox.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/button.h" +#include "gui/widgets/radiobutton.h" + +#include "net/charhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" + +#include + +QuitDialog::QuitDialog(QuitDialog** pointerToMe): + Window(_("Quit"), true, NULL), mMyPointer(pointerToMe) +{ +// int width = 200; +// int height = 120; + + mForceQuit = new RadioButton(_("Quit"), "quitdialog"); + mLogoutQuit = new RadioButton(_("Quit"), "quitdialog"); + mSaveState = new CheckBox(_("Save state"), true); + mSwitchAccountServer = new RadioButton(_("Switch server"), "quitdialog"); + mSwitchCharacter = new RadioButton(_("Switch character"), "quitdialog"); + mOkButton = new Button(_("OK"), "ok", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); +// setContentSize(width, height); + + addKeyListener(this); + + ContainerPlacer place = getPlacer(0, 0); + + const State state = Client::getState(); + + // All states, when we're not logged in to someone. + if (state == STATE_CHOOSE_SERVER || + state == STATE_CONNECT_SERVER || + state == STATE_LOGIN || + state == STATE_LOGIN_ATTEMPT || + state == STATE_UPDATE || + state == STATE_LOAD_DATA) + { + placeOption(place, mForceQuit); + } + else + { + // Only added if we are connected to an accountserver or gameserver + placeOption(place, mLogoutQuit); + placeOption(place, mSwitchAccountServer); + + // Only added if we are connected to a gameserver + if (state == STATE_GAME) + placeOption(place, mSwitchCharacter); + } + + mOptions[0]->setSelected(true); + + place = getPlacer(0, 1); + + place(0, 0, mSaveState, 3); + place(1, 1, mOkButton, 1); + place(2, 1, mCancelButton, 1); + + reflowLayout(200, 0); + setLocationRelativeTo(getParent()); + setVisible(true); + requestModalFocus(); + mOkButton->requestFocus(); +} + +QuitDialog::~QuitDialog() +{ + if (mMyPointer) + *mMyPointer = 0; + // Optional widgets, so delete them by hand. + delete mForceQuit; + mForceQuit = 0; + delete mLogoutQuit; + mLogoutQuit = 0; + delete mSwitchAccountServer; + mSwitchAccountServer = 0; + delete mSwitchCharacter; + mSwitchCharacter = 0; +} + +void QuitDialog::placeOption(ContainerPlacer &place, gcn::RadioButton *option) +{ + place(0, static_cast(mOptions.size()), option, 3); + mOptions.push_back(option); +} + +void QuitDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + { + if (viewport) + { + Map *map = viewport->getCurrentMap(); + if (map) + map->saveExtraLayer(); + } + if (chatWindow && mSaveState->isSelected()) + chatWindow->saveState(); + + if (mForceQuit->isSelected()) + { + Client::setState(STATE_FORCE_QUIT); + } + else if (mLogoutQuit->isSelected()) + { + Client::setState(STATE_EXIT); + } + else if (mSwitchAccountServer->isSelected()) + { + Client::setState(STATE_SWITCH_SERVER); + } + else if (mSwitchCharacter->isSelected()) + { + assert(Client::getState() == STATE_GAME); + + Net::getCharHandler()->switchCharacter(); + } + } + scheduleDelete(); +} + +void QuitDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + const gcn::Key &key = keyEvent.getKey(); + int dir = 0; + + switch (key.getValue()) + { + case Key::ENTER: + action(gcn::ActionEvent(NULL, mOkButton->getActionEventId())); + break; + case Key::ESCAPE: + action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId())); + break; + case Key::UP: + dir = -1; + break; + case Key::DOWN: + dir = 1; + break; + default: + break; + } + + if (dir != 0) + { + std::vector::iterator it = mOptions.begin(); + + for (; it < mOptions.end(); it++) + { + if ((*it)->isSelected()) + break; + } + + if (it == mOptions.end()) + { + if (mOptions[0]) + mOptions[0]->setSelected(true); + return; + } + else if (it == mOptions.begin() && dir < 0) + it = mOptions.end(); + + it += dir; + + if (it == mOptions.end()) + it = mOptions.begin(); + + (*it)->setSelected(true); + } +} diff --git a/src/gui/quitdialog.h b/src/gui/quitdialog.h new file mode 100644 index 000000000..a6763079e --- /dev/null +++ b/src/gui/quitdialog.h @@ -0,0 +1,77 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef QUITDIALOG_H +#define QUITDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include +#include + +#include + +/** + * The quit dialog. + * + * \ingroup Interface + */ +class QuitDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor + * + * @pointerToMe will be set to NULL when the QuitDialog is destroyed + */ + QuitDialog(QuitDialog **pointerToMe); + + /** + * Destructor + */ + ~QuitDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + private: + void placeOption(ContainerPlacer &place, gcn::RadioButton *option); + std::vector mOptions; + + gcn::RadioButton *mLogoutQuit; + gcn::CheckBox *mSaveState; + gcn::RadioButton *mForceQuit; + gcn::RadioButton *mSwitchAccountServer; + gcn::RadioButton *mSwitchCharacter; + gcn::Button *mOkButton; + gcn::Button *mCancelButton; + + QuitDialog **mMyPointer; +}; + +#endif diff --git a/src/gui/register.cpp b/src/gui/register.cpp new file mode 100644 index 000000000..fbd63195f --- /dev/null +++ b/src/gui/register.cpp @@ -0,0 +1,258 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/register.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "gui/login.h" +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/radiobutton.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +WrongDataNoticeListener::WrongDataNoticeListener(): + mTarget(0) +{ +} + +void WrongDataNoticeListener::setTarget(gcn::TextField *textField) +{ + mTarget = textField; +} + +void WrongDataNoticeListener::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok" && mTarget) + mTarget->requestFocus(); +} + +RegisterDialog::RegisterDialog(LoginData *loginData): + Window(_("Register")), + mEmailField(0), + mMaleButton(0), + mFemaleButton(0), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + int optionalActions = Net::getLoginHandler()->supportedOptionalActions(); + + gcn::Label *userLabel = new Label(_("Name:")); + gcn::Label *passwordLabel = new Label(_("Password:")); + gcn::Label *confirmLabel = new Label(_("Confirm:")); + mUserField = new TextField(loginData->username); + mPasswordField = new PasswordField(loginData->password); + mConfirmField = new PasswordField; + mRegisterButton = new Button(_("Register"), "register", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + ContainerPlacer place; + place = getPlacer(0, 0); + place(0, 0, userLabel); + place(0, 1, passwordLabel); + place(0, 2, confirmLabel); + + place(1, 0, mUserField, 3).setPadding(2); + place(1, 1, mPasswordField, 3).setPadding(2); + place(1, 2, mConfirmField, 3).setPadding(2); + + int row = 3; + + if (optionalActions & Net::LoginHandler::SetGenderOnRegister) + { + mMaleButton = new RadioButton(_("Male"), "sex", true); + mFemaleButton = new RadioButton(_("Female"), "sex", false); + place(1, row, mMaleButton); + place(2, row, mFemaleButton); + + row++; + } + + if (optionalActions & Net::LoginHandler::SetEmailOnRegister) + { + gcn::Label *emailLabel = new Label(_("Email:")); + mEmailField = new TextField; + place(0, row, emailLabel); + place(1, row, mEmailField, 3).setPadding(2); + + row++; + } + + place = getPlacer(0, 2); + place(1, 0, mRegisterButton); + place(2, 0, mCancelButton); + reflowLayout(250, 0); + + mUserField->addKeyListener(this); + mPasswordField->addKeyListener(this); + mConfirmField->addKeyListener(this); + + /* TODO: + * This is a quick and dirty way to respond to the ENTER key, regardless of + * which text field is selected. There may be a better way now with the new + * input system of Guichan 0.6.0. See also the login dialog. + */ + mUserField->setActionEventId("register"); + mPasswordField->setActionEventId("register"); + mConfirmField->setActionEventId("register"); + + mUserField->addActionListener(this); + mPasswordField->addActionListener(this); + mConfirmField->addActionListener(this); + + center(); + setVisible(true); + mUserField->requestFocus(); + mUserField->setCaretPosition(static_cast( + mUserField->getText().length())); + + mRegisterButton->setEnabled(canSubmit()); +} + +RegisterDialog::~RegisterDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void RegisterDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_LOGIN); + } + else if (event.getId() == "register" && canSubmit()) + { + const std::string user = mUserField->getText(); + logger->log("RegisterDialog::register Username is %s", user.c_str()); + + std::string errorMessage; + int error = 0; + + unsigned int minUser = Net::getLoginHandler()->getMinUserNameLength(); + unsigned int maxUser = Net::getLoginHandler()->getMaxUserNameLength(); + unsigned int minPass = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int maxPass = Net::getLoginHandler()->getMaxPasswordLength(); + + if (user.length() < minUser) + { + // Name too short + errorMessage = strprintf + (_("The username needs to be at least %d characters long."), + minUser); + error = 1; + } + else if (user.length() > maxUser - 1 ) + { + // Name too long + errorMessage = strprintf + (_("The username needs to be less than %d characters long."), + maxUser); + error = 1; + } + else if (mPasswordField->getText().length() < minPass) + { + // Pass too short + errorMessage = strprintf + (_("The password needs to be at least %d characters long."), + minPass); + error = 2; + } + else if (mPasswordField->getText().length() > maxPass - 1 ) + { + // Pass too long + errorMessage = strprintf + (_("The password needs to be less than %d characters long."), + maxPass); + error = 2; + } + else if (mPasswordField->getText() != mConfirmField->getText()) + { + // Password does not match with the confirmation one + errorMessage = _("Passwords do not match."); + error = 2; + } + + // TODO: Check if a valid email address was given + + if (error > 0) + { + if (error == 1) + { + mWrongDataNoticeListener->setTarget(this->mUserField); + } + else if (error == 2) + { + // Reset password confirmation + mPasswordField->setText(""); + mConfirmField->setText(""); + + mWrongDataNoticeListener->setTarget(this->mPasswordField); + } + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage); + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, register the new user. + mRegisterButton->setEnabled(false); + + mLoginData->username = mUserField->getText(); + mLoginData->password = mPasswordField->getText(); + if (mFemaleButton) + mLoginData->gender = mFemaleButton->isSelected() ? + GENDER_FEMALE : GENDER_MALE; + if (mEmailField) + mLoginData->email = mEmailField->getText(); + mLoginData->registerLogin = true; + + Client::setState(STATE_REGISTER_ATTEMPT); + } + } +} + +void RegisterDialog::keyPressed(gcn::KeyEvent &keyEvent _UNUSED_) +{ + mRegisterButton->setEnabled(canSubmit()); +} + +bool RegisterDialog::canSubmit() const +{ + return !mUserField->getText().empty() && + !mPasswordField->getText().empty() && + !mConfirmField->getText().empty() && + Client::getState() == STATE_REGISTER; +} diff --git a/src/gui/register.h b/src/gui/register.h new file mode 100644 index 000000000..f9c866768 --- /dev/null +++ b/src/gui/register.h @@ -0,0 +1,110 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef REGISTER_H +#define REGISTER_H + +#include "gui/widgets/window.h" + +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class LoginData; +class OkDialog; + +/** + * Listener used while dealing with wrong data. It is used to direct the focus + * to the field which contained wrong data when the Ok button was pressed on + * the error notice. + */ +class WrongDataNoticeListener : public gcn::ActionListener +{ + public: + WrongDataNoticeListener(); + void setTarget(gcn::TextField *textField); + void action(const gcn::ActionEvent &event); + private: + gcn::TextField *mTarget; +}; + +/** + * The registration dialog. + * + * \ingroup Interface + */ +class RegisterDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor. Name, password and server fields will be initialized to + * the information already present in the LoginData instance. + * + * @see Window::Window + */ + RegisterDialog(LoginData *loginData); + + /** + * Destructor + */ + ~RegisterDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Called when a key is pressed in one of the text fields. + */ + void keyPressed(gcn::KeyEvent &keyEvent); + + private: + /** + * Returns whether submit can be enabled. This is true in the register + * state, when all necessary fields have some text. + */ + bool canSubmit() const; + + gcn::TextField *mUserField; + gcn::TextField *mPasswordField; + gcn::TextField *mConfirmField; + gcn::TextField *mEmailField; + + gcn::Button *mRegisterButton; + gcn::Button *mCancelButton; + gcn::RadioButton *mMaleButton; + gcn::RadioButton *mFemaleButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif diff --git a/src/gui/sdlinput.cpp b/src/gui/sdlinput.cpp new file mode 100644 index 000000000..a48f35be8 --- /dev/null +++ b/src/gui/sdlinput.cpp @@ -0,0 +1,432 @@ +/* _______ __ __ __ ______ __ __ _______ __ __ + * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\ + * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / / + * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / / + * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / / + * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ / + * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ + * + * Copyright (c) 2004, 2005, 2006, 2007 Olof Naessén and Per Larsson + * Copyright (C) 2007-2010 The Mana World Development Team + * + * Js_./ + * Per Larsson a.k.a finalman _RqZ{a<^_aa + * Olof Naessén a.k.a jansem/yakslem _asww7!uY`> )\a// + * _Qhm`] _f "'c 1!5m + * Visit: http://guichan.darkbits.org )Qk

ws?a-?' ._/L #' + * binary forms, with or without )4d[#7r, . ' )d`)[ + * modification, are permitted provided _Q-5'5W..j/?' -?!\)cam' + * that the following conditions are met: j<. a J@\ + * this list of conditions and the j(]1u + +SDLInput::SDLInput() +{ + mMouseInWindow = true; + mMouseDown = false; +} + +bool SDLInput::isKeyQueueEmpty() +{ + return mKeyInputQueue.empty(); +} + +gcn::KeyInput SDLInput::dequeueKeyInput() +{ + gcn::KeyInput keyInput; + + if (mKeyInputQueue.empty()) + { + throw GCN_EXCEPTION("The queue is empty."); + } + + keyInput = mKeyInputQueue.front(); + mKeyInputQueue.pop(); + + return keyInput; +} + +bool SDLInput::isMouseQueueEmpty() +{ + return mMouseInputQueue.empty(); +} + +gcn::MouseInput SDLInput::dequeueMouseInput() +{ + gcn::MouseInput mouseInput; + + if (mMouseInputQueue.empty()) + { + throw GCN_EXCEPTION("The queue is empty."); + } + + mouseInput = mMouseInputQueue.front(); + mMouseInputQueue.pop(); + + return mouseInput; +} + +void SDLInput::pushInput(SDL_Event event) +{ + gcn::KeyInput keyInput; + gcn::MouseInput mouseInput; + + switch (event.type) + { + case SDL_KEYDOWN: + keyInput.setKey(gcn::Key(convertKeyCharacter(event))); + keyInput.setType(gcn::KeyInput::PRESSED); + keyInput.setShiftPressed(event.key.keysym.mod & KMOD_SHIFT); + keyInput.setControlPressed(event.key.keysym.mod & KMOD_CTRL); + keyInput.setAltPressed(event.key.keysym.mod & KMOD_ALT); + keyInput.setMetaPressed(event.key.keysym.mod & KMOD_META); + keyInput.setNumericPad(event.key.keysym.sym >= SDLK_KP0 + && event.key.keysym.sym <= SDLK_KP_EQUALS); + + mKeyInputQueue.push(keyInput); + break; + + case SDL_KEYUP: + keyInput.setKey(gcn::Key(convertKeyCharacter(event))); + keyInput.setType(gcn::KeyInput::RELEASED); + keyInput.setShiftPressed(event.key.keysym.mod & KMOD_SHIFT); + keyInput.setControlPressed(event.key.keysym.mod & KMOD_CTRL); + keyInput.setAltPressed(event.key.keysym.mod & KMOD_ALT); + keyInput.setMetaPressed(event.key.keysym.mod & KMOD_META); + keyInput.setNumericPad(event.key.keysym.sym >= SDLK_KP0 + && event.key.keysym.sym <= SDLK_KP_EQUALS); + + mKeyInputQueue.push(keyInput); + break; + + case SDL_MOUSEBUTTONDOWN: + mMouseDown = true; + mouseInput.setX(event.button.x); + mouseInput.setY(event.button.y); + mouseInput.setButton(convertMouseButton(event.button.button)); + + if (event.button.button == SDL_BUTTON_WHEELDOWN) + { + mouseInput.setType(gcn::MouseInput::WHEEL_MOVED_DOWN); + } + else if (event.button.button == SDL_BUTTON_WHEELUP) + { + mouseInput.setType(gcn::MouseInput::WHEEL_MOVED_UP); + } + else + { + mouseInput.setType(gcn::MouseInput::PRESSED); + } + mouseInput.setTimeStamp(SDL_GetTicks()); + mMouseInputQueue.push(mouseInput); + break; + + case SDL_MOUSEBUTTONUP: + mMouseDown = false; + mouseInput.setX(event.button.x); + mouseInput.setY(event.button.y); + mouseInput.setButton(convertMouseButton(event.button.button)); + mouseInput.setType(gcn::MouseInput::RELEASED); + mouseInput.setTimeStamp(SDL_GetTicks()); + mMouseInputQueue.push(mouseInput); + break; + + case SDL_MOUSEMOTION: + mouseInput.setX(event.button.x); + mouseInput.setY(event.button.y); + mouseInput.setButton(gcn::MouseInput::EMPTY); + mouseInput.setType(gcn::MouseInput::MOVED); + mouseInput.setTimeStamp(SDL_GetTicks()); + mMouseInputQueue.push(mouseInput); + break; + + case SDL_ACTIVEEVENT: + /* + * This occurs when the mouse leaves the window and the Gui-chan + * application loses its mousefocus. + */ + if ((event.active.state & SDL_APPMOUSEFOCUS) + && !event.active.gain) + { + mMouseInWindow = false; + + if (!mMouseDown) + { + mouseInput.setX(-1); + mouseInput.setY(-1); + mouseInput.setButton(gcn::MouseInput::EMPTY); + mouseInput.setType(gcn::MouseInput::MOVED); + mMouseInputQueue.push(mouseInput); + } + } + + if ((event.active.state & SDL_APPMOUSEFOCUS) + && event.active.gain) + { + mMouseInWindow = true; + } + break; + + default: + break; + + } // end switch +} + +int SDLInput::convertMouseButton(int button) +{ + switch (button) + { + case SDL_BUTTON_LEFT: + return gcn::MouseInput::LEFT; + case SDL_BUTTON_RIGHT: + return gcn::MouseInput::RIGHT; + case SDL_BUTTON_MIDDLE: + return gcn::MouseInput::MIDDLE; + default: + // We have an unknown mouse type which is ignored. + return button; + } +} + +int SDLInput::convertKeyCharacter(SDL_Event event) +{ + SDL_keysym keysym = event.key.keysym; + + int value = keysym.unicode; + + switch (keysym.sym) + { + case SDLK_TAB: + value = Key::TAB; + break; + case SDLK_LALT: + value = Key::LEFT_ALT; + break; + case SDLK_RALT: + value = Key::RIGHT_ALT; + break; + case SDLK_LSHIFT: + value = Key::LEFT_SHIFT; + break; + case SDLK_RSHIFT: + value = Key::RIGHT_SHIFT; + break; + case SDLK_LCTRL: + value = Key::LEFT_CONTROL; + break; + case SDLK_RCTRL: + value = Key::RIGHT_CONTROL; + break; + case SDLK_BACKSPACE: + value = Key::BACKSPACE; + break; + case SDLK_PAUSE: + value = Key::PAUSE; + break; + case SDLK_SPACE: + // Special characters like ~ (tilde) ends up + // with the keysym.sym SDLK_SPACE which + // without this check would be lost. The check + // is only valid on key down events in SDL. + if (event.type == SDL_KEYUP || keysym.unicode == ' ') + { + value = Key::SPACE; + } + break; + case SDLK_ESCAPE: + value = Key::ESCAPE; + break; + case SDLK_DELETE: + value = Key::DELETE; + break; + case SDLK_INSERT: + value = Key::INSERT; + break; + case SDLK_HOME: + value = Key::HOME; + break; + case SDLK_END: + value = Key::END; + break; + case SDLK_PAGEUP: + value = Key::PAGE_UP; + break; + case SDLK_PRINT: + value = Key::PRINT_SCREEN; + break; + case SDLK_PAGEDOWN: + value = Key::PAGE_DOWN; + break; + case SDLK_F1: + value = Key::F1; + break; + case SDLK_F2: + value = Key::F2; + break; + case SDLK_F3: + value = Key::F3; + break; + case SDLK_F4: + value = Key::F4; + break; + case SDLK_F5: + value = Key::F5; + break; + case SDLK_F6: + value = Key::F6; + break; + case SDLK_F7: + value = Key::F7; + break; + case SDLK_F8: + value = Key::F8; + break; + case SDLK_F9: + value = Key::F9; + break; + case SDLK_F10: + value = Key::F10; + break; + case SDLK_F11: + value = Key::F11; + break; + case SDLK_F12: + value = Key::F12; + break; + case SDLK_F13: + value = Key::F13; + break; + case SDLK_F14: + value = Key::F14; + break; + case SDLK_F15: + value = Key::F15; + break; + case SDLK_NUMLOCK: + value = Key::NUM_LOCK; + break; + case SDLK_CAPSLOCK: + value = Key::CAPS_LOCK; + break; + case SDLK_SCROLLOCK: + value = Key::SCROLL_LOCK; + break; + case SDLK_RMETA: + value = Key::RIGHT_META; + break; + case SDLK_LMETA: + value = Key::LEFT_META; + break; + case SDLK_LSUPER: + value = Key::LEFT_SUPER; + break; + case SDLK_RSUPER: + value = Key::RIGHT_SUPER; + break; + case SDLK_MODE: + value = Key::ALT_GR; + break; + case SDLK_UP: + value = Key::UP; + break; + case SDLK_DOWN: + value = Key::DOWN; + break; + case SDLK_LEFT: + value = Key::LEFT; + break; + case SDLK_RIGHT: + value = Key::RIGHT; + break; + case SDLK_RETURN: + value = Key::ENTER; + break; + case SDLK_KP_ENTER: + value = Key::ENTER; + break; + + default: + break; + } + + if (!(keysym.mod & KMOD_NUM)) + { + switch (keysym.sym) + { + case SDLK_KP0: + value = Key::INSERT; + break; + case SDLK_KP1: + value = Key::END; + break; + case SDLK_KP2: + value = Key::DOWN; + break; + case SDLK_KP3: + value = Key::PAGE_DOWN; + break; + case SDLK_KP4: + value = Key::LEFT; + break; + case SDLK_KP5: + value = 0; + break; + case SDLK_KP6: + value = Key::RIGHT; + break; + case SDLK_KP7: + value = Key::HOME; + break; + case SDLK_KP8: + value = Key::UP; + break; + case SDLK_KP9: + value = Key::PAGE_UP; + break; + default: + break; + } + } + + return value; +} diff --git a/src/gui/sdlinput.h b/src/gui/sdlinput.h new file mode 100644 index 000000000..b441380a4 --- /dev/null +++ b/src/gui/sdlinput.h @@ -0,0 +1,188 @@ +/* _______ __ __ __ ______ __ __ _______ __ __ + * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\ + * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / / + * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / / + * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / / + * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ / + * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ + * + * Copyright (c) 2004, 2005, 2006, 2007 Olof Naessén and Per Larsson + * Copyright (C) 2007-2010 The Mana World Development Team + * + * Js_./ + * Per Larsson a.k.a finalman _RqZ{a<^_aa + * Olof Naessén a.k.a jansem/yakslem _asww7!uY`> )\a// + * _Qhm`] _f "'c 1!5m + * Visit: http://guichan.darkbits.org )Qk

ws?a-?' ._/L #' + * binary forms, with or without )4d[#7r, . ' )d`)[ + * modification, are permitted provided _Q-5'5W..j/?' -?!\)cam' + * that the following conditions are met: j<. a J@\ + * this list of conditions and the j(]1u + +#include + +#include +#include +#include +#include + +namespace Key +{ + enum + { + SPACE = ' ', + TAB = '\t', + ENTER = '\n', + // Negative values, to avoid conflicts with higher character codes. + LEFT_ALT = -1000, + RIGHT_ALT, + LEFT_SHIFT, + RIGHT_SHIFT, + LEFT_CONTROL, + RIGHT_CONTROL, + LEFT_META, + RIGHT_META, + LEFT_SUPER, + RIGHT_SUPER, + INSERT, + HOME, + PAGE_UP, + DELETE, + END, + PAGE_DOWN, + ESCAPE, + CAPS_LOCK, + BACKSPACE, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + PRINT_SCREEN, + SCROLL_LOCK, + PAUSE, + NUM_LOCK, + ALT_GR, + LEFT, + RIGHT, + UP, + DOWN + }; +} + +/** + * SDL implementation of Input. + */ +class SDLInput : public gcn::Input +{ +public: + + /** + * Constructor. + */ + SDLInput(); + + /** + * Pushes an SDL event. It should be called at least once per frame to + * update input with user input. + * + * @param event an event from SDL. + */ + virtual void pushInput(SDL_Event event); + + /** + * Polls all input. It exists for input driver compatibility. If you + * only use SDL and plan sticking with SDL you can safely ignore this + * function as it in the SDL case does nothing. + */ + virtual void _pollInput() { } + + + // Inherited from Input + + virtual bool isKeyQueueEmpty(); + + virtual gcn::KeyInput dequeueKeyInput(); + + virtual bool isMouseQueueEmpty(); + + virtual gcn::MouseInput dequeueMouseInput(); + +protected: + /** + * Converts a mouse button from SDL to a Guichan mouse button + * representation. + * + * @param button an SDL mouse button. + * @return a Guichan mouse button. + */ + int convertMouseButton(int button); + + /** + * Converts an SDL event key to a key value. + * + * @param event an SDL event with a key to convert. + * @return a key value. + * @see Key + */ + int convertKeyCharacter(SDL_Event event); + + std::queue mKeyInputQueue; + std::queue mMouseInputQueue; + + bool mMouseDown; + bool mMouseInWindow; +}; + +#endif diff --git a/src/gui/sell.cpp b/src/gui/sell.cpp new file mode 100644 index 000000000..85577b245 --- /dev/null +++ b/src/gui/sell.cpp @@ -0,0 +1,333 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/sell.h" + +#include "shopitem.h" +#include "units.h" + +#include "gui/setup.h" +#include "gui/trade.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shopitems.h" +#include "gui/widgets/shoplistbox.h" +#include "gui/widgets/slider.h" + +#include "net/buysellhandler.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +SellDialog::DialogList SellDialog::instances; + +SellDialog::SellDialog(int npcId): + Window(_("Sell")), + mNpcId(npcId), mMaxItems(0), mAmountItems(0), mNick("") +{ + init(); +} + +SellDialog::SellDialog(std::string nick): + Window(_("Sell")), + mNpcId(-1), mMaxItems(0), mAmountItems(0), mNick(nick) +{ + init(); +} + +void SellDialog::init() +{ + setWindowName("Sell"); + //setupWindow->registerWindowForReset(this); + setResizable(true); + setCloseButton(true); + setMinWidth(260); + setMinHeight(230); + setDefaultSize(260, 230, ImageRect::CENTER); + + // Create a ShopItems instance, that is aware of duplicate entries. + mShopItems = new ShopItems(true); + + mShopItemList = new ShopListBox(mShopItems, mShopItems); + mScrollArea = new ScrollArea(mShopItemList); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mSlider = new Slider(1.0); + + mQuantityLabel = new Label(strprintf("%d / %d", mAmountItems, mMaxItems)); + mQuantityLabel->setAlignment(gcn::Graphics::CENTER); + mMoneyLabel = new Label(strprintf(_("Price: %s / Total: %s"), + "", "")); + + mIncreaseButton = new Button(_("+"), "inc", this); + mDecreaseButton = new Button(_("-"), "dec", this); + mSellButton = new Button(_("Sell"), "sell", this); + mQuitButton = new Button(_("Quit"), "quit", this); + mAddMaxButton = new Button(_("Max"), "max", this); + + mDecreaseButton->adjustSize(); + mDecreaseButton->setWidth(mIncreaseButton->getWidth()); + + mIncreaseButton->setEnabled(false); + mDecreaseButton->setEnabled(false); + mSellButton->setEnabled(false); + mSlider->setEnabled(false); + + mShopItemList->setPriceCheck(false); + mShopItemList->addSelectionListener(this); + mSlider->setActionEventId("slider"); + mSlider->addActionListener(this); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mScrollArea, 8, 5).setPadding(3); + place(0, 5, mDecreaseButton); + place(1, 5, mSlider, 3); + place(4, 5, mIncreaseButton); + place(5, 5, mQuantityLabel, 2); + place(7, 5, mAddMaxButton); + place(0, 6, mMoneyLabel, 8); + place(6, 7, mSellButton); + place(7, 7, mQuitButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(true); +} + +SellDialog::~SellDialog() +{ + delete mShopItems; + mShopItems = 0; + + instances.remove(this); +} + +void SellDialog::reset() +{ + mShopItems->clear(); + mSlider->setValue(0); + + // Reset previous selected item to prevent failing asserts + mShopItemList->setSelected(-1); + + updateButtonsAndLabels(); +} + +void SellDialog::addItem(const Item *item, int price) +{ + if (!item) + return; + + mShopItems->addItem(item->getInvIndex(), item->getId(), + item->getQuantity(), price); + + mShopItemList->adjustSize(); +} + +void SellDialog::addItem(int id, int amount, int price) +{ + mShopItems->addItem(id, amount, price); + mShopItemList->adjustSize(); +} + + +void SellDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "quit") + { + close(); + return; + } + + int selectedItem = mShopItemList->getSelected(); + + // The following actions require a valid item selection + if (selectedItem == -1 || + selectedItem >= static_cast(mShopItems->getNumberOfElements())) + { + return; + } + + if (event.getId() == "slider") + { + mAmountItems = static_cast(mSlider->getValue()); + updateButtonsAndLabels(); + } + else if (event.getId() == "inc" && mAmountItems < mMaxItems) + { + mAmountItems++; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "dec" && mAmountItems > 1) + { + mAmountItems--; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "max") + { + mAmountItems = mMaxItems; + mSlider->setValue(mAmountItems); + updateButtonsAndLabels(); + } + else if (event.getId() == "sell" && mAmountItems > 0 + && mAmountItems <= mMaxItems) + { + if (mNpcId != -1) + { + // Attempt sell + ShopItem *item = mShopItems->at(selectedItem); + int sellCount, itemIndex; + mPlayerMoney += + mAmountItems * mShopItems->at(selectedItem)->getPrice(); + mMaxItems -= mAmountItems; + while (mAmountItems > 0) + { + // This order is important, item->getCurrentInvIndex() would return + // the inventory index of the next Duplicate otherwise. + itemIndex = item->getCurrentInvIndex(); + sellCount = item->sellCurrentDuplicate(mAmountItems); + + // For Manaserv, the Item id is to be given as index. + if ((Net::getNetworkType() == ServerInfo::MANASERV)) + itemIndex = item->getId(); + + Net::getNpcHandler()->sellItem(mNpcId, itemIndex, sellCount); + mAmountItems -= sellCount; + } + + mPlayerMoney += + mAmountItems * mShopItems->at(selectedItem)->getPrice(); + mAmountItems = 1; + mSlider->setValue(0); + + if (!mMaxItems) + { + // All were sold + mShopItemList->setSelected(-1); + delete mShopItems->at(selectedItem); + mShopItems->erase(selectedItem); + + gcn::Rectangle scroll; + scroll.y = mShopItemList->getRowHeight() * (selectedItem + 1); + scroll.height = mShopItemList->getRowHeight(); + mShopItemList->showPart(scroll); + } + } + else + { + ShopItem *item = mShopItems->at(selectedItem); + + Net::getBuySellHandler()->sendSellRequest(mNick, + item, mAmountItems); + + if (tradeWindow) + tradeWindow->addAutoItem(mNick, item, mAmountItems); + } + } +} + +void SellDialog::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + // Reset amount of items and update labels + mAmountItems = 1; + mSlider->setValue(0); + + updateButtonsAndLabels(); + mSlider->gcn::Slider::setScale(1, mMaxItems); +} + +void SellDialog::setMoney(int amount) +{ + mPlayerMoney = amount; + mShopItemList->setPlayersMoney(amount); +} + +void SellDialog::updateButtonsAndLabels() +{ + int selectedItem = mShopItemList->getSelected(); + int income = 0; + + if (selectedItem > -1 && mShopItems->at(selectedItem)) + { + mMaxItems = mShopItems->at(selectedItem)->getQuantity(); + if (mAmountItems > mMaxItems) + mAmountItems = mMaxItems; + + income = mAmountItems * mShopItems->at(selectedItem)->getPrice(); + } + else + { + mMaxItems = 0; + mAmountItems = 0; + } + + // Update Buttons and slider + mSellButton->setEnabled(mAmountItems > 0); + mDecreaseButton->setEnabled(mAmountItems > 1); + mIncreaseButton->setEnabled(mAmountItems < mMaxItems); + mSlider->setEnabled(mMaxItems > 1); + + // Update the quantity and money labels + mQuantityLabel->setCaption(strprintf("%d / %d", mAmountItems, mMaxItems)); + mMoneyLabel->setCaption(strprintf(_("Price: %s / Total: %s"), + Units::formatCurrency(income).c_str(), + Units::formatCurrency(mPlayerMoney + income).c_str())); +} + +void SellDialog::setVisible(bool visible) +{ + Window::setVisible(visible); + + if (visible) + { + if (mShopItemList) + mShopItemList->requestFocus(); + } + else + { + scheduleDelete(); + } +} + +void SellDialog::closeAll() +{ + DialogList::iterator it = instances.begin(); + DialogList::iterator it_end = instances.end(); + + for (; it != it_end; it++) + (*it)->close(); +} diff --git a/src/gui/sell.h b/src/gui/sell.h new file mode 100644 index 000000000..fd6245eb8 --- /dev/null +++ b/src/gui/sell.h @@ -0,0 +1,145 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SELL_H +#define SELL_H + +#include "gui/widgets/window.h" + +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Item; +class ShopItems; +class ShopListBox; + +/** + * The sell dialog. + * + * \ingroup Interface + */ +class SellDialog : public Window, gcn::ActionListener, gcn::SelectionListener +{ + public: + /** + * Constructor. + * + * @see Window::Window + */ + SellDialog(int npcId); + + /** + * Constructor. + */ + SellDialog(std::string nick); + + /** + * Destructor + */ + virtual ~SellDialog(); + + void init(); + + /** + * Resets the dialog, clearing inventory. + */ + void reset(); + + /** + * Adds an item to the inventory. + */ + void addItem(const Item *item, int price); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Updates labels according to selected item. + * + * @see SelectionListener::selectionChanged + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Gives Player's Money amount + */ + void setMoney(int amount); + + /** + * Sets the visibility of this window. + */ + void setVisible(bool visible); + + void addItem(int id, int amount, int price); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return !instances.empty(); } + + /** + * Closes all instances. + */ + static void closeAll(); + + private: + typedef std::list DialogList; + static DialogList instances; + + /** + * Updates the state of buttons and labels. + */ + void updateButtonsAndLabels(); + + int mNpcId; + + gcn::Button *mSellButton; + gcn::Button *mQuitButton; + gcn::Button *mAddMaxButton; + gcn::Button *mIncreaseButton; + gcn::Button *mDecreaseButton; + ShopListBox *mShopItemList; + gcn::ScrollArea *mScrollArea; + gcn::Label *mMoneyLabel; + gcn::Label *mQuantityLabel; + gcn::Slider *mSlider; + + ShopItems *mShopItems; + int mPlayerMoney; + + int mMaxItems; + int mAmountItems; + + std::string mNick; +}; + +#endif diff --git a/src/gui/serverdialog.cpp b/src/gui/serverdialog.cpp new file mode 100644 index 000000000..c7e1d0f94 --- /dev/null +++ b/src/gui/serverdialog.cpp @@ -0,0 +1,768 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/serverdialog.h" + +#include "chatlog.h" +#include "client.h" +#include "configuration.h" +#include "gui.h" +#include "log.h" +#include "main.h" + +#include "gui/login.h" +#include "gui/okdialog.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/textfield.h" + +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" +#include "widgets/dropdown.h" + +#include + +#include +#include +#include + +static const int MAX_SERVERLIST = 15; + +static std::string serverTypeToString(ServerInfo::Type type) +{ + switch (type) + { + case ServerInfo::TMWATHENA: + return "TmwAthena"; + case ServerInfo::MANASERV: + return "ManaServ"; + default: + return ""; + } +} + +static unsigned short defaultPortForServerType(ServerInfo::Type type) +{ + switch (type) + { + default: + case ServerInfo::TMWATHENA: + return 6901; + case ServerInfo::MANASERV: + return 9601; + } +} + +ServersListModel::ServersListModel(ServerInfos *servers, ServerDialog *parent): + mServers(servers), + mVersionStrings(servers->size(), VersionString(0, "")), + mParent(parent) +{ +} + +int ServersListModel::getNumberOfElements() +{ + MutexLocker lock = mParent->lock(); + return static_cast(mServers->size()); +} + +std::string ServersListModel::getElementAt(int elementIndex) +{ + MutexLocker lock = mParent->lock(); + const ServerInfo &server = mServers->at(elementIndex); + std::string myServer; + myServer += server.hostname; + myServer += ":"; + myServer += toString(server.port); + return myServer; +} + +void ServersListModel::setVersionString(int index, const std::string &version) +{ + if (version.empty()) + { + mVersionStrings[index] = VersionString(0, ""); + } + else + { + int width = gui->getFont()->getWidth(version); + mVersionStrings[index] = VersionString(width, version); + } +} + +std::string TypeListModel::getElementAt(int elementIndex) +{ + if (elementIndex == 0) + return "TmwAthena"; + else if (elementIndex == 1) + return "ManaServ"; + else + return "Unknown"; +} + +class ServersListBox : public ListBox +{ +public: + ServersListBox(ServersListModel *model): + ListBox(model) + { + } + + void draw(gcn::Graphics *graphics) + { + if (!mListModel) + return; + + ServersListModel *model = static_cast(mListModel); + + updateAlpha(); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + const int height = getRowHeight(); + const gcn::Color unsupported = + Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED, + static_cast(mAlpha * 255.0f)); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + graphics->fillRectangle(gcn::Rectangle(0, height * mSelected, + getWidth(), height)); + } + + // Draw the list elements + for (int i = 0, y = 0; i < model->getNumberOfElements(); + ++i, y += height) + { + ServerInfo info = model->getServer(i); + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + int top; + + if (!info.name.empty()) + { + graphics->setFont(boldFont); + graphics->drawText(info.name, 2, y); + top = y + height / 2; + } + else + { + top = y + height / 4; + } + + graphics->setFont(getFont()); + + graphics->drawText(model->getElementAt(i), 2, top); + + if (info.version.first > 0) + { + graphics->setColor(unsupported); + + graphics->drawText(info.version.second, + getWidth() - info.version.first - 2, top); + } + } + } + + unsigned int getRowHeight() const + { + return 2 * getFont()->getHeight(); + } +}; + + +ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir): + Window(_("Choose Your Server")), + mDir(dir), +// mDownloadStatus(DOWNLOADING_PREPARING), + mDownloadStatus(DOWNLOADING_UNKNOWN), + mDownload(0), + mDownloadProgress(-1.0f), + mServers(ServerInfos()), + mServerInfo(serverInfo) +{ + if (isSafeMode) + setCaption("Choose Your Server *** SAFE MODE ***"); + + setWindowName("ServerDialog"); + + Label *serverLabel = new Label(_("Server:")); + Label *portLabel = new Label(_("Port:")); + Label *typeLabel = new Label(_("Server type:")); + mServerNameField = new TextField(mServerInfo->hostname); + mPortField = new TextField(toString(mServerInfo->port)); + + loadCustomServers(); + + mServersListModel = new ServersListModel(&mServers, this); + + mServersList = new ServersListBox(mServersListModel); + mServersList->addMouseListener(this); + + ScrollArea *usedScroll = new ScrollArea(mServersList); + usedScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mTypeListModel = new TypeListModel(); + mTypeField = new DropDown(mTypeListModel); + mTypeField->setSelected((serverInfo->type == ServerInfo::MANASERV) ? + 1 : 0); + + mDescription = new Label(std::string()); + + mQuitButton = new Button(_("Quit"), "quit", this); + mLoadButton = new Button(_("Load"), "load", this); + mConnectButton = new Button(_("Connect"), "connect", this); + mManualEntryButton = new Button(_("Custom Server"), "addEntry", this); + mDeleteButton = new Button(_("Delete"), "remove", this); + + mServerNameField->setActionEventId("connect"); + mPortField->setActionEventId("connect"); + + mServerNameField->addActionListener(this); + mPortField->addActionListener(this); + mManualEntryButton->addActionListener(this); + mServersList->addSelectionListener(this); + usedScroll->setVerticalScrollAmount(0); + + place(0, 0, serverLabel); + place(1, 0, mServerNameField, 5).setPadding(3); + place(0, 1, portLabel); + place(1, 1, mPortField, 5).setPadding(3); + place(0, 2, typeLabel); + place(1, 2, mTypeField, 5).setPadding(3); + place(0, 3, usedScroll, 6, 5).setPadding(3); + place(0, 8, mDescription, 6); + place(0, 9, mManualEntryButton); + place(1, 9, mDeleteButton); + place(2, 9, mLoadButton); + place(4, 9, mQuitButton); + place(5, 9, mConnectButton); + + // Make sure the list has enough height + getLayout().setRowHeight(3, 80); + +/* + reflowLayout(400, 300); + setDefaultSize(400, 300, ImageRect::CENTER); +*/ + // Do this manually instead of calling reflowLayout so we can enforce a + // minimum width. + int width = 0, height = 0; + getLayout().reflow(width, height); + if (width < 400) + { + width = 400; + getLayout().reflow(width, height); + } + + setContentSize(width, height); + + setMinWidth(getWidth()); + setMinHeight(getHeight()); + setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER); + + setResizable(true); + addKeyListener(this); + + loadWindowState(); + + setFieldsReadOnly(true); + mServersList->setSelected(0); // Do this after for the Delete button + setVisible(true); + + if (mServerNameField->getText().empty()) + { + mServerNameField->requestFocus(); + } + else + { + if (mPortField->getText().empty()) + mPortField->requestFocus(); + else + mConnectButton->requestFocus(); + } + + loadServers(false); + + if (mServers.size() == 0) + downloadServerList(); +} + +ServerDialog::~ServerDialog() +{ + if (mDownload) + { + mDownload->cancel(); + delete mDownload; + mDownload = 0; + } + delete mServersListModel; + mServersListModel = 0; + delete mTypeListModel; + mTypeListModel = 0; +} + +void ServerDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + { + // Give focus back to the server dialog. + mServerNameField->requestFocus(); + } + else if (event.getId() == "connect") + { + // Check login + if (mServerNameField->getText().empty() + || mPortField->getText().empty()) + { + OkDialog *dlg = new OkDialog(_("Error"), + _("Please type both the address and the port of a server.")); + dlg->addActionListener(this); + } + else + { + if (mDownload) + mDownload->cancel(); + + mQuitButton->setEnabled(false); + mConnectButton->setEnabled(false); + mLoadButton->setEnabled(false); + + mServerInfo->hostname = mServerNameField->getText(); + mServerInfo->port = static_cast( + atoi(mPortField->getText().c_str())); + + switch (mTypeField->getSelected()) + { + case 0: + mServerInfo->type = ServerInfo::TMWATHENA; + break; + case 1: + mServerInfo->type = ServerInfo::MANASERV; + break; + default: + mServerInfo->type = ServerInfo::UNKNOWN; + } + + // Save the selected server + mServerInfo->save = true; + + if (chatLogger) + chatLogger->setServerName(mServerInfo->hostname); + + saveCustomServers(*mServerInfo); + + if (!LoginDialog::savedPasswordKey.empty()) + { + if (mServerInfo->hostname != LoginDialog::savedPasswordKey) + LoginDialog::savedPassword = ""; + } + + Client::setState(STATE_CONNECT_SERVER); + } + } + else if (event.getId() == "quit") + { + if (mDownload) + mDownload->cancel(); + Client::setState(STATE_FORCE_QUIT); + } + else if (event.getId() == "load") + { + downloadServerList(); + } + else if (event.getId() == "addEntry") + { + setFieldsReadOnly(false); + } + else if (event.getId() == "remove") + { + int index = mServersList->getSelected(); + mServersList->setSelected(0); + mServers.erase(mServers.begin() + index); + + saveCustomServers(); + } +} + +void ServerDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + Client::setState(STATE_EXIT); + else if (key.getValue() == Key::ENTER) + action(gcn::ActionEvent(NULL, mConnectButton->getActionEventId())); +} + +void ServerDialog::valueChanged(const gcn::SelectionEvent &) +{ + const int index = mServersList->getSelected(); + if (index == -1) + { + mDeleteButton->setEnabled(false); + return; + } + + // Update the server and post fields according to the new selection + const ServerInfo &myServer = mServersListModel->getServer(index); + mDescription->setCaption(myServer.description); + mServerNameField->setText(myServer.hostname); + mPortField->setText(toString(myServer.port)); + switch (myServer.type) + { + case ServerInfo::TMWATHENA: + case ServerInfo::UNKNOWN: + default: + mTypeField->setSelected(0); + break; + case ServerInfo::MANASERV: + mTypeField->setSelected(1); + break; + } + setFieldsReadOnly(true); + + mDeleteButton->setEnabled(myServer.save); +} + +void ServerDialog::mouseClicked(gcn::MouseEvent &mouseEvent) +{ + if (mouseEvent.getClickCount() == 2 && + mouseEvent.getSource() == mServersList) + { + action(gcn::ActionEvent(mConnectButton, + mConnectButton->getActionEventId())); + } +} + +void ServerDialog::logic() +{ + { + MutexLocker lock(&mMutex); + if (mDownloadStatus == DOWNLOADING_COMPLETE) + { + mDownloadStatus = DOWNLOADING_OVER; + + mDescription->setCaption(std::string()); + } + else if (mDownloadStatus == DOWNLOADING_IN_PROGRESS) + { + mDescription->setCaption(strprintf(_("Downloading server list..." + "%2.2f%%"), + mDownloadProgress * 100)); + } + else if (mDownloadStatus == DOWNLOADING_IDLE) + { + mDescription->setCaption(_("Waiting for server...")); + } + else if (mDownloadStatus == DOWNLOADING_PREPARING) + { + mDescription->setCaption(_("Preparing download")); + } + else if (mDownloadStatus == DOWNLOADING_ERROR) + { + mDescription->setCaption(_("Error retreiving server list!")); + } + } + + Window::logic(); +} + +void ServerDialog::setFieldsReadOnly(bool readOnly) +{ + if (!readOnly) + { + mDescription->setCaption(std::string()); + mServersList->setSelected(-1); + + mServerNameField->setText(std::string()); + mPortField->setText(std::string("6901")); + + mServerNameField->requestFocus(); + } + + mManualEntryButton->setEnabled(readOnly); + mDeleteButton->setEnabled(false); + mLoadButton->setEnabled(readOnly); + mDescription->setVisible(readOnly); + + mServerNameField->setEnabled(!readOnly); + mPortField->setEnabled(!readOnly); + mTypeField->setEnabled(!readOnly); +} + +void ServerDialog::downloadServerList() +{ + // Try to load the configuration value for the onlineServerList + std::string listFile = branding.getStringValue("onlineServerList"); + + if (listFile.empty()) + listFile = config.getStringValue("onlineServerList"); + + // Fall back to manasource.org when neither branding nor config set it + if (listFile.empty()) + listFile = "http://manasource.org/serverlist.xml"; + + mDownload = new Net::Download(this, listFile, &downloadUpdate); + mDownload->setFile(mDir + "/serverlist.xml"); + mDownload->start(); +} + +void ServerDialog::loadServers(bool addNew) +{ + XML::Document doc(mDir + "/serverlist.xml", false); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "serverlist")) + { + logger->log1("Error loading server list!"); + return; + } + + int version = XML::getProperty(rootNode, "version", 0); + if (version != 1) + { + logger->log("Error: unsupported online server list version: %d", + version); + return; + } + + for_each_xml_child_node(serverNode, rootNode) + { + if (!xmlStrEqual(serverNode->name, BAD_CAST "server")) + continue; + + ServerInfo server; + + std::string type = XML::getProperty(serverNode, "type", "unknown"); + + server.type = ServerInfo::parseType(type); + + // Ignore unknown server types + if (server.type == ServerInfo::UNKNOWN) + { + logger->log("Ignoring server entry with unknown type: %s", + type.c_str()); + continue; + } + + server.name = XML::getProperty(serverNode, "name", std::string()); + + std::string version = XML::getProperty(serverNode, "minimumVersion", + std::string()); + + bool meetsMinimumVersion = (compareStrI(version, PACKAGE_VERSION) + <= 0); + + // For display in the list + if (meetsMinimumVersion) + version.clear(); + else if (version.empty()) + version = _("requires a newer version"); + else + version = strprintf(_("requires v%s"), version.c_str()); + + for_each_xml_child_node(subNode, serverNode) + { + if (xmlStrEqual(subNode->name, BAD_CAST "connection")) + { + server.hostname = XML::getProperty(subNode, "hostname", ""); + server.port = static_cast( + XML::getProperty(subNode, "port", 0)); + + if (server.port == 0) + { + // If no port is given, use the default for the given type + server.port = defaultPortForServerType(server.type); + } + } + else if (xmlStrEqual(subNode->name, BAD_CAST "description")) + { + server.description = + (const char*)subNode->xmlChildrenNode->content; + } + } + + server.version.first = gui->getFont()->getWidth(version); + server.version.second = version; + + MutexLocker lock(&mMutex); + // Add the server to the local list if it's not already present + bool found = false; + for (unsigned int i = 0; i < mServers.size(); i++) + { + if (mServers[i] == server) + { + // Use the name listed in the server list + mServers[i].name = server.name; + mServers[i].version = server.version; + mServersListModel->setVersionString(i, version); + found = true; + break; + } + } + + if (!found && addNew) + mServers.push_back(server); + } +} + +void ServerDialog::loadCustomServers() +{ + for (int i = 0; i < MAX_SERVERLIST; ++i) + { + const std::string index = toString(i); + const std::string nameKey = "MostUsedServerName" + index; + const std::string typeKey = "MostUsedServerType" + index; + const std::string portKey = "MostUsedServerPort" + index; + + ServerInfo server; + server.hostname = config.getValue(nameKey, ""); + server.type = ServerInfo::parseType(config.getValue(typeKey, "")); + + const int defaultPort = defaultPortForServerType(server.type); + server.port = static_cast( + config.getValue(portKey, defaultPort)); + + // Stop on the first invalid server + if (!server.isValid()) + break; + + server.save = true; + mServers.push_back(server); + } +} + +void ServerDialog::saveCustomServers(const ServerInfo ¤tServer) +{ + // Make sure the current server is mentioned first + if (currentServer.isValid()) + { + ServerInfos::iterator i, i_end = mServers.end(); + for (i = mServers.begin(); i != i_end; ++i) + { + if (*i == currentServer) + { + mServers.erase(i); + break; + } + } + mServers.insert(mServers.begin(), currentServer); + } + + int savedServerCount = 0; + + for (unsigned i = 0; + i < mServers.size() && savedServerCount < MAX_SERVERLIST; ++i) + { + const ServerInfo &server = mServers.at(i); + + // Only save servers that were loaded from settings + if (!(server.save && server.isValid())) + continue; + + const std::string index = toString(savedServerCount); + const std::string nameKey = "MostUsedServerName" + index; + const std::string typeKey = "MostUsedServerType" + index; + const std::string portKey = "MostUsedServerPort" + index; + + config.setValue(nameKey, toString(server.hostname)); + config.setValue(typeKey, serverTypeToString(server.type)); + config.setValue(portKey, toString(server.port)); + ++savedServerCount; + } + + // Insert an invalid entry at the end to make the loading stop there + if (savedServerCount < MAX_SERVERLIST) + config.setValue("MostUsedServerName" + toString(savedServerCount), ""); +} + +int ServerDialog::downloadUpdate(void *ptr, DownloadStatus status, + size_t total, size_t remaining) +{ + if (status == DOWNLOAD_STATUS_CANCELLED) + return -1; + + ServerDialog *sd = reinterpret_cast(ptr); + bool finished = false; + + if (!sd->mDownload) + return -1; + + if (status == DOWNLOAD_STATUS_COMPLETE) + { + finished = true; + } + else if (status < 0) + { + logger->log("Error retreiving server list: %s\n", + sd->mDownload->getError()); + sd->mDownloadStatus = DOWNLOADING_ERROR; + } + else + { + float progress = static_cast(remaining); + if (total) + progress /= static_cast(total); + + if (progress != progress) + { + progress = 0.0f; // check for NaN + } + else if (progress < 0.0f) + { + progress = 0.0f; // no idea how this could ever happen, + // but why not check for it anyway. + } + else if (progress > 1.0f) + { + progress = 1.0f; + } + + MutexLocker lock(&sd->mMutex); + sd->mDownloadStatus = DOWNLOADING_IN_PROGRESS; + sd->mDownloadProgress = progress; + } + + if (finished) + { + sd->loadServers(); + + MutexLocker lock(&sd->mMutex); + sd->mDownloadStatus = DOWNLOADING_COMPLETE; + } + + return 0; +} diff --git a/src/gui/serverdialog.h b/src/gui/serverdialog.h new file mode 100644 index 000000000..b9a67e246 --- /dev/null +++ b/src/gui/serverdialog.h @@ -0,0 +1,204 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SERVERDIALOG_H +#define SERVERDIALOG_H + +#include "gui/widgets/window.h" + +#include "net/download.h" +#include "net/serverinfo.h" + +#include "utils/mutex.h" + +#include +#include +#include +#include +#include + +#include +#include + +class Button; +class Label; +class ListBox; +class ServerDialog; +class TextField; +class DropDown; + +/** + * Server and Port List Model + */ +class ServersListModel : public gcn::ListModel +{ + public: + typedef std::pair VersionString; + + ServersListModel(ServerInfos *servers, ServerDialog *parent); + + /** + * Used to get number of line in the list + */ + int getNumberOfElements(); + + /** + * Used to get an element from the list + */ + std::string getElementAt(int elementIndex); + + /** + * Used to get the corresponding Server struct + */ + const ServerInfo &getServer(int elementIndex) const + { return mServers->at(elementIndex); } + + void setVersionString(int index, const std::string &version); + + private: + typedef std::vector VersionStrings; + + ServerInfos *mServers; + VersionStrings mVersionStrings; + ServerDialog *mParent; +}; + +/** + * Server Type List Model + */ +class TypeListModel : public gcn::ListModel +{ + public: + TypeListModel() {} + + /** + * Used to get number of line in the list + */ + int getNumberOfElements() + { return 2; } + + /** + * Used to get an element from the list + */ + std::string getElementAt(int elementIndex); +}; + + +/** + * The server choice dialog. + * + * \ingroup Interface + */ +class ServerDialog : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public gcn::SelectionListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + ServerDialog(ServerInfo *serverInfo, const std::string &dir); + + /** + * Destructor + */ + ~ServerDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + /** + * Called when the selected value changed in the servers list box. + */ + void valueChanged(const gcn::SelectionEvent &event); + + void mouseClicked(gcn::MouseEvent &mouseEvent); + + void logic(); + + protected: + friend class ServersListModel; + MutexLocker lock() + { return MutexLocker(&mMutex); } + + private: + /** + * Called to load a list of available server from an online xml file. + */ + void downloadServerList(); + void loadServers(bool addNew = true); + + void loadCustomServers(); + void saveCustomServers(const ServerInfo ¤tServer = ServerInfo()); + + static int downloadUpdate(void *ptr, DownloadStatus status, + size_t total, size_t remaining); + + void setFieldsReadOnly(bool readOnly); + + TextField *mServerNameField; + TextField *mPortField; + Label *mDescription; + Button *mQuitButton; + Button *mConnectButton; + Button *mManualEntryButton; + Button *mDeleteButton; + Button *mLoadButton; + + ListBox *mServersList; + ServersListModel *mServersListModel; + + DropDown *mTypeField; + TypeListModel *mTypeListModel; + + const std::string &mDir; + + enum ServerDialogDownloadStatus + { + DOWNLOADING_UNKNOWN = 0, + DOWNLOADING_ERROR, + DOWNLOADING_PREPARING, + DOWNLOADING_IDLE, + DOWNLOADING_IN_PROGRESS, + DOWNLOADING_COMPLETE, + DOWNLOADING_OVER + }; + + /** Status of the current download. */ + ServerDialogDownloadStatus mDownloadStatus; + + Net::Download *mDownload; + + Mutex mMutex; + float mDownloadProgress; + + ServerInfos mServers; + ServerInfo *mServerInfo; +}; + +#endif diff --git a/src/gui/setup.cpp b/src/gui/setup.cpp new file mode 100644 index 000000000..8206b0e7e --- /dev/null +++ b/src/gui/setup.cpp @@ -0,0 +1,178 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "setup.h" + +#include "configuration.h" +#include "main.h" + +#include "gui/setup_audio.h" +#include "gui/setup_colors.h" +#include "gui/setup_joystick.h" +#include "gui/setup_other.h" +#include "gui/setup_theme.h" +#include "gui/setup_keyboard.h" +#include "gui/setup_players.h" +#include "gui/setup_video.h" +#include "gui/setup_chat.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/tabbedarea.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +extern Window *statusWindow; + +Setup::Setup(): + Window(_("Setup")) +{ + setCloseButton(true); + setResizable(true); + + int width = 620; + int height = 450; + + if (config.getIntValue("screenwidth") >= 730) + width += 100; + + setContentSize(width, height); + //setMaxHeight(height); + + static const char *buttonNames[] = + { + N_("Apply"), + N_("Cancel"), + N_("Store"), + N_("Reset Windows"), + 0 + }; + int x = width; + for (const char **curBtn = buttonNames; *curBtn; ++curBtn) + { + Button *btn = new Button(gettext(*curBtn), *curBtn, this); + x -= btn->getWidth() + 5; + btn->setPosition(x, height - btn->getHeight() - 5); + add(btn); + + // Store this button, as it needs to be enabled/disabled + if (!strcmp(*curBtn, "Reset Windows")) + mResetWindows = btn; + } + + mPanel = new TabbedArea; + mPanel->setDimension(gcn::Rectangle(5, 5, width - 10, height - 40)); + + mTabs.push_back(new Setup_Video); + mTabs.push_back(new Setup_Audio); + mTabs.push_back(new Setup_Joystick); + mTabs.push_back(new Setup_Keyboard); + mTabs.push_back(new Setup_Colors); + mTabs.push_back(new Setup_Chat); + mTabs.push_back(new Setup_Players); + mTabs.push_back(new Setup_Theme); + mTabs.push_back(new Setup_Other); + + for (std::list::iterator i = mTabs.begin(), i_end = mTabs.end(); + i != i_end; ++i) + { + SetupTab *tab = *i; + mPanel->addTab(tab->getName(), tab); + } + + add(mPanel); + + Label *version = new Label(FULL_VERSION); +// version->setPosition(9, height - version->getHeight() - 9); + if (mResetWindows) + { + version->setPosition(9, + height - version->getHeight() - mResetWindows->getHeight() - 9); + } + else + { + version->setPosition(9, height - version->getHeight() - 30); + } + add(version); + + center(); + + setInGame(false); +} + +Setup::~Setup() +{ + delete_all(mTabs); +} + +void Setup::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "Apply") + { + setVisible(false); + for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::apply)); + } + else if (event.getId() == "Cancel") + { + setVisible(false); + for_each(mTabs.begin(), mTabs.end(), std::mem_fun(&SetupTab::cancel)); + } + else if (event.getId() == "Store") + { + config.write(); + serverConfig.write(); + } + else if (event.getId() == "Reset Windows") + { + // Bail out if this action happens to be activated before the windows + // are created (though it should be disabled then) + if (!statusWindow) + return; + + for (std::list::iterator it = mWindowsToReset.begin(); + it != mWindowsToReset.end(); it++) + { + (*it)->resetToDefaultSize(); + } + } +} + +void Setup::setInGame(bool inGame) +{ + mResetWindows->setEnabled(inGame); +} + +void Setup::externalUpdate() +{ + for (std::list::iterator it = mTabs.begin(); + it != mTabs.end(); it++) + { + (*it)->externalUpdated(); + } +} + +void Setup::registerWindowForReset(Window *window) +{ + mWindowsToReset.push_back(window); +} + +Setup *setupWindow; diff --git a/src/gui/setup.h b/src/gui/setup.h new file mode 100644 index 000000000..1f10a2a0d --- /dev/null +++ b/src/gui/setup.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETUP_H +#define SETUP_H + +#include "gui/widgets/tabbedarea.h" + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include + +#include + +class SetupTab; + +/** + * The setup dialog. Displays several tabs for configuring different aspects + * of the game. + * + * @see Setup_Audio + * @see Setup_Colors + * @see Setup_Joystick + * @see Setup_Keyboard + * @see Setup_Players + * @see Setup_Video + * + * \ingroup GUI + */ +class Setup : public Window, public gcn::ActionListener +{ + public: + Setup(); + ~Setup(); + + /** + * Event handling method. + */ + void action(const gcn::ActionEvent &event); + + /** + * Enables the reset button when in game. + */ + void setInGame(bool inGame); + + void externalUpdate(); + + void registerWindowForReset(Window *window); + + void clearWindowsForReset() + { mWindowsToReset.clear(); } + + private: + std::list mTabs; + std::list mWindowsToReset; + gcn::Button *mResetWindows; + TabbedArea *mPanel; +}; + +extern Setup* setupWindow; + +#endif diff --git a/src/gui/setup_audio.cpp b/src/gui/setup_audio.cpp new file mode 100644 index 000000000..66a7a4e88 --- /dev/null +++ b/src/gui/setup_audio.cpp @@ -0,0 +1,179 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/setup_audio.h" + +#include "configuration.h" +#include "log.h" +#include "sound.h" + +#include "gui/okdialog.h" + +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/slider.h" + +#include "utils/gettext.h" + +Setup_Audio::Setup_Audio(): + mMusicVolume(config.getIntValue("musicVolume")), + mSfxVolume(config.getIntValue("sfxVolume")), + mAudioEnabled(config.getBoolValue("sound")), + mGameSoundEnabled(config.getBoolValue("playBattleSound")), + mGuiSoundEnabled(config.getBoolValue("playGuiSound")), + mMusicEnabled(config.getBoolValue("playMusic")), + mMumbleEnabled(config.getBoolValue("enableMumble")), + mDownloadEnabled(config.getBoolValue("download-music")), + mAudioCheckBox(new CheckBox(_("Enable Audio"), mAudioEnabled)), + mGameSoundCheckBox(new CheckBox(_("Enable game sfx"), mGameSoundEnabled)), + mGuiSoundCheckBox(new CheckBox(_("Enable gui sfx"), mGuiSoundEnabled)), + mMusicCheckBox(new CheckBox(_("Enable music"), mMusicEnabled)), + mMumbleCheckBox(new CheckBox(_("Enable mumble voice chat"), + mMumbleEnabled)), + mDownloadMusicCheckBox(new CheckBox(_("Download music"), + mDownloadEnabled)), + mSfxSlider(new Slider(0, sound.getMaxVolume())), + mMusicSlider(new Slider(0, sound.getMaxVolume())) +{ + setName(_("Audio")); + setDimension(gcn::Rectangle(0, 0, 250, 200)); + + gcn::Label *sfxLabel = new Label(_("Sfx volume")); + gcn::Label *musicLabel = new Label(_("Music volume")); + + mSfxSlider->setActionEventId("sfx"); + mMusicSlider->setActionEventId("music"); + + mSfxSlider->addActionListener(this); + mMusicSlider->addActionListener(this); + + mAudioCheckBox->setPosition(10, 10); + + mSfxSlider->setValue(mSfxVolume); + mMusicSlider->setValue(mMusicVolume); + + mSfxSlider->setWidth(90); + mMusicSlider->setWidth(90); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mAudioCheckBox); + place(0, 1, mMusicCheckBox); + place(0, 2, mGameSoundCheckBox); + place(0, 3, mGuiSoundCheckBox); + place(0, 4, mSfxSlider); + place(1, 4, sfxLabel); + place(0, 5, mMusicSlider); + place(1, 5, musicLabel); + place(0, 6, mMumbleCheckBox); + place(0, 7, mDownloadMusicCheckBox); + + setDimension(gcn::Rectangle(0, 0, 365, 280)); +} + +void Setup_Audio::apply() +{ + mAudioEnabled = mAudioCheckBox->isSelected(); + mGameSoundEnabled = mGameSoundCheckBox->isSelected(); + mGuiSoundEnabled = mGuiSoundCheckBox->isSelected(); + mMusicEnabled = mMusicCheckBox->isSelected(); + mMumbleEnabled = mMumbleCheckBox->isSelected(); + mDownloadEnabled = mDownloadMusicCheckBox->isSelected(); + mSfxVolume = config.getIntValue("sfxVolume"); + mMusicVolume = config.getIntValue("musicVolume"); + + config.setValue("sound", mAudioEnabled); + config.setValue("playBattleSound", mGameSoundEnabled); + config.setValue("playGuiSound", mGuiSoundEnabled); + config.setValue("playMusic", mMusicEnabled); + + config.setValue("enableMumble", mMumbleEnabled); + + // Display a message if user has selected to download music, + // And if downloadmusic is not already enabled + if (mDownloadEnabled && !config.getBoolValue("download-music")) + { + new OkDialog(_("Notice"), _("You may have to restart your client " + "if you want to download new music")); + } + config.setValue("download-music", mDownloadEnabled); + + if (mAudioEnabled) + { + try + { + sound.init(); + } + catch (const char *err) + { + new OkDialog(_("Sound Engine"), err); + logger->log("Warning: %s", err); + } + } + else + { + sound.close(); + } +} + +void Setup_Audio::cancel() +{ + mAudioCheckBox->setSelected(mAudioEnabled); + mGameSoundCheckBox->setSelected(mGameSoundEnabled); + mGuiSoundCheckBox->setSelected(mGuiSoundEnabled); + mMusicCheckBox->setSelected(mMusicEnabled); + mMumbleCheckBox->setSelected(mMumbleEnabled); + mDownloadMusicCheckBox->setSelected(mDownloadEnabled); + + sound.setSfxVolume(mSfxVolume); + mSfxSlider->setValue(mSfxVolume); + + sound.setMusicVolume(mMusicVolume); + mMusicSlider->setValue(mMusicVolume); + + config.setValue("sound", mAudioEnabled); + config.setValue("playBattleSound", mGameSoundEnabled); + config.setValue("playGuiSound", mGuiSoundEnabled); + config.setValue("playMusic", mMusicEnabled); + config.setValue("enableMumble", mMumbleEnabled); + config.setValue("download-music", mDownloadEnabled); + config.setValue("sfxVolume", mSfxVolume); + config.setValue("musicVolume", mMusicVolume); +} + +void Setup_Audio::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "sfx") + { + config.setValueInt("sfxVolume", + static_cast(mSfxSlider->getValue())); + sound.setSfxVolume(static_cast(mSfxSlider->getValue())); + } + else if (event.getId() == "music") + { + config.setValueInt("musicVolume", + static_cast(mMusicSlider->getValue())); + sound.setMusicVolume(static_cast(mMusicSlider->getValue())); + } +} diff --git a/src/gui/setup_audio.h b/src/gui/setup_audio.h new file mode 100644 index 000000000..c16c4fb23 --- /dev/null +++ b/src/gui/setup_audio.h @@ -0,0 +1,53 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_SETUP_AUDIO_H +#define GUI_SETUP_AUDIO_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include + +class Setup_Audio : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Audio(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + int mMusicVolume, mSfxVolume; + bool mAudioEnabled, mGameSoundEnabled, mGuiSoundEnabled; + bool mMusicEnabled, mMumbleEnabled; + bool mDownloadEnabled; + + gcn::CheckBox *mAudioCheckBox, *mGameSoundCheckBox, *mGuiSoundCheckBox; + gcn::CheckBox *mMusicCheckBox, *mMumbleCheckBox; + gcn::CheckBox *mDownloadMusicCheckBox; + gcn::Slider *mSfxSlider, *mMusicSlider; +}; + +#endif diff --git a/src/gui/setup_chat.cpp b/src/gui/setup_chat.cpp new file mode 100644 index 000000000..1dba2ed85 --- /dev/null +++ b/src/gui/setup_chat.cpp @@ -0,0 +1,306 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/setup_chat.h" +#include "gui/editdialog.h" +#include "gui/chat.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/chattab.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "utils/gettext.h" + +#define ACTION_REMOVE_COLORS "remove colors" +#define ACTION_MAGIC_IN_DEBUG "magic in debug" +#define ACTION_ALLOW_COMMANDS_IN_CHATTABS "allow commands" +#define ACTION_SERVER_MSG_IN_DEBUG "server in debug" +#define ACTION_SHOW_CHAT_COLORS "show chat colors" +#define ACTION_MAX_CHAR_LIMIT "char limit" +#define ACTION_EDIT_CHAR_LIMIT "edit char limit" +#define ACTION_EDIT_CHAR_OK "edit char ok" +#define ACTION_MAX_LINES_LIMIT "lines limit" +#define ACTION_EDIT_LINES_LIMIT "edit lines limit" +#define ACTION_EDIT_LINES_OK "edit lines ok" +#define ACTION_CHAT_LOGGER "chat logger" +#define ACTION_TRADE_TAB "trade tab" +#define ACTION_HIDE_SHOP_MESSAGES "hide shop messages" +#define ACTION_SHOW_CHAT_HISTORY "show chat history" +#define ACTION_ENABLE_BATTLE_TAB "show battle tab" +#define ACTION_SHOW_BATTLE_EVENTS "show battle events" + +Setup_Chat::Setup_Chat() +{ + setName(_("Chat")); + + mRemoveColors = config.getBoolValue("removeColors"); + mRemoveColorsCheckBox = new CheckBox( + _("Remove colors from received chat messages"), + mRemoveColors, this, ACTION_REMOVE_COLORS); + + mMagicInDebug = config.getBoolValue("showMagicInDebug"); + mMagicInDebugCheckBox = new CheckBox(_("Log magic messages in debug tab"), + mMagicInDebug, this, ACTION_MAGIC_IN_DEBUG); + + mAllowCommandsInChatTabs = config.getBoolValue( + "allowCommandsInChatTabs"); + + mAllowCommandsInChatTabsCheckBox = new CheckBox( + _("Allow magic and GM commands in all chat tabs"), + mAllowCommandsInChatTabs, this, ACTION_ALLOW_COMMANDS_IN_CHATTABS); + + mServerMsgInDebug = config.getBoolValue("serverMsgInDebug"); + mServerMsgInDebugCheckBox = new CheckBox( + _("Show server messages in debug tab"), + mServerMsgInDebug, this, ACTION_SERVER_MSG_IN_DEBUG); + + mEnableChatLogger = config.getBoolValue("enableChatLog"); + mEnableChatLoggerCheckBox = new CheckBox(_("Enable chat Log"), + mEnableChatLogger, this, ACTION_CHAT_LOGGER); + + mEnableTradeTab = config.getBoolValue("enableTradeTab"); + mEnableTradeTabCheckBox = new CheckBox(_("Enable trade tab"), + mEnableTradeTab, this, ACTION_TRADE_TAB); + + mHideShopMessages = config.getBoolValue("hideShopMessages"); + mHideShopMessagesCheckBox = new CheckBox(_("Hide shop messages"), + mHideShopMessages, this, ACTION_HIDE_SHOP_MESSAGES); + + mShowChatHistory = config.getBoolValue("showChatHistory"); + mShowChatHistoryCheckBox = new CheckBox(_("Show chat history"), + mShowChatHistory, this, ACTION_SHOW_CHAT_HISTORY); + + mEnableBattleTab = config.getBoolValue("enableBattleTab"); + mEnableBattleTabCheckBox = new CheckBox(_("Enable battle tab"), + mEnableBattleTab, this, ACTION_ENABLE_BATTLE_TAB); + + mShowBattleEvents = config.getBoolValue("showBattleEvents"); + mShowBattleEventsCheckBox = new CheckBox(_("Show battle events"), + mShowBattleEvents, this, ACTION_SHOW_BATTLE_EVENTS); + + mShowChatColors = config.getBoolValue("showChatColorsList"); + mShowChatColorsCheckBox = new CheckBox(_("Show chat colors list"), + mShowChatColors, this, ACTION_SHOW_CHAT_COLORS); + + mMaxCharButton = new Button(_("Edit"), ACTION_EDIT_CHAR_LIMIT, this); + int maxCharLimit = config.getIntValue("chatMaxCharLimit"); + mMaxChar = (maxCharLimit != 0); + mMaxCharCheckBox = new CheckBox(_("Limit max chars in chat line"), + mMaxChar, this, ACTION_MAX_CHAR_LIMIT); + + mMaxCharField = new IntTextField(maxCharLimit, 0, 500, mMaxChar, 20); + + mMaxLinesButton = new Button(_("Edit"), ACTION_EDIT_LINES_LIMIT, this); + int maxLinesLimit = config.getIntValue("chatMaxLinesLimit"); + mMaxLines = (maxLinesLimit != 0); + + mMaxLinesCheckBox = new CheckBox(_("Limit max lines in chat"), + mMaxLines, + this, ACTION_MAX_LINES_LIMIT); + + mMaxLinesField = new IntTextField(maxLinesLimit, 0, 500, mMaxLines, 20); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mRemoveColorsCheckBox, 10); + place(0, 1, mMagicInDebugCheckBox, 10); + place(0, 2, mAllowCommandsInChatTabsCheckBox, 10); + place(0, 3, mServerMsgInDebugCheckBox, 10); + place(0, 4, mShowChatColorsCheckBox, 10); + place(0, 5, mMaxCharCheckBox, 6); + place(6, 5, mMaxCharField, 2); + place(8, 5, mMaxCharButton, 2); + place(0, 6, mEnableChatLoggerCheckBox, 10); + place(0, 7, mMaxLinesCheckBox, 6); + place(6, 7, mMaxLinesField, 2); + place(8, 7, mMaxLinesButton, 2); + place(0, 8, mEnableTradeTabCheckBox, 10); + place(0, 9, mHideShopMessagesCheckBox, 10); + place(0, 10, mShowChatHistoryCheckBox, 10); + place(0, 11, mEnableBattleTabCheckBox, 10); + place(0, 12, mShowBattleEventsCheckBox, 10); + + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + + setDimension(gcn::Rectangle(0, 0, 500, 500)); +} + +void Setup_Chat::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_REMOVE_COLORS) + { + mRemoveColors = mRemoveColorsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_MAGIC_IN_DEBUG) + { + mMagicInDebug = mMagicInDebugCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ALLOW_COMMANDS_IN_CHATTABS) + { + mAllowCommandsInChatTabs + = mAllowCommandsInChatTabsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SERVER_MSG_IN_DEBUG) + { + mServerMsgInDebug = mServerMsgInDebugCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_CHAT_COLORS) + { + mShowChatColors = mShowChatColorsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_MAX_CHAR_LIMIT) + { + mMaxChar = mMaxCharCheckBox->isSelected(); + } + else if (event.getId() == ACTION_EDIT_CHAR_LIMIT) + { + mEditDialog = new EditDialog("Limit max chars in chat line", + toString(mMaxCharField->getValue()), + ACTION_EDIT_CHAR_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_CHAR_OK) + { + mMaxCharField->setValue(atoi(mEditDialog->getMsg().c_str())); + } + else if (event.getId() == ACTION_MAX_LINES_LIMIT) + { + mMaxLines = mMaxLinesCheckBox->isSelected(); + } + else if (event.getId() == ACTION_EDIT_LINES_LIMIT) + { + mEditDialog = new EditDialog("Limit max lines in chat", + toString(mMaxLinesField->getValue()), + ACTION_EDIT_LINES_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_LINES_OK) + { + mMaxLinesField->setValue(atoi(mEditDialog->getMsg().c_str())); + } + else if (event.getId() == ACTION_CHAT_LOGGER) + { + mEnableChatLogger = mEnableChatLoggerCheckBox->isSelected(); + } + else if (event.getId() == ACTION_TRADE_TAB) + { + mEnableTradeTab = mEnableTradeTabCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIDE_SHOP_MESSAGES) + { + mHideShopMessages = mHideShopMessagesCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_CHAT_HISTORY) + { + mShowChatHistory = mShowChatHistoryCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ENABLE_BATTLE_TAB) + { + mEnableBattleTab = mEnableBattleTabCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_BATTLE_EVENTS) + { + mShowBattleEvents = mShowBattleEventsCheckBox->isSelected(); + } +} + +void Setup_Chat::cancel() +{ + mRemoveColors = config.getBoolValue("removeColors"); + mRemoveColorsCheckBox->setSelected(mRemoveColors); + + mMagicInDebug = config.getBoolValue("showMagicInDebug"); + mMagicInDebugCheckBox->setSelected(mMagicInDebug); + + mAllowCommandsInChatTabs + = config.getBoolValue("allowCommandsInChatTabs"); + mAllowCommandsInChatTabsCheckBox->setSelected(mAllowCommandsInChatTabs); + + mServerMsgInDebug = config.getBoolValue("serverMsgInDebug"); + mServerMsgInDebugCheckBox->setSelected(mServerMsgInDebug); + + mShowChatColors = config.getBoolValue("showChatColorsList"); + mShowChatColorsCheckBox->setSelected(mShowChatColors); + + int maxCharLimit = config.getIntValue("chatMaxCharLimit"); + mMaxChar = (maxCharLimit != 0); + mMaxCharCheckBox->setSelected(mMaxChar); + mMaxCharField->setValue(maxCharLimit); + mMaxCharField->setEnabled(mMaxChar); + + int maxLinesLimit = config.getIntValue("chatMaxLinesLimit"); + mMaxLines = (maxLinesLimit != 0); + mMaxLinesCheckBox->setSelected(mMaxLines); + mMaxLinesField->setValue(maxLinesLimit); + mMaxLinesField->setEnabled(mMaxLines); + + mEnableChatLogger = config.getBoolValue("enableChatLog"); + mEnableChatLoggerCheckBox->setSelected(mEnableChatLogger); + + mEnableTradeTab = config.getBoolValue("enableTradeTab"); + mEnableTradeTabCheckBox->setSelected(mEnableTradeTab); + + mHideShopMessages = config.getBoolValue("hideShopMessages"); + mHideShopMessagesCheckBox->setSelected(mHideShopMessages); + + mShowChatHistory = config.getBoolValue("showChatHistory"); + mShowChatHistoryCheckBox->setSelected(mShowChatHistory); + + mEnableBattleTab = config.getBoolValue("enableBattleTab"); + mEnableBattleTabCheckBox->setSelected(mEnableBattleTab); + + mShowBattleEvents = config.getBoolValue("showBattleEvents"); + mShowBattleEventsCheckBox->setSelected(mShowBattleEvents); +} + +void Setup_Chat::apply() +{ + config.setValue("removeColors", mRemoveColors); + config.setValue("showMagicInDebug", mMagicInDebug); + config.setValue("allowCommandsInChatTabs", mAllowCommandsInChatTabs); + config.setValue("serverMsgInDebug", mServerMsgInDebug); + config.setValue("showChatColorsList", mShowChatColors); + if (mMaxChar) + config.setValue("chatMaxCharLimit", mMaxCharField->getValue()); + else + config.setValue("chatMaxCharLimit", 0); + if (mMaxLines) + config.setValue("chatMaxLinesLimit", mMaxLinesField->getValue()); + else + config.setValue("chatMaxLinesLimit", 0); + config.setValue("enableChatLog", mEnableChatLogger); + config.setValue("enableTradeTab", mEnableTradeTab); + config.setValue("hideShopMessages", mHideShopMessages); + config.setValue("showChatHistory", mShowChatHistory); + config.setValue("enableBattleTab", mEnableBattleTab); + config.setValue("showBattleEvents", mShowBattleEvents); +} diff --git a/src/gui/setup_chat.h b/src/gui/setup_chat.h new file mode 100644 index 000000000..30a59895a --- /dev/null +++ b/src/gui/setup_chat.h @@ -0,0 +1,92 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GUI_SETUP_CHAT_H +#define GUI_SETUP_CHAT_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include + +class IntTextField; +class EditDialog; + +class Setup_Chat : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Chat(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + gcn::CheckBox *mRemoveColorsCheckBox; + bool mRemoveColors; + + gcn::CheckBox *mMagicInDebugCheckBox; + bool mMagicInDebug; + + gcn::CheckBox *mAllowCommandsInChatTabsCheckBox; + bool mAllowCommandsInChatTabs; + + gcn::CheckBox *mServerMsgInDebugCheckBox; + bool mServerMsgInDebug; + + gcn::CheckBox *mShowChatColorsCheckBox; + bool mShowChatColors; + + gcn::CheckBox *mMaxCharCheckBox; + IntTextField *mMaxCharField; + gcn::Button *mMaxCharButton; + bool mMaxChar; + + gcn::CheckBox *mMaxLinesCheckBox; + IntTextField *mMaxLinesField; + gcn::Button *mMaxLinesButton; + bool mMaxLines; + + gcn::CheckBox *mEnableChatLoggerCheckBox; + bool mEnableChatLogger; + + gcn::CheckBox *mEnableTradeTabCheckBox; + bool mEnableTradeTab; + + gcn::CheckBox *mHideShopMessagesCheckBox; + bool mHideShopMessages; + + gcn::CheckBox *mShowChatHistoryCheckBox; + bool mShowChatHistory; + + gcn::CheckBox *mEnableBattleTabCheckBox; + bool mEnableBattleTab; + + gcn::CheckBox *mShowBattleEventsCheckBox; + bool mShowBattleEvents; + + EditDialog *mEditDialog; +}; + +#endif diff --git a/src/gui/setup_colors.cpp b/src/gui/setup_colors.cpp new file mode 100644 index 000000000..76510a283 --- /dev/null +++ b/src/gui/setup_colors.cpp @@ -0,0 +1,443 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/setup_colors.h" + +#include "configuration.h" + +#include "gui/gui.h" +#include "gui/theme.h" +#include "gui/userpalette.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/textpreview.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +const std::string Setup_Colors::rawmsg = + _("This is what the color looks like"); + +Setup_Colors::Setup_Colors() : + mSelected(-1) +{ + setName(_("Colors")); + + mColorBox = new ListBox(userPalette); + mColorBox->addSelectionListener(this); + + mScroll = new ScrollArea(mColorBox); + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mTextPreview = new TextPreview(rawmsg); + + mPreview = new BrowserBox(BrowserBox::AUTO_WRAP); + mPreview->setOpaque(false); + + // don't do anything with links + mPreview->setLinkHandler(NULL); + + mPreviewBox = new ScrollArea(mPreview); + mPreviewBox->setHeight(20); + mPreviewBox->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER, + gcn::ScrollArea::SHOW_NEVER); + + mGradTypeLabel = new Label(_("Type:")); + + mGradTypeSlider = new Slider(0, 3); + mGradTypeSlider->setWidth(180); + mGradTypeSlider->setActionEventId("slider_grad"); + mGradTypeSlider->setValue(0); + mGradTypeSlider->addActionListener(this); + mGradTypeSlider->setEnabled(false); + + mGradTypeText = new Label; + + std::string longText = _("Static"); + + if (getFont()->getWidth(_("Pulse")) > getFont()->getWidth(longText)) + longText = _("Pulse"); + if (getFont()->getWidth(_("Rainbow")) > getFont()->getWidth(longText)) + longText = _("Rainbow"); + if (getFont()->getWidth(_("Spectrum")) > getFont()->getWidth(longText)) + longText = _("Spectrum"); + + mGradTypeText->setCaption(longText); + + mGradDelayLabel = new Label(_("Delay:")); + + mGradDelayText = new TextField(); + mGradDelayText->setWidth(40); + mGradDelayText->setRange(20, 100); + mGradDelayText->setNumeric(true); + mGradDelayText->setEnabled(false); + + mGradDelaySlider = new Slider(20, 100); + mGradDelaySlider->setWidth(180); + mGradDelaySlider->setValue(mGradDelayText->getValue()); + mGradDelaySlider->setActionEventId("slider_graddelay"); + mGradDelaySlider->addActionListener(this); + mGradDelaySlider->setEnabled(false); + + mRedLabel = new Label(_("Red:")); + + mRedText = new TextField; + mRedText->setWidth(40); + mRedText->setRange(0, 255); + mRedText->setNumeric(true); + mRedText->setEnabled(false); + + mRedSlider = new Slider(0, 255); + mRedSlider->setWidth(180); + mRedSlider->setValue(mRedText->getValue()); + mRedSlider->setActionEventId("slider_red"); + mRedSlider->addActionListener(this); + mRedSlider->setEnabled(false); + + mGreenLabel = new Label(_("Green:")); + + mGreenText = new TextField; + mGreenText->setWidth(40); + mGreenText->setRange(0, 255); + mGreenText->setNumeric(true); + mGreenText->setEnabled(false); + + mGreenSlider = new Slider(0, 255); + mGreenSlider->setWidth(180); + mGreenSlider->setValue(mGreenText->getValue()); + mGreenSlider->setActionEventId("slider_green"); + mGreenSlider->addActionListener(this); + mGreenSlider->setEnabled(false); + + mBlueLabel = new Label(_("Blue:")); + + mBlueText = new TextField; + mBlueText->setWidth(40); + mBlueText->setRange(0, 255); + mBlueText->setNumeric(true); + mBlueText->setEnabled(false); + + mBlueSlider = new Slider(0, 255); + mBlueSlider->setWidth(180); + mBlueSlider->setValue(mBlueText->getValue()); + mBlueSlider->setActionEventId("slider_blue"); + mBlueSlider->addActionListener(this); + mBlueSlider->setEnabled(false); + + setOpaque(false); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mScroll, 6, 6).setPadding(2); + place(0, 6, mPreviewBox, 6).setPadding(2); + place(0, 7, mGradTypeLabel, 3); + place(3, 7, mGradTypeSlider); + place(4, 7, mGradTypeText, 2).setPadding(1); + place(0, 8, mRedLabel, 3); + place(3, 8, mRedSlider); + place(5, 8, mRedText).setPadding(1); + place(0, 9, mGreenLabel, 3); + place(3, 9, mGreenSlider); + place(5, 9, mGreenText).setPadding(1); + place(0, 10, mBlueLabel, 3); + place(3, 10, mBlueSlider); + place(5, 10, mBlueText).setPadding(1); + place(0, 11, mGradDelayLabel, 3); + place(3, 11, mGradDelaySlider); + place(5, 11, mGradDelayText).setPadding(1); + + mGradTypeText->setCaption(""); + + setDimension(gcn::Rectangle(0, 0, 365, 350)); +} + +Setup_Colors::~Setup_Colors() +{ + if (mPreviewBox && mPreviewBox->getContent() == mPreview) + { + delete mTextPreview; + mTextPreview = 0; + } + else + { + delete mPreview; + mPreview = 0; + } +} + +void Setup_Colors::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "slider_grad") + { + updateGradType(); + updateColor(); + return; + } + + if (event.getId() == "slider_graddelay") + { + mGradDelayText->setText(toString( + std::floor(mGradDelaySlider->getValue()))); + updateColor(); + return; + } + + if (event.getId() == "slider_red") + { + mRedText->setText(toString(std::floor(mRedSlider->getValue()))); + updateColor(); + return; + } + + if (event.getId() == "slider_green") + { + mGreenText->setText(toString(std::floor(mGreenSlider->getValue()))); + updateColor(); + return; + } + + if (event.getId() == "slider_blue") + { + mBlueText->setText(toString(std::floor(mBlueSlider->getValue()))); + updateColor(); + return; + } +} + +void Setup_Colors::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + if (!userPalette) + return; + + mSelected = mColorBox->getSelected(); + int type = userPalette->getColorTypeAt(mSelected); + const gcn::Color *col = &userPalette->getColor(type); + Palette::GradientType grad = userPalette->getGradientType(type); + const int delay = userPalette->getGradientDelay(type); + + mPreview->clearRows(); + mPreviewBox->setContent(mTextPreview); + mTextPreview->setFont(boldFont); + mTextPreview->setTextColor(col); + mTextPreview->setTextBGColor(NULL); + mTextPreview->setOpaque(false); + mTextPreview->setShadow(true); + mTextPreview->setOutline(true); + mTextPreview->useTextAlpha(false); + + switch (type) + { + case UserPalette::COLLISION_HIGHLIGHT: + case UserPalette::PORTAL_HIGHLIGHT: + case UserPalette::HOME_PLACE: + case UserPalette::ROAD_POINT: + mTextPreview->setBGColor(col); + mTextPreview->setOpaque(true); + mTextPreview->setOutline(false); + mTextPreview->setShadow(false); + break; + case UserPalette::ATTACK_RANGE_BORDER: + case UserPalette::HOME_PLACE_BORDER: + mTextPreview->setFont(gui->getFont()); + mTextPreview->setTextColor(col); + mTextPreview->setOutline(false); + mTextPreview->setShadow(false); + break; + case UserPalette::PARTICLE: + case UserPalette::EXP_INFO: + case UserPalette::PICKUP_INFO: + case UserPalette::HIT_PLAYER_MONSTER: + case UserPalette::HIT_MONSTER_PLAYER: + case UserPalette::HIT_CRITICAL: + case UserPalette::MISS: + case UserPalette::HIT_LOCAL_PLAYER_MONSTER: + case UserPalette::HIT_LOCAL_PLAYER_CRITICAL: + case UserPalette::HIT_LOCAL_PLAYER_MISS: + case UserPalette::ATTACK_RANGE: + case UserPalette::MONSTER_ATTACK_RANGE: + mTextPreview->setShadow(false); + default: + break; + } + + switch (type) + { + case UserPalette::PORTAL_HIGHLIGHT: + case UserPalette::ATTACK_RANGE: + case UserPalette::ATTACK_RANGE_BORDER: + case UserPalette::MONSTER_ATTACK_RANGE: + case UserPalette::HOME_PLACE: + case UserPalette::HOME_PLACE_BORDER: + case UserPalette::COLLISION_HIGHLIGHT: + case UserPalette::WALKABLE_HIGHLIGHT: + case UserPalette::ROAD_POINT: + case UserPalette::MONSTER_HP: + case UserPalette::MONSTER_HP2: + mGradDelayLabel->setCaption(_("Alpha:")); + mGradDelayText->setRange(0, 255); + mGradDelaySlider->setScale(0, 255); + break; + default: + mGradDelayLabel->setCaption(_("Delay:")); + mGradDelayText->setRange(20, 100); + mGradDelaySlider->setScale(20, 100); + break; + } + if (grad != Palette::STATIC && grad != Palette::PULSE) + { // If nonstatic color, don't display the current, but the committed + // color at the sliders + col = &userPalette->getCommittedColor(type); + } + else if (grad == Palette::PULSE) + { + col = &userPalette->getTestColor(type); + } + + setEntry(mGradDelaySlider, mGradDelayText, delay); + setEntry(mRedSlider, mRedText, col->r); + setEntry(mGreenSlider, mGreenText, col->g); + setEntry(mBlueSlider, mBlueText, col->b); + + mGradTypeSlider->setValue(grad); + updateGradType(); + mGradTypeSlider->setEnabled(true); +} + +void Setup_Colors::setEntry(gcn::Slider *s, TextField *t, int value) +{ + if (s) + s->setValue(value); + if (t) + { + char buffer[100]; + sprintf(buffer, "%d", value); + t->setText(buffer); + } +} + +void Setup_Colors::apply() +{ + if (userPalette) + userPalette->commit(); +} + +void Setup_Colors::cancel() +{ + if (!userPalette) + return; + + userPalette->rollback(); + int type = userPalette->getColorTypeAt(mSelected); + const gcn::Color *col = &userPalette->getColor(type); + mGradTypeSlider->setValue(userPalette->getGradientType(type)); + const int delay = userPalette->getGradientDelay(type); + setEntry(mGradDelaySlider, mGradDelayText, delay); + setEntry(mRedSlider, mRedText, col->r); + setEntry(mGreenSlider, mGreenText, col->g); + setEntry(mBlueSlider, mBlueText, col->b); +} + +#if 0 +void Setup_Colors::listen(const TextField *tf) +{ + if (!tf) + return; + + if (tf == mGradDelayText) + mGradDelaySlider->setValue(tf->getValue()); + else if (tf == mRedText) + mRedSlider->setValue(tf->getValue()); + else if (tf == mGreenText) + mGreenSlider->setValue(tf->getValue()); + else if (tf == mBlueText) + mBlueSlider->setValue(tf->getValue()); + + updateColor(); +} +#endif + +void Setup_Colors::updateGradType() +{ + if (mSelected == -1 || !userPalette) + return; + + mSelected = mColorBox->getSelected(); + int type = userPalette->getColorTypeAt(mSelected); + Palette::GradientType grad = userPalette->getGradientType(type); + + mGradTypeText->setCaption( + (grad == Palette::STATIC) ? _("Static") : + (grad == Palette::PULSE) ? _("Pulse") : + (grad == Palette::RAINBOW) ? _("Rainbow") : _("Spectrum")); + + const bool enable = (grad == Palette::STATIC || grad == Palette::PULSE); +// const bool delayEnable = (grad != Palette::STATIC); + const bool delayEnable = true; + + mGradDelayText->setEnabled(delayEnable); + mGradDelaySlider->setEnabled(delayEnable); + + mRedText->setEnabled(enable); + mRedSlider->setEnabled(enable); + mGreenText->setEnabled(enable); + mGreenSlider->setEnabled(enable); + mBlueText->setEnabled(enable); + mBlueSlider->setEnabled(enable); +} + +void Setup_Colors::updateColor() +{ + if (mSelected == -1 || !userPalette) + return; + + int type = userPalette->getColorTypeAt(mSelected); + Palette::GradientType grad = static_cast( + static_cast(mGradTypeSlider->getValue())); + int delay = static_cast(mGradDelaySlider->getValue()); + userPalette->setGradient(type, grad); + userPalette->setGradientDelay(type, delay); + + if (grad == Palette::STATIC) + { + userPalette->setColor(type, + static_cast(mRedSlider->getValue()), + static_cast(mGreenSlider->getValue()), + static_cast(mBlueSlider->getValue())); + } + else if (grad == Palette::PULSE) + { + userPalette->setTestColor(type, gcn::Color( + static_cast(mRedSlider->getValue()), + static_cast(mGreenSlider->getValue()), + static_cast(mBlueSlider->getValue()))); + } +} diff --git a/src/gui/setup_colors.h b/src/gui/setup_colors.h new file mode 100644 index 000000000..1b3985850 --- /dev/null +++ b/src/gui/setup_colors.h @@ -0,0 +1,96 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SETUP_COLORS_H +#define SETUP_COLORS_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class BrowserBox; +class TextField; +class TextPreview; + +class Setup_Colors : public SetupTab, + public gcn::ActionListener, + public gcn::SelectionListener +{ + public: + Setup_Colors(); + ~Setup_Colors(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + void valueChanged(const gcn::SelectionEvent &event); + + private: + static const std::string rawmsg; + + gcn::ListBox *mColorBox; + gcn::ScrollArea *mScroll; + BrowserBox *mPreview; + TextPreview *mTextPreview; + gcn::ScrollArea *mPreviewBox; + int mSelected; + + gcn::Label *mGradTypeLabel; + gcn::Slider *mGradTypeSlider; + gcn::Label *mGradTypeText; + + gcn::Label *mGradDelayLabel; + gcn::Slider *mGradDelaySlider; + TextField *mGradDelayText; + + gcn::Label *mRedLabel; + gcn::Slider *mRedSlider; + TextField *mRedText; + int mRedValue; + + gcn::Label *mGreenLabel; + gcn::Slider *mGreenSlider; + TextField *mGreenText; + int mGreenValue; + + gcn::Label *mBlueLabel; + gcn::Slider *mBlueSlider; + TextField *mBlueText; + int mBlueValue; + + void setEntry(gcn::Slider *s, TextField *t, int value); + void updateColor(); + void updateGradType(); +}; + +#endif // SETUP_COLORS_H diff --git a/src/gui/setup_joystick.cpp b/src/gui/setup_joystick.cpp new file mode 100644 index 000000000..5f8f11993 --- /dev/null +++ b/src/gui/setup_joystick.cpp @@ -0,0 +1,101 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/setup_joystick.h" + +#include "configuration.h" +#include "joystick.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" + +#include "utils/gettext.h" + +extern Joystick *joystick; + +Setup_Joystick::Setup_Joystick(): + mCalibrateLabel(new Label(_("Press the button to start calibration"))), + mCalibrateButton(new Button(_("Calibrate"), "calibrate", this)), + mJoystickEnabled(new CheckBox(_("Enable joystick"))) +{ + setName(_("Joystick")); + + mOriginalJoystickEnabled = !config.getBoolValue("joystickEnabled"); + mJoystickEnabled->setSelected(mOriginalJoystickEnabled); + + mJoystickEnabled->addActionListener(this); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mJoystickEnabled); + place(0, 1, mCalibrateLabel); + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + place(0, 0, mCalibrateButton); + + setDimension(gcn::Rectangle(0, 0, 365, 75)); +} + +void Setup_Joystick::action(const gcn::ActionEvent &event) +{ + if (!joystick) + return; + + if (event.getSource() == mJoystickEnabled) + { + joystick->setEnabled(mJoystickEnabled->isSelected()); + } + else + { + if (joystick->isCalibrating()) + { + mCalibrateButton->setCaption(_("Calibrate")); + mCalibrateLabel->setCaption + (_("Press the button to start calibration")); + joystick->finishCalibration(); + } + else + { + mCalibrateButton->setCaption(_("Stop")); + mCalibrateLabel->setCaption(_("Rotate the stick")); + joystick->startCalibration(); + } + } +} + +void Setup_Joystick::cancel() +{ + if (joystick) + joystick->setEnabled(mOriginalJoystickEnabled); + + mJoystickEnabled->setSelected(mOriginalJoystickEnabled); +} + +void Setup_Joystick::apply() +{ + config.setValue("joystickEnabled", + joystick ? joystick->isEnabled() : false); +} + diff --git a/src/gui/setup_joystick.h b/src/gui/setup_joystick.h new file mode 100644 index 000000000..f848f45c1 --- /dev/null +++ b/src/gui/setup_joystick.h @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_SETUP_JOYSTICK_H +#define GUI_SETUP_JOYSTICK_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include + +class Setup_Joystick : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Joystick(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + gcn::Label *mCalibrateLabel; + gcn::Button *mCalibrateButton; + bool mOriginalJoystickEnabled; + gcn::CheckBox *mJoystickEnabled; +}; + +#endif diff --git a/src/gui/setup_keyboard.cpp b/src/gui/setup_keyboard.cpp new file mode 100644 index 000000000..4e55ecb35 --- /dev/null +++ b/src/gui/setup_keyboard.cpp @@ -0,0 +1,210 @@ +/* + * Custom keyboard shortcuts configuration + * Copyright (C) 2007 Joshua Langley + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/setup_keyboard.h" + +#include "keyboardconfig.h" + +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#include + +/** + * The list model for key function list. + * + * \ingroup Interface + */ +class KeyListModel : public gcn::ListModel +{ + public: + /** + * Returns the number of elements in container. + */ + int getNumberOfElements() + { return keyboard.KEY_TOTAL; } + + /** + * Returns element from container. + */ + std::string getElementAt(int i) + { return mKeyFunctions[i]; } + + /** + * Sets element from container. + */ + void setElementAt(int i, const std::string &caption) + { mKeyFunctions[i] = caption; } + + private: + std::string mKeyFunctions[KeyboardConfig::KEY_TOTAL]; +}; + +Setup_Keyboard::Setup_Keyboard(): + mKeyListModel(new KeyListModel), + mKeyList(new ListBox(mKeyListModel)), + mKeySetting(false) +{ + keyboard.setSetupKeyboard(this); + setName(_("Keyboard")); + + refreshKeys(); + + mKeyList->addActionListener(this); + + ScrollArea *scrollArea = new ScrollArea(mKeyList); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mAssignKeyButton = new Button(_("Assign"), "assign", this); + mAssignKeyButton->addActionListener(this); + mAssignKeyButton->setEnabled(false); + + mUnassignKeyButton = new Button(_("Unassign"), "unassign", this); + mUnassignKeyButton->addActionListener(this); + mUnassignKeyButton->setEnabled(false); + + mMakeDefaultButton = new Button(_("Default"), "makeDefault", this); + mMakeDefaultButton->addActionListener(this); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, scrollArea, 4, 6).setPadding(2); + place(0, 6, mMakeDefaultButton); + place(2, 6, mAssignKeyButton); + place(3, 6, mUnassignKeyButton); + + setDimension(gcn::Rectangle(0, 0, 500, 350)); +} + +Setup_Keyboard::~Setup_Keyboard() +{ + delete mKeyList; + mKeyList = 0; + delete mKeyListModel; + mKeyListModel = 0; + + delete mAssignKeyButton; + mAssignKeyButton = 0; + delete mUnassignKeyButton; + mUnassignKeyButton = 0; + delete mMakeDefaultButton; + mMakeDefaultButton = 0; +} + +void Setup_Keyboard::apply() +{ + keyUnresolved(); + + if (keyboard.hasConflicts()) + { + new OkDialog(_("Key Conflict(s) Detected."), + keyboard.getBindError()); + } + keyboard.setEnabled(true); + keyboard.store(); +} + +void Setup_Keyboard::cancel() +{ + keyUnresolved(); + + keyboard.retrieve(); + keyboard.setEnabled(true); + + refreshKeys(); +} + +void Setup_Keyboard::action(const gcn::ActionEvent &event) +{ + if (event.getSource() == mKeyList) + { + if (!mKeySetting) + { + mAssignKeyButton->setEnabled(true); + mUnassignKeyButton->setEnabled(true); + } + } + else if (event.getId() == "assign") + { + mKeySetting = true; + mAssignKeyButton->setEnabled(false); + keyboard.setEnabled(false); + int i(mKeyList->getSelected()); + keyboard.setNewKeyIndex(i); + mKeyListModel->setElementAt(i, keyboard.getKeyCaption(i) + ": ?"); + } + else if (event.getId() == "unassign") + { + int i(mKeyList->getSelected()); + keyboard.setNewKeyIndex(i); + refreshAssignedKey(mKeyList->getSelected()); + keyboard.setNewKey(keyboard.KEY_NO_VALUE); + mAssignKeyButton->setEnabled(true); + } + else if (event.getId() == "makeDefault") + { + keyboard.makeDefault(); + refreshKeys(); + } +} + +void Setup_Keyboard::refreshAssignedKey(int index) +{ + std::string caption; + char *temp = SDL_GetKeyName( + static_cast(keyboard.getKeyValue(index))); + caption = keyboard.getKeyCaption(index) + ": " + toString(temp); + mKeyListModel->setElementAt(index, caption); +} + +void Setup_Keyboard::newKeyCallback(int index) +{ + mKeySetting = false; + refreshAssignedKey(index); + mAssignKeyButton->setEnabled(true); +} + +void Setup_Keyboard::refreshKeys() +{ + for (int i = 0; i < keyboard.KEY_TOTAL; i++) + refreshAssignedKey(i); +} + +void Setup_Keyboard::keyUnresolved() +{ + if (mKeySetting) + { + newKeyCallback(keyboard.getNewKeyIndex()); + keyboard.setNewKeyIndex(keyboard.KEY_NO_VALUE); + } +} diff --git a/src/gui/setup_keyboard.h b/src/gui/setup_keyboard.h new file mode 100644 index 000000000..4c916705c --- /dev/null +++ b/src/gui/setup_keyboard.h @@ -0,0 +1,81 @@ +/* + * Custom keyboard shortcuts configuration + * Copyright (C) 2007 Joshua Langley + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_SETUP_KEYBOARD_H +#define GUI_SETUP_KEYBOARD_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include + +#include + +class Setup_Keyboard : public SetupTab, public gcn::ActionListener +{ + public: + /** + * Constructor + */ + Setup_Keyboard(); + + /** + * Destructor + */ + ~Setup_Keyboard(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + /** + * Get an update on the assigned key. + */ + void refreshAssignedKey(int index); + + /** + * The callback function when a new key has been pressed. + */ + void newKeyCallback(int index); + + /** + * Shorthand method to update all the keys. + */ + void refreshKeys(); + + /** + * If a key function is unresolved, then this reverts it. + */ + void keyUnresolved(); + + private: + class KeyListModel *mKeyListModel; + gcn::ListBox *mKeyList; + + gcn::Button *mAssignKeyButton; + gcn::Button *mUnassignKeyButton; + gcn::Button *mMakeDefaultButton; + + bool mKeySetting; /**< flag to check if key being set. */ +}; + +#endif diff --git a/src/gui/setup_other.cpp b/src/gui/setup_other.cpp new file mode 100644 index 000000000..54ec1c65d --- /dev/null +++ b/src/gui/setup_other.cpp @@ -0,0 +1,426 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/setup_other.h" +#include "gui/editdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/textfield.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "utils/gettext.h" + +#define ACTION_SHOW_TAKEDDAMAGE "taked damage" +#define ACTION_NO_RAIN "no rain" +#define ACTION_ONLY_REACHABLE "only reachable" +#define ACTION_ERRORS_IN_DEBUG "errors in debug" +#define ACTION_HIGHLIGHT_PORTALS "highlight portals" +#define ACTION_HIGHLIGHT_ATTACK_RANGE "highlight attack" +#define ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE "highlight monster attack" +#define ACTION_CYCLE_PLAYERS "cycle players" +#define ACTION_CYCLE_MONSTERS "cycle monsters" +#define ACTION_ENABLE_BOTCHECKER "bot checker" +#define ACTION_FLOORITEMS_HIGHLIGHT "floor items" +#define ACTION_MOVE_PROGRAM "move program" +#define ACTION_EDIT_PROGRAM "edit program" +#define ACTION_EDIT_PROGRAM_OK "edit program ok" +#define ACTION_AFK "move afk" +#define ACTION_EDIT_AFK "edit afk" +#define ACTION_EDIT_AFK_OK "edit afk ok" +#define ACTION_ENABLE_AFK "enable afk" +#define ACTION_TRADEBOT "trade bot" +#define ACTION_BUGGY_SERVERS "buggy servers" +#define ACTION_DEBUG_LOG "debug log" +#define ACTION_SERVER_ATTACK "server attack" +#define ACTION_FIX_POS "fix pos" +#define ACTION_ATTACK_MOVING "attack moving" +#define ACTION_QUICK_STATS "quick stats" +#define ACTION_WARP_PARTICLE "warp particle" +#define ACTION_AUTO_SHOP "auto shop" +#define ACTION_SHOW_MOB_HP "show mob hp" + +Setup_Other::Setup_Other(): + mShowMonstersTakedDamage(config.getBoolValue("showMonstersTakedDamage")), + mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")), + mErrorsInDebug(config.getBoolValue("errorsInDebug")), + mHighlightPortals(config.getBoolValue("highlightMapPortals")), + mHighlightAttackRange(config.getBoolValue("highlightAttackRange")), + mHighlightMonsterAttackRange( + config.getBoolValue("highlightMonsterAttackRange")), + mCyclePlayers(config.getBoolValue("cyclePlayers")), + mCycleMonsters(config.getBoolValue("cycleMonsters")), + mEnableBotChecker(config.getBoolValue("enableBotCheker")), + mFloorItemsHighlight(config.getBoolValue("floorItemsHighlight")), + mMoveProgram(config.getStringValue("crazyMoveProgram")), + mAfk(config.getStringValue("afkMessage")), + mTradeBot(config.getBoolValue("tradebot")), + mBuggyServers(serverConfig.getValueBool("enableBuggyServers", true)), + mDebugLog(config.getBoolValue("debugLog")), + mServerAttack(config.getBoolValue("serverAttack")), + mAutofixPos(config.getBoolValue("autofixPos")), + mAttackMoving(config.getBoolValue("attackMoving")), + mQuickStats(config.getBoolValue("quickStats")), + mWarpParticle(config.getBoolValue("warpParticle")), + mAutoShop(config.getBoolValue("autoShop")), + mShowMobHP(config.getBoolValue("showMobHP")) +{ + setName(_("Misc")); + + mShowMonstersTakedDamageCheckBox = new CheckBox( + _("Show damage inflicted to monsters"), + mShowMonstersTakedDamage, + this, ACTION_SHOW_TAKEDDAMAGE); + + mTargetOnlyReachableCheckBox = new CheckBox( + _("Auto target only reachable monsters"), + mTargetOnlyReachable, + this, ACTION_ONLY_REACHABLE); + + mHighlightPortalsCheckBox = new CheckBox(_("Highlight map portals"), + mHighlightPortals, + this, ACTION_HIGHLIGHT_PORTALS); + + mHighlightAttackRangeCheckBox = new CheckBox( + _("Highlight player attack range"), + mHighlightAttackRange, + this, ACTION_HIGHLIGHT_ATTACK_RANGE); + + mHighlightMonsterAttackRangeCheckBox = new CheckBox( + _("Highlight monster attack range"), + mHighlightMonsterAttackRange, + this, ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE); + + mCyclePlayersCheckBox = new CheckBox(_("Cycle player targets"), + mCyclePlayers, this, ACTION_CYCLE_PLAYERS); + + mCycleMonstersCheckBox = new CheckBox(_("Cycle monster targets"), + mCycleMonsters, this, ACTION_CYCLE_MONSTERS); + + mEnableBotCheckerCheckBox = new CheckBox(_("Enable bot checker"), + mEnableBotChecker, this, ACTION_ENABLE_BOTCHECKER); + + mFloorItemsHighlightCheckBox = new CheckBox(_("Highlight floor items"), + mFloorItemsHighlight, this, ACTION_FLOORITEMS_HIGHLIGHT); + + mMoveProgramLabel = new Label(_("Crazy move A program")); + + mMoveProgramField = new TextField(mMoveProgram, true, + this, ACTION_MOVE_PROGRAM); + + mMoveProgramButton = new Button(_("Edit"), ACTION_EDIT_PROGRAM, this); + + mAfkField = new TextField(mAfk, true, this, ACTION_AFK); + + mAfkButton = new Button(_("Edit"), ACTION_EDIT_AFK, this); + + mTradeBotCheckBox = new CheckBox(_("Enable shop mode"), + mTradeBot, + this, ACTION_TRADEBOT); + + mBuggyServersCheckBox = new CheckBox(_("Enable buggy servers protection"), + mBuggyServers, + this, ACTION_BUGGY_SERVERS); + + mDebugLogCheckBox = new CheckBox(_("Enable debug log"), + mDebugLog, + this, ACTION_DEBUG_LOG); + + mServerAttackCheckBox = new CheckBox(_("Enable server side attack"), + mServerAttack, + this, ACTION_SERVER_ATTACK); + + mAutofixPosCheckBox = new CheckBox(_("Auto fix position"), + mAutofixPos, + this, ACTION_FIX_POS); + + mAttackMovingCheckBox = new CheckBox(_("Attack while moving"), + mAttackMoving, + this, ACTION_ATTACK_MOVING); + + mQuickStatsCheckBox = new CheckBox(_("Enable quick stats"), + mQuickStats, + this, ACTION_QUICK_STATS); + + mWarpParticleCheckBox = new CheckBox(_("Show warps particles"), + mWarpParticle, + this, ACTION_WARP_PARTICLE); + + mAutoShopCheckBox = new CheckBox(_("Accept sell/buy requests"), + mAutoShop, + this, ACTION_AUTO_SHOP); + + mShowMobHPCheckBox = new CheckBox(_("Show monster hp bar"), + mShowMobHP, + this, ACTION_SHOW_MOB_HP); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mShowMonstersTakedDamageCheckBox, 12); + place(12, 0, mServerAttackCheckBox, 10); + place(0, 1, mTargetOnlyReachableCheckBox, 12); + place(12, 1, mAutofixPosCheckBox, 10); + place(0, 2, mHighlightPortalsCheckBox, 12); + place(12, 2, mAttackMovingCheckBox, 10); + place(12, 3, mQuickStatsCheckBox, 10); + place(12, 4, mWarpParticleCheckBox, 10); + place(12, 5, mAutoShopCheckBox, 10); + place(12, 6, mShowMobHPCheckBox, 10); + place(0, 3, mFloorItemsHighlightCheckBox, 12); + place(0, 4, mHighlightAttackRangeCheckBox, 12); + place(0, 5, mHighlightMonsterAttackRangeCheckBox, 12); + place(0, 6, mCyclePlayersCheckBox, 12); + place(0, 7, mCycleMonstersCheckBox, 12); + place(0, 8, mEnableBotCheckerCheckBox, 12); + place(0, 9, mMoveProgramLabel, 12); + place(0, 10, mMoveProgramField, 9); + place(9, 10, mMoveProgramButton, 2); + place(0, 11, mAfkField, 9); + place(9, 11, mAfkButton, 2); + place(0, 12, mTradeBotCheckBox, 12); + place(0, 13, mBuggyServersCheckBox, 12); + place(0, 14, mDebugLogCheckBox, 12); + + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + + setDimension(gcn::Rectangle(0, 0, 550, 500)); +} + +void Setup_Other::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_SHOW_TAKEDDAMAGE) + { + mShowMonstersTakedDamage = mShowMonstersTakedDamageCheckBox + ->isSelected(); + } + else if (event.getId() == ACTION_ONLY_REACHABLE) + { + mTargetOnlyReachable = mTargetOnlyReachableCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIGHLIGHT_PORTALS) + { + mHighlightPortals = mHighlightPortalsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIGHLIGHT_ATTACK_RANGE) + { + mHighlightAttackRange = mHighlightAttackRangeCheckBox->isSelected(); + } + else if (event.getId() == ACTION_HIGHLIGHT_MONSTER_ATTACK_RANGE) + { + mHighlightMonsterAttackRange = mHighlightMonsterAttackRangeCheckBox + ->isSelected(); + } + else if (event.getId() == ACTION_CYCLE_PLAYERS) + { + mCyclePlayers = mCyclePlayersCheckBox->isSelected(); + } + else if (event.getId() == ACTION_CYCLE_MONSTERS) + { + mCycleMonsters = mCycleMonstersCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ENABLE_BOTCHECKER) + { + mEnableBotChecker = mEnableBotCheckerCheckBox->isSelected(); + } + else if (event.getId() == ACTION_FLOORITEMS_HIGHLIGHT) + { + mFloorItemsHighlight = mFloorItemsHighlightCheckBox->isSelected(); + } + else if (event.getId() == ACTION_MOVE_PROGRAM) + { + mMoveProgram = mMoveProgramField->getText(); + } + else if (event.getId() == ACTION_EDIT_PROGRAM) + { + mEditDialog = new EditDialog("Crazy Move A", + mMoveProgramField->getText(), ACTION_EDIT_PROGRAM_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_PROGRAM_OK) + { + mMoveProgramField->setText(mEditDialog->getMsg()); + } + + else if (event.getId() == ACTION_AFK) + mAfk = mAfkField->getText(); + else if (event.getId() == ACTION_EDIT_AFK) + { + mEditDialog = new EditDialog("Afk message", mAfkField->getText(), + ACTION_EDIT_AFK_OK); + mEditDialog->addActionListener(this); + } + else if (event.getId() == ACTION_EDIT_AFK_OK) + { + mAfkField->setText(mEditDialog->getMsg()); + } + else if (event.getId() == ACTION_TRADEBOT) + { + mTradeBot = mTradeBotCheckBox->isSelected(); + } + else if (event.getId() == ACTION_BUGGY_SERVERS) + { + mBuggyServers = mBuggyServersCheckBox->isSelected(); + } + else if (event.getId() == ACTION_DEBUG_LOG) + { + mDebugLog = mDebugLogCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SERVER_ATTACK) + { + mServerAttack = mServerAttackCheckBox->isSelected(); + } + else if (event.getId() == ACTION_FIX_POS) + { + mAutofixPos = mAutofixPosCheckBox->isSelected(); + } + else if (event.getId() == ACTION_ATTACK_MOVING) + { + mAttackMoving = mAttackMovingCheckBox->isSelected(); + } + else if (event.getId() == ACTION_QUICK_STATS) + { + mQuickStats = mQuickStatsCheckBox->isSelected(); + } + else if (event.getId() == ACTION_WARP_PARTICLE) + { + mWarpParticle = mWarpParticleCheckBox->isSelected(); + } + else if (event.getId() == ACTION_AUTO_SHOP) + { + mAutoShop = mAutoShopCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_MOB_HP) + { + mShowMobHP = mShowMobHPCheckBox->isSelected(); + } +} + +void Setup_Other::cancel() +{ + mShowMonstersTakedDamage = config.getBoolValue( + "showMonstersTakedDamage"); + mShowMonstersTakedDamageCheckBox->setSelected(mShowMonstersTakedDamage); + + mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable"); + mTargetOnlyReachableCheckBox->setSelected(mTargetOnlyReachable); + + mHighlightPortals = config.getBoolValue("highlightMapPortals"); + mHighlightPortalsCheckBox->setSelected(mHighlightPortals); + + mHighlightAttackRange = config.getBoolValue("highlightAttackRange"); + mHighlightAttackRangeCheckBox->setSelected(mHighlightAttackRange); + + mHighlightMonsterAttackRange = config.getBoolValue( + "highlightMonsterAttackRange"); + mHighlightMonsterAttackRangeCheckBox->setSelected( + mHighlightMonsterAttackRange); + + mCyclePlayers = config.getBoolValue("cyclePlayers"); + mCyclePlayersCheckBox->setSelected(mCyclePlayers); + + mCycleMonsters = config.getBoolValue("cycleMonsters"); + mCycleMonstersCheckBox->setSelected(mCycleMonsters); + + mEnableBotChecker = config.getBoolValue("enableBotCheker"); + mEnableBotCheckerCheckBox->setSelected(mEnableBotChecker); + + mFloorItemsHighlight = config.getBoolValue("floorItemsHighlight"); + mFloorItemsHighlightCheckBox->setSelected(mFloorItemsHighlight); + + mMoveProgram = config.getStringValue("crazyMoveProgram"); + mMoveProgramField->setText(mMoveProgram); + + mAfk = config.getStringValue("afkMessage"); + mAfkField->setText(mAfk); + + mTradeBot = config.getBoolValue("tradebot"); + mTradeBotCheckBox->setSelected(mTradeBot); + + mBuggyServers = serverConfig.getValueBool("enableBuggyServers", true); + mBuggyServersCheckBox->setSelected(mBuggyServers); + + mDebugLog = config.getBoolValue("debugLog"); + mDebugLogCheckBox->setSelected(mDebugLog); + + mServerAttack = config.getBoolValue("serverAttack"); + mServerAttackCheckBox->setSelected(mServerAttack); + + mAutofixPos = config.getBoolValue("autofixPos"); + mAutofixPosCheckBox->setSelected(mAutofixPos); + + mAttackMoving = config.getBoolValue("attackMoving"); + mAttackMovingCheckBox->setSelected(mAttackMoving); + + mQuickStats = config.getBoolValue("quickStats"); + mQuickStatsCheckBox->setSelected(mQuickStats); + + mWarpParticle = config.getBoolValue("warpParticle"); + mWarpParticleCheckBox->setSelected(mWarpParticle); + + mAutoShop = config.getBoolValue("autoShop"); + mAutoShopCheckBox->setSelected(mAutoShop); + + mShowMobHP = config.getBoolValue("showMobHP"); + mShowMobHPCheckBox->setSelected(mShowMobHP); +} + +void Setup_Other::apply() +{ + config.setValue("showMonstersTakedDamage", mShowMonstersTakedDamage); + config.setValue("targetOnlyReachable", mTargetOnlyReachable); + config.setValue("errorsInDebug", mErrorsInDebug); + config.setValue("highlightMapPortals", mHighlightPortals); + config.setValue("highlightAttackRange", mHighlightAttackRange); + config.setValue("highlightMonsterAttackRange", + mHighlightMonsterAttackRange); + config.setValue("cyclePlayers", mCyclePlayers); + config.setValue("cycleMonsters", mCycleMonsters); + config.setValue("enableBotCheker", mEnableBotChecker); + config.setValue("floorItemsHighlight", mFloorItemsHighlight); + config.setValue("crazyMoveProgram", mMoveProgramField->getText()); + config.setValue("afkMessage", mAfkField->getText()); + config.setValue("tradebot", mTradeBot); + serverConfig.setValue("enableBuggyServers", mBuggyServers); + config.setValue("debugLog", mDebugLog); + config.setValue("serverAttack", mServerAttack); + config.setValue("autofixPos", mAutofixPos); + config.setValue("attackMoving", mAttackMoving); + config.setValue("quickStats", mQuickStats); + config.setValue("warpParticle", mWarpParticle); + config.setValue("autoShop", mAutoShop); + config.setValue("showMobHP", mShowMobHP); + logger->setDebugLog(mDebugLog); +} + +void Setup_Other::externalUpdated() +{ + mBuggyServers = serverConfig.getValueBool("enableBuggyServers", true); + mBuggyServersCheckBox->setSelected(mBuggyServers); +} \ No newline at end of file diff --git a/src/gui/setup_other.h b/src/gui/setup_other.h new file mode 100644 index 000000000..b20401be5 --- /dev/null +++ b/src/gui/setup_other.h @@ -0,0 +1,125 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GUI_Setup_Other_H +#define GUI_Setup_Other_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include + +class EditDialog; + +class Setup_Other : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Other(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + virtual void externalUpdated(); + + private: + gcn::CheckBox *mShowMonstersTakedDamageCheckBox; + bool mShowMonstersTakedDamage; + + gcn::CheckBox *mNoRainCheckBox; + bool mNoRain; + + gcn::CheckBox *mTargetOnlyReachableCheckBox; + bool mTargetOnlyReachable; + + int mOverlayDetail; + gcn::DropDown *mFontSizeDropDown; + + gcn::CheckBox *mErrorsInDebugCheckBox; + bool mErrorsInDebug; + + gcn::CheckBox *mHighlightPortalsCheckBox; + bool mHighlightPortals; + + gcn::CheckBox *mHighlightAttackRangeCheckBox; + bool mHighlightAttackRange; + + gcn::CheckBox *mHighlightMonsterAttackRangeCheckBox; + bool mHighlightMonsterAttackRange; + + gcn::CheckBox *mCyclePlayersCheckBox; + bool mCyclePlayers; + + gcn::CheckBox *mCycleMonstersCheckBox; + bool mCycleMonsters; + + gcn::CheckBox *mEnableBotCheckerCheckBox; + bool mEnableBotChecker; + + gcn::CheckBox *mFloorItemsHighlightCheckBox; + bool mFloorItemsHighlight; + + gcn::Label *mMoveProgramLabel; + gcn::TextField *mMoveProgramField; + gcn::Button *mMoveProgramButton; + std::string mMoveProgram; + + gcn::TextField *mAfkField; + gcn::Button *mAfkButton; + std::string mAfk; + + gcn::CheckBox *mTradeBotCheckBox; + bool mTradeBot; + + gcn::CheckBox *mBuggyServersCheckBox; + bool mBuggyServers; + + gcn::CheckBox *mDebugLogCheckBox; + bool mDebugLog; + + gcn::CheckBox *mServerAttackCheckBox; + bool mServerAttack; + + gcn::CheckBox *mAutofixPosCheckBox; + bool mAutofixPos; + + gcn::CheckBox *mAttackMovingCheckBox; + bool mAttackMoving; + + gcn::CheckBox *mQuickStatsCheckBox; + bool mQuickStats; + + gcn::CheckBox *mWarpParticleCheckBox; + bool mWarpParticle; + + gcn::CheckBox *mAutoShopCheckBox; + bool mAutoShop; + + gcn::CheckBox *mShowMobHPCheckBox; + bool mShowMobHP; + + EditDialog *mEditDialog; +}; + +#endif diff --git a/src/gui/setup_players.cpp b/src/gui/setup_players.cpp new file mode 100644 index 000000000..6f4d50888 --- /dev/null +++ b/src/gui/setup_players.cpp @@ -0,0 +1,505 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/setup_players.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/okdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/table.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +#include +#include + +#define COLUMNS_NR 2 // name plus listbox +#define NAME_COLUMN 0 +#define RELATION_CHOICE_COLUMN 1 + +#define ROW_HEIGHT 12 +// The following column widths really shouldn't be hardcoded but should scale with the size of the widget... except +// that, right now, the widget doesn't exactly scale either. +#define NAME_COLUMN_WIDTH 230 +#define RELATION_CHOICE_COLUMN_WIDTH 80 + +#define WIDGET_AT(row, column) (((row) * COLUMNS_NR) + column) + +static const char *table_titles[COLUMNS_NR] = +{ + N_("Name"), + N_("Relation") +}; + +static const char *RELATION_NAMES[PlayerRelation::RELATIONS_NR] = +{ + N_("Neutral"), + N_("Friend"), + N_("Disregarded"), + N_("Ignored"), + N_("Erased") +}; + +class PlayerRelationListModel : public gcn::ListModel +{ +public: + virtual ~PlayerRelationListModel() { } + + virtual int getNumberOfElements() + { + return PlayerRelation::RELATIONS_NR; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return ""; + return gettext(RELATION_NAMES[i]); + } +}; + +class PlayerTableModel : public TableModel +{ +public: + PlayerTableModel() : + mPlayers(NULL), + mListModel(new PlayerRelationListModel) + { + playerRelationsUpdated(); + } + + virtual ~PlayerTableModel() + { + freeWidgets(); + delete mListModel; + mListModel = 0; + delete mPlayers; + mPlayers = 0; + } + + virtual int getRows() const + { + if (mPlayers) + return static_cast(mPlayers->size()); + else + return 0; + } + + virtual int getColumns() const + { + return COLUMNS_NR; + } + + virtual int getRowHeight() const + { + return ROW_HEIGHT; + } + + virtual int getColumnWidth(int index) const + { + if (index == NAME_COLUMN) + return NAME_COLUMN_WIDTH; + else + return RELATION_CHOICE_COLUMN_WIDTH; + } + + virtual void playerRelationsUpdated() + { + signalBeforeUpdate(); + + freeWidgets(); + std::vector *player_names = player_relations.getPlayers(); + + if (!player_names) + return; + + delete mPlayers; + mPlayers = player_names; + + // set up widgets + for (unsigned int r = 0; r < player_names->size(); ++r) + { + std::string name = (*player_names)[r]; + gcn::Widget *widget = new Label(name); + mWidgets.push_back(widget); + + gcn::DropDown *choicebox = new DropDown(mListModel); + choicebox->setSelected(player_relations.getRelation(name)); + mWidgets.push_back(choicebox); + } + + signalAfterUpdate(); + } + + virtual void updateModelInRow(int row) + { + gcn::DropDown *choicebox = static_cast( + getElementAt(row, RELATION_CHOICE_COLUMN)); + player_relations.setRelation(getPlayerAt(row), + static_cast( + choicebox->getSelected())); + } + + + virtual gcn::Widget *getElementAt(int row, int column) const + { + return mWidgets[WIDGET_AT(row, column)]; + } + + virtual void freeWidgets() + { + delete mPlayers; + mPlayers = 0; + + delete_all(mWidgets); + mWidgets.clear(); + } + + std::string getPlayerAt(int index) const + { + return (*mPlayers)[index]; + } + +protected: + std::vector *mPlayers; + std::vector mWidgets; + PlayerRelationListModel *mListModel; +}; + +/** + * Class for choosing one of the various `what to do when ignoring a player' options + */ +class IgnoreChoicesListModel : public gcn::ListModel +{ +public: + virtual ~IgnoreChoicesListModel() { } + + virtual int getNumberOfElements() + { + return static_cast(player_relations.getPlayerIgnoreStrategies() + ->size()); + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return (*player_relations.getPlayerIgnoreStrategies()) + [i]->mDescription; + } +}; + +#define ACTION_DELETE "delete" +#define ACTION_OLD "old" +#define ACTION_TABLE "table" +#define ACTION_STRATEGY "strategy" +#define ACTION_WHISPER_TAB "whisper tab" +#define ACTION_SHOW_GENDER "show gender" +#define ACTION_SHOW_LEVEL "show level" +#define ACTION_TARGET_DEAD "target dead" +#define ACTION_SHOW_OWN_NAME "show own name" + +Setup_Players::Setup_Players(): + mPlayerTableTitleModel(new StaticTableModel(1, COLUMNS_NR)), + mPlayerTableModel(new PlayerTableModel), + mPlayerTable(new GuiTable(mPlayerTableModel)), + mPlayerTitleTable(new GuiTable(mPlayerTableTitleModel)), + mPlayerScrollArea(new ScrollArea(mPlayerTable)), + mDefaultTrading(new CheckBox(_("Allow trading"), + player_relations.getDefault() & PlayerRelation::TRADE)), + mDefaultWhisper(new CheckBox(_("Allow whispers"), + player_relations.getDefault() & PlayerRelation::WHISPER)), + mDeleteButton(new Button(_("Delete"), ACTION_DELETE, this)), + mOldButton(new Button(_("Old"), ACTION_OLD, this)), + mWhisperTab(config.getBoolValue("whispertab")), + mWhisperTabCheckBox(new CheckBox(_("Put all whispers in tabs"), + mWhisperTab)), + mShowGender(config.getBoolValue("showgender")), + mShowGenderCheckBox(new CheckBox(_("Show gender"), mShowGender)), + mShowLevel(config.getBoolValue("showlevel")), + mShowOwnName(config.getBoolValue("showownname")), + mTargetDead(config.getBoolValue("targetDeadPlayers")) +{ + setName(_("Players")); + + mPlayerTable->setOpaque(false); + + mPlayerTableTitleModel->fixColumnWidth(NAME_COLUMN, NAME_COLUMN_WIDTH); + mPlayerTableTitleModel->fixColumnWidth(RELATION_CHOICE_COLUMN, + RELATION_CHOICE_COLUMN_WIDTH); + mPlayerTitleTable->setBackgroundColor(gcn::Color(0xbf, 0xbf, 0xbf)); + + mIgnoreActionChoicesModel = new IgnoreChoicesListModel; + mIgnoreActionChoicesBox = new DropDown(mIgnoreActionChoicesModel); + + for (int i = 0; i < COLUMNS_NR; i++) + { + mPlayerTableTitleModel->set(0, i, + new Label(gettext(table_titles[i]))); + } + + mPlayerTitleTable->setLinewiseSelection(true); + + mPlayerScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mPlayerTable->setActionEventId(ACTION_TABLE); + mPlayerTable->setLinewiseSelection(true); + mPlayerTable->addActionListener(this); + + gcn::Label *ignore_action_label = new Label(_("When ignoring:")); + + mIgnoreActionChoicesBox->setActionEventId(ACTION_STRATEGY); + mIgnoreActionChoicesBox->addActionListener(this); + + int ignore_strategy_index = 0; // safe default + + if (player_relations.getPlayerIgnoreStrategy()) + { + ignore_strategy_index = player_relations.getPlayerIgnoreStrategyIndex( + player_relations.getPlayerIgnoreStrategy()->mShortName); + if (ignore_strategy_index < 0) + ignore_strategy_index = 0; + } + mIgnoreActionChoicesBox->setSelected(ignore_strategy_index); + mIgnoreActionChoicesBox->adjustHeight(); + + mWhisperTabCheckBox->setActionEventId(ACTION_WHISPER_TAB); + mWhisperTabCheckBox->addActionListener(this); + + mShowGenderCheckBox->setActionEventId(ACTION_SHOW_GENDER); + mShowGenderCheckBox->addActionListener(this); + + mShowLevelCheckBox = new CheckBox(_("Show level"), mShowLevel); + mShowLevelCheckBox->setActionEventId(ACTION_SHOW_LEVEL); + mShowLevelCheckBox->addActionListener(this); + + mShowOwnNameCheckBox = new CheckBox(_("Show own name"), mShowOwnName); + mShowOwnNameCheckBox->setActionEventId(ACTION_SHOW_OWN_NAME); + mShowOwnNameCheckBox->addActionListener(this); + + mTargetDeadCheckBox = new CheckBox(_("Target dead players"), mTargetDead); + mTargetDeadCheckBox->setActionEventId(ACTION_TARGET_DEAD); + mTargetDeadCheckBox->addActionListener(this); + + reset(); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mPlayerTitleTable, 5); + place(0, 1, mPlayerScrollArea, 5, 4).setPadding(2); + place(0, 5, mDeleteButton); + place(0, 6, mShowGenderCheckBox, 3).setPadding(2); + place(0, 7, mShowLevelCheckBox, 3).setPadding(2); + place(0, 8, mShowOwnNameCheckBox, 3).setPadding(2); + place(1, 5, mOldButton, 1); + place(3, 5, ignore_action_label); + place(3, 6, mIgnoreActionChoicesBox, 2).setPadding(2); + place(3, 7, mDefaultTrading); + place(3, 8, mDefaultWhisper); + place(0, 9, mWhisperTabCheckBox, 4).setPadding(4); + place(0, 10, mTargetDeadCheckBox, 4).setPadding(4); + + player_relations.addListener(this); + + setDimension(gcn::Rectangle(0, 0, 500, 350)); +} + +Setup_Players::~Setup_Players() +{ + player_relations.removeListener(this); + delete mIgnoreActionChoicesModel; + mIgnoreActionChoicesModel = 0; +} + + +void Setup_Players::reset() +{ + // We now have to search through the list of ignore choices to find the + // current selection. We could use an index into the table of config + // options in player_relations instead of strategies to sidestep this. + int selection = 0; + for (unsigned int i = 0; + i < player_relations.getPlayerIgnoreStrategies()->size(); + ++i) + if ((*player_relations.getPlayerIgnoreStrategies())[i] == + player_relations.getPlayerIgnoreStrategy()) + { + + selection = i; + break; + } + + mIgnoreActionChoicesBox->setSelected(selection); +} + +void Setup_Players::apply() +{ + player_relations.store(); + + unsigned int old_default_relations = player_relations.getDefault() & + ~(PlayerRelation::TRADE | + PlayerRelation::WHISPER); + player_relations.setDefault(old_default_relations + | (mDefaultTrading->isSelected() ? + PlayerRelation::TRADE : 0) + | (mDefaultWhisper->isSelected() ? + PlayerRelation::WHISPER : 0)); + config.setValue("whispertab", mWhisperTab); + config.setValue("showlevel", mShowLevel); + config.setValue("showownname", mShowOwnName); + config.setValue("targetDeadPlayers", mTargetDead); + config.setValue("showgender", mShowGender); + + if (actorSpriteManager) + actorSpriteManager->updatePlayerNames(); + + if (player_node) + player_node->setCheckNameSetting(true); +} + +void Setup_Players::cancel() +{ + mWhisperTab = config.getBoolValue("whispertab"); + mWhisperTabCheckBox->setSelected(mWhisperTab); + mShowGender = config.getBoolValue("showgender"); + mShowGenderCheckBox->setSelected(mShowGender); + mShowLevel = config.getBoolValue("showlevel"); + mShowLevelCheckBox->setSelected(mShowLevel); + mShowOwnName = config.getBoolValue("showownname"); + mShowOwnNameCheckBox->setSelected(mShowOwnName); + mTargetDead = config.getBoolValue("targetDeadPlayers"); + mTargetDeadCheckBox->setSelected(mTargetDead); +} + +void Setup_Players::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_TABLE) + { + // temporarily eliminate ourselves: we are fully aware of this change, + // so there is no need for asynchronous updates. (In fact, thouse + // might destroy the widet that triggered them, which would be rather + // embarrassing.) + player_relations.removeListener(this); + + int row = mPlayerTable->getSelectedRow(); + if (row >= 0) + mPlayerTableModel->updateModelInRow(row); + + player_relations.addListener(this); + + } + else if (event.getId() == ACTION_DELETE) + { + int player_index = mPlayerTable->getSelectedRow(); + + if (player_index < 0) + return; + + std::string name = mPlayerTableModel->getPlayerAt(player_index); + + player_relations.removePlayer(name); + } + else if (event.getId() == ACTION_OLD) + { + player_relations.load(true); + updateAll(); + } + else if (event.getId() == ACTION_STRATEGY) + { + PlayerIgnoreStrategy *s = + (*player_relations.getPlayerIgnoreStrategies())[ + mIgnoreActionChoicesBox->getSelected()]; + + player_relations.setPlayerIgnoreStrategy(s); + } + else if (event.getId() == ACTION_WHISPER_TAB) + { + mWhisperTab = mWhisperTabCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_GENDER) + { + mShowGender = mShowGenderCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_LEVEL) + { + mShowLevel = mShowLevelCheckBox->isSelected(); + } + else if (event.getId() == ACTION_SHOW_OWN_NAME) + { + mShowOwnName = mShowOwnNameCheckBox->isSelected(); + } + else if (event.getId() == ACTION_TARGET_DEAD) + { + mTargetDead = mTargetDeadCheckBox->isSelected(); + } +} + + +void Setup_Players::updatedPlayer(const std::string &name _UNUSED_) +{ + mPlayerTableModel->playerRelationsUpdated(); + mDefaultTrading->setSelected( + player_relations.getDefault() & PlayerRelation::TRADE); + mDefaultWhisper->setSelected( + player_relations.getDefault() & PlayerRelation::WHISPER); + if (player_node) + player_node->updateName(); +} + +void Setup_Players::updateAll() +{ + PlayerTableModel *model = new PlayerTableModel(); + mPlayerTable->setModel(model); + delete mPlayerTableModel; + mPlayerTableModel = model; + int ignore_strategy_index = 0; // safe default + + if (player_relations.getPlayerIgnoreStrategy()) + { + ignore_strategy_index = player_relations.getPlayerIgnoreStrategyIndex( + player_relations.getPlayerIgnoreStrategy()->mShortName); + if (ignore_strategy_index < 0) + ignore_strategy_index = 0; + } + mIgnoreActionChoicesBox->setSelected(ignore_strategy_index); + mIgnoreActionChoicesBox->adjustHeight(); + reset(); +} +void Setup_Players::externalUpdated() +{ + mDefaultTrading->setSelected( + player_relations.getDefault() & PlayerRelation::TRADE); + mDefaultWhisper->setSelected( + player_relations.getDefault() & PlayerRelation::WHISPER); +} \ No newline at end of file diff --git a/src/gui/setup_players.h b/src/gui/setup_players.h new file mode 100644 index 000000000..21d3bb3e8 --- /dev/null +++ b/src/gui/setup_players.h @@ -0,0 +1,95 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_SETUP_PLAYERS_H +#define GUI_SETUP_PLAYERS_H + +#include "guichanfwd.h" +#include "playerrelations.h" + +#include "gui/widgets/setuptab.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class GuiTable; +class PlayerTableModel; +class StaticTableModel; + +class Setup_Players : public SetupTab, + public gcn::ActionListener, + public PlayerRelationsListener +{ +public: + Setup_Players(); + virtual ~Setup_Players(); + + void apply(); + void cancel(); + + void reset(); + + void action(const gcn::ActionEvent &event); + + virtual void updatedPlayer(const std::string &name); + + virtual void updateAll(); + + virtual void externalUpdated(); + +private: + StaticTableModel *mPlayerTableTitleModel; + PlayerTableModel *mPlayerTableModel; + GuiTable *mPlayerTable; + GuiTable *mPlayerTitleTable; + gcn::ScrollArea *mPlayerScrollArea; + + gcn::CheckBox *mDefaultTrading; + gcn::CheckBox *mDefaultWhisper; + + gcn::Button *mDeleteButton; + gcn::Button *mOldButton; + + gcn::ListModel *mIgnoreActionChoicesModel; + gcn::DropDown *mIgnoreActionChoicesBox; + + bool mWhisperTab; + gcn::CheckBox *mWhisperTabCheckBox; + + bool mShowGender; + gcn::CheckBox *mShowGenderCheckBox; + + bool mShowLevel; + gcn::CheckBox *mShowLevelCheckBox; + + bool mShowOwnName; + gcn::CheckBox *mShowOwnNameCheckBox; + + bool mTargetDead; + gcn::CheckBox *mTargetDeadCheckBox; +}; + +#endif diff --git a/src/gui/setup_theme.cpp b/src/gui/setup_theme.cpp new file mode 100644 index 000000000..faaeeb00f --- /dev/null +++ b/src/gui/setup_theme.cpp @@ -0,0 +1,239 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/setup_theme.h" + +#include "gui/gui.h" +#include "gui/editdialog.h" +#include "gui/okdialog.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/dropdown.h" + +#include "configuration.h" +#include "localplayer.h" +#include "log.h" + +#include "utils/gettext.h" + +#include "resources/resourcemanager.h" + +const char* ACTION_THEME = "theme"; +const char* ACTION_FONT = "font"; +const char* ACTION_BOLD_FONT = "bold font"; +const char* ACTION_PARTICLE_FONT = "particle font"; +const char* ACTION_HELP_FONT = "help font"; + +class NamesModel : public gcn::ListModel +{ +public: + NamesModel() + { + } + + virtual ~NamesModel() { } + + virtual int getNumberOfElements() + { + return static_cast(mNames.size()); + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return mNames[i]; + } + +protected: + std::vector mNames; +}; + +class ThemesModel : public NamesModel +{ +public: + ThemesModel() + { + mNames.push_back("(default)"); + Theme::fillSkinsList(mNames); + } + + virtual ~ThemesModel() + { } +}; + +class FontsModel : public NamesModel +{ +public: + FontsModel() + { Theme::fillFontsList(mNames); } + + virtual ~FontsModel() + { } +}; + +Setup_Theme::Setup_Theme(): + mTheme(config.getValue("theme", config.getValue("selectedSkin", ""))), + mFont(config.getStringValue("font")), + mBoldFont(config.getStringValue("boldFont")), + mParticleFont(config.getStringValue("particleFont")), + mHelpFont(config.getStringValue("helpFont")) +{ + setName(_("Theme")); + + mThemeLabel = new Label(_("Gui theme")); + mFontLabel = new Label(_("Main Font")); + mBoldFontLabel = new Label(_("Bold font")); + mParticleFontLabel = new Label(_("Particle font")); + mHelpFontLabel = new Label(_("Help font")); + mThemesModel = new ThemesModel(); + mFontsModel = new FontsModel(); + + mThemeDropDown = new DropDown(mThemesModel); + mThemeDropDown->setActionEventId(ACTION_THEME); + mThemeDropDown->addActionListener(this); + + mFontDropDown = new DropDown(mFontsModel); + mFontDropDown->setActionEventId(ACTION_FONT); + mFontDropDown->addActionListener(this); + + mBoldFontDropDown = new DropDown(mFontsModel); + mBoldFontDropDown->setActionEventId(ACTION_BOLD_FONT); + mBoldFontDropDown->addActionListener(this); + + mParticleFontDropDown = new DropDown(mFontsModel); + mParticleFontDropDown->setActionEventId(ACTION_PARTICLE_FONT); + mParticleFontDropDown->addActionListener(this); + + mHelpFontDropDown = new DropDown(mFontsModel); + mHelpFontDropDown->setActionEventId(ACTION_HELP_FONT); + mHelpFontDropDown->addActionListener(this); + + std::string skin = Theme::getThemeName(); + if (!skin.empty()) + mThemeDropDown->setSelectedString(skin); + else + mThemeDropDown->setSelected(0); + + mFontDropDown->setSelectedString(getFileName( + config.getStringValue("font"))); + mBoldFontDropDown->setSelectedString(getFileName( + config.getStringValue("boldFont"))); + mParticleFontDropDown->setSelectedString(getFileName( + config.getStringValue("particleFont"))); + mHelpFontDropDown->setSelectedString(getFileName( + config.getStringValue("helpFont"))); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, mThemeLabel, 10); + place(0, 1, mThemeDropDown, 6); + place(0, 2, mFontLabel, 10); + place(0, 3, mFontDropDown, 6); + place(0, 4, mBoldFontLabel, 10); + place(0, 5, mBoldFontDropDown, 6); + place(0, 6, mParticleFontLabel, 10); + place(0, 7, mParticleFontDropDown, 6); + place(0, 8, mHelpFontLabel, 10); + place(0, 9, mHelpFontDropDown, 6); + + place.getCell().matchColWidth(0, 0); + place = h.getPlacer(0, 1); + + setDimension(gcn::Rectangle(0, 0, 365, 500)); +} + +Setup_Theme::~Setup_Theme() +{ + delete mThemesModel; + mThemesModel = 0; + + delete mFontsModel; + mFontsModel = 0; +} + +void Setup_Theme::action(const gcn::ActionEvent &event) +{ + if (event.getId() == ACTION_THEME) + { + if (mThemeDropDown->getSelected() == 0) + mTheme = ""; + else + mTheme = mThemeDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_FONT) + { + mFont = mFontDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_BOLD_FONT) + { + mBoldFont = mBoldFontDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_PARTICLE_FONT) + { + mParticleFont = mParticleFontDropDown->getSelectedString(); + } + else if (event.getId() == ACTION_HELP_FONT) + { + mHelpFont = mHelpFontDropDown->getSelectedString(); + } +} + +void Setup_Theme::cancel() +{ + mTheme = config.getValue("theme", config.getValue("selectedSkin", "")); + mFont = getFileName(config.getStringValue("font")); + mBoldFont = getFileName(config.getStringValue("boldFont")); + mParticleFont = getFileName(config.getStringValue("particleFont")); + mHelpFont = getFileName(config.getStringValue("helpFont")); +} + +void Setup_Theme::apply() +{ + if (config.getValue("theme", + config.getValue("selectedSkin", "")) != mTheme) + { + new OkDialog(_("Theme Changed"), + _("Restart your client for the change to take effect.")); + } + config.setValue("selectedSkin", ""); + config.setValue("theme", mTheme); + if (config.getValue("font", "dejavusans.ttf") != mFont + || config.getValue("boldFont", "dejavusans-bold.ttf") != mBoldFont + || config.getValue("particleFont", "dejavusans.ttf") != mParticleFont + || config.getValue("helpFont", "dejavusansmono.ttf") != mHelpFont) + { + config.setValue("font", "fonts/" + getFileName(mFont)); + config.setValue("boldFont", "fonts/" + getFileName(mBoldFont)); + config.setValue("particleFont", "fonts/" + getFileName(mParticleFont)); + config.setValue("helpFont", "fonts/" + getFileName(mHelpFont)); + gui->updateFonts(); + } +} diff --git a/src/gui/setup_theme.h b/src/gui/setup_theme.h new file mode 100644 index 000000000..6b2c99999 --- /dev/null +++ b/src/gui/setup_theme.h @@ -0,0 +1,74 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef GUI_Setup_Theme_H +#define GUI_Setup_Theme_H + +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include + +class FontsModel; +class EditDialog; +class DropDown; +class ThemesModel; + +class Setup_Theme : public SetupTab, public gcn::ActionListener +{ + public: + Setup_Theme(); + ~Setup_Theme(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + private: + gcn::Label *mThemeLabel; + DropDown *mThemeDropDown; + std::string mTheme; + ThemesModel *mThemesModel; + FontsModel *mFontsModel; + + gcn::Label *mFontLabel; + DropDown *mFontDropDown; + std::string mFont; + + gcn::Label *mBoldFontLabel; + DropDown *mBoldFontDropDown; + std::string mBoldFont; + + gcn::Label *mParticleFontLabel; + DropDown *mParticleFontDropDown; + std::string mParticleFont; + + gcn::Label *mHelpFontLabel; + DropDown *mHelpFontDropDown; + std::string mHelpFont; + + EditDialog *mEditDialog; +}; + +#endif diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp new file mode 100644 index 000000000..9399756aa --- /dev/null +++ b/src/gui/setup_video.cpp @@ -0,0 +1,819 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/setup_video.h" + +#include "configuration.h" +#include "game.h" +#include "graphics.h" +#include "localplayer.h" +#include "log.h" +#include "main.h" +#include "particle.h" + +#include "gui/gui.h" +#include "gui/okdialog.h" +#include "gui/textdialog.h" + +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/dropdown.h" + +#include "resources/image.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +#include + +#include +#include + +extern Graphics *graphics; + +/** + * The list model for mode list. + * + * \ingroup Interface + */ +class ModeListModel : public gcn::ListModel +{ + public: + /** + * Constructor. + */ + ModeListModel(); + + /** + * Destructor. + */ + virtual ~ModeListModel() + { } + + /** + * Returns the number of elements in container. + */ + int getNumberOfElements() + { return static_cast(mVideoModes.size()); } + + /** + * Returns element from container. + */ + std::string getElementAt(int i) + { return mVideoModes[i]; } + + /** + * Returns the index corresponding to the given video mode. + * E.g.: "800x600". + * or -1 if not found. + */ + int getIndexOf(const std::string &widthXHeightMode); + + private: + std::vector mVideoModes; +}; + +ModeListModel::ModeListModel() +{ + /* Get available fullscreen/hardware modes */ + SDL_Rect **modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); + + /* Check which modes are available */ + if (modes == static_cast(0)) + { + logger->log1("No modes available"); + } + else if (modes == (SDL_Rect **)-1) + { + logger->log1("All resolutions available"); + } + else + { + for (int i = 0; modes[i]; ++i) + { + const std::string modeString = + toString(static_cast(modes[i]->w)) + "x" + + toString(static_cast(modes[i]->h)); + mVideoModes.push_back(modeString); + } + } + mVideoModes.push_back("custom"); +} + +int ModeListModel::getIndexOf(const std::string &widthXHeightMode) +{ + std::string currentMode = ""; + for (int i = 0; i < getNumberOfElements(); i++) + { + currentMode = getElementAt(i); + if (currentMode == widthXHeightMode) + return i; + } + return -1; +} + +const char *SIZE_NAME[6] = +{ + N_("Tiny (10)"), + N_("Small (11)"), + N_("Medium (12)"), + N_("Large (13)"), + N_("Big (14)"), + N_("Huge (15)"), +}; + +class FontSizeChoiceListModel : public gcn::ListModel +{ +public: + virtual ~FontSizeChoiceListModel() + { } + + virtual int getNumberOfElements() + { return 6; } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return SIZE_NAME[i]; + } +}; + +const char *OPENGL_NAME[3] = +{ + N_("Software"), + N_("Fast OpenGL"), + N_("Safe OpenGL"), +}; + +class OpenGLListModel : public gcn::ListModel +{ +public: + virtual ~OpenGLListModel() + { } + + virtual int getNumberOfElements() + { return 3; } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return OPENGL_NAME[i]; + } +}; + +static const char *speechModeToString(Being::Speech mode) +{ + switch (mode) + { + case Being::NO_SPEECH: + default: + return _("No text"); + case Being::TEXT_OVERHEAD: + return _("Text"); + case Being::NO_NAME_IN_BUBBLE: + return _("Bubbles, no names"); + case Being::NAME_IN_BUBBLE: + return _("Bubbles with names"); + } + return ""; +} + +const char *Setup_Video::overlayDetailToString(int detail) +{ + if (detail == -1) + detail = config.getIntValue("OverlayDetail"); + + switch (detail) + { + case 0: + return _("off"); + case 1: + return _("low"); + case 2: + return _("high"); + default: + return ""; + } + return ""; +} + +const char *Setup_Video::particleDetailToString(int detail) +{ + if (detail == -1) + detail = 3 - config.getIntValue("particleEmitterSkip"); + + switch (detail) + { + case 0: + return _("low"); + case 1: + return _("medium"); + case 2: + return _("high"); + case 3: + return _("max"); + default: + return ""; + } + return ""; +} + +Setup_Video::Setup_Video(): + mFullScreenEnabled(config.getBoolValue("screen")), + mOpenGLEnabled(config.getIntValue("opengl")), + mHwAccelEnabled(config.getBoolValue("hwaccel")), + mCustomCursorEnabled(config.getBoolValue("customcursor")), + mVisibleNamesEnabled(config.getBoolValue("visiblenames")), + mParticleEffectsEnabled(config.getBoolValue("particleeffects")), + mNPCLogEnabled(config.getBoolValue("logNpcInGui")), + mPickupChatEnabled(config.getBoolValue("showpickupchat")), + mPickupParticleEnabled(config.getBoolValue("showpickupparticle")), + mOpacity(config.getFloatValue("guialpha")), + mFps(config.getIntValue("fpslimit")), + mAltFps(config.getIntValue("altfpslimit")), + mHideShieldSprite(config.getBoolValue("hideShield")), + mLowTraffic(config.getBoolValue("lowTraffic")), + mSyncPlayerMove(config.getBoolValue("syncPlayerMove")), + mDrawHotKeys(config.getBoolValue("drawHotKeys")), + mDrawPath(config.getBoolValue("drawPath")), + mShowJob(serverConfig.getBoolValue("showJob")), + mAlphaCache(config.getBoolValue("alphaCache")), + mShowBackground(config.getBoolValue("showBackground")), + mSpeechMode(static_cast( + config.getIntValue("speech"))), + mModeListModel(new ModeListModel), + mModeList(new ListBox(mModeListModel)), + mFsCheckBox(new CheckBox(_("Full screen"), mFullScreenEnabled)), + mHwAccelCheckBox(new CheckBox(_("Hw acceleration"), mHwAccelEnabled)), + mCustomCursorCheckBox(new CheckBox(_("Custom cursor"), + mCustomCursorEnabled)), + mVisibleNamesCheckBox(new CheckBox(_("Visible names"), + mVisibleNamesEnabled)), + mParticleEffectsCheckBox(new CheckBox(_("Particle effects"), + mParticleEffectsEnabled)), + mNPCLogCheckBox(new CheckBox(_("Log NPC dialogue"), mNPCLogEnabled)), + mPickupNotifyLabel(new Label(_("Show pickup notification"))), + // TRANSLATORS: Refers to "Show own name" + mPickupChatCheckBox(new CheckBox(_("in chat"), mPickupChatEnabled)), + // TRANSLATORS: Refers to "Show own name" + mPickupParticleCheckBox(new CheckBox(_("as particle"), + mPickupParticleEnabled)), + mHideShieldSpriteCheckBox(new CheckBox(_("Hide shield sprite"), + mHideShieldSprite)), + mLowTrafficCheckBox(new CheckBox(_("Low traffic mode"), + mLowTraffic)), + mSyncPlayerMoveCheckBox(new CheckBox(_("Sync player move"), + mSyncPlayerMove)), + mDrawHotKeysCheckBox(new CheckBox(_("Draw hotkeys on map"), + mDrawHotKeys)), + mDrawPathCheckBox(new CheckBox(_("Draw path"), mDrawPath)), + mShowJobCheckBox(new CheckBox(_("Show job"), mShowJob)), + mAlphaCacheCheckBox(new CheckBox(_("Enable opacity cache"), mAlphaCache)), + mShowBackgroundCheckBox(new CheckBox(_("Show background"), + mShowBackground)), + mSpeechSlider(new Slider(0, 3)), + mSpeechLabel(new Label("")), + mAlphaSlider(new Slider(0.1, 1.0)), + mFpsCheckBox(new CheckBox(_("FPS limit:"))), + mFpsSlider(new Slider(2, 160)), + mFpsLabel(new Label), + mAltFpsSlider(new Slider(2, 160)), + mAltFpsLabel(new Label(_("Alt FPS limit: "))), + mOverlayDetail(config.getIntValue("OverlayDetail")), + mOverlayDetailSlider(new Slider(0, 2)), + mOverlayDetailField(new Label), + mParticleDetail(3 - config.getIntValue("particleEmitterSkip")), + mParticleDetailSlider(new Slider(0, 3)), + mParticleDetailField(new Label), + mFontSize(config.getIntValue("fontSize")), + mDialog(0) +{ + setName(_("Video")); + + ScrollArea *scrollArea = new ScrollArea(mModeList); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + speechLabel = new Label(_("Overhead text")); + alphaLabel = new Label(_("Gui opacity")); + overlayDetailLabel = new Label(_("Ambient FX")); + particleDetailLabel = new Label(_("Particle detail")); + fontSizeLabel = new Label(_("Font size")); + + mOpenGLListModel = new OpenGLListModel; + mOpenGLDropDown = new DropDown(mOpenGLListModel), + mOpenGLDropDown->setSelected(mOpenGLEnabled); + + mFontSizeListModel = new FontSizeChoiceListModel; + mFontSizeDropDown = new DropDown(mFontSizeListModel); + + mModeList->setEnabled(true); + +#ifndef USE_OPENGL + mOpenGLDropDown->setSelected(0); +#endif + + mAlphaSlider->setValue(mOpacity); + mAlphaSlider->setWidth(90); + + mFpsLabel->setCaption(mFps > 0 ? toString(mFps) : _("None")); + mFpsLabel->setWidth(60); + mAltFpsLabel->setCaption(_("Alt FPS limit: ") + (mAltFps > 0 + ? toString(mAltFps) : _("None"))); + mAltFpsLabel->setWidth(150); + mFpsSlider->setValue(mFps); + mFpsSlider->setEnabled(mFps > 0); + mAltFpsSlider->setValue(mAltFps); + mAltFpsSlider->setEnabled(mAltFps > 0); + mFpsCheckBox->setSelected(mFps > 0); + + // Pre-select the current video mode. + std::string videoMode = toString(graphics->getWidth()) + "x" + + toString(graphics->getHeight()); + mModeList->setSelected(mModeListModel->getIndexOf(videoMode)); + + mModeList->setActionEventId("videomode"); + mCustomCursorCheckBox->setActionEventId("customcursor"); + mVisibleNamesCheckBox->setActionEventId("visiblenames"); + mParticleEffectsCheckBox->setActionEventId("particleeffects"); + mPickupChatCheckBox->setActionEventId("pickupchat"); + mPickupParticleCheckBox->setActionEventId("pickupparticle"); + mNPCLogCheckBox->setActionEventId("lognpc"); + mAlphaSlider->setActionEventId("guialpha"); + mFpsCheckBox->setActionEventId("fpslimitcheckbox"); + mSpeechSlider->setActionEventId("speech"); + mFpsSlider->setActionEventId("fpslimitslider"); + mAltFpsSlider->setActionEventId("altfpslimitslider"); + mOverlayDetailSlider->setActionEventId("overlaydetailslider"); + mOverlayDetailField->setActionEventId("overlaydetailfield"); + mParticleDetailSlider->setActionEventId("particledetailslider"); + mParticleDetailField->setActionEventId("particledetailfield"); + mHideShieldSpriteCheckBox->setActionEventId("hideshieldsprite1"); + mLowTrafficCheckBox->setActionEventId("lowTraffic1"); + mSyncPlayerMoveCheckBox->setActionEventId("syncPlayerMove1"); + mDrawHotKeysCheckBox->setActionEventId("drawHotKeys"); + mDrawPathCheckBox->setActionEventId("drawPath1"); + mShowJobCheckBox->setActionEventId("showJob"); + mAlphaCacheCheckBox->setActionEventId("alphaCache"); + + mModeList->addActionListener(this); + mCustomCursorCheckBox->addActionListener(this); + mVisibleNamesCheckBox->addActionListener(this); + mParticleEffectsCheckBox->addActionListener(this); + mPickupChatCheckBox->addActionListener(this); + mPickupParticleCheckBox->addActionListener(this); + mNPCLogCheckBox->addActionListener(this); + mAlphaSlider->addActionListener(this); + mFpsCheckBox->addActionListener(this); + mSpeechSlider->addActionListener(this); + mFpsSlider->addActionListener(this); + mAltFpsSlider->addActionListener(this); + mOverlayDetailSlider->addActionListener(this); + mOverlayDetailField->addKeyListener(this); + mParticleDetailSlider->addActionListener(this); + mParticleDetailField->addKeyListener(this); + mHideShieldSpriteCheckBox->addKeyListener(this); + mLowTrafficCheckBox->addKeyListener(this); + mSyncPlayerMoveCheckBox->addKeyListener(this); + mDrawHotKeysCheckBox->addKeyListener(this); + mDrawPathCheckBox->addKeyListener(this); + mShowJobCheckBox->addKeyListener(this); + mAlphaCacheCheckBox->addKeyListener(this); + + mSpeechLabel->setCaption(speechModeToString(mSpeechMode)); + mSpeechSlider->setValue(mSpeechMode); + + mOverlayDetailField->setCaption(overlayDetailToString(mOverlayDetail)); + mOverlayDetailSlider->setValue(mOverlayDetail); + + mParticleDetailField->setCaption(particleDetailToString(mParticleDetail)); + mParticleDetailSlider->setValue(mParticleDetail); + + mFontSizeDropDown->setSelected(mFontSize - 10); + mFontSizeDropDown->adjustHeight(); + + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = h.getPlacer(0, 0); + + place(0, 0, scrollArea, 1, 5).setPadding(2); + place(1, 0, mFsCheckBox, 2); + place(3, 0, mOpenGLDropDown, 1); + + place(1, 1, mCustomCursorCheckBox, 3); + place(3, 1, mHwAccelCheckBox, 1); + + place(1, 2, mVisibleNamesCheckBox, 3); + + place(3, 2, mAlphaCacheCheckBox, 4); + + place(1, 3, mParticleEffectsCheckBox, 2); + + place(1, 4, mPickupNotifyLabel, 4); + + place(1, 5, mPickupChatCheckBox, 1); + place(2, 5, mPickupParticleCheckBox, 2); + + place(0, 6, fontSizeLabel, 3); + place(1, 6, mFontSizeDropDown, 1); + + place(0, 7, mAlphaSlider); + place(1, 7, alphaLabel, 3); + + place(0, 8, mFpsSlider); + place(1, 8, mFpsCheckBox).setPadding(3); + place(2, 8, mFpsLabel).setPadding(1); + + place(0, 9, mAltFpsSlider); + place(1, 9, mAltFpsLabel).setPadding(3); + + place(0, 10, mSpeechSlider); + place(1, 10, speechLabel); + place(2, 10, mSpeechLabel, 3).setPadding(2); + + place(0, 11, mOverlayDetailSlider); + place(1, 11, overlayDetailLabel); + place(2, 11, mOverlayDetailField, 3).setPadding(2); + + place(0, 12, mParticleDetailSlider); + place(1, 12, particleDetailLabel); + place(2, 12, mParticleDetailField, 3).setPadding(2); + + place(3, 7, mHideShieldSpriteCheckBox); + place(3, 8, mLowTrafficCheckBox); + place(0, 13, mShowBackgroundCheckBox); + place(0, 14, mSyncPlayerMoveCheckBox); + place(0, 15, mDrawHotKeysCheckBox); + place(2, 13, mDrawPathCheckBox, 2); + place(2, 14, mShowJobCheckBox, 2); + place(2, 15, mNPCLogCheckBox, 2); + + int width = 600; + + if (config.getIntValue("screenwidth") >= 730) + width += 100; + + setDimension(gcn::Rectangle(0, 0, width, 300)); +} + +Setup_Video::~Setup_Video() +{ + delete mModeListModel; + mModeListModel = 0; + delete mModeList; + mModeList = 0; + delete mFontSizeListModel; + mFontSizeListModel = 0; + delete mOpenGLListModel; + mOpenGLListModel = 0; + delete mDialog; + mDialog = 0; +} + +void Setup_Video::apply() +{ + // Full screen changes + bool fullscreen = mFsCheckBox->isSelected(); + if (fullscreen != config.getBoolValue("screen")) + { + /* The OpenGL test is only necessary on Windows, since switching + * to/from full screen works fine on Linux. On Windows we'd have to + * reinitialize the OpenGL state and reload all textures. + * + * See http://libsdl.org/cgi/docwiki.cgi/SDL_SetVideoMode + */ + +#if defined(WIN32) || defined(__APPLE__) + // checks for opengl usage + if (!config.getIntValue("opengl")) + { +#endif + if (!graphics->setFullscreen(fullscreen)) + { + fullscreen = !fullscreen; + if (!graphics->setFullscreen(fullscreen)) + { + std::stringstream errorMessage; + if (fullscreen) + { + errorMessage << _("Failed to switch to windowed mode " + "and restoration of old mode also " + "failed!") << std::endl; + } + else + { + errorMessage << _("Failed to switch to fullscreen mode" + " and restoration of old mode also " + "failed!") << std::endl; + } + logger->error(errorMessage.str()); + } + } +#if defined(WIN32) || defined(__APPLE__) + } + else + { + new OkDialog(_("Switching to Full Screen"), + _("Restart needed for changes to take effect.")); + } +#endif + config.setValue("screen", fullscreen); + } + + // OpenGL change + if (mOpenGLDropDown->getSelected() != mOpenGLEnabled) + { + config.setValue("opengl", mOpenGLDropDown->getSelected()); + + // OpenGL can currently only be changed by restarting, notify user. + new OkDialog(_("Changing to OpenGL"), + _("Applying change to OpenGL requires restart.")); + } + + mFps = mFpsCheckBox->isSelected() ? + static_cast(mFpsSlider->getValue()) : 0; + + mAltFps = static_cast(mAltFpsSlider->getValue()); + + mFpsSlider->setEnabled(mFps > 0); + + mAltFpsSlider->setEnabled(mAltFps > 0); + + config.setValue("hwaccel", mHwAccelCheckBox->isSelected()); + + // FPS change + config.setValue("fpslimit", mFps); + config.setValue("altfpslimit", mAltFps); + + if (config.getIntValue("fontSize") + != static_cast(mFontSizeDropDown->getSelected()) + 10) + { + config.setValue("fontSize", mFontSizeDropDown->getSelected() + 10); + gui->updateFonts(); + } + + config.setValue("hideShield", mHideShieldSpriteCheckBox->isSelected()); + config.setValue("lowTraffic", mLowTrafficCheckBox->isSelected()); + config.setValue("syncPlayerMove", mSyncPlayerMoveCheckBox->isSelected()); + config.setValue("drawHotKeys", mDrawHotKeysCheckBox->isSelected()); + config.setValue("drawPath", mDrawPathCheckBox->isSelected()); + serverConfig.setValue("showJob", mShowJobCheckBox->isSelected()); + config.setValue("alphaCache", mAlphaCacheCheckBox->isSelected()); + config.setValue("showBackground", mShowBackgroundCheckBox->isSelected()); + + // We sync old and new values at apply time + mFullScreenEnabled = config.getBoolValue("screen"); + mCustomCursorEnabled = config.getBoolValue("customcursor"); + mVisibleNamesEnabled = config.getBoolValue("visiblenames"); + mParticleEffectsEnabled = config.getBoolValue("particleeffects"); + mNPCLogEnabled = config.getBoolValue("logNpcInGui"); + mHideShieldSprite = config.getBoolValue("hideShield"); + mLowTraffic = config.getBoolValue("lowTraffic"); + mSyncPlayerMove = config.getBoolValue("syncPlayerMove"); + mDrawHotKeys = config.getBoolValue("drawHotKeys"); + mDrawPath = config.getBoolValue("drawPath"); + mShowJob = serverConfig.getBoolValue("showJob"); + mAlphaCache = config.getBoolValue("alphaCache"); + mShowBackground = config.getBoolValue("showBackground"); + + mSpeechMode = static_cast( + config.getIntValue("speech")); + mOpacity = config.getFloatValue("guialpha"); + mOverlayDetail = config.getIntValue("OverlayDetail"); + mOpenGLEnabled = config.getIntValue("opengl"); + mHwAccelEnabled = config.getBoolValue("hwaccel"); + mPickupChatEnabled = config.getBoolValue("showpickupchat"); + mPickupParticleEnabled = config.getBoolValue("showpickupparticle"); +} + +void Setup_Video::cancel() +{ + mFpsCheckBox->setSelected(mFps > 0); + mFsCheckBox->setSelected(mFullScreenEnabled); + mOpenGLDropDown->setSelected(mOpenGLEnabled); + mHwAccelCheckBox->setSelected(mHwAccelEnabled); + mCustomCursorCheckBox->setSelected(mCustomCursorEnabled); + mVisibleNamesCheckBox->setSelected(mVisibleNamesEnabled); + mParticleEffectsCheckBox->setSelected(mParticleEffectsEnabled); + mFpsSlider->setValue(mFps); + mFpsSlider->setEnabled(mFps > 0); + mAltFpsSlider->setValue(mAltFps); + mAltFpsSlider->setEnabled(mAltFps > 0); + mSpeechSlider->setValue(mSpeechMode); + mNPCLogCheckBox->setSelected(mNPCLogEnabled); + mHideShieldSpriteCheckBox->setSelected(mHideShieldSprite); + mLowTrafficCheckBox->setSelected(mLowTraffic); + mSyncPlayerMoveCheckBox->setSelected(mSyncPlayerMove); + mDrawHotKeysCheckBox->setSelected(mDrawHotKeys); + mDrawPathCheckBox->setSelected(mDrawPath); + mShowJobCheckBox->setSelected(mShowJob); + mAlphaCacheCheckBox->setSelected(mAlphaCache); + mShowBackgroundCheckBox->setSelected(mShowBackground); + mAlphaSlider->setValue(mOpacity); + mOverlayDetailSlider->setValue(mOverlayDetail); + mParticleDetailSlider->setValue(mParticleDetail); + mFpsLabel->setCaption(mFpsCheckBox->isSelected() + ? toString(mFps) : _("None")); + mAltFpsLabel->setCaption(_("Alt FPS limit: ") + toString(mAltFps)); + + config.setValue("screen", mFullScreenEnabled); + + // Set back to the current video mode. + std::string videoMode = toString(graphics->getWidth()) + "x" + + toString(graphics->getHeight()); + mModeList->setSelected(mModeListModel->getIndexOf(videoMode)); + config.setValue("screenwidth", graphics->getWidth()); + config.setValue("screenheight", graphics->getHeight()); + + config.setValue("customcursor", mCustomCursorEnabled); + config.setValue("visiblenames", mVisibleNamesEnabled); + config.setValue("particleeffects", mParticleEffectsEnabled); + config.setValue("speech", static_cast(mSpeechMode)); + config.setValue("logNpcInGui", mNPCLogEnabled); + config.setValue("hideShield", mHideShieldSprite); + config.setValue("lowTraffic", mLowTraffic); + config.setValue("syncPlayerMove", mSyncPlayerMove); + config.setValue("drawHotKeys", mDrawHotKeys); + config.setValue("drawPath", mDrawPath); + serverConfig.setValue("showJob", mShowJob); + config.setValue("alphaCache", mAlphaCache); + config.setValue("showBackground", mShowBackground); + config.setValue("guialpha", mOpacity); + Image::setEnableAlpha(mOpacity != 1.0f); + config.setValue("opengl", mOpenGLEnabled); + config.setValue("hwaccel", mHwAccelEnabled); + config.setValue("showpickupchat", mPickupChatEnabled); + config.setValue("showpickupparticle", mPickupParticleEnabled); +} + +void Setup_Video::action(const gcn::ActionEvent &event) +{ + const std::string &id = event.getId(); + + if (id == "videomode") + { + std::string mode = mModeListModel->getElementAt( + mModeList->getSelected()); + + if (mode == "custom") + { + if (mDialog) + { + mode = mDialog->getText(); + mDialog = 0; + } + else + { + mDialog = new TextDialog( + _("Custom resolution (example: 1024x768)"), + _("Enter new resolution: ")); + mDialog->setActionEventId("videomode"); + mDialog->addActionListener(this); + return; + } + } + const int width = atoi(mode.substr(0, mode.find("x")).c_str()); + const int height = atoi(mode.substr(mode.find("x") + 1).c_str()); + if (!width || !height) + return; + + // TODO: Find out why the drawing area doesn't resize without a restart. + if (width != graphics->getWidth() || height != graphics->getHeight()) + { + if (width < graphics->getWidth() || height < graphics->getHeight()) + new OkDialog(_("Screen Resolution Changed"), + _("Restart your client for the change to take effect.") + + std::string("\n") + + _("Some windows may be moved to fit the lowered resolution.")); + else + new OkDialog(_("Screen Resolution Changed"), + _("Restart your client for the change to take effect.")); + } + + config.setValue("screenwidth", width); + config.setValue("screenheight", height); + } + if (id == "~videomode") + { + mDialog = 0; + } + else if (id == "guialpha") + { + config.setValue("guialpha", mAlphaSlider->getValue()); + Image::setEnableAlpha(config.getFloatValue("guialpha") != 1.0f); + } + else if (id == "customcursor") + { + config.setValue("customcursor", mCustomCursorCheckBox->isSelected()); + } + else if (id == "visiblenames") + { + config.setValue("visiblenames", mVisibleNamesCheckBox->isSelected()); + } + else if (id == "particleeffects") + { + config.setValue("particleeffects", + mParticleEffectsCheckBox->isSelected()); + Particle::enabled = mParticleEffectsCheckBox->isSelected(); + + if (Game::instance()) + { + new OkDialog(_("Particle Effect Settings Changed."), + _("Changes will take effect on map change.")); + } + } + else if (id == "pickupchat") + { + config.setValue("showpickupchat", mPickupChatCheckBox->isSelected()); + } + else if (id == "pickupparticle") + { + config.setValue("showpickupparticle", + mPickupParticleCheckBox->isSelected()); + } + else if (id == "speech") + { + Being::Speech val = static_cast( + mSpeechSlider->getValue()); + mSpeechLabel->setCaption(speechModeToString(val)); + mSpeechSlider->setValue(val); + config.setValue("speech", (int)val); + } + else if (id == "lognpc") + { + config.setValue("logNpcInGui", mNPCLogCheckBox->isSelected()); + } + else if (id == "overlaydetailslider") + { + int val = static_cast(mOverlayDetailSlider->getValue()); + mOverlayDetailField->setCaption(overlayDetailToString(val)); + config.setValue("OverlayDetail", val); + } + else if (id == "particledetailslider") + { + int val = static_cast(mParticleDetailSlider->getValue()); + mParticleDetailField->setCaption(particleDetailToString(val)); + config.setValue("particleEmitterSkip", 3 - val); + Particle::emitterSkip = 4 - val; + } + else if (id == "fpslimitcheckbox" || id == "fpslimitslider") + { + int fps = static_cast(mFpsSlider->getValue()); + if (id == "fpslimitcheckbox" && !mFpsSlider->isEnabled()) + fps = 60; + else + fps = fps > 0 ? fps : 60; + mFps = mFpsCheckBox->isSelected() ? fps : 0; + const std::string text = mFps > 0 ? toString(mFps) : _("None"); + + mFpsLabel->setCaption(text); + mFpsSlider->setValue(mFps); + mFpsSlider->setEnabled(mFps > 0); + } + else if (id == "altfpslimitslider") + { + int fps = static_cast(mAltFpsSlider->getValue()); + fps = fps > 0 ? fps : static_cast(mAltFpsSlider->getScaleStart()); + mAltFps = fps; + const std::string text = mAltFps > 0 ? toString(mAltFps) : _("None"); + + mAltFpsLabel->setCaption(_("Alt FPS limit: ") + text); + mAltFpsSlider->setValue(mAltFps); + mAltFpsSlider->setEnabled(mAltFps > 0); + } +} + +void Setup_Video::externalUpdated() +{ + mShowJob = serverConfig.getBoolValue("showJob"); + mShowJobCheckBox->setSelected(mShowJob); +} \ No newline at end of file diff --git a/src/gui/setup_video.h b/src/gui/setup_video.h new file mode 100644 index 000000000..c9a72666a --- /dev/null +++ b/src/gui/setup_video.h @@ -0,0 +1,136 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_SETUP_VIDEO_H +#define GUI_SETUP_VIDEO_H + +#include "being.h" +#include "guichanfwd.h" + +#include "gui/widgets/setuptab.h" + +#include +#include + +class FontSizeChoiceListModel; +class ModeListModel; +class OpenGLListModel; +class TextDialog; + +class Setup_Video : public SetupTab, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + Setup_Video(); + ~Setup_Video(); + + void apply(); + void cancel(); + + void action(const gcn::ActionEvent &event); + + static const char *overlayDetailToString(int detail = -1); + + static const char *particleDetailToString(int detail = -1); + + virtual void externalUpdated(); + + private: + bool mFullScreenEnabled; + int mOpenGLEnabled; + bool mHwAccelEnabled; + bool mCustomCursorEnabled; + bool mVisibleNamesEnabled; + bool mParticleEffectsEnabled; + bool mNPCLogEnabled; + bool mPickupChatEnabled; + bool mPickupParticleEnabled; + double mOpacity; + int mFps; + int mAltFps; + bool mHideShieldSprite; + bool mLowTraffic; + bool mSyncPlayerMove; + bool mDrawHotKeys; + bool mDrawPath; + bool mShowJob; + bool mAlphaCache; + bool mShowBackground; + Being::Speech mSpeechMode; + + ModeListModel *mModeListModel; + FontSizeChoiceListModel *mFontSizeListModel; + + OpenGLListModel *mOpenGLListModel; + + gcn::Label *speechLabel; + gcn::Label *alphaLabel; + gcn::Label *scrollRadiusLabel; + gcn::Label *scrollLazinessLabel; + gcn::Label *overlayDetailLabel; + gcn::Label *particleDetailLabel; + gcn::Label *fontSizeLabel; + + gcn::ListBox *mModeList; + gcn::CheckBox *mFsCheckBox; + gcn::DropDown *mOpenGLDropDown; + gcn::CheckBox *mHwAccelCheckBox; + gcn::CheckBox *mCustomCursorCheckBox; + gcn::CheckBox *mVisibleNamesCheckBox; + gcn::CheckBox *mParticleEffectsCheckBox; + gcn::CheckBox *mNPCLogCheckBox; + + gcn::Label *mPickupNotifyLabel; + gcn::CheckBox *mPickupChatCheckBox; + gcn::CheckBox *mPickupParticleCheckBox; + + gcn::CheckBox *mHideShieldSpriteCheckBox; + gcn::CheckBox *mLowTrafficCheckBox; + gcn::CheckBox *mSyncPlayerMoveCheckBox; + gcn::CheckBox *mDrawHotKeysCheckBox; + gcn::CheckBox *mDrawPathCheckBox; + gcn::CheckBox *mShowJobCheckBox; + gcn::CheckBox *mAlphaCacheCheckBox; + gcn::CheckBox *mShowBackgroundCheckBox; + gcn::Slider *mSpeechSlider; + gcn::Label *mSpeechLabel; + gcn::Slider *mAlphaSlider; + gcn::CheckBox *mFpsCheckBox; + gcn::Slider *mFpsSlider; + gcn::Label *mFpsLabel; +// gcn::CheckBox *mAltFpsCheckBox; + gcn::Slider *mAltFpsSlider; + gcn::Label *mAltFpsLabel; + + int mOverlayDetail; + gcn::Slider *mOverlayDetailSlider; + gcn::Label *mOverlayDetailField; + + int mParticleDetail; + gcn::Slider *mParticleDetailSlider; + gcn::Label *mParticleDetailField; + + int mFontSize; + gcn::DropDown *mFontSizeDropDown; + TextDialog *mDialog; +}; + +#endif diff --git a/src/gui/shopwindow.cpp b/src/gui/shopwindow.cpp new file mode 100644 index 000000000..410e79dbb --- /dev/null +++ b/src/gui/shopwindow.cpp @@ -0,0 +1,788 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/shopwindow.h" + +#include "gui/buy.h" +#include "gui/itemamount.h" +#include "gui/sell.h" +#include "gui/setup.h" +#include "gui/trade.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shopitems.h" +#include "gui/widgets/shoplistbox.h" +#include "gui/widgets/slider.h" +#include "gui/widgets/tradetab.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "confirmdialog.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "playerrelations.h" +#include "shopitem.h" +#include "sound.h" +#include "units.h" + +#include "net/net.h" +#include "net/chathandler.h" +#include "net/npchandler.h" +#include "net/tradehandler.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#include + +extern std::string tradePartnerName; +ShopWindow::DialogList ShopWindow::instances; + +ShopWindow::ShopWindow(): + Window(_("Personal Shop")), + mSelectedItem(-1), + mAnnonceTime(0), + mLastRequestTimeList(0), + mLastRequestTimeItem(0), + mRandCounter(0), + mAcceptPlayer(""), + mTradeItem(0), + mTradeNick(""), + mTradeMoney(0) +{ + setWindowName("Personal Shop"); + setResizable(true); + setCloseButton(true); + setMinWidth(260); + setMinHeight(230); + setDefaultSize(380, 300, ImageRect::CENTER); + + mBuyShopItems = new ShopItems; + mSellShopItems = new ShopItems; + + mAnnounceCounter[BUY] = 0; + mAnnounceCounter[SELL] = 0; + + loadList(); + + mBuyShopItemList = new ShopListBox(mBuyShopItems, mBuyShopItems); + mSellShopItemList = new ShopListBox(mSellShopItems, mSellShopItems); + + mBuyShopItemList->setPriceCheck(false); + mSellShopItemList->setPriceCheck(false); + + mBuyScrollArea = new ScrollArea(mBuyShopItemList); + mBuyScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mSellScrollArea = new ScrollArea(mSellShopItemList); + mSellScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mCloseButton = new Button(_("Close"), "close", this); + + mBuyShopItemList->addSelectionListener(this); + mSellShopItemList->addSelectionListener(this); + + mBuyLabel = new Label(_("Buy items")); + mSellLabel = new Label(_("Sell items")); + + mBuyAddButton = new Button(_("Add"), "add buy", this); + mBuyDeleteButton = new Button(_("Delete"), "delete buy", this); + mBuyAnnounceButton = new Button(_("Announce"), "announce buy", this); + mSellAddButton = new Button(_("Add"), "add sell", this); + mSellDeleteButton = new Button(_("Delete"), "delete sell", this); + mSellAnnounceButton = new Button(_("Announce"), "announce sell", this); + mAnnounceLinks = new CheckBox(_("Show links in announce"), false, + this, "link announce"); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mBuyLabel, 8).setPadding(3); + place(8, 0, mSellLabel, 8).setPadding(3); + place(0, 1, mBuyScrollArea, 8, 5).setPadding(3); + place(8, 1, mSellScrollArea, 8, 5).setPadding(3); + place(0, 6, mBuyAddButton); + place(1, 6, mBuyDeleteButton); + place(3, 6, mBuyAnnounceButton); + place(8, 6, mSellAddButton); + place(9, 6, mSellDeleteButton); + place(11, 6, mSellAnnounceButton); + place(0, 7, mAnnounceLinks, 8); + place(15, 7, mCloseButton); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + center(); + loadWindowState(); + + instances.push_back(this); + setVisible(false); + + updateButtonsAndLabels(); +} + +ShopWindow::~ShopWindow() +{ + saveList(); + + delete mBuyShopItems; + mBuyShopItems = 0; + + delete mSellShopItems; + mSellShopItems = 0; + + instances.remove(this); +} + +void ShopWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "close") + { + close(); + return; + } + + if (event.getId() == "yes") + { + startTrade(); + } + else if (event.getId() == "no") + { + mTradeNick = ""; + } + else if (event.getId() == "ignore") + { + player_relations.ignoreTrade(mTradeNick); + mTradeNick = ""; + } + else if (event.getId() == "delete buy" && mBuyShopItemList + && mBuyShopItemList->getSelected() >= 0) + { + mBuyShopItems->erase(mBuyShopItemList->getSelected()); + } + else if (event.getId() == "delete sell" && mSellShopItemList + && mSellShopItemList->getSelected() >= 0) + { + mSellShopItems->erase(mSellShopItemList->getSelected()); + } + else if (event.getId() == "announce buy" && mBuyShopItems + && mBuyShopItems->getNumberOfElements() > 0) + { + announce(mBuyShopItems, BUY); + } + else if (event.getId() == "announce sell" && mSellShopItems + && mSellShopItems->getNumberOfElements() > 0) + { + announce(mSellShopItems, SELL); + } + + if (mSelectedItem < 1) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(mSelectedItem); + if (item) + { + if (event.getId() == "add buy") + { + ItemAmountWindow::showWindow(ItemAmountWindow::ShopBuyAdd, + this, item, sumAmount(item)); + } + else if (event.getId() == "add sell") + { + ItemAmountWindow::showWindow(ItemAmountWindow::ShopSellAdd, + this, item, sumAmount(item)); + } + } + +} + +void ShopWindow::startTrade() +{ + if (!actorSpriteManager || !tradeWindow) + return; + + Being *being = actorSpriteManager->findBeingByName( + mTradeNick, Being::PLAYER); + tradeWindow->clear(); + if (mTradeMoney) + { + tradeWindow->addAutoMoney(mTradeNick, mTradeMoney); + } + else + { + tradeWindow->addAutoItem(mTradeNick, mTradeItem, + mTradeItem->getQuantity()); + } + Net::getTradeHandler()->request(being); + tradePartnerName = mTradeNick; + mTradeNick = ""; +} + +void ShopWindow::valueChanged(const gcn::SelectionEvent &event _UNUSED_) +{ + updateButtonsAndLabels(); +} + +void ShopWindow::updateButtonsAndLabels() +{ + mBuyAddButton->setEnabled(mSelectedItem != -1); + mSellAddButton->setEnabled(mSelectedItem != -1); + mBuyDeleteButton->setEnabled( + mBuyShopItemList->getSelected() != -1 + && mBuyShopItems->getNumberOfElements() > 0); + mSellDeleteButton->setEnabled( + mSellShopItemList->getSelected() != -1 + && mSellShopItems->getNumberOfElements() > 0); +} + +void ShopWindow::setVisible(bool visible) +{ + Window::setVisible(visible); +} + +void ShopWindow::addBuyItem(Item *item, int amount, int price) +{ + if (!mBuyShopItems || !item) + return; + mBuyShopItems->addItemNoDup(item->getId(), amount, price); + updateButtonsAndLabels(); +} + +void ShopWindow::addSellItem(Item *item, int amount, int price) +{ + if (!mBuyShopItems || !item) + return; + mSellShopItems->addItemNoDup(item->getId(), amount, price); + updateButtonsAndLabels(); +} + +void ShopWindow::loadList() +{ + if (!mBuyShopItems || !mSellShopItems) + return; + + std::ifstream shopFile; + struct stat statbuf; + + mBuyShopItems->clear(); + mSellShopItems->clear(); + + std::string shopListName = Client::getServerConfigDirectory() + + "/shoplist.txt"; + + if (!stat(shopListName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + shopFile.open(shopListName.c_str(), std::ios::in); + char line[101]; + while (shopFile.getline(line, 100)) + { + std::string buf; + std::string str = line; + if (!str.empty()) + { + std::vector tokens; + + std::stringstream ss(str); + + while (ss >> buf) + tokens.push_back(atoi(buf.c_str())); + + if (tokens.size() == 5 && tokens[0]) + { + if (tokens[1] && tokens[2] && mBuyShopItems) + { + mBuyShopItems->addItem( + tokens[0], tokens[1], tokens[2]); + } + if (tokens[3] && tokens[4] && mSellShopItems) + { + mSellShopItems->addItem( + tokens[0], tokens[3], tokens[4]); + } + } + } + } + shopFile.close(); + } +} + +void ShopWindow::saveList() +{ + if (!mBuyShopItems || !mSellShopItems) + return; + + std::ofstream shopFile; + std::string shopListName = Client::getServerConfigDirectory() + + "/shoplist.txt"; + std::list procesList; + std::map mapItems; + + shopFile.open(shopListName.c_str(), std::ios::binary); + if (!shopFile.is_open()) + { + logger->log1("Unable to open shoplist.txt for writing"); + return; + } + + std::vector items = mBuyShopItems->items(); + std::vector::iterator it; + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (item) + mapItems[item->getId()] = item; + } + + items = mSellShopItems->items(); + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *sellItem = *(it); + ShopItem *buyItem = mapItems[sellItem->getId()]; + + shopFile << sellItem->getId(); + if (buyItem) + { + shopFile << strprintf(" %d %d ", buyItem->getQuantity(), + buyItem->getPrice()); + mapItems.erase(sellItem->getId()); + } + else + { + shopFile << " 0 0 "; + } + + if (sellItem) + { + shopFile << strprintf("%d %d", sellItem->getQuantity(), + sellItem->getPrice()) << std::endl; + } + } + + std::map::iterator mapIt; + for (mapIt = mapItems.begin(); mapIt != mapItems.end(); mapIt++) + { + ShopItem *buyItem = (*mapIt).second; + if (buyItem) + { + shopFile << buyItem->getId(); + shopFile << strprintf(" %d %d ", buyItem->getQuantity(), + buyItem->getPrice()); + shopFile << "0 0" << std::endl; + } + } + + shopFile.close(); +} + +void ShopWindow::announce(ShopItems *list, int mode) +{ + if (!list) + return; + + std::string data = "\302\202"; + if (mode == BUY) + data += "Buy "; + else + data += "Sell "; + + if (mAnnonceTime && (mAnnonceTime + (2 * 60) > cur_time + || mAnnonceTime > cur_time)) + { + return; + } + + mAnnonceTime = cur_time; + if (mBuyAnnounceButton) + mBuyAnnounceButton->setEnabled(false); + if (mSellAnnounceButton) + mSellAnnounceButton->setEnabled(false); + + std::vector items = list->items(); + std::vector::iterator it; + + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (item->getQuantity() > 1) + { + if (mAnnounceLinks->isSelected()) + { + data += strprintf("[@@%d|%s@@] (%dGP) %d, ", item->getId(), + item->getInfo().getName().c_str(), + item->getPrice(), item->getQuantity()); + } + else + { + data += strprintf("%s (%dGP) %d, ", + item->getInfo().getName().c_str(), + item->getPrice(), item->getQuantity()); + } + } + else + { + if (mAnnounceLinks->isSelected()) + { + data += strprintf("[@@%d|%s@@] (%dGP), ", item->getId(), + item->getInfo().getName().c_str(), + item->getPrice()); + } + else + { + data += strprintf("%s (%dGP), ", + item->getInfo().getName().c_str(), + item->getPrice()); + } + } + } + + Net::getChatHandler()->talk(data); +} + +void ShopWindow::giveList(const std::string &nick, int mode) +{ + if (!checkFloodCounter(mLastRequestTimeList)) + return; + + std::string data = "\302\202"; + + ShopItems *list; + if (mode == BUY) + { + list = mBuyShopItems; + data += "S1"; + } + else + { + list = mSellShopItems; + data += "B1"; + } + if (!list) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + std::vector items = list->items(); + std::vector::iterator it; + + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (!item) + continue; + + if (mode == SELL) + { + Item *item2 = inv->findItem(item->getId()); + if (item2) + { + int amount = item->getQuantity(); + if (item2->getQuantity() < amount) + amount = item2->getQuantity(); + + if (amount) + { + data += strprintf("%s%s%s", + encodeStr(item->getId(), 2).c_str(), + encodeStr(item->getPrice(), 4).c_str(), + encodeStr(amount, 3).c_str()); + } + } + } + else + { + int amount = item->getQuantity(); + if (item->getPrice() * amount > PlayerInfo::getAttribute(MONEY)) + amount = PlayerInfo::getAttribute(MONEY) / item->getPrice(); + + if (amount > 0) + { + data += strprintf("%s%s%s", + encodeStr(item->getId(), 2).c_str(), + encodeStr(item->getPrice(), 4).c_str(), + encodeStr(amount, 3).c_str()); + } + } + } + sendMessage(nick, data, true); +} + +void ShopWindow::sendMessage(const std::string &nick, + std::string data, bool random) +{ + if (!chatWindow) + return; + + if (random) + { + mRandCounter ++; + if (mRandCounter > 200) + mRandCounter = 0; + data += encodeStr(mRandCounter, 2); + } + + if (config.getBoolValue("hideShopMessages")) + Net::getChatHandler()->privateMessage(nick, data); + else if (chatWindow) + chatWindow->whisper(nick, data, BY_PLAYER); +//here was true +} + +void ShopWindow::showList(const std::string &nick, std::string data) +{ + BuyDialog *buyDialog = 0; + SellDialog *sellDialog = 0; + if (data.find("B1") == 0) + { + data = data.substr(2); + buyDialog = new BuyDialog(nick); + } + else if (data.find("S1") == 0) + { + data = data.substr(2); + sellDialog = new SellDialog(nick); + } + else + { + return; + } + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + if (buyDialog) + buyDialog->setMoney(PlayerInfo::getAttribute(MONEY)); + if (sellDialog) + sellDialog->setMoney(PlayerInfo::getAttribute(MONEY)); + + for (unsigned f = 0; f < data.length(); f += 9) + { + if (f + 9 > data.length()) + break; + + int id = decodeStr(data.substr(f, 2)); + int price = decodeStr(data.substr(f + 2, 4)); + int amount = decodeStr(data.substr(f + 6, 3)); + if (buyDialog && amount > 0) + buyDialog->addItem(id, amount, price); + if (sellDialog) + { + Item *item = inv->findItem(id); + if (item) + { + if (item->getQuantity() < amount) + amount = item->getQuantity(); + if (amount > 0) + sellDialog->addItem(id, amount, price); + else + sellDialog->addItem(id, -1, price); + } + } + } +} + +void ShopWindow::processRequest(std::string nick, std::string data, int mode) +{ + if (!player_node || !mTradeNick.empty() || PlayerInfo::isTrading() + || !actorSpriteManager + || !actorSpriteManager->findBeingByName(nick, Being::PLAYER)) + { + return; + } + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + unsigned long idx = 0; + + idx = data.find(" "); + if (idx == std::string::npos) + return; + + if (!checkFloodCounter(mLastRequestTimeItem)) + return; + + if (!mTradeNick.empty()) + { + sendMessage(nick, "error: player busy ", true); + return; + } + + data = data.substr(idx + 1); + + std::string part1; + std::string part2; + std::string part3; + std::stringstream ss(data); + std::string msg; + int id; + int price; + int amount; + + if (!(ss >> part1)) + return; + + if (!(ss >> part2)) + return; + + if (!(ss >> part3)) + return; + + id = atoi(part1.c_str()); + price = atoi(part2.c_str()); + amount = atoi(part3.c_str()); + + delete mTradeItem; + mTradeItem = new ShopItem(-1, id, amount, price); + + if (mode == BUY) + { + Item *item2 = inv->findItem(mTradeItem->getId()); + if (!item2 || item2->getQuantity() < amount + || !findShopItem(mTradeItem, SELL)) + { + sendMessage(nick, "error: Cant sell this item ", true); + return; + } + msg = "buy"; + mTradeMoney = 0; + } + else + { + if (!findShopItem(mTradeItem, BUY)) + { + sendMessage(nick, "error: Cant buy this item ", true); + return; + } + msg = "sell"; + mTradeMoney = mTradeItem->getPrice() * mTradeItem->getQuantity(); + } + + mTradeNick = nick; + + if (config.getBoolValue("autoShop")) + { + sound.playGuiSfx("system/newmessage.ogg"); + startTrade(); + } + else + { + ConfirmDialog *confirmDlg = new ConfirmDialog(_("Request for Trade"), + strprintf(_("%s wants to %s %s do you " + "accept?"), nick.c_str(), msg.c_str(), + mTradeItem->getInfo().getName().c_str()), true); + confirmDlg->addActionListener(this); + } +} + +void ShopWindow::updateTimes() +{ + if (mAnnonceTime + (2 * 60) < cur_time + || mAnnonceTime > cur_time) + { + mBuyAnnounceButton->setEnabled(true); + mSellAnnounceButton->setEnabled(true); + } +} + +bool ShopWindow::checkFloodCounter(int &counterTime) +{ + if (!counterTime || counterTime > cur_time) + counterTime = cur_time; + else if (counterTime + 10 > cur_time) + return false; + + counterTime = cur_time; + return true; +} + +bool ShopWindow::findShopItem(ShopItem *shopItem, int mode) +{ + if (!shopItem) + return false; + + std::vector items; + std::vector::iterator it; + if (mode == SELL) + { + if (!mSellShopItems) + return false; + items = mSellShopItems->items(); + } + else + { + if (!mBuyShopItems) + return false; + items = mBuyShopItems->items(); + } + + for (it = items.begin(); it != items.end(); it++) + { + ShopItem *item = *(it); + if (!item) + continue; + + if (item && item->getId() == shopItem->getId() + && item->getPrice() == shopItem->getPrice() + && item->getQuantity() >= shopItem->getQuantity()) + { + return true; + } + } + return false; +} + +int ShopWindow::sumAmount(Item *shopItem) +{ + if (!player_node || !shopItem) + return 0; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return 0; + int sum = 0; + + for (unsigned f = 0; f < inv->getSize(); f ++) + { + Item *item = inv->getItem(f); + if (item && item->getId() == shopItem->getId()) + sum += item->getQuantity(); + } + return sum; +} diff --git a/src/gui/shopwindow.h b/src/gui/shopwindow.h new file mode 100644 index 000000000..6b31b67a5 --- /dev/null +++ b/src/gui/shopwindow.h @@ -0,0 +1,173 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SHOP_H +#define SHOP_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class CheckBox; +class Item; +class ListBox; +class ShopItem; +class ShopItems; +class ShopListBox; + +/** + * The buy dialog. + * + * \ingroup Interface + */ +class ShopWindow : public Window, public gcn::ActionListener, + public gcn::SelectionListener +{ + public: + + enum ShopMode + { + BUY = 0, + SELL = 1 + }; + + /** + * Constructor. + * + * @see Window::Window + */ + ShopWindow(); + + /** + * Destructor + */ + ~ShopWindow(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Updates the labels according to the selected item. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Updates the state of buttons and labels. + */ + void updateButtonsAndLabels(); + + /** + * Sets the visibility of this window. + */ + void setVisible(bool visible); + + /** + * Returns true if any instances exist. + */ + static bool isActive() + { return instances.size() > 0; } + + void setItemSelected(int id) + { mSelectedItem = id; updateButtonsAndLabels(); } + + void addBuyItem(Item *item, int amount, int price); + + void addSellItem(Item *item, int amount, int price); + + void loadList(); + + void saveList(); + + void announce(ShopItems *list, int mode); + + void giveList(const std::string &nick, int mode); + + void setAcceptPlayer(std::string name) + { mAcceptPlayer = name; } + + const std::string &getAcceptPlayer() + { return mAcceptPlayer; } + + void sendMessage(const std::string &nick, std::string data, + bool random = false); + + void showList(const std::string &nick, std::string data); + + void processRequest(std::string nick, std::string data, int mode); + + bool findShopItem(ShopItem *shopItem, int mode); + + int sumAmount(Item *shopItem); + + void updateTimes(); + + bool checkFloodCounter(int &counterTime); + + private: + void startTrade(); + + typedef std::list DialogList; + static DialogList instances; + + gcn::Button *mCloseButton; + ShopListBox *mBuyShopItemList; + ShopListBox *mSellShopItemList; + gcn::ScrollArea *mBuyScrollArea; + gcn::ScrollArea *mSellScrollArea; + gcn::Label *mBuyLabel; + gcn::Label *mSellLabel; + gcn::Button *mBuyAddButton; + gcn::Button *mBuyDeleteButton; + gcn::Button *mBuyAnnounceButton; + gcn::Button *mSellAddButton; + gcn::Button *mSellDeleteButton; + gcn::Button *mSellAnnounceButton; + gcn::CheckBox *mAnnounceLinks; + + ShopItems *mBuyShopItems; + ShopItems *mSellShopItems; + + int mSelectedItem; + int mAnnonceTime; + int mLastRequestTimeList; + int mLastRequestTimeItem; + int mRandCounter; + std::string mAcceptPlayer; + ShopItem *mTradeItem; + std::string mTradeNick; + int mTradeMoney; + int mAnnounceCounter[2]; +}; + +extern ShopWindow *shopWindow; + +#endif diff --git a/src/gui/shortcutwindow.cpp b/src/gui/shortcutwindow.cpp new file mode 100644 index 000000000..c3aa8454f --- /dev/null +++ b/src/gui/shortcutwindow.cpp @@ -0,0 +1,152 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/shortcutwindow.h" + +#include "configuration.h" + +#include "gui/setup.h" + +#include "gui/widgets/layout.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/shortcutcontainer.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" + +static const int SCROLL_PADDING = 0; + +int ShortcutWindow::mBoxesWidth = 0; + +class ShortcutTab : public Tab +{ + public: + ShortcutTab(std::string name, ShortcutContainer* content) + { + setCaption(name); + mContent = content; + } + + ShortcutContainer* mContent; +}; + +ShortcutWindow::ShortcutWindow(const std::string &title, + ShortcutContainer *content, + int width, int height) +{ + setWindowName(title); + // no title presented, title bar is padding so window can be moved. + gcn::Window::setTitleBarHeight(gcn::Window::getPadding()); + setShowTitle(false); + setResizable(true); + setDefaultVisible(false); + setSaveVisible(true); + + setupWindow->registerWindowForReset(this); + + mTabs = 0; + mItems = content; + + const int border = SCROLL_PADDING * 2 + getPadding() * 2; + setMinWidth(mItems->getBoxWidth() + border); + setMinHeight(mItems->getBoxHeight() + border); + setMaxWidth(mItems->getBoxWidth() * mItems->getMaxItems() + border); + setMaxHeight(mItems->getBoxHeight() * mItems->getMaxItems() + border); + + if (width == 0) + width = mItems->getBoxWidth() + border; + if (height == 0) + height = (mItems->getBoxHeight() * mItems->getMaxItems()) + border; + + setDefaultSize(width, height, ImageRect::LOWER_RIGHT); + + mBoxesWidth += mItems->getBoxWidth() + border; + + mScrollArea = new ScrollArea(mItems); + mScrollArea->setPosition(SCROLL_PADDING, SCROLL_PADDING); + mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + mScrollArea->setOpaque(false); + + place(0, 0, mScrollArea, 5, 5).setPadding(0); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + layout.setMargin(0); + + loadWindowState(); +} + +ShortcutWindow::ShortcutWindow(const std::string &title, int width, int height) +{ + setWindowName(title); + // no title presented, title bar is padding so window can be moved. + gcn::Window::setTitleBarHeight(gcn::Window::getPadding()); + setShowTitle(false); + setResizable(true); + setDefaultVisible(false); + setSaveVisible(true); + + setupWindow->registerWindowForReset(this); + + mTabs = new TabbedArea; + + mItems = 0; + + const int border = SCROLL_PADDING * 2 + getPadding() * 2; + + if (width && height) + setDefaultSize(width, height, ImageRect::LOWER_RIGHT); + + setMinWidth(32 + border); + setMinHeight(32 + border); + + place(0, 0, mTabs, 5, 5); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + layout.setMargin(0); + + loadWindowState(); +} + +ShortcutWindow::~ShortcutWindow() +{ + delete mTabs; + mTabs = 0; + delete mItems; + mItems = 0; +} + +void ShortcutWindow::addTab(std::string name, ShortcutContainer *content) +{ + ScrollArea *scroll = new ScrollArea(content); + scroll->setPosition(SCROLL_PADDING, SCROLL_PADDING); + scroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + scroll->setOpaque(false); + Tab *tab = new ShortcutTab(name, content); + mTabs->addTab(tab, scroll); +} + +int ShortcutWindow::getTabIndex() +{ + if (!mTabs) + return 0; + return mTabs->getSelectedTabIndex(); +} diff --git a/src/gui/shortcutwindow.h b/src/gui/shortcutwindow.h new file mode 100644 index 000000000..bd3dedb61 --- /dev/null +++ b/src/gui/shortcutwindow.h @@ -0,0 +1,71 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SHORTCUTWINDOW_H +#define SHORTCUTWINDOW_H + +#include "gui/widgets/window.h" + +class ScrollArea; +class ShortcutContainer; +class TabbedArea; + +/** + * A window around a ShortcutContainer. + * + * \ingroup Interface + */ +class ShortcutWindow : public Window +{ + public: + /** + * Constructor. + */ + ShortcutWindow(const std::string &title, ShortcutContainer *content, + int width = 0, int height = 0); + + ShortcutWindow(const std::string &title, + int width = 0, int height = 0); + + /** + * Destructor. + */ + ~ShortcutWindow(); + + void addTab(std::string name, ShortcutContainer *content); + + int getTabIndex(); + + private: + ShortcutWindow(); + ShortcutContainer *mItems; + + ScrollArea *mScrollArea; + TabbedArea *mTabs; + + static int mBoxesWidth; +}; + +extern ShortcutWindow *itemShortcutWindow; +extern ShortcutWindow *emoteShortcutWindow; +extern ShortcutWindow *dropShortcutWindow; + +#endif diff --git a/src/gui/skilldialog.cpp b/src/gui/skilldialog.cpp new file mode 100644 index 000000000..04222d6f5 --- /dev/null +++ b/src/gui/skilldialog.cpp @@ -0,0 +1,523 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/skilldialog.h" + +#include "log.h" +#include "playerinfo.h" +#include "configuration.h" + +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/container.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include + +#include +#include + +class SkillModel; +class SkillEntry; + + +struct SkillInfo +{ + unsigned short id; + std::string name; + Image *icon; + bool modifiable; + bool visible; + SkillModel *model; + + std::string skillLevel; + int skillLevelWidth; + + std::string skillExp; + float progress; + gcn::Color color; + + SkillInfo() : + id(0), name(""), icon(0), modifiable(false), visible(false), + model(0), skillLevel(""), skillLevelWidth(0), skillExp(""), + progress(0.0f) + { + } + + ~SkillInfo() + { + if (icon) + icon->decRef(); + } + + void setIcon(const std::string &iconPath) + { + ResourceManager *res = ResourceManager::getInstance(); + if (!iconPath.empty()) + { + icon = res->getImage(iconPath); + } + + if (!icon) + { + icon = Theme::getImageFromTheme( + paths.getStringValue("unknownItemFile")); + } + } + + void update(); + + void draw(Graphics *graphics, int y, int width); +}; + + +typedef std::vector SkillList; + +class SkillModel : public gcn::ListModel +{ +public: + int getNumberOfElements() + { return static_cast(mVisibleSkills.size()); } + + SkillInfo *getSkillAt(int i) const + { return mVisibleSkills.at(i); } + + std::string getElementAt(int i) + { + if (getSkillAt(i)) + return getSkillAt(i)->name; + else + return ""; + } + + void updateVisibilities(); + + void addSkill(SkillInfo *info) + { mSkills.push_back(info); } + +private: + SkillList mSkills; + SkillList mVisibleSkills; +}; + +class SkillListBox : public ListBox +{ +public: + SkillListBox(SkillModel *model): + ListBox(model) + {} + + SkillInfo *getSelectedInfo() + { + const int selected = getSelected(); + if (!mListModel || selected < 0 + || selected > mListModel->getNumberOfElements()) + { + return 0; + } + + return static_cast(mListModel)->getSkillAt(selected); + } + + void draw(gcn::Graphics *gcnGraphics) + { + if (!mListModel) + return; + + SkillModel* model = static_cast(mListModel); + + updateAlpha(); + + Graphics *graphics = static_cast(gcnGraphics); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + graphics->fillRectangle(gcn::Rectangle(0, getRowHeight() + * mSelected, getWidth(), getRowHeight())); + } + + // Draw the list elements + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + for (int i = 0, y = 1; + i < model->getNumberOfElements(); + ++i, y += getRowHeight()) + { + SkillInfo *e = model->getSkillAt(i); + + if (e) + e->draw(graphics, y, getWidth()); + } + } + + unsigned int getRowHeight() const + { return 34; } +}; + +class SkillTab : public Tab +{ +public: + SkillTab(const std::string &name, SkillListBox *listBox): + mListBox(listBox) + { + setCaption(name); + } + + ~SkillTab() + { + delete mListBox; + mListBox = 0; + } + + SkillInfo *getSelectedInfo() + { + if (mListBox) + return mListBox->getSelectedInfo(); + else + return 0; + } + +private: + SkillListBox *mListBox; +}; + +SkillDialog::SkillDialog(): + Window(_("Skills")) +{ + setWindowName("Skills"); + setCloseButton(true); + setResizable(true); + setSaveVisible(true); + setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425); + setupWindow->registerWindowForReset(this); + + mTabs = new TabbedArea(); + mPointsLabel = new Label("0"); + mIncreaseButton = new Button(_("Up"), "inc", this); + + place(0, 0, mTabs, 5, 5); + place(0, 5, mPointsLabel, 4); + place(4, 5, mIncreaseButton); + + setLocationRelativeTo(getParent()); + loadWindowState(); +} + +SkillDialog::~SkillDialog() +{ + // Clear gui + loadSkills(""); +} + +void SkillDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "inc") + { + SkillTab *tab = static_cast(mTabs->getSelectedTab()); + if (tab) + { + if (SkillInfo *info = tab->getSelectedInfo()) + Net::getPlayerHandler()->increaseSkill(info->id); + } + } + else if (event.getId() == "close") + { + setVisible(false); + } +} + +std::string SkillDialog::update(int id) +{ + SkillMap::iterator i = mSkills.find(id); + + if (i != mSkills.end()) + { + SkillInfo *info = i->second; + if (info) + { + info->update(); + return info->name; + } + } + + return std::string(); +} + +void SkillDialog::update() +{ + mPointsLabel->setCaption(strprintf(_("Skill points available: %d"), + PlayerInfo::getAttribute(SKILL_POINTS))); + mPointsLabel->adjustSize(); + + for (SkillMap::iterator it = mSkills.begin(); it != mSkills.end(); it++) + { + if ((*it).second && (*it).second->modifiable) + (*it).second->update(); + } +} + +void SkillDialog::loadSkills(const std::string &file) +{ + // Fixes issues with removing tabs + if (mTabs->getSelectedTabIndex() != -1) + { + mTabs->setSelectedTab(static_cast(0)); + + while (mTabs->getSelectedTabIndex() != -1) + { + gcn::Tab *tab = mTabs->getSelectedTab(); + if (tab) + mTabs->removeTabWithIndex(mTabs->getSelectedTabIndex()); + delete tab; + } + } + + delete_all(mSkills); + mSkills.clear(); + + if (file.length() == 0) + return; + + XML::Document doc(file); + xmlNodePtr root = doc.rootNode(); + + int setCount = 0; + std::string setName; + ScrollArea *scroll; + SkillListBox *listbox; + SkillTab *tab; + + if (!root || !xmlStrEqual(root->name, BAD_CAST "skills")) + { + logger->log("Error loading skills file: %s", file.c_str()); + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + SkillModel *model = new SkillModel(); + SkillInfo *skill = new SkillInfo; + skill->id = 1; + skill->name = "basic"; + skill->setIcon(""); + skill->modifiable = true; + skill->visible = true; + skill->model = model; + skill->update(); + + model->addSkill(skill); + mSkills[1] = skill; + + model->updateVisibilities(); + + listbox = new SkillListBox(model); + scroll = new ScrollArea(listbox); + scroll->setOpaque(false); + scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS); + + tab = new SkillTab("Skills", listbox); + + mTabs->addTab(tab, scroll); + + update(); + } + return; + } + + for_each_xml_child_node(set, root) + { + if (xmlStrEqual(set->name, BAD_CAST "set")) + { + setCount++; + setName = XML::getProperty(set, "name", + strprintf(_("Skill Set %d"), setCount)); + + SkillModel *model = new SkillModel(); + + for_each_xml_child_node(node, set) + { + if (xmlStrEqual(node->name, BAD_CAST "skill")) + { + int id = atoi(XML::getProperty(node, "id", "-1").c_str()); + std::string name = XML::getProperty(node, "name", + strprintf(_("Skill %d"), id)); + std::string icon = XML::getProperty(node, "icon", ""); + + SkillInfo *skill = new SkillInfo; + skill->id = static_cast(id); + skill->name = name; + skill->setIcon(icon); + skill->modifiable = false; + skill->visible = false; + skill->model = model; + skill->update(); + + model->addSkill(skill); + + mSkills[id] = skill; + } + } + + model->updateVisibilities(); + + // possible leak listbox, scroll + listbox = new SkillListBox(model); + scroll = new ScrollArea(listbox); + scroll->setOpaque(false); + scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS); + + tab = new SkillTab(setName, listbox); + + mTabs->addTab(tab, scroll); + } + } + update(); +} + +void SkillDialog::setModifiable(int id, bool modifiable) +{ + SkillMap::iterator it = mSkills.find(id); + + if (it != mSkills.end()) + { + SkillInfo *info = it->second; + if (info) + { + info->modifiable = modifiable; + info->update(); + } + } +} + +void SkillModel::updateVisibilities() +{ + mVisibleSkills.clear(); + + for (SkillList::iterator it = mSkills.begin(); it != mSkills.end(); it++) + { + if ((*it)->visible) + mVisibleSkills.push_back((*it)); + } +} + +void SkillInfo::update() +{ + int baseLevel = PlayerInfo::getStatBase(id); + int effLevel = PlayerInfo::getStatEffective(id); + + std::pair exp = PlayerInfo::getStatExperience(id); + + if (!modifiable && baseLevel == 0 && effLevel == 0 && exp.second == 0) + { + if (visible) + { + visible = false; + if (model) + model->updateVisibilities(); + } + + return; + } + + bool updateVisibility = !visible; + visible = true; + + if (effLevel != baseLevel) + { + skillLevel = strprintf(_("Lvl: %d (%+d)"), baseLevel, + effLevel - baseLevel); + } + else + { + if (baseLevel == 0) + skillLevel.clear(); + else + skillLevel = strprintf(_("Lvl: %d"), baseLevel); + } + skillLevelWidth = -1; + + if (exp.second) + { + skillExp = strprintf("%d / %d", exp.first, exp.second); + progress = static_cast(exp.first) + / static_cast(exp.second); + } + else + { + skillExp.clear(); + progress = 0.0f; + } + + color = Theme::getProgressColor(Theme::PROG_EXP, progress); + + if (updateVisibility && model) + model->updateVisibilities(); +} + +void SkillInfo::draw(Graphics *graphics, int y, int width) +{ + graphics->drawImage(icon, 1, y); + graphics->drawText(name, 34, y); + + if (skillLevelWidth < 0) + { + // Add one for padding + skillLevelWidth = graphics->getFont()->getWidth(skillLevel) + 1; + } + + graphics->drawText(skillLevel, width - skillLevelWidth, y); + + if (!skillExp.empty()) + { + gcn::Rectangle rect(33, y + 15, width - 33, 17); + + ProgressBar::render(graphics, rect, color, progress, skillExp); + } +} + +SkillInfo* SkillDialog::getSkill(int id) +{ + return mSkills[id]; +} diff --git a/src/gui/skilldialog.h b/src/gui/skilldialog.h new file mode 100644 index 000000000..4ba4afe8e --- /dev/null +++ b/src/gui/skilldialog.h @@ -0,0 +1,91 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SKILLDIALOG_H +#define SKILLDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +//include "resources/image.h" +//include "resources/resourcemanager.h" + +#include + +#include + +class Button; +//class Image; +class Label; +class ScrollArea; +class Tab; +class TabbedArea; + +struct SkillInfo; + +/** + * The skill dialog. + * + * \ingroup Interface + */ +class SkillDialog : public Window, public gcn::ActionListener +{ + public: + SkillDialog(); + + ~SkillDialog(); + + /** + * Called when receiving actions from widget. + */ + void action(const gcn::ActionEvent &event); + + /** + * Update the given skill's display + */ + std::string update(int id); + + /** + * Update other parts of the display + */ + void update(); + + void loadSkills(const std::string &file); + + void setModifiable(int id, bool modifiable); + + SkillInfo* getSkill(int id); + + bool hasSkills() + { return !mSkills.empty(); } + + private: + typedef std::map SkillMap; + SkillMap mSkills; + TabbedArea *mTabs; + Label *mPointsLabel; + Button *mIncreaseButton; +}; + +extern SkillDialog *skillDialog; + +#endif diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp new file mode 100644 index 000000000..7e86e4d9c --- /dev/null +++ b/src/gui/socialwindow.cpp @@ -0,0 +1,1306 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/socialwindow.h" + +#include "actorspritemanager.h" +#include "guild.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "log.h" +#include "map.h" +#include "party.h" + +#include "gui/confirmdialog.h" +#include "gui/okdialog.h" +#include "gui/outfitwindow.h" +#include "gui/setup.h" +#include "gui/textdialog.h" +#include "gui/theme.h" + +#include "gui/widgets/avatarlistbox.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/container.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/popup.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" + +#include "net/net.h" +#include "net/guildhandler.h" +#include "net/partyhandler.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +class SocialTab : public Tab +{ +protected: + friend class SocialWindow; + + SocialTab(): + mInviteDialog(0), + mConfirmDialog(0), + mScroll(0), + mList(0) + {} + + virtual ~SocialTab() + { + // Cleanup dialogs + if (mInviteDialog) + { + mInviteDialog->close(); + mInviteDialog->scheduleDelete(); + mInviteDialog = NULL; + } + + if (mConfirmDialog) + { + mConfirmDialog->close(); + mConfirmDialog->scheduleDelete(); + mConfirmDialog = NULL; + } + } + + virtual void invite() = 0; + + virtual void leave() = 0; + + virtual void updateList() = 0; + + virtual void updateAvatar(std::string name) = 0; + + virtual void resetDamage(std::string name) = 0; + + virtual void selectIndex(unsigned num _UNUSED_) + { } + + TextDialog *mInviteDialog; + ConfirmDialog *mConfirmDialog; + ScrollArea *mScroll; + AvatarListBox *mList; +}; + +class GuildTab : public SocialTab, public gcn::ActionListener +{ +public: + GuildTab(Guild *guild): + mGuild(guild) + { + setCaption(_("Guild")); + + setTabColor(&Theme::getThemeColor(Theme::GUILD_SOCIAL_TAB)); + + mList = new AvatarListBox(guild); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + } + + ~GuildTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + } + + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "do invite") + { + std::string name = mInviteDialog->getText(); + Net::getGuildHandler()->invite(mGuild->getId(), name); + + if (localChatTab) + { + localChatTab->chatLog(strprintf( + _("Invited user %s to guild %s."), + name.c_str(), mGuild->getName().c_str()), BY_SERVER); + } + mInviteDialog = 0; + } + else if (event.getId() == "~do invite") + { + mInviteDialog = 0; + } + else if (event.getId() == "yes") + { + Net::getGuildHandler()->leave(mGuild->getId()); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Guild %s quit requested."), + mGuild->getName().c_str()), BY_SERVER); + } + mConfirmDialog = 0; + } + else if (event.getId() == "~yes") + { + mConfirmDialog = 0; + } + } + + void updateList() + { + } + + void updateAvatar(std::string name _UNUSED_) + { + } + + void resetDamage(std::string name _UNUSED_) + { + } + +protected: + void invite() + { + // TODO - Give feedback on whether the invite succeeded + mInviteDialog = new TextDialog(_("Member Invite to Guild"), + strprintf(_("Who would you like to invite to guild %s?"), + mGuild->getName().c_str()), + socialWindow); + mInviteDialog->setActionEventId("do invite"); + mInviteDialog->addActionListener(this); + } + + void leave() + { + mConfirmDialog = new ConfirmDialog(_("Leave Guild?"), + strprintf(_("Are you sure you want to leave guild %s?"), + mGuild->getName().c_str()), + socialWindow); + + mConfirmDialog->addActionListener(this); + } + +private: + Guild *mGuild; +}; + +class PartyTab : public SocialTab, public gcn::ActionListener +{ +public: + PartyTab(Party *party): + mParty(party) + { + setCaption(_("Party")); + + setTabColor(&Theme::getThemeColor(Theme::PARTY_SOCIAL_TAB)); + + mList = new AvatarListBox(party); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + } + + ~PartyTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + } + + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "do invite") + { + std::string name = mInviteDialog->getText(); + Net::getPartyHandler()->invite(name); + + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Invited user %s to party."), + name.c_str()), BY_SERVER); + } + mInviteDialog = NULL; + } + else if (event.getId() == "~do invite") + { + mInviteDialog = NULL; + } + else if (event.getId() == "yes") + { + Net::getPartyHandler()->leave(); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Party %s quit requested."), + mParty->getName().c_str()), BY_SERVER); + } + mConfirmDialog = NULL; + } + else if (event.getId() == "~yes") + { + mConfirmDialog = NULL; + } + } + + void updateList() + { + } + + void updateAvatar(std::string name _UNUSED_) + { + } + + void resetDamage(std::string name _UNUSED_) + { + } + +protected: + void invite() + { + // TODO - Give feedback on whether the invite succeeded + mInviteDialog = new TextDialog(_("Member Invite to Party"), + strprintf(_("Who would you like to invite to party %s?"), + mParty->getName().c_str()), + socialWindow); + mInviteDialog->setActionEventId("do invite"); + mInviteDialog->addActionListener(this); + } + + void leave() + { + mConfirmDialog = new ConfirmDialog(_("Leave Party?"), + strprintf(_("Are you sure you want to leave party %s?"), + mParty->getName().c_str()), + socialWindow); + + mConfirmDialog->addActionListener(this); + } + +private: + Party *mParty; +}; + +/*class BuddyTab : public SocialTab +{ + // TODO? +};*/ + + +class BeingsListModal : public AvatarListModel +{ +public: + BeingsListModal() + { + } + + ~BeingsListModal() + { + delete_all(mMembers); + mMembers.clear(); + } + + std::vector *getMembers() + { + return &mMembers; + } + + virtual Avatar *getAvatarAt(int index) + { + return mMembers[index]; + } + + int getNumberOfElements() + { + return static_cast(mMembers.size()); + } + + std::vector mMembers; +}; + +class PlayersTab : public SocialTab +{ +public: + PlayersTab(std::string name) + { + mBeings = new BeingsListModal(); + + mList = new AvatarListBox(mBeings); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + +// mBeings->getMembers().push_back(new Avatar("test")); + updateList(); + setCaption(name); + } + + ~PlayersTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + delete mBeings; + mBeings = 0; + } + + void updateList() + { + getPlayersAvatars(); + } + + void updateAvatar(std::string name) + { + if (!actorSpriteManager) + return; + + Avatar *avatar = findAvatarbyName(name); + if (!avatar) + return; + if (Party::getParty(1)) + { + PartyMember *pm = Party::getParty(1)->getMember(name); + if (pm && pm->getMaxHp() > 0) + { + avatar->setMaxHp(pm->getMaxHp()); + avatar->setHp(pm->getHp()); + } + } + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + if (being) + { + avatar->setDamageHp(being->getDamageTaken()); + avatar->setLevel(being->getLevel()); + avatar->setGender(being->getGender()); + avatar->setIp(being->getIp()); + } + } + + void resetDamage(std::string name) + { + if (!actorSpriteManager) + return; + + Avatar *avatar = findAvatarbyName(name); + if (!avatar) + return; + avatar->setDamageHp(0); + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + + if (being) + being->setDamageTaken(0); + } + + Avatar* findAvatarbyName(std::string name) + { + std::vector *avatars = mBeings->getMembers(); + if (!avatars) + return 0; + + Avatar *ava = 0; + std::vector::iterator i = avatars->begin(); + while (i != avatars->end()) + { + ava = (*i); + if (ava && ava->getName() == name) + return ava; + ++i; + } + ava = new Avatar(name); + ava->setOnline(true); + avatars->push_back(ava); + return ava; + } + + void getPlayersAvatars() + { + std::vector *avatars = mBeings->getMembers(); + if (!avatars) + return; + + if (actorSpriteManager) + { +// std::list beings = actorSpriteManager->getAll(); + std::vector names; + actorSpriteManager->getPlayerNames(names, false); + + std::vector::iterator ai = avatars->begin(); + while (ai != avatars->end()) + { + bool finded = false; + Avatar *ava = (*ai); + if (!ava) + break; + + std::vector::iterator i = names.begin(); + while (i != names.end()) + { + if (ava->getName() == (*i) && (*i) != "") + { + finded = true; + break; + } + ++i; + } + + if (!finded) + avatars->erase(ai); + else + ++ai; + } + + std::vector::iterator i = names.begin(); + + while (i != names.end()) + { + if ((*i) != "") + updateAvatar(*i); + ++i; + } + } + } + +protected: + void invite() + { + } + + void leave() + { + } + +private: + BeingsListModal *mBeings; +}; + + +class NavigationTab : public SocialTab +{ + +public: + NavigationTab() + { + mBeings = new BeingsListModal(); + + mList = new AvatarListBox(mBeings); + mScroll = new ScrollArea(mList); + + mScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); + mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_ALWAYS); + + setCaption(_("Nav")); + + } + + ~NavigationTab() + { + delete mList; + mList = 0; + delete mScroll; + mScroll = 0; + delete mBeings; + mBeings = 0; + } + + void invite() + { + } + + void leave() + { + } + + void updateList() + { + if (!socialWindow || !player_node) + return; + + Map* map = socialWindow->getMap(); + if (!map) + return; + + if (socialWindow->getProcessedPortals()) + return; + + std::vector *avatars = mBeings->getMembers(); + std::list portals = map->getPortals(); + + std::list::iterator i = portals.begin(); + SpecialLayer *specialLayer = map->getSpecialLayer(); + + avatars->clear(); + + int idx = 0; + while (i != portals.end()) + { + MapItem *portal = *i; + if (!portal) + continue; + + int x = portal->getX(); + int y = portal->getY(); + + std::string name = strprintf("%s [%d %d]", + portal->getComment().c_str(), x, y); + + Avatar *ava = new Avatar(name); + if (player_node) + ava->setOnline(player_node->isReachable(x, y, 0)); + else + ava->setOnline(false); + ava->setLevel(-1); + ava->setType(portal->getType()); + ava->setX(x); + ava->setY(y); + avatars->push_back(ava); + + if (config.getBoolValue("drawHotKeys") && idx < 80 && outfitWindow) + { + Being *being = actorSpriteManager->findPortalByTile(x, y); + if (being) + { + being->setName(keyboard.getKeyShortString( + outfitWindow->keyName(idx))); + } + + if (specialLayer) + { + portal = specialLayer->getTile(ava->getX(), ava->getY()); + if (portal) + { + portal->setName(keyboard.getKeyShortString( + outfitWindow->keyName(idx))); + } + } + } + + i++; + idx ++; + } + if (socialWindow) + socialWindow->setProcessedPortals(true); + } + + + virtual void selectIndex(unsigned num) + { + if (!player_node) + return; + + std::vector *avatars = mBeings->getMembers(); + if (!avatars || avatars->size() <= num) + return; + + Avatar *ava = avatars->at(num); + if (ava && player_node) + player_node->navigateTo(ava->getX(), ava->getY()); + } + + void updateNames() + { + if (!socialWindow) + return; + + std::vector *avatars = mBeings->getMembers(); + if (!avatars) + return; + + Map *map = socialWindow->getMap(); + if (!map) + return; + + Avatar *ava = 0; + std::vector::iterator i = avatars->begin(); + while (i != avatars->end()) + { + ava = (*i); + if (!ava) + break; + + MapItem *item = map->findPortalXY(ava->getX(), ava->getY()); + if (item) + { + std::string name = strprintf("%s [%d %d]", + item->getComment().c_str(), item->getX(), item->getY()); + ava->setName(name); + ava->setOriginalName(name); + } + + ++i; + } + } + + int getPortalIndex(int x, int y) + { + if (!socialWindow) + return -1; + + std::vector *avatars = mBeings->getMembers(); + if (!avatars) + return -1; + + Map *map = socialWindow->getMap(); + if (!map) + return 01; + + Avatar *ava = 0; + std::vector::iterator i = avatars->begin(); + unsigned num = 0; + while (i != avatars->end()) + { + ava = (*i); + + if (!ava) + break; + + if (ava->getX() == x && ava->getY() == y) + return num; + + ++i; + num ++; + } + return -1; + } + + void addPortal(int x, int y) + { + if (!socialWindow || !player_node) + return; + + Map* map = socialWindow->getMap(); + if (!map) + return; + + std::vector *avatars = mBeings->getMembers(); + + if (!avatars) + return; + + MapItem *portal = map->findPortalXY(x, y); + if (!portal) + return; + + std::string name = strprintf("%s [%d %d]", + portal->getComment().c_str(), x, y); + + Avatar *ava = new Avatar(name); + if (player_node) + ava->setOnline(player_node->isReachable(x, y, 0)); + else + ava->setOnline(false); + ava->setLevel(-1); + ava->setType(portal->getType()); + ava->setX(x); + ava->setY(y); + avatars->push_back(ava); + } + + void removePortal(int x, int y) + { + if (!socialWindow || !player_node) + return; + + Map* map = socialWindow->getMap(); + if (!map) + return; + + std::vector *avatars = mBeings->getMembers(); + + std::vector::iterator i = avatars->begin(); + + if (!avatars) + return; + + while (i != avatars->end()) + { + Avatar *ava = (*i); + + if (!ava) + break; + + if (ava && ava->getX() == x && ava->getY() == y) + { + avatars->erase(i); + return; + } + + ++ i; + } + } + + void updateAvatar(std::string) + { + } + + void resetDamage(std::string) + { + } + +private: + BeingsListModal *mBeings; + +protected: +// friend class SocialWindow; +}; + + +class CreatePopup : public Popup, public LinkHandler +{ +public: + CreatePopup(): + Popup("SocialCreatePopup") + { + mBrowserBox = new BrowserBox; + mBrowserBox->setPosition(4, 4); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mBrowserBox->setOpaque(false); + mBrowserBox->setLinkHandler(this); + + if (Net::getGuildHandler()->isSupported()) + mBrowserBox->addRow(strprintf("@@guild|%s@@", _("Create Guild"))); + mBrowserBox->addRow(strprintf("@@party|%s@@", _("Create Party"))); + mBrowserBox->addRow("##3---"); + mBrowserBox->addRow(strprintf("@@cancel|%s@@", _("Cancel"))); + + add(mBrowserBox); + + setContentSize(mBrowserBox->getWidth() + 8, + mBrowserBox->getHeight() + 8); + } + + void handleLink(const std::string &link, gcn::MouseEvent *event _UNUSED_) + { + if (link == "guild" && socialWindow) + { + socialWindow->showGuildCreate(); + } + else if (link == "party" && socialWindow) + { + socialWindow->showPartyCreate(); + } + + setVisible(false); + } + + void show(gcn::Widget *parent) + { + if (!parent) + return; + + int x, y; + parent->getAbsolutePosition(x, y); + y += parent->getHeight(); + setPosition(x, y); + setVisible(true); + requestMoveToTop(); + } + +private: + BrowserBox* mBrowserBox; +}; + +SocialWindow::SocialWindow() : + Window(_("Social")), + mGuildInvited(0), + mGuildAcceptDialog(0), + mPartyAcceptDialog(0), + mMap(0), + mLastUpdateTime(0), + mNeedUpdate(false), + mProcessedPortals(false) +{ + setWindowName("Social"); + setVisible(false); + setSaveVisible(true); + setResizable(true); + setSaveVisible(true); + setCloseButton(true); + setMinWidth(120); + setMinHeight(55); + setDefaultSize(590, 200, 150, 120); + setupWindow->registerWindowForReset(this); + + mCreateButton = new Button(_("Create"), "create", this); + mInviteButton = new Button(_("Invite"), "invite", this); + mLeaveButton = new Button(_("Leave"), "leave", this); + mTabs = new TabbedArea; + + place(0, 0, mCreateButton); + place(1, 0, mInviteButton); + place(2, 0, mLeaveButton); + place(0, 1, mTabs, 4, 4); + + widgetResized(NULL); + + mCreatePopup = new CreatePopup(); + + loadWindowState(); + + mPlayers = new PlayersTab("P"); + mTabs->addTab(mPlayers, mPlayers->mScroll); + + mNavigation = new NavigationTab(); + mTabs->addTab(mNavigation, mNavigation->mScroll); + + if (player_node && player_node->getParty()) + addTab(player_node->getParty()); + + if (player_node && player_node->getGuild()) + addTab(player_node->getGuild()); + + updateButtons(); +} + +SocialWindow::~SocialWindow() +{ + // Cleanup invites + if (mGuildAcceptDialog) + { + mGuildAcceptDialog->close(); + mGuildAcceptDialog->scheduleDelete(); + mGuildAcceptDialog = NULL; + + mGuildInvited = 0; + } + + if (mPartyAcceptDialog) + { + mPartyAcceptDialog->close(); + mPartyAcceptDialog->scheduleDelete(); + mPartyAcceptDialog = NULL; + + mPartyInviter = ""; + } + delete mCreatePopup; + mCreatePopup = 0; + delete mPlayers; + mPlayers = 0; +} + +bool SocialWindow::addTab(Guild *guild) +{ + if (mGuilds.find(guild) != mGuilds.end()) + return false; + + GuildTab *tab = new GuildTab(guild); + mGuilds[guild] = tab; + + mTabs->addTab(tab, tab->mScroll); + + updateButtons(); + + return true; +} + +bool SocialWindow::removeTab(Guild *guild) +{ + GuildMap::iterator it = mGuilds.find(guild); + if (it == mGuilds.end()) + return false; + + mTabs->removeTab(it->second); + delete it->second; + mGuilds.erase(it); + + updateButtons(); + + return true; +} + +bool SocialWindow::addTab(Party *party) +{ + if (mParties.find(party) != mParties.end()) + return false; + + PartyTab *tab = new PartyTab(party); + mParties[party] = tab; + + mTabs->addTab(tab, tab->mScroll); + + updateButtons(); + + return true; +} + +bool SocialWindow::removeTab(Party *party) +{ + PartyMap::iterator it = mParties.find(party); + if (it == mParties.end()) + return false; + + mTabs->removeTab(it->second); + delete it->second; + mParties.erase(it); + + updateButtons(); + + return true; +} + +void SocialWindow::action(const gcn::ActionEvent &event) +{ + const std::string &eventId = event.getId(); + + if (event.getSource() == mPartyAcceptDialog) + { + // check if they accepted the invite + if (eventId == "yes") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Accepted party invite from %s."), + mPartyInviter.c_str())); + } + Net::getPartyHandler()->inviteResponse(mPartyInviter, true); + } + else if (eventId == "no") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Rejected party invite from %s."), + mPartyInviter.c_str())); + } + Net::getPartyHandler()->inviteResponse(mPartyInviter, false); + } + + mPartyInviter = ""; + mPartyAcceptDialog = NULL; + } + else if (event.getSource() == mGuildAcceptDialog) + { + // check if they accepted the invite + if (eventId == "yes") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Accepted guild invite from %s."), + mPartyInviter.c_str())); + } + Net::getGuildHandler()->inviteResponse(mGuildInvited, true); + } + else if (eventId == "no") + { + if (localChatTab) + { + localChatTab->chatLog( + strprintf(_("Rejected guild invite from %s."), + mPartyInviter.c_str())); + } + Net::getGuildHandler()->inviteResponse(mGuildInvited, false); + } + + mGuildInvited = 0; + mGuildAcceptDialog = NULL; + } + else if (event.getId() == "create") + { + if (Net::getGuildHandler()->isSupported()) + { + if (mCreatePopup) + mCreatePopup->show(mCreateButton); + } + else + { + showPartyCreate(); + } + } + else if (event.getId() == "invite" && mTabs->getSelectedTabIndex() > -1) + { + if (mTabs->getSelectedTab()) + static_cast(mTabs->getSelectedTab())->invite(); + } + else if (event.getId() == "leave" && mTabs->getSelectedTabIndex() > -1) + { + if (mTabs->getSelectedTab()) + static_cast(mTabs->getSelectedTab())->leave(); + } + else if (event.getId() == "create guild") + { + std::string name = mGuildCreateDialog->getText(); + + if (name.size() > 16) + { + // TODO : State too many characters in input. + return; + } + + Net::getGuildHandler()->create(name); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Creating guild called %s."), + name.c_str()), BY_SERVER); + } + + mGuildCreateDialog = NULL; + } + else if (event.getId() == "~create guild") + { + mGuildCreateDialog = NULL; + } + else if (event.getId() == "create party") + { + std::string name = mPartyCreateDialog->getText(); + + if (name.size() > 16) + { + // TODO : State too many characters in input. + return; + } + + Net::getPartyHandler()->create(name); + if (localChatTab) + { + localChatTab->chatLog(strprintf(_("Creating party called %s."), + name.c_str()), BY_SERVER); + } + + mPartyCreateDialog = NULL; + } + else if (event.getId() == "~create party") + { + mPartyCreateDialog = NULL; + } +} + +void SocialWindow::showGuildCreate() +{ + mGuildCreateDialog = new TextDialog(_("Guild Name"), + _("Choose your guild's name."), this); + mGuildCreateDialog->setActionEventId("create guild"); + mGuildCreateDialog->addActionListener(this); +} + +void SocialWindow::showGuildInvite(const std::string &guildName, + const int guildId, + const std::string &inviterName) +{ + // check there isnt already an invite showing + if (mGuildInvited != 0) + { + if (localChatTab) + { + localChatTab->chatLog(_("Received guild request, but one already " + "exists."), BY_SERVER); + } + return; + } + + std::string msg = strprintf(_("%s has invited you to join the guild %s."), + inviterName.c_str(), guildName.c_str()); + if (localChatTab) + localChatTab->chatLog(msg, BY_SERVER); + + // show invite + mGuildAcceptDialog = new ConfirmDialog(_("Accept Guild Invite"), + msg, false, false, this); + mGuildAcceptDialog->addActionListener(this); + + mGuildInvited = guildId; +} + +void SocialWindow::showPartyInvite(const std::string &partyName, + const std::string &inviter) +{ + // check there isnt already an invite showing + if (mPartyInviter != "") + { + if (localChatTab) + { + localChatTab->chatLog(_("Received party request, but one already " + "exists."), BY_SERVER); + } + return; + } + + std::string msg; + if (inviter.empty()) + { + if (partyName.empty()) + { + msg = _("You have been invited you to join a party."); + } + else + { + msg = strprintf(_("You have been invited to join the %s party."), + partyName.c_str()); + } + } + else + { + if (partyName.empty()) + { + msg = strprintf(_("%s has invited you to join their party."), + inviter.c_str()); + } + else + { + msg = strprintf(_("%s has invited you to join the %s party."), + inviter.c_str(), partyName.c_str()); + } + } + + if (localChatTab) + localChatTab->chatLog(msg, BY_SERVER); + + // show invite + mPartyAcceptDialog = new ConfirmDialog(_("Accept Party Invite"), + msg, false, false, this); + mPartyAcceptDialog->addActionListener(this); + + mPartyInviter = inviter; +} + +void SocialWindow::showPartyCreate() +{ + if (!player_node) + return; + + if (player_node->getParty()) + { + new OkDialog(_("Create Party"), + _("Cannot create party. You are already in a party"), + this); + return; + } + + mPartyCreateDialog = new TextDialog(_("Party Name"), + _("Choose your party's name."), this); + mPartyCreateDialog->setActionEventId("create party"); + mPartyCreateDialog->addActionListener(this); +} + +void SocialWindow::updateActiveList() +{ + mNeedUpdate = true; +} + +void SocialWindow::logic() +{ + unsigned int nowTime = cur_time; + if (nowTime - mLastUpdateTime > 1 && mNeedUpdate) + { + mPlayers->updateList(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + else if (nowTime - mLastUpdateTime > 5) + { + mPlayers->updateList(); + mNeedUpdate = false; + mLastUpdateTime = nowTime; + } + + Window::logic(); +} + +void SocialWindow::updateAvatar(std::string name) +{ + mPlayers->updateAvatar(name); +} + +void SocialWindow::resetDamage(std::string name) +{ + mPlayers->resetDamage(name); +} + +void SocialWindow::updateButtons() +{ + if (!mTabs) + return; + + bool hasTabs = mTabs->getNumberOfTabs() > 0; + mInviteButton->setEnabled(hasTabs); + mLeaveButton->setEnabled(hasTabs); +} + +void SocialWindow::updatePortals() +{ + if (mNavigation) + mNavigation->updateList(); +} + +void SocialWindow::updatePortalNames() +{ + if (mNavigation) + static_cast(mNavigation)->updateNames(); +} + +void SocialWindow::selectPortal(unsigned num) +{ + if (mNavigation) + mNavigation->selectIndex(num); +} + +int SocialWindow::getPortalIndex(int x, int y) +{ + if (mNavigation) + return static_cast(mNavigation)->getPortalIndex(x, y); + else + return -1; +} + +void SocialWindow::addPortal(int x, int y) +{ + if (mNavigation) + static_cast(mNavigation)->addPortal(x, y); +} + +void SocialWindow::removePortal(int x, int y) +{ + if (mNavigation) + static_cast(mNavigation)->removePortal(x, y); +} + +void SocialWindow::nextTab() +{ + if (!mTabs) + return; + + int tab = mTabs->getSelectedTabIndex(); + + tab++; + if (tab == mTabs->getNumberOfTabs()) + tab = 0; + + mTabs->setSelectedTab(tab); +} + +void SocialWindow::prevTab() +{ + if (!mTabs) + return; + + int tab = mTabs->getSelectedTabIndex(); + + if (tab == 0) + tab = mTabs->getNumberOfTabs(); + tab--; + + mTabs->setSelectedTab(tab); +} \ No newline at end of file diff --git a/src/gui/socialwindow.h b/src/gui/socialwindow.h new file mode 100644 index 000000000..6e654c4c6 --- /dev/null +++ b/src/gui/socialwindow.h @@ -0,0 +1,159 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SOCIALWINDOW_H +#define SOCIALWINDOW_H + +#include "gui/widgets/window.h" + +#include +#include + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Button; +class ConfirmDialog; +class CreatePopup; +class Guild; +class Map; +class NavigateTab; +class Party; +class SocialTab; +class Tab; +class TabbedArea; +class TextDialog; +class PlayersTab; + +/** + * Party window. + * + * \ingroup Interface + */ +class SocialWindow : public Window, gcn::ActionListener +{ +public: + SocialWindow(); + + ~SocialWindow(); + + bool addTab(Guild *guild); + + bool removeTab(Guild *guild); + + bool addTab(Party *party); + + bool removeTab(Party *party); + + /** + * Handle events. + */ + void action(const gcn::ActionEvent &event); + + void showGuildInvite(const std::string &guildName, const int guildId, + const std::string &inviterName); + + void showGuildCreate(); + + void showPartyInvite(const std::string &partyName, + const std::string &inviter = ""); + + void showPartyCreate(); + + void updateActiveList(); + + void updateAvatar(std::string name); + + void resetDamage(std::string name); + + void logic(); + + void updatePortals(); + + void updatePortalNames(); + + int getPortalIndex(int x, int y); + + void addPortal(int x, int y); + + void removePortal(int x, int y); + + void nextTab(); + + void prevTab(); + + Map* getMap() + { return mMap; } + + void setMap(Map *map) + { mMap = map; mProcessedPortals = false; } + + bool getProcessedPortals() + { return mProcessedPortals; } + + void setProcessedPortals(int n) + { mProcessedPortals = n; } + + void selectPortal(unsigned num); + +protected: + friend class SocialTab; + + void updateButtons(); + + int mGuildInvited; + ConfirmDialog *mGuildAcceptDialog; + TextDialog *mGuildCreateDialog; + + std::string mPartyInviter; + ConfirmDialog *mPartyAcceptDialog; + TextDialog *mPartyCreateDialog; + + typedef std::map GuildMap; + GuildMap mGuilds; + + typedef std::map PartyMap; + PartyMap mParties; + + SocialTab *mPlayers; + SocialTab *mNavigation; + + CreatePopup *mCreatePopup; + + Button *mCreateButton; + Button *mInviteButton; + Button *mLeaveButton; + TabbedArea *mTabs; + Map *mMap; + + int mLastUpdateTime; + bool mNeedUpdate; + bool mProcessedPortals; +}; + +extern SocialWindow *socialWindow; + +#endif // SOCIALWINDOW_H diff --git a/src/gui/specialswindow.cpp b/src/gui/specialswindow.cpp new file mode 100644 index 000000000..bd968191d --- /dev/null +++ b/src/gui/specialswindow.cpp @@ -0,0 +1,257 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/specialswindow.h" + +#include "log.h" + +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/container.h" +#include "gui/widgets/icon.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/tabbedarea.h" +#include "gui/widgets/flowcontainer.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/specialhandler.h" + +#include "resources/specialdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include + +#define SPECIALS_WIDTH 200 +#define SPECIALS_HEIGHT 32 + +class SpecialEntry; + + +class SpecialEntry : public Container +{ + public: + SpecialEntry(SpecialInfo *info); + + void update(int current, int needed); + + protected: + friend class SpecialsWindow; + SpecialInfo *mInfo; + + private: + Icon *mIcon; // icon to display + Label *mNameLabel; // name to display + Label *mLevelLabel; // level number label (only shown when applicable) + Button *mUse; // use button (only shown when applicable) + ProgressBar *mRechargeBar; // recharge bar (only shown when applicable) +}; + +SpecialsWindow::SpecialsWindow(): + Window(_("Specials")) +{ + setWindowName("Specials"); + setCloseButton(true); + setResizable(true); + setSaveVisible(true); + setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425); + setupWindow->registerWindowForReset(this); + + mTabs = new TabbedArea(); + + place(0, 0, mTabs, 5, 5); + + setLocationRelativeTo(getParent()); + loadWindowState(); +} + +SpecialsWindow::~SpecialsWindow() +{ + // Clear gui +} + +void SpecialsWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "use") + { + if (!event.getSource()) + return; + + SpecialEntry *disp = dynamic_cast( + event.getSource()->getParent()); + + if (disp) + { + /*Being *target = player_node->getTarget(); + + if (target) + Net::getSpecialHandler()->use(disp->mInfo->id, + 1, target->getId()); + else*/ + Net::getSpecialHandler()->use(disp->mInfo->id); + } + } + else if (event.getId() == "close") + { + setVisible(false); + } +} + +void SpecialsWindow::draw(gcn::Graphics *graphics) +{ + // update the progress bars + std::map specialData = PlayerInfo::getSpecialStatus(); + bool foundNew = false; + unsigned int found = 0; // number of entries in specialData + // which match mEntries + + for (std::map::iterator i = specialData.begin(); + i != specialData.end(); i++) + { + std::map::iterator e = mEntries.find(i->first); + if (e == mEntries.end()) + { + // found a new special - abort update and rebuild from scratch + foundNew = true; + break; + } + else + { + // update progress bar of special + if (e->second) + e->second->update(i->second.currentMana, i->second.neededMana); + found++; + } + } + // a rebuild is needed when a) the number of specials changed or b) + // an existing entry isn't found anymore + if (foundNew || found != mEntries.size()) + rebuild(specialData); + + Window::draw(graphics); +} + +void SpecialsWindow::rebuild(const std::map &specialData) +{ + make_dtor(mEntries); + mEntries.clear(); + int vPos = 0; //vertical position of next placed element + + for (std::map::const_iterator i = specialData.begin(); + i != specialData.end(); + i++) + { + logger->log("Updating special GUI for %d", i->first); + + SpecialInfo* info = SpecialDB::get(i->first); + if (info) + { + info->rechargeCurrent = i->second.currentMana; + info->rechargeNeeded = i->second.neededMana; + SpecialEntry* entry = new SpecialEntry(info); + entry->setPosition(0, vPos); + vPos += entry->getHeight(); + add(entry); + mEntries[i->first] = entry; + } + else + { + logger->log("Warning: No info available of special %d", i->first); + } + } +} + + +SpecialEntry::SpecialEntry(SpecialInfo *info) : + mInfo(info), + mIcon(NULL), + mLevelLabel(NULL), + mUse(NULL), + mRechargeBar(NULL) +{ + setFrameSize(1); + setOpaque(false); + setSize(SPECIALS_WIDTH, SPECIALS_HEIGHT); + + if (info && !info->icon.empty()) + mIcon = new Icon(info->icon); + else + mIcon = new Icon(Theme::resolveThemePath("unknown-item.png")); + + mIcon->setPosition(1, 0); + add(mIcon); + + if (info) + mNameLabel = new Label(info->name); + else + mNameLabel = new Label(""); + + mNameLabel->setPosition(35, 0); + add(mNameLabel); + + if (info && info->hasLevel) + { + mLevelLabel = new Label(toString(info->level)); + mLevelLabel->setPosition(getWidth() - mLevelLabel->getWidth(), 0); + add(mLevelLabel); + } + + if (info && info->isActive) + { + mUse = new Button("Use", "use", specialsWindow); + mUse->setPosition(getWidth() - mUse->getWidth(), 13); + add(mUse); + } + + if (info->hasRechargeBar) + { + float progress = 0; + if (info->rechargeNeeded) + { + progress = (float)info->rechargeCurrent + / (float)info->rechargeNeeded; + } + mRechargeBar = new ProgressBar(progress, 100, 10, Theme::PROG_MP); + mRechargeBar->setSmoothProgress(false); + mRechargeBar->setPosition(0, 13); + add(mRechargeBar); + } + +} + +void SpecialEntry::update(int current, int needed) +{ + if (mRechargeBar && needed) + { + float progress = (float)current / (float)needed; + mRechargeBar->setProgress(progress); + } +} diff --git a/src/gui/specialswindow.h b/src/gui/specialswindow.h new file mode 100644 index 000000000..66ef5e375 --- /dev/null +++ b/src/gui/specialswindow.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SPECIALSWINDOW_H +#define SPECIALSWINDOW_H + +#include + +#include "guichanfwd.h" + +#include "playerinfo.h" + +#include "gui/widgets/window.h" + +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Label; +class ScrollArea; +class Tab; +class TabbedArea; + +struct SpecialEntry; + +class SpecialsWindow : public Window, public gcn::ActionListener +{ + public: + SpecialsWindow(); + + ~SpecialsWindow(); + + /** + * Called when receiving actions from widget. + */ + void action(const gcn::ActionEvent &actionEvent); + + void draw(gcn::Graphics *graphics); + + private: + // (re)constructs the list of specials + void rebuild(const std::map &specialData); + + TabbedArea *mTabs; + std::map mEntries; +}; + +extern SpecialsWindow *specialsWindow; + +#endif // SPECIALSWINDOW_H diff --git a/src/gui/speechbubble.cpp b/src/gui/speechbubble.cpp new file mode 100644 index 000000000..08d000380 --- /dev/null +++ b/src/gui/speechbubble.cpp @@ -0,0 +1,91 @@ +/* + * Speech bubbles + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/speechbubble.h" + +#include "graphics.h" + +#include "gui/gui.h" +#include "gui/theme.h" + +#include "gui/widgets/textbox.h" + +#include + +#include + +SpeechBubble::SpeechBubble(): + Popup("Speech", "speechbubble.xml") +{ + setContentSize(140, 46); + setMinWidth(29); + setMinHeight(29); + + mCaption = new gcn::Label; + mCaption->setFont(boldFont); + + mSpeechBox = new TextBox; + mSpeechBox->setEditable(false); + mSpeechBox->setOpaque(false); + mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::CHAT)); + + add(mCaption); + add(mSpeechBox); +} + +void SpeechBubble::setCaption(const std::string &name, const gcn::Color *color) +{ + mCaption->setCaption(name); + mCaption->adjustSize(); + mCaption->setForegroundColor(*color); +} + +void SpeechBubble::setText(const std::string &text, bool showName) +{ + if (text == mText && (mCaption->getWidth() <= mSpeechBox->getMinWidth())) + return; + + mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::TEXT)); + + int width = mCaption->getWidth() + 2 * getPadding(); + mSpeechBox->setTextWrapped(text, 130 > width ? 130 : width); + const int speechWidth = mSpeechBox->getMinWidth() + 2 * getPadding(); + + const int fontHeight = getFont()->getHeight(); + const int nameHeight = showName ? mCaption->getHeight() + + (getPadding() / 2) : 0; + const int numRows = mSpeechBox->getNumberOfRows(); + const int height = (numRows * fontHeight) + nameHeight + getPadding(); + + if (width < speechWidth) + width = speechWidth; + + width += 2 * getPadding(); + + setContentSize(width, height); + + const int xPos = ((getWidth() - width) / 2); + const int yPos = ((getHeight() - height) / 2) + nameHeight; + + mCaption->setPosition(xPos, getPadding()); + mSpeechBox->setPosition(xPos, yPos); +} diff --git a/src/gui/speechbubble.h b/src/gui/speechbubble.h new file mode 100644 index 000000000..8682ab7e9 --- /dev/null +++ b/src/gui/speechbubble.h @@ -0,0 +1,58 @@ +/* + * Speech bubbles + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SPEECHBUBBLE_H +#define SPEECHBUBBLE_H + +#include "gui/theme.h" + +#include "gui/widgets/popup.h" + +class TextBox; + +class SpeechBubble : public Popup +{ + public: + /** + * Constructor. Initializes the speech bubble. + */ + SpeechBubble(); + + /** + * Sets the name displayed for the speech bubble, and in what color. + */ + void setCaption(const std::string &name, + const gcn::Color *color = + &Theme::getThemeColor(Theme::TEXT)); + + /** + * Sets the text to be displayed. + */ + void setText(const std::string &text, bool showName = true); + + private: + std::string mText; + gcn::Label *mCaption; + TextBox *mSpeechBox; +}; + +#endif diff --git a/src/gui/spellpopup.cpp b/src/gui/spellpopup.cpp new file mode 100644 index 000000000..80fa9f378 --- /dev/null +++ b/src/gui/spellpopup.cpp @@ -0,0 +1,105 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/spellpopup.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "textcommand.h" + +#include "graphics.h" +#include "units.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +SpellPopup::SpellPopup(): + Popup("SpellPopup") +{ + // Item Name + mItemName = new gcn::Label; + mItemName->setFont(boldFont); + mItemName->setPosition(getPadding(), getPadding()); + + add(mItemName); + addMouseListener(this); +} + +SpellPopup::~SpellPopup() +{ +} + +void SpellPopup::setItem(TextCommand *spell) +{ + if (spell) + mItemName->setCaption(spell->getName()); + else + mItemName->setCaption("?"); + + mItemName->adjustSize(); + int minWidth = mItemName->getWidth(); + + minWidth += 8; + setWidth(minWidth); + + setContentSize(minWidth, getPadding() + getFont()->getHeight()); +} + +void SpellPopup::view(int x, int y) +{ + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + { + if (graphics->getWidth() > getWidth()) + posX = graphics->getWidth() - getWidth(); + else + posX = 0; + } + if (posY + getHeight() > graphics->getHeight()) + { + if (y > getHeight() + distance) + posY = y - getHeight() - distance; + else + y = 0; + } + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void SpellPopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + // When the mouse moved on top of the popup, hide it + setVisible(false); +} diff --git a/src/gui/spellpopup.h b/src/gui/spellpopup.h new file mode 100644 index 000000000..1b14e0e4c --- /dev/null +++ b/src/gui/spellpopup.h @@ -0,0 +1,67 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPELLPOPUP_H +#define SPELLPOPUP_H + +#include "gui/widgets/popup.h" + +#include "textcommand.h" + +#include + +class TextBox; + +/** + * A popup that displays information about an item. + */ +class SpellPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + SpellPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~SpellPopup(); + + /** + * Sets the info to be displayed given a particular item. + */ + void setItem(TextCommand *spell); + + /** + * Sets the location to display the item popup. + */ + void view(int x, int y); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + + private: + gcn::Label *mItemName; +}; + +#endif // SPELLPOPUP_H diff --git a/src/gui/statuspopup.cpp b/src/gui/statuspopup.cpp new file mode 100644 index 000000000..4d2c48fa7 --- /dev/null +++ b/src/gui/statuspopup.cpp @@ -0,0 +1,543 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/statuspopup.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "gui/widgets/layout.h" +#include "gui/widgets/textbox.h" + +#include "graphics.h" +#include "localplayer.h" +#include "units.h" +#include "viewport.h" +#include "keyboardconfig.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +StatusPopup::StatusPopup(): + Popup("StatusPopup") +{ + + const int fontHeight = getFont()->getHeight(); + + mMoveType = new gcn::Label; + mMoveType->setPosition(getPadding(), getPadding()); + + mCrazyMoveType = new gcn::Label; + mCrazyMoveType->setPosition(getPadding(), fontHeight + getPadding()); + + mMoveToTargetType = new gcn::Label; + mMoveToTargetType->setPosition(getPadding(), + 2 * fontHeight + getPadding()); + + mFollowMode = new gcn::Label; + mFollowMode->setPosition(getPadding(), 3 * fontHeight + getPadding()); + + mAttackWeaponType = new gcn::Label; + mAttackWeaponType->setPosition(getPadding(), + 4 + 4 * fontHeight + getPadding()); + + mAttackType = new gcn::Label; + mAttackType->setPosition(getPadding(), 4 + 5 * fontHeight + getPadding()); + + mMagicAttackType = new gcn::Label; + mMagicAttackType->setPosition(getPadding(), + 4 + 6 * fontHeight + getPadding()); + + mDropCounter = new gcn::Label; + mDropCounter->setPosition(getPadding(), 8 + 7 * fontHeight + getPadding()); + + mPickUpType = new gcn::Label; + mPickUpType->setPosition(getPadding(), 8 + 8 * fontHeight + getPadding()); + + mMapType = new gcn::Label; + mMapType->setPosition(getPadding(), 12 + 9 * fontHeight + getPadding()); + + mImitationMode = new gcn::Label; + mImitationMode->setPosition(getPadding(), + 16 + 10 * fontHeight + getPadding()); + + mAwayMode = new gcn::Label; + mAwayMode->setPosition(getPadding(), 16 + 11 * fontHeight + getPadding()); + + mCameraMode = new gcn::Label; + mCameraMode->setPosition(getPadding(), + 16 + 12 * fontHeight + getPadding()); + + mDisableGameModifiers = new gcn::Label; + mDisableGameModifiers->setPosition(getPadding(), + 20 + 13 * fontHeight + getPadding()); + + add(mMoveType); + add(mCrazyMoveType); + add(mMoveToTargetType); + add(mFollowMode); + add(mAttackWeaponType); + add(mAttackType); + add(mDropCounter); + add(mPickUpType); + add(mMapType); + add(mMagicAttackType); + add(mDisableGameModifiers); + add(mImitationMode); + add(mAwayMode); + add(mCameraMode); + +// addMouseListener(this); +} + +StatusPopup::~StatusPopup() +{ +} + +void StatusPopup::update() +{ + updateLabels(); + + int minWidth = mMoveType->getWidth(); + + if (mMoveToTargetType->getWidth() > minWidth) + minWidth = mMoveToTargetType->getWidth(); + if (mFollowMode->getWidth() > minWidth) + minWidth = mFollowMode->getWidth(); + if (mCrazyMoveType->getWidth() > minWidth) + minWidth = mCrazyMoveType->getWidth(); + if (mAttackWeaponType->getWidth() > minWidth) + minWidth = mAttackWeaponType->getWidth(); + if (mAttackType->getWidth() > minWidth) + minWidth = mAttackType->getWidth(); + if (mDropCounter->getWidth() > minWidth) + minWidth = mDropCounter->getWidth(); + if (mPickUpType->getWidth() > minWidth) + minWidth = mPickUpType->getWidth(); + if (mMapType->getWidth() > minWidth) + minWidth = mMapType->getWidth(); + if (mMagicAttackType->getWidth() > minWidth) + minWidth = mMagicAttackType->getWidth(); + if (mDisableGameModifiers->getWidth() > minWidth) + minWidth = mDisableGameModifiers->getWidth(); + if (mAwayMode->getWidth() > minWidth) + minWidth = mAwayMode->getWidth(); + if (mCameraMode->getWidth() > minWidth) + minWidth = mCameraMode->getWidth(); + if (mImitationMode->getWidth() > minWidth) + minWidth = mImitationMode->getWidth(); + + minWidth += 16 + 2 * getPadding(); + setWidth(minWidth); + + const int fontHeight = getFont()->getHeight(); + + setHeight(24 + 8 + 14 * fontHeight + getPadding()); +} + +void StatusPopup::view(int x, int y) +{ + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + posX = graphics->getWidth() - getWidth(); + if (posY + getHeight() > graphics->getHeight()) + posY = y - getHeight() - distance; + + update(); + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void StatusPopup::updateLabels() +{ + if (!player_node || !viewport) + return; + + switch (player_node->getInvertDirection()) + { + case 0: + mMoveType->setCaption("(D) default moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + case 1: + mMoveType->setCaption("(I) invert moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + case 2: + mMoveType->setCaption("(c) moves with some crazy moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + + case 3: + mMoveType->setCaption("(C) moves with crazy moves " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + case 4: + mMoveType->setCaption("(d) double normal + crazy " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + + default: + mMoveType->setCaption("(?) move " + + keyboard.getKeyValueString(keyboard.KEY_INVERT_DIRECTION)); + break; + } + mMoveType->adjustSize(); + + if (player_node->getCrazyMoveType() < 10) + { + mCrazyMoveType->setCaption(strprintf("(%d) crazy move number %d ", + player_node->getCrazyMoveType(), player_node->getCrazyMoveType()) + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE)); + } + else + { + switch (player_node->getCrazyMoveType()) + { + case 10: + mCrazyMoveType->setCaption("(a) custom crazy move " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE)); + break; + default: + mCrazyMoveType->setCaption("(?) crazy move " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_CRAZY_MOVES_TYPE)); + break; + } + } + mCrazyMoveType->adjustSize(); + + switch (player_node->getMoveToTargetType()) + { + case 0: + mMoveToTargetType->setCaption("(0) default moves to target " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 1: + mMoveToTargetType->setCaption("(1) moves to target in distance 1 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 2: + mMoveToTargetType->setCaption("(2) moves to target in distance 3 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 3: + mMoveToTargetType->setCaption("(3) moves to target in distance 3 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 4: + mMoveToTargetType->setCaption("(5) moves to target in distance 5 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 5: + mMoveToTargetType->setCaption("(7) moves to target in distance 7 " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + case 6: + mMoveToTargetType->setCaption( + "(A) moves to target in attack range " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + default: + mMoveToTargetType->setCaption("(?) move to target " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_MOVE_TO_TARGET)); + break; + } + mMoveToTargetType->adjustSize(); + + switch (player_node->getFollowMode()) + { + case 0: + mFollowMode->setCaption("(D) default follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + case 1: + mFollowMode->setCaption("(R) relative follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + case 2: + mFollowMode->setCaption("(M) mirror follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + case 3: + mFollowMode->setCaption("(P) pet follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + default: + mFollowMode->setCaption("(?) unknown follow " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_FOLLOW_MODE)); + break; + } + mFollowMode->adjustSize(); + + switch (player_node->getAttackWeaponType()) + { + case 1: + mAttackWeaponType->setCaption("(D) default attack " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + case 2: + mAttackWeaponType->setCaption("(s) switch attack without shield " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + case 3: + mAttackWeaponType->setCaption("(S) switch attack with shield " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + default: + mAttackWeaponType->setCaption("(?) attack " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_ATTACK_WEAPON_TYPE)); + break; + } + mAttackWeaponType->adjustSize(); + + switch (player_node->getAttackType()) + { + case 0: + mAttackType->setCaption("(D) default attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + case 1: + mAttackType->setCaption("(G) go and attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + case 2: + mAttackType->setCaption("(A) go, attack, pickup " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + case 3: + mAttackType->setCaption("(d) without auto attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + default: + mAttackType->setCaption("(?) attack " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_ATTACK_TYPE)); + break; + } + mAttackType->adjustSize(); + + mDropCounter->setCaption(strprintf("(%d) drop counter %d ", + player_node->getQuickDropCounter(), player_node->getQuickDropCounter()) + + keyboard.getKeyValueString(keyboard.KEY_SWITCH_QUICK_DROP)); + mDropCounter->adjustSize(); + + switch (player_node->getPickUpType()) + { + case 0: + mPickUpType->setCaption("(S) small pick up 1x1 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 1: + mPickUpType->setCaption("(D) default pick up 2x1 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 2: + mPickUpType->setCaption("(F) forward pick up 2x3 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 3: + mPickUpType->setCaption("(3) pick up 3x3 cells " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 4: + mPickUpType->setCaption("(g) go and pick up in distance 4 " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 5: + mPickUpType->setCaption("(G) go and pick up in distance 8 " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + case 6: + mPickUpType->setCaption("(A) go and pick up in max distance " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + default: + mPickUpType->setCaption("(?) pick up " + + keyboard.getKeyValueString(keyboard.KEY_CHANGE_PICKUP_TYPE)); + break; + } + mPickUpType->adjustSize(); + + switch (viewport->getDebugPath()) + { + case 0: + mMapType->setCaption("(N) normal map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 1: + mMapType->setCaption("(D) debug map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 2: + mMapType->setCaption("(u) ultra map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 3: + mMapType->setCaption("(U) ultra map view 2 " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 4: + mMapType->setCaption("(e) empty map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + case 5: + mMapType->setCaption("(b) black & white map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + default: + mMapType->setCaption("(?) map view " + + keyboard.getKeyValueString(keyboard.KEY_PATHFIND)); + break; + } + mMapType->adjustSize(); + + switch (player_node->getMagicAttackType()) + { + case 0: + mMagicAttackType->setCaption("(f) use #flar for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 1: + mMagicAttackType->setCaption("(c) use #chiza for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 2: + mMagicAttackType->setCaption("(I) use #ingrav for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 3: + mMagicAttackType->setCaption("(F) use #frillyar for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + case 4: + mMagicAttackType->setCaption("(U) use #upmarmu for magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + default: + mMagicAttackType->setCaption("(?) magic attack " + + keyboard.getKeyValueString( + keyboard.KEY_SWITCH_MAGIC_ATTACK)); + break; + } + mMagicAttackType->adjustSize(); + + switch (player_node->getImitationMode()) + { + case 0: + mImitationMode->setCaption("(D) default imitation " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_IMITATION_MODE)); + break; + case 1: + mImitationMode->setCaption("(O) outfits imitation " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_IMITATION_MODE)); + break; + default: + mImitationMode->setCaption("(?) imitation " + + keyboard.getKeyValueString( + keyboard.KEY_CHANGE_IMITATION_MODE)); + break; + } + mImitationMode->adjustSize(); + + switch (player_node->getAwayMode()) + { + case 0: + mAwayMode->setCaption("(O) on keyboard " + + keyboard.getKeyValueString(keyboard.KEY_AWAY)); + break; + case 1: + mAwayMode->setCaption("(A) away " + + keyboard.getKeyValueString(keyboard.KEY_AWAY)); + break; + default: + mAwayMode->setCaption("(?) away " + + keyboard.getKeyValueString(keyboard.KEY_AWAY)); + break; + } + mAwayMode->adjustSize(); + + switch (viewport->getCameraMode()) + { + case 0: + mCameraMode->setCaption("(G) game camera mode " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + case 1: + mCameraMode->setCaption("(F) free camera mode " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + case 2: + mCameraMode->setCaption("(D) design camera mode " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + default: + mCameraMode->setCaption("(?) away " + + keyboard.getKeyValueString(keyboard.KEY_CAMERA)); + break; + } + mCameraMode->adjustSize(); + + if (player_node->getDisableGameModifiers()) + { + mDisableGameModifiers->setCaption("Game modifiers are disabled " + + keyboard.getKeyValueString(keyboard.KEY_DISABLE_GAME_MODIFIERS)); + } + else + { + mDisableGameModifiers->setCaption("Game modifiers are enabled " + + keyboard.getKeyValueString(keyboard.KEY_DISABLE_GAME_MODIFIERS)); + } + mDisableGameModifiers->adjustSize(); +} diff --git a/src/gui/statuspopup.h b/src/gui/statuspopup.h new file mode 100644 index 000000000..62d952384 --- /dev/null +++ b/src/gui/statuspopup.h @@ -0,0 +1,78 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef StatusPopup_H +#define StatusPopup_H + +#include "gui/widgets/popup.h" + +#include "resources/iteminfo.h" + +#include + +class TextBox; + +/** + * A popup that displays information about an item. + */ +class StatusPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + StatusPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~StatusPopup(); + + /** + * Sets the location to display the item popup. + */ + void view(int x, int y); + +// void mouseMoved(gcn::MouseEvent &mouseEvent); + + void update(); + + private: + void updateLabels(); + gcn::Label *mMoveType; + gcn::Label *mCrazyMoveType; + gcn::Label *mMoveToTargetType; + gcn::Label *mFollowMode; + gcn::Label *mAttackType; + gcn::Label *mAttackWeaponType; + gcn::Label *mDropCounter; + gcn::Label *mPickUpType; + gcn::Label *mMapType; + gcn::Label *mMagicAttackType; + gcn::Label *mDisableGameModifiers; + gcn::Label *mImitationMode; + gcn::Label *mAwayMode; + gcn::Label *mCameraMode; +}; + +#endif // StatusPopup_H diff --git a/src/gui/statuswindow.cpp b/src/gui/statuswindow.cpp new file mode 100644 index 000000000..349893bfa --- /dev/null +++ b/src/gui/statuswindow.cpp @@ -0,0 +1,880 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/statuswindow.h" + +#include "configuration.h" +#include "event.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "units.h" +#include "viewport.h" + +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/vertcontainer.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/playerhandler.h" +#include "net/gamehandler.h" + +#include "utils/gettext.h" +#include "utils/mathutils.h" +#include "utils/stringutils.h" + +class AttrDisplay : public Container +{ + public: + enum Type + { + DERIVED = 0, + CHANGEABLE, + UNKNOWN + }; + + ~AttrDisplay(); + + virtual std::string update(); + + virtual Type getType() + { return UNKNOWN; } + + protected: + AttrDisplay(int id, const std::string &name); + + const int mId; + const std::string mName; + + LayoutHelper *mLayout; + Label *mLabel; + Label *mValue; +}; + +class DerDisplay : public AttrDisplay +{ + public: + DerDisplay(int id, const std::string &name); + + virtual Type getType() + { return DERIVED; } +}; + +class ChangeDisplay : public AttrDisplay, gcn::ActionListener +{ + public: + ChangeDisplay(int id, const std::string &name); + + std::string update(); + + virtual Type getType() + { return CHANGEABLE; } + + void setPointsNeeded(int needed); + + private: + void action(const gcn::ActionEvent &event); + + int mNeeded; + + Label *mPoints; + Button *mDec; + Button *mInc; +}; + +StatusWindow::StatusWindow(): + Window(player_node ? player_node->getName() : "?") +{ + listen(CHANNEL_ATTRIBUTES); + + setWindowName("Status"); + setupWindow->registerWindowForReset(this); + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + setDefaultSize((windowContainer->getWidth() - 365) / 2, + (windowContainer->getHeight() - 255) / 2, 365, 275); + + // ---------------------- + // Status Part + // ---------------------- + + mLvlLabel = new Label(strprintf(_("Level: %d"), 0)); + mMoneyLabel = new Label(strprintf(_("Money: %s"), "")); + + int max = PlayerInfo::getAttribute(MAX_HP); + if (!max) + max = 1; + + mHpLabel = new Label(_("HP:")); + mHpBar = new ProgressBar(max ? + static_cast(PlayerInfo::getAttribute(HP)) + / static_cast(max): + static_cast(0), 80, 15, Theme::PROG_HP); + + max = PlayerInfo::getAttribute(EXP_NEEDED); + mXpLabel = new Label(_("Exp:")); + mXpBar = new ProgressBar(max ? + static_cast(PlayerInfo::getAttribute(EXP)) + / static_cast(max): + static_cast(0), 80, 15, Theme::PROG_EXP); + + bool magicBar = Net::getGameHandler()->canUseMagicBar(); + + int job = Net::getPlayerHandler()->getJobLocation() + && serverConfig.getValueBool("showJob", false); + + if (magicBar) + { + max = PlayerInfo::getAttribute(MAX_MP); + mMpLabel = new Label(_("MP:")); + mMpBar = new ProgressBar(max ? + static_cast(PlayerInfo::getAttribute(MAX_MP)) + / static_cast(max) : static_cast(0), + 80, 15, Net::getPlayerHandler()->canUseMagic() ? + Theme::PROG_MP : Theme::PROG_NO_MP); + } + else + { + mMpLabel = 0; + mMpBar = 0; + } + + place(0, 0, mLvlLabel, 3); + // 5, 0 Job Level + place(8, 0, mMoneyLabel, 3); + place(0, 1, mHpLabel).setPadding(3); + place(1, 1, mHpBar, 4); + place(5, 1, mXpLabel).setPadding(3); + place(6, 1, mXpBar, 5); + if (magicBar) + { + place(0, 2, mMpLabel).setPadding(3); + // 5, 2 and 6, 2 Job Progress Bar + if (job) + place(1, 2, mMpBar, 4); + else + place(1, 2, mMpBar, 10); + } + + if (job) + { + mJobLvlLabel = new Label(strprintf(_("Job: %d"), 0)); + mJobLabel = new Label(_("Job:")); + mJobBar = new ProgressBar(0.0f, 80, 15, Theme::PROG_JOB); + + place(5, 0, mJobLvlLabel, 3); + place(5, 2, mJobLabel).setPadding(3); + place(6, 2, mJobBar, 5); + } + else + { + mJobLvlLabel = 0; + mJobLabel = 0; + mJobBar = 0; + } + + // ---------------------- + // Stats Part + // ---------------------- + + mAttrCont = new VertContainer(32); + mAttrScroll = new ScrollArea(mAttrCont); + mAttrScroll->setOpaque(false); + mAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + mAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO); + place(0, 3, mAttrScroll, 5, 3); + + mDAttrCont = new VertContainer(32); + mDAttrScroll = new ScrollArea(mDAttrCont); + mDAttrScroll->setOpaque(false); + mDAttrScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); + mDAttrScroll->setVerticalScrollPolicy(ScrollArea::SHOW_AUTO); + place(6, 3, mDAttrScroll, 5, 3); + + getLayout().setRowHeight(3, Layout::AUTO_SET); + + mCharacterPointsLabel = new Label("C"); + place(0, 6, mCharacterPointsLabel, 5); + + if (Net::getPlayerHandler()->canCorrectAttributes()) + { + mCorrectionPointsLabel = new Label("C"); + place(0, 7, mCorrectionPointsLabel, 5); + } + + loadWindowState(); + + // Update bars + updateHPBar(mHpBar, true); + if (magicBar) + updateMPBar(mMpBar, true); + updateXPBar(mXpBar, false); + + mMoneyLabel->setCaption(strprintf(_("Money: %s"), + Units::formatCurrency(PlayerInfo::getAttribute(MONEY)).c_str())); + mMoneyLabel->adjustSize(); + mCharacterPointsLabel->setCaption(strprintf(_("Character points: %d"), + PlayerInfo::getAttribute(CHAR_POINTS))); + mCharacterPointsLabel->adjustSize(); + + if (player_node && player_node->isGM()) + { + mLvlLabel->setCaption(strprintf(_("Level: %d (GM %d)"), + PlayerInfo::getAttribute(LEVEL), player_node->getGMLevel())); + } + else + { + mLvlLabel->setCaption(strprintf(_("Level: %d"), + PlayerInfo::getAttribute(LEVEL))); + } + mLvlLabel->adjustSize(); +} + +void StatusWindow::event(Channels channel _UNUSED_, + const Mana::Event &event) +{ + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + switch(event.getInt("id")) + { + case HP: case MAX_HP: + updateHPBar(mHpBar, true); + break; + + case MP: case MAX_MP: + updateMPBar(mMpBar, true); + break; + + case EXP: case EXP_NEEDED: + updateXPBar(mXpBar, false); + break; + + case MONEY: + mMoneyLabel->setCaption(strprintf(_("Money: %s"), + Units::formatCurrency(event.getInt("newValue")).c_str())); + mMoneyLabel->adjustSize(); + break; + + case CHAR_POINTS: + mCharacterPointsLabel->setCaption(strprintf( + _("Character points: %d"), event.getInt("newValue"))); + + mCharacterPointsLabel->adjustSize(); + // Update all attributes + for (Attrs::iterator it = mAttrs.begin(); + it != mAttrs.end(); it++) + { + if (it->second) + it->second->update(); + } + break; + + case CORR_POINTS: + mCorrectionPointsLabel->setCaption(strprintf( + _("Correction points: %d"), event.getInt("newValue"))); + mCorrectionPointsLabel->adjustSize(); + // Update all attributes + for (Attrs::iterator it = mAttrs.begin(); + it != mAttrs.end(); it++) + { + if (it->second) + it->second->update(); + } + break; + + case LEVEL: + mLvlLabel->setCaption(strprintf(_("Level: %d"), + event.getInt("newValue"))); + mLvlLabel->adjustSize(); + break; + + default: + break; + } + } + else if (event.getName() == EVENT_UPDATESTAT) + { + int id = event.getInt("id"); + if (id == Net::getPlayerHandler()->getJobLocation()) + { + if (mJobLvlLabel) + { + mJobLvlLabel->setCaption(strprintf(_("Job: %d"), + PlayerInfo::getStatBase(id))); + mJobLvlLabel->adjustSize(); + + updateProgressBar(mJobBar, id, false); + } + } + else + { + updateMPBar(mMpBar, true); + Attrs::iterator it = mAttrs.find(id); + if (it != mAttrs.end() && it->second) + { + if (it->second) + it->second->update(); + } + } + } +} + +void StatusWindow::setPointsNeeded(int id, int needed) +{ + Attrs::iterator it = mAttrs.find(id); + + if (it != mAttrs.end()) + { + AttrDisplay *disp = it->second; + if (disp && disp->getType() == AttrDisplay::CHANGEABLE) + static_cast(disp)->setPointsNeeded(needed); + } +} + +void StatusWindow::addAttribute(int id, const std::string &name, + bool modifiable, + const std::string &description _UNUSED_) +{ + AttrDisplay *disp; + + if (modifiable) + { + disp = new ChangeDisplay(id, name); + mAttrCont->add(disp); + } + else + { + disp = new DerDisplay(id, name); + mDAttrCont->add(disp); + } + + mAttrs[id] = disp; +} + +void StatusWindow::updateHPBar(ProgressBar *bar, bool showMax) +{ + if (!bar) + return; + + if (showMax) + bar->setText(toString(PlayerInfo::getAttribute(HP)) + + "/" + toString(PlayerInfo::getAttribute(MAX_HP))); + else + bar->setText(toString(PlayerInfo::getAttribute(HP))); + + float prog = 1.0; + + if (PlayerInfo::getAttribute(MAX_HP) > 0) + { + prog = static_cast(PlayerInfo::getAttribute(HP)) + / static_cast(PlayerInfo::getAttribute(MAX_HP)); + } + bar->setProgress(prog); +} + +void StatusWindow::updateMPBar(ProgressBar *bar, bool showMax) +{ + if (!bar) + return; + + if (showMax) + { + bar->setText(toString(PlayerInfo::getAttribute(MP)) + + "/" + toString(PlayerInfo::getAttribute(MAX_MP))); + } + else + { + bar->setText(toString(PlayerInfo::getAttribute(MP))); + } + + float prog = 1.0f; + + if (PlayerInfo::getAttribute(MAX_MP) > 0) + { + if (PlayerInfo::getAttribute(MAX_MP)) + { + prog = static_cast(PlayerInfo::getAttribute(MP)) + / static_cast(PlayerInfo::getAttribute(MAX_MP)); + } + else + { + prog = static_cast(PlayerInfo::getAttribute(MP)); + } + } + + if (Net::getPlayerHandler()->canUseMagic()) + bar->setProgressPalette(Theme::PROG_MP); + else + bar->setProgressPalette(Theme::PROG_NO_MP); + + bar->setProgress(prog); +} + +void StatusWindow::updateProgressBar(ProgressBar *bar, int value, int max, + bool percent) +{ + if (!bar) + return; + + if (max == 0) + { + bar->setText(_("Max")); + bar->setProgress(1.0); + } + else + { + float progress = static_cast(value) + / static_cast(max); + + if (percent) + bar->setText(strprintf("%2.5f", 100 * progress) + "%"); + else + bar->setText(toString(value) + "/" + toString(max)); + + bar->setProgress(progress); + } +} + +void StatusWindow::updateXPBar(ProgressBar *bar, bool percent) +{ + if (!bar) + return; + + updateProgressBar(bar, PlayerInfo::getAttribute(EXP), + PlayerInfo::getAttribute(EXP_NEEDED), percent); +} + +void StatusWindow::updateProgressBar(ProgressBar *bar, int id, bool percent) +{ + std::pair exp = PlayerInfo::getStatExperience(id); + updateProgressBar(bar, exp.first, exp.second, percent); +} + +void StatusWindow::updateStatusBar(ProgressBar *bar, bool percent _UNUSED_) +{ + if (!player_node || !viewport) + return; + + std::string str; + + switch (player_node->getInvertDirection()) + { + case 0: + str = "D"; + break; + case 1: + str = "I"; + break; + case 2: + str = "c"; + break; + case 3: + str = "C"; + break; + case 4: + str = "d"; + break; + default: + str = "?"; + break; + } + + if (player_node->getCrazyMoveType() < 10) + str += toString(player_node->getCrazyMoveType()); + else + { + switch (player_node->getCrazyMoveType()) + { + case 10: + str += "a"; + break; + default: + str += "?"; + break; + } + } + + switch (player_node->getMoveToTargetType()) + { + case 0: + str += "0"; + break; + case 1: + str += "1"; + break; + case 2: + str += "2"; + break; + case 3: + str += "3"; + break; + case 4: + str += "5"; + break; + case 5: + str += "7"; + break; + case 6: + str += "A"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getFollowMode()) + { + case 0: + str += "D"; + break; + case 1: + str += "R"; + break; + case 2: + str += "M"; + break; + case 3: + str += "P"; + break; + default: + str += "?"; + break; + } + + str += " "; + switch (player_node->getAttackWeaponType()) + { + case 1: + str += "D"; + break; + case 2: + str += "s"; + break; + case 3: + str += "S"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getAttackType()) + { + case 0: + str += "D"; + break; + case 1: + str += "G"; + break; + case 2: + str += "A"; + break; + case 3: + str += "d"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getMagicAttackType()) + { + case 0: + str += "f"; + break; + case 1: + str += "c"; + break; + case 2: + str += "I"; + break; + case 3: + str += "F"; + break; + case 4: + str += "U"; + break; + default: + str += "?"; + break; + } + + str += " " + toString(player_node->getQuickDropCounter()); + + switch (player_node->getPickUpType()) + { + case 0: + str += "S"; + break; + case 1: + str += "D"; + break; + case 2: + str += "F"; + break; + case 3: + str += "3"; + break; + case 4: + str += "g"; + break; + case 5: + str += "G"; + break; + case 6: + str += "A"; + break; + default: + str += "?"; + break; + } + + switch (viewport->getDebugPath()) + { + case 0: + str += " N"; + break; + case 1: + str += " D"; + break; + case 2: + str += " u"; + break; + case 3: + str += " U"; + break; + case 4: + str += " e"; + break; + case 5: + str += " b"; + break; + default: + str += " ?"; + break; + } + + switch (player_node->getImitationMode()) + { + case 0: + str += " D"; + break; + case 1: + str += " O"; + break; + default: + str += " ?"; + break; + } + + switch (viewport->getCameraMode()) + { + case 0: + str += "G"; + break; + case 1: + str += "F"; + break; + case 2: + str += "D"; + break; + default: + str += "?"; + break; + } + + switch (player_node->getAwayMode()) + { + case 0: + str += "O"; + break; + case 1: + str += "A"; + break; + default: + str += "?"; + break; + } + + bar->setText(str); + bar->setProgress(50); + if (player_node->getDisableGameModifiers()) + { + gcn::Color col; + col.r = 100; + col.g = 100; + col.b = 100; +// bar->setColor(new gcn::Color(100, 100, 100)); + bar->setColor(col); + } + else + { + gcn::Color col; + col.r = 255; + col.g = 255; + col.b = 0; +// bar->setColor(new gcn::Color(255, 255, 0)); + bar->setColor(col); + } +} + +AttrDisplay::AttrDisplay(int id, const std::string &name): + mId(id), + mName(name) +{ + setSize(100, 32); + mLabel = new Label(name); + mValue = new Label("1 "); + + mLabel->setAlignment(Graphics::CENTER); + mValue->setAlignment(Graphics::CENTER); + + mLayout = new LayoutHelper(this); +} + +AttrDisplay::~AttrDisplay() +{ + delete mLayout; +} + +std::string AttrDisplay::update() +{ + int base = PlayerInfo::getStatBase(mId); + int bonus = PlayerInfo::getStatMod(mId); + std::string value = toString(base + bonus); + if (bonus) + value += strprintf("=%d%+d", base, bonus); + mValue->setCaption(value); + return mName; +} + +DerDisplay::DerDisplay(int id, const std::string &name): + AttrDisplay(id, name) +{ + // Do the layout + LayoutHelper h(this); + ContainerPlacer place = mLayout->getPlacer(0, 0); + + place(0, 0, mLabel, 3); + place(3, 0, mValue, 2); + + update(); +} + +ChangeDisplay::ChangeDisplay(int id, const std::string &name): + AttrDisplay(id, name), mNeeded(1) +{ + mPoints = new Label(_("Max")); + mInc = new Button(_("+"), "inc", this); + + // Do the layout + ContainerPlacer place = mLayout->getPlacer(0, 0); + + place(0, 0, mLabel, 3); + place(4, 0, mValue, 2); + place(6, 0, mInc); + place(7, 0, mPoints); + + if (Net::getPlayerHandler()->canCorrectAttributes()) + { + mDec = new Button(_("-"), "dec", this); + mDec->setWidth(mInc->getWidth()); + + place(3, 0, mDec); + } + else + { + mDec = 0; + } + + update(); +} + +std::string ChangeDisplay::update() +{ + if (mNeeded > 0) + mPoints->setCaption(toString(mNeeded)); + else + mPoints->setCaption(_("Max")); + + if (mDec) + mDec->setEnabled(PlayerInfo::getAttribute(CORR_POINTS)); + + mInc->setEnabled(PlayerInfo::getAttribute(CHAR_POINTS) >= mNeeded && + mNeeded > 0); + + return AttrDisplay::update(); +} + +void ChangeDisplay::setPointsNeeded(int needed) +{ + mNeeded = needed; + + update(); +} + +void ChangeDisplay::action(const gcn::ActionEvent &event) +{ + if (Net::getPlayerHandler()->canCorrectAttributes() && + event.getSource() == mDec) + { + int newcorpoints = PlayerInfo::getAttribute(CORR_POINTS) - 1; + PlayerInfo::setAttribute(CORR_POINTS, newcorpoints); + + int newpoints = PlayerInfo::getAttribute(CHAR_POINTS) + 1; + PlayerInfo::setAttribute(CHAR_POINTS, newpoints); + + int newbase = PlayerInfo::getStatBase(mId) - 1; + PlayerInfo::setStatBase(mId, newbase); + + Net::getPlayerHandler()->decreaseAttribute(mId); + } + else if (event.getSource() == mInc) + { + int cnt = 1; + if (config.getBoolValue("quickStats")) + { + cnt = mInc->getClickCount(); + if (cnt > 10) + cnt = 10; + } + + int newpoints = PlayerInfo::getAttribute(CHAR_POINTS) - cnt; + PlayerInfo::setAttribute(CHAR_POINTS, newpoints); + + int newbase = PlayerInfo::getStatBase(mId) + cnt; + PlayerInfo::setStatBase(mId, newbase); + + for (unsigned f = 0; f < mInc->getClickCount(); f ++) + { + Net::getPlayerHandler()->increaseAttribute(mId); + if (cnt != 1) + SDL_Delay(100); + } + } +} diff --git a/src/gui/statuswindow.h b/src/gui/statuswindow.h new file mode 100644 index 000000000..4b324073e --- /dev/null +++ b/src/gui/statuswindow.h @@ -0,0 +1,99 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef STATUS_H +#define STATUS_H + +#include "guichanfwd.h" +#include "listener.h" + +#include "gui/widgets/window.h" + +#include + +#include + +class AttrDisplay; +class ProgressBar; +class ScrollArea; +class VertContainer; + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * The player status dialog. + * + * \ingroup Interface + */ +class StatusWindow : public Window, public Mana::Listener +{ + public: + /** + * Constructor. + */ + StatusWindow(); + + void event(Channels channel, const Mana::Event &event); + + void setPointsNeeded(int id, int needed); + + void addAttribute(int id, const std::string &name, bool modifiable, + const std::string &description); + + static void updateHPBar(ProgressBar *bar, bool showMax = false); + static void updateMPBar(ProgressBar *bar, bool showMax = false); + static void updateXPBar(ProgressBar *bar, bool percent = true); + static void updateStatusBar(ProgressBar *bar, bool percent = true); + static void updateProgressBar(ProgressBar *bar, int value, int max, + bool percent); + void updateProgressBar(ProgressBar *bar, int id, + bool percent = true); + + private: + /** + * Status Part + */ + gcn::Label *mLvlLabel, *mMoneyLabel; + gcn::Label *mHpLabel, *mMpLabel, *mXpLabel; + ProgressBar *mHpBar, *mMpBar, *mXpBar; + + gcn::Label *mJobLvlLabel, *mJobLabel; + ProgressBar *mJobBar; + + VertContainer *mAttrCont; + ScrollArea *mAttrScroll; + VertContainer *mDAttrCont; + ScrollArea *mDAttrScroll; + + gcn::Label *mCharacterPointsLabel; + gcn::Label *mCorrectionPointsLabel; + + typedef std::map Attrs; + Attrs mAttrs; +}; + +extern StatusWindow *statusWindow; + +#endif diff --git a/src/gui/textcommandeditor.cpp b/src/gui/textcommandeditor.cpp new file mode 100644 index 000000000..2300033a5 --- /dev/null +++ b/src/gui/textcommandeditor.cpp @@ -0,0 +1,390 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/textcommandeditor.h" + +#include +#include +#include +#include + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/dropdown.h" +#include "gui/widgets/inttextfield.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/layouthelper.h" +#include "gui/widgets/radiobutton.h" +#include "gui/widgets/table.h" +#include "gui/widgets/textfield.h" + +#include "chat.h" +#include "configuration.h" +#include "item.h" +#include "localplayer.h" +#include "main.h" +#include "keyboardconfig.h" +#include "spellmanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "resources/itemdb.h" +#include "resources/iteminfo.h" + +class IconsModal : public gcn::ListModel +{ +public: + IconsModal() + { + std::map info = ItemDB::getItemInfos(); + std::list tempStrings; + + for (std::map::const_iterator + i = info.begin(), i_end = info.end(); + i != i_end; ++i) + { + if (i->first < 0) + continue; + + ItemInfo info = (*i->second); + std::string name = info.getName(); + if (name != "unnamed" && !info.getName().empty() + && info.getName() != "unnamed") + { + tempStrings.push_back(name); + } + } + tempStrings.sort(); + mStrings.push_back(""); + for (std::list::const_iterator i = tempStrings.begin(), + i_end = tempStrings.end(); i != i_end; ++i) + { + mStrings.push_back(*i); + } + } + + virtual ~IconsModal() + {} + + virtual int getNumberOfElements() + { + return static_cast(mStrings.size()); + } + + virtual std::string getElementAt(int i) + { + if (i < 0 || i >= getNumberOfElements()) + return _("???"); + + return mStrings.at(i); + } +private: + std::vector mStrings; +}; + + +const char *TARGET_TYPE_TEXT[3] = +{ + N_("No Target"), + N_("Allow Target"), + N_("Need Target"), +}; + +const char *MAGIC_SCHOOL_TEXT[6] = +{ + N_("General Magic"), + N_("Life Magic"), + N_("War Magic"), + N_("Transmute Magic"), + N_("Nature Magic"), + N_("Astral Magic") +}; + +class TargetTypeModel : public gcn::ListModel +{ +public: + virtual ~TargetTypeModel() { } + + virtual int getNumberOfElements() + { + return 3; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return TARGET_TYPE_TEXT[i]; + } +}; + +class MagicSchoolModel : public gcn::ListModel +{ +public: + virtual ~MagicSchoolModel() { } + + virtual int getNumberOfElements() + { + return 6; + } + + virtual std::string getElementAt(int i) + { + if (i >= getNumberOfElements() || i < 0) + return _("???"); + + return MAGIC_SCHOOL_TEXT[i]; + } +}; + + +TextCommandEditor::TextCommandEditor(TextCommand *command): + Window(_("Command Editor")) +{ + int w = 350; + int h = 350; + + mEnabledKeyboard = keyboard.isEnabled(); + keyboard.setEnabled(false); + + setWindowName("TextCommandEditor"); + //setCloseButton(true); + setDefaultSize(w, h, ImageRect::CENTER); + + mAdvanced = false; + mCommand = command; + + mIsMagicCommand = (command->getCommandType() == TEXT_COMMAND_MAGIC); + + mIsMagic = new RadioButton(_("magic"), "magic", mIsMagicCommand); + mIsMagic->setActionEventId("magic"); + mIsMagic->addActionListener(this); + + mIsOther = new RadioButton(_("other"), "magic", !mIsMagicCommand); + mIsOther->setActionEventId("other"); + mIsOther->addActionListener(this); + + + mSymbolLabel = new Label(_("Symbol:")); + mSymbolTextField = new TextField(); + + mCommandLabel = new Label(_("Command:")); + mCommandTextField = new TextField(); + + mManaLabel = new Label(_("Mana:")); + mManaField = new IntTextField(0); + mManaField->setRange(0, 500); + mManaField->setWidth(20); + + mTypeLabel = new Label(_("Target Type:")); + mTypeDropDown = new DropDown(new TargetTypeModel); + mTypeDropDown->setActionEventId("type"); + mTypeDropDown->addActionListener(this); + + mIconLabel = new Label(_("Icon:")); + mIconDropDown = new DropDown(new IconsModal); + mIconDropDown->setActionEventId("icon"); + mIconDropDown->addActionListener(this); + mIconDropDown->setSelectedString(mCommand->getIcon()); + + mMagicLvlLabel = new Label(_("Magic level:")); + mMagicLvlField = new IntTextField(0); + mMagicLvlField->setRange(0, 5); + mMagicLvlField->setWidth(20); + + mSchoolLabel = new Label(_("Magic School:")); + mSchoolDropDown = new DropDown(new MagicSchoolModel); + mSchoolDropDown->setActionEventId("school"); + mSchoolDropDown->addActionListener(this); + mSchoolDropDown->setSelected(0); + + mSchoolLvlLabel = new Label(_("School level:")); + mSchoolLvlField = new IntTextField(0); + mSchoolLvlField->setRange(0, 5); + mSchoolLvlField->setWidth(20); + + mSaveButton = new Button(_("Save"), "save", this); + mSaveButton->adjustSize(); + + mCancelButton = new Button(_("Cancel"), "cancel", this); + mCancelButton->adjustSize(); + + mDeleteButton = new Button(_("Delete"), "delete", this); + mDeleteButton->adjustSize(); + + if (command->getCommandType() == TEXT_COMMAND_MAGIC) + showControls(true); + else + showControls(false); + + mSymbolTextField->setText(command->getSymbol()); + mCommandTextField->setText(command->getCommand()); + mManaField->setValue(command->getMana()); + mTypeDropDown->setSelected(command->getTargetType()); + mMagicLvlField->setValue(command->getBaseLvl()); + mSchoolDropDown->setSelected(command->getSchool() - MAGIC_START_ID); + mSchoolLvlField->setValue(command->getSchoolLvl()); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mIsMagic, 1); + place(2, 0, mIsOther, 1); + place(0, 1, mSymbolLabel, 2).setPadding(3); + place(2, 1, mSymbolTextField, 3).setPadding(3); + place(0, 2, mCommandLabel, 2).setPadding(3); + place(2, 2, mCommandTextField, 4).setPadding(3); + place(0, 3, mTypeLabel, 2).setPadding(3); + place(2, 3, mTypeDropDown, 3).setPadding(3); + + place(0, 4, mIconLabel, 2).setPadding(3); + place(2, 4, mIconDropDown, 3).setPadding(3); + + place(0, 5, mManaLabel, 2).setPadding(3); + place(2, 5, mManaField, 3).setPadding(3); + place(0, 6, mMagicLvlLabel, 2).setPadding(3); + place(2, 6, mMagicLvlField, 3).setPadding(3); + + place(0, 7, mSchoolLabel, 2).setPadding(3); + place(2, 7, mSchoolDropDown, 3).setPadding(3); + place(0, 8, mSchoolLvlLabel, 2).setPadding(3); + place(2, 8, mSchoolLvlField, 3).setPadding(3); + + place(0, 9, mSaveButton, 2).setPadding(3); + place(2, 9, mCancelButton, 2).setPadding(3); + place(4, 9, mDeleteButton, 2).setPadding(3); + + setWidth(w); + setHeight(h); + + center(); + + setVisible(true); +} + +TextCommandEditor::~TextCommandEditor() +{ +} + +void TextCommandEditor::action(const gcn::ActionEvent &event) +{ + const std::string &eventId = event.getId(); + + if (eventId == "magic") + { + mIsMagicCommand = true; + showControls(true); + } + else if (eventId == "other") + { + mIsMagicCommand = false; + showControls(false); + } + else if (eventId == "save") + { + save(); + scheduleDelete(); + } + else if (eventId == "cancel") + { + scheduleDelete(); + } + else if (eventId == "delete") + { + deleteCommand(); + scheduleDelete(); + } +} + +void TextCommandEditor::update() +{ +} + +void TextCommandEditor::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); +} + +void TextCommandEditor::updateList() +{ +} + +void TextCommandEditor::reset() +{ +} + +void TextCommandEditor::showControls(bool show) +{ + mManaField->setVisible(show); + mManaLabel->setVisible(show); + mMagicLvlLabel->setVisible(show); + mMagicLvlField->setVisible(show); + mSchoolLabel->setVisible(show); + mSchoolDropDown->setVisible(show); + mSchoolLvlLabel->setVisible(show); + mSchoolLvlField->setVisible(show); +} + +void TextCommandEditor::scheduleDelete() +{ + keyboard.setEnabled(mEnabledKeyboard); + Window::scheduleDelete(); +} + +void TextCommandEditor::save() +{ + if (mIsMagicCommand) + mCommand->setCommandType(TEXT_COMMAND_MAGIC); + else + mCommand->setCommandType(TEXT_COMMAND_TEXT); + + mCommand->setSymbol(mSymbolTextField->getText()); + mCommand->setCommand(mCommandTextField->getText()); + mCommand->setMana(mManaField->getValue()); + mCommand->setTargetType( + static_cast(mTypeDropDown->getSelected())); + mCommand->setIcon(mIconDropDown->getSelectedString()); + mCommand->setBaseLvl(mMagicLvlField->getValue()); + mCommand->setSchool(static_cast( + mSchoolDropDown->getSelected() + MAGIC_START_ID)); + mCommand->setSchoolLvl(mSchoolLvlField->getValue()); + if (spellManager) + spellManager->save(); +} + +void TextCommandEditor::deleteCommand() +{ + mCommand->setCommandType(TEXT_COMMAND_TEXT); + mCommand->setSymbol(""); + mCommand->setCommand(""); + mCommand->setMana(0); + mCommand->setTargetType(NOTARGET); + mCommand->setIcon(""); + mCommand->setBaseLvl(0); + mCommand->setSchool(SKILL_MAGIC); + mCommand->setSchoolLvl(0); + if (spellManager) + spellManager->save(); +} diff --git a/src/gui/textcommandeditor.h b/src/gui/textcommandeditor.h new file mode 100644 index 000000000..9f8c0ad51 --- /dev/null +++ b/src/gui/textcommandeditor.h @@ -0,0 +1,105 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TEXTCOMMANDEDITOR_H +#define TEXTCOMMANDEDITOR_H + +#include "gui/widgets/window.h" + +#include "textcommand.h" + +#include + +class RadioButton; +class Label; +class TextBox; +class TextField; +class DropDown; +class ListModel; +class Button; +class TextCommand; +class IntTextField; + +class TextCommandEditor : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor. + */ + TextCommandEditor(TextCommand *command); + + /** + * Destructor. + */ + ~TextCommandEditor(); + + void action(const gcn::ActionEvent &event); + + void update(); + + void widgetResized(const gcn::Event &event); + + void updateList(); + + void reset(); + + void scheduleDelete(); + + private: + void showControls(bool show); + + void save(); + + void deleteCommand(); + + TextCommand *mCommand; + bool mAdvanced; + + RadioButton *mIsMagic; + RadioButton *mIsOther; + Label *mSymbolLabel; + TextField *mSymbolTextField; + Label *mCommandLabel; + TextField *mCommandTextField; + Label *mTypeLabel; + DropDown *mTypeDropDown; + Label *mIconLabel; + DropDown *mIconDropDown; + Label *mManaLabel; + IntTextField *mManaField; + Label *mMagicLvlLabel; + IntTextField *mMagicLvlField; + Label *mSchoolLabel; + DropDown *mSchoolDropDown; + Label *mSchoolLvlLabel; + IntTextField *mSchoolLvlField; + + //Button *mAdvancedButton; + Button *mCancelButton; + Button *mSaveButton; + Button *mDeleteButton; + + bool mEnabledKeyboard; + bool mIsMagicCommand; +}; + +#endif diff --git a/src/gui/textdialog.cpp b/src/gui/textdialog.cpp new file mode 100644 index 000000000..c47e028c4 --- /dev/null +++ b/src/gui/textdialog.cpp @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/textdialog.h" + +#include "keyboardconfig.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/textfield.h" + +#include "utils/gettext.h" + +int TextDialog::instances = 0; + +TextDialog::TextDialog(const std::string &title, const std::string &msg, + Window *parent): + Window(title, true, parent), + mTextField(new TextField) +{ + mEnabledKeyboard = keyboard.isEnabled(); + keyboard.setEnabled(false); + + gcn::Label *textLabel = new Label(msg); + mOkButton = new Button(_("OK"), "OK", this); + gcn::Button *cancelButton = new Button(_("Cancel"), "CANCEL", this); + + place(0, 0, textLabel, 4); + place(0, 1, mTextField, 4); + place(2, 2, mOkButton); + place(3, 2, cancelButton); + + reflowLayout(static_cast(textLabel->getWidth() + 20)); + + if (getParent()) + { + setLocationRelativeTo(getParent()); + getParent()->moveToTop(this); + } + setVisible(true); + requestModalFocus(); + mTextField->requestFocus(); + + instances++; +} + +TextDialog::~TextDialog() +{ + instances--; +} + +void TextDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "CANCEL") + setActionEventId("~" + getActionEventId()); + + distributeActionEvent(); + close(); +} + +const std::string &TextDialog::getText() const +{ + return mTextField->getText(); +} + +void TextDialog::setText(std::string text) +{ + if (mTextField) + mTextField->setText(text); +} + +void TextDialog::close() +{ + keyboard.setEnabled(mEnabledKeyboard); + scheduleDelete(); +} \ No newline at end of file diff --git a/src/gui/textdialog.h b/src/gui/textdialog.h new file mode 100644 index 000000000..9c7e8d312 --- /dev/null +++ b/src/gui/textdialog.h @@ -0,0 +1,74 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_GUILD_DIALOG_H +#define GUI_GUILD_DIALOG_H + +#include "gui/widgets/window.h" + +#include + +class TextField; + +/** +* An option dialog. + * + * \ingroup GUI + */ +class TextDialog : public Window, public gcn::ActionListener +{ +public: + /** + * Constructor. + * + * @see Window::Window + */ + TextDialog(const std::string &title, const std::string &msg, + Window *parent = 0); + + ~TextDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Get the text in the textfield + */ + const std::string &getText() const; + + void setText(std::string text); + + static bool isActive() + { return instances; } + + void close(); + +private: + static int instances; + + TextField *mTextField; + gcn::Button *mOkButton; + bool mEnabledKeyboard; +}; + +#endif diff --git a/src/gui/textpopup.cpp b/src/gui/textpopup.cpp new file mode 100644 index 000000000..270b0f759 --- /dev/null +++ b/src/gui/textpopup.cpp @@ -0,0 +1,99 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/textpopup.h" + +#include "gui/gui.h" +#include "gui/palette.h" + +#include "graphics.h" +#include "units.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +TextPopup::TextPopup(): + Popup("TextPopup") +{ + const int fontHeight = getFont()->getHeight(); + + mText1 = new gcn::Label; + mText1->setPosition(getPadding(), getPadding()); + + mText2 = new gcn::Label; + mText2->setPosition(getPadding(), fontHeight + 2 * getPadding()); + + add(mText1); + add(mText2); + addMouseListener(this); +} + +TextPopup::~TextPopup() +{ +} + +void TextPopup::show(int x, int y, const std::string &str1, + const std::string &str2) +{ + mText1->setCaption(str1); + mText1->adjustSize(); + mText2->setCaption(str2); + mText2->adjustSize(); + + int minWidth = mText1->getWidth(); + if (mText2->getWidth() > minWidth) + minWidth = mText2->getWidth(); + + minWidth += 4 * getPadding(); + setWidth(minWidth); + + if (!str2.empty()) + setHeight((2 * getPadding() + mText1->getFont()->getHeight()) * 2); + else + setHeight(2 * getPadding() + mText1->getFont()->getHeight()); + + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + posX = graphics->getWidth() - getWidth(); + if (posY + getHeight() > graphics->getHeight()) + posY = y - getHeight() - distance; + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void TextPopup::mouseMoved(gcn::MouseEvent &event) +{ + Popup::mouseMoved(event); + + // When the mouse moved on top of the popup, hide it + setVisible(false); +} diff --git a/src/gui/textpopup.h b/src/gui/textpopup.h new file mode 100644 index 000000000..3b4158f6e --- /dev/null +++ b/src/gui/textpopup.h @@ -0,0 +1,68 @@ +/* + * The Mana World + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TEXTPOPUP_H +#define TEXTPOPUP_H + +#include "gui/widgets/popup.h" + +#include + +class TextBox; + +/** + * A popup that displays information about an item. + */ +class TextPopup : public Popup +{ + public: + /** + * Constructor. Initializes the item popup. + */ + TextPopup(); + + /** + * Destructor. Cleans up the item popup on deletion. + */ + ~TextPopup(); + + /** + * Sets the text to be displayed. + */ + void show(int x, int y, const std::string &str1) + { show(x, y, str1, static_cast("")); }; + + /** + * Sets the text to be displayed. + */ + void show(int x, int y, const std::string &str1, + const std::string &str2); + + void mouseMoved(gcn::MouseEvent &mouseEvent); + + private: + gcn::Label *mText1; + gcn::Label *mText2; +}; + +#endif // TEXTPOPUP_H diff --git a/src/gui/theme.cpp b/src/gui/theme.cpp new file mode 100644 index 000000000..b45cef5b0 --- /dev/null +++ b/src/gui/theme.cpp @@ -0,0 +1,791 @@ +/* + * Gui Skinning + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/theme.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "resources/dye.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include "utils/dtor.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include + +#include +#include + +static std::string defaultThemePath; + +std::string Theme::mThemePath; +std::string Theme::mThemeName; +Theme *Theme::mInstance = 0; + +// Set the theme path... +static void initDefaultThemePath() +{ + ResourceManager *resman = ResourceManager::getInstance(); + defaultThemePath = branding.getStringValue("guiThemePath"); + + logger->log("defaultThemePath: " + defaultThemePath); + if (!defaultThemePath.empty() && resman->isDirectory(defaultThemePath)) + return; + else + defaultThemePath = "themes/"; +} + +Skin::Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown, + const std::string &filePath, + const std::string &name): + instances(0), + mFilePath(filePath), + mName(name), + mBorder(skin), + mCloseImage(close), + mStickyImageUp(stickyUp), + mStickyImageDown(stickyDown) +{} + +Skin::~Skin() +{ + // Clean up static resources + for (int i = 0; i < 9; i++) + { + delete mBorder.grid[i]; + mBorder.grid[i] = 0; + } + + if (mCloseImage) + { + mCloseImage->decRef(); + mCloseImage = 0; + } + delete mStickyImageUp; + mStickyImageUp = 0; + delete mStickyImageDown; + mStickyImageDown = 0; +} + +void Skin::updateAlpha(float minimumOpacityAllowed) +{ + const float alpha = static_cast( + std::max(static_cast(minimumOpacityAllowed), + static_cast(Client::getGuiAlpha()))); + + for_each(mBorder.grid, mBorder.grid + 9, + std::bind2nd(std::mem_fun(&Image::setAlpha), alpha)); + + if (mCloseImage) + mCloseImage->setAlpha(alpha); + if (mStickyImageUp) + mStickyImageUp->setAlpha(alpha); + if (mStickyImageDown) + mStickyImageDown->setAlpha(alpha); +} + +int Skin::getMinWidth() const +{ + if (!mBorder.grid[ImageRect::UPPER_LEFT] + || !mBorder.grid[ImageRect::UPPER_RIGHT]) + { + return 1; + } + + return mBorder.grid[ImageRect::UPPER_LEFT]->getWidth() + + mBorder.grid[ImageRect::UPPER_RIGHT]->getWidth(); +} + +int Skin::getMinHeight() const +{ + if (!mBorder.grid[ImageRect::UPPER_LEFT] + || !mBorder.grid[ImageRect::LOWER_LEFT]) + { + return 1; + } + + return mBorder.grid[ImageRect::UPPER_LEFT]->getHeight() + + mBorder.grid[ImageRect::LOWER_LEFT]->getHeight(); +} + +Theme::Theme(): + Palette(THEME_COLORS_END), + mMinimumOpacity(-1.0f), + mProgressColors(ProgressColors(THEME_PROG_END)) +{ + initDefaultThemePath(); + + config.addListener("guialpha", this); + loadColors(); + + mColors[HIGHLIGHT].ch = 'H'; + mColors[CHAT].ch = 'C'; + mColors[GM].ch = 'G'; + mColors[PLAYER].ch = 'Y'; + mColors[WHISPER].ch = 'W'; + mColors[WHISPER_OFFLINE].ch = 'w'; + mColors[IS].ch = 'I'; + mColors[PARTY_CHAT_TAB].ch = 'P'; + mColors[GUILD_CHAT_TAB].ch = 'U'; + mColors[SERVER].ch = 'S'; + mColors[LOGGER].ch = 'L'; + mColors[HYPERLINK].ch = '<'; +} + +Theme::~Theme() +{ + delete_all(mSkins); + config.removeListener("guialpha", this); + delete_all(mProgressColors); +} + +Theme *Theme::instance() +{ + if (!mInstance) + mInstance = new Theme; + + return mInstance; +} + +void Theme::deleteInstance() +{ + delete mInstance; + mInstance = 0; +} + +gcn::Color Theme::getProgressColor(int type, float progress) +{ + int color[3] = {0, 0, 0}; + + if (mInstance) + { + DyePalette *dye = mInstance->mProgressColors[type]; + + if (dye) + dye->getColor(progress, color); + else + logger->log("color not found: " + toString(type)); + } + + return gcn::Color(color[0], color[1], color[2]); +} + +Skin *Theme::load(const std::string &filename, const std::string &defaultPath) +{ + // Check if this skin was already loaded + + SkinIterator skinIterator = mSkins.find(filename); + if (mSkins.end() != skinIterator) + { + if (skinIterator->second) + skinIterator->second->instances++; + return skinIterator->second; + } + + Skin *skin = readSkin(filename); + + if (!skin) + { + // Try falling back on the defaultPath if this makes sense + if (filename != defaultPath) + { + logger->log("Error loading skin '%s', falling back on default.", + filename.c_str()); + + skin = readSkin(defaultPath); + } + + if (!skin) + { + logger->log(strprintf("Error: Loading default skin '%s' failed. " + "Make sure the skin file is valid.", + defaultPath.c_str())); + } + } + + // Add the skin to the loaded skins + mSkins[filename] = skin; + + return skin; +} + +void Theme::setMinimumOpacity(float minimumOpacity) +{ + if (minimumOpacity > 1.0f) + return; + + mMinimumOpacity = minimumOpacity; + updateAlpha(); +} + +void Theme::updateAlpha() +{ + for (SkinIterator iter = mSkins.begin(); iter != mSkins.end(); ++iter) + { + if (iter->second) + iter->second->updateAlpha(mMinimumOpacity); + } +} + +void Theme::optionChanged(const std::string &) +{ + updateAlpha(); +} + +Skin *Theme::readSkin(const std::string &filename) +{ + if (filename.empty()) + return 0; + +// std::string filename = filename0; +// ResourceManager *resman = ResourceManager::getInstance(); + logger->log("Loading skin '%s'.", filename.c_str()); +// filename = resman->mapPathToSkin(filename0); + + XML::Document doc(resolveThemePath(filename)); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "skinset")) + return 0; + + const std::string skinSetImage = XML::getProperty(rootNode, "image", ""); + + if (skinSetImage.empty()) + { + logger->log1("Theme::readSkin(): Skinset does not define an image!"); + return 0; + } + + logger->log("Theme::load(): defines '%s' as a skin image.", + skinSetImage.c_str()); + + Image *dBorders = Theme::getImageFromTheme(skinSetImage); + ImageRect border; + memset(&border, 0, sizeof(ImageRect)); + + // iterate 's + for_each_xml_child_node(widgetNode, rootNode) + { + if (!xmlStrEqual(widgetNode->name, BAD_CAST "widget")) + continue; + + const std::string widgetType = + XML::getProperty(widgetNode, "type", "unknown"); + if (widgetType == "Window") + { + // Iterate through 's + // LEEOR / TODO: + // We need to make provisions to load in a CloseButton image. For + // now it can just be hard-coded. + for_each_xml_child_node(partNode, widgetNode) + { + if (!xmlStrEqual(partNode->name, BAD_CAST "part")) + continue; + + const std::string partType = + XML::getProperty(partNode, "type", "unknown"); + // TOP ROW + const int xPos = XML::getProperty(partNode, "xpos", 0); + const int yPos = XML::getProperty(partNode, "ypos", 0); + const int width = XML::getProperty(partNode, "width", 1); + const int height = XML::getProperty(partNode, "height", 1); + + if (partType == "top-left-corner") + { + if (dBorders) + { + border.grid[0] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[0] = 0; + } + } + else if (partType == "top-edge") + { + if (dBorders) + { + border.grid[1] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[1] = 0; + } + } + else if (partType == "top-right-corner") + { + if (dBorders) + { + border.grid[2] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[2] = 0; + } + } + + // MIDDLE ROW + else if (partType == "left-edge") + { + if (dBorders) + { + border.grid[3] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[3] = 0; + } + } + else if (partType == "bg-quad") + { + if (dBorders) + { + border.grid[4] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[4] = 0; + } + } + else if (partType == "right-edge") + { + if (dBorders) + { + border.grid[5] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[5] = 0; + } + } + + // BOTTOM ROW + else if (partType == "bottom-left-corner") + { + if (dBorders) + { + border.grid[6] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[6] = 0; + } + } + else if (partType == "bottom-edge") + { + if (dBorders) + { + border.grid[7] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[7] = 0; + } + } + else if (partType == "bottom-right-corner") + { + if (dBorders) + { + border.grid[8] = dBorders->getSubImage( + xPos, yPos, width, height); + } + else + { + border.grid[8] = 0; + } + } + + else + { + logger->log("Theme::readSkin(): Unknown part type '%s'", + partType.c_str()); + } + } + } + else + { + logger->log("Theme::readSkin(): Unknown widget type '%s'", + widgetType.c_str()); + } + } + + if (dBorders) + dBorders->decRef(); + + logger->log1("Finished loading skin."); + + // Hard-coded for now until we update the above code + // to look for window buttons + Image *closeImage = Theme::getImageFromTheme("close_button.png"); + Image *sticky = Theme::getImageFromTheme("sticky_button.png"); + Image *stickyImageUp = 0; + Image *stickyImageDown = 0; + if (sticky) + { + stickyImageUp = sticky->getSubImage(0, 0, 15, 15); + stickyImageDown = sticky->getSubImage(15, 0, 15, 15); + sticky->decRef(); + } + + Skin *skin = new Skin(border, closeImage, stickyImageUp, stickyImageDown, + filename); + skin->updateAlpha(mMinimumOpacity); + return skin; +} + +bool Theme::tryThemePath(std::string themeName) +{ + if (!themeName.empty()) + { + std::string path = defaultThemePath + themeName; + if (PHYSFS_exists(path.c_str())) + { + mThemePath = path; + mThemeName = themeName; + return true; + } + } + + return false; +} + +void Theme::fillSkinsList(std::vector &list) +{ + char **skins = PHYSFS_enumerateFiles( + branding.getStringValue("guiThemePath").c_str()); + + for (char **i = skins; *i != 0; i++) + { + if (PHYSFS_isDirectory(( + branding.getStringValue("guiThemePath") + *i).c_str())) + { + list.push_back(*i); + } + } + + PHYSFS_freeList(skins); +} + +void Theme::fillFontsList(std::vector &list) +{ + PHYSFS_permitSymbolicLinks(1); + char **fonts = PHYSFS_enumerateFiles( + branding.getStringValue("fontsPath").c_str()); + + for (char **i = fonts; *i != 0; i++) + { + if (!PHYSFS_isDirectory(( + branding.getStringValue("fontsPath") + *i).c_str())) + { + list.push_back(*i); + } + } + + PHYSFS_freeList(fonts); + PHYSFS_permitSymbolicLinks(0); +} + +void Theme::selectSkin() +{ + prepareThemePath(); +} + +void Theme::prepareThemePath() +{ + initDefaultThemePath(); + + mThemePath = ""; + mThemeName = ""; + + // Try theme from settings + if (tryThemePath(config.getValue("selectedSkin", ""))) + return; + + // Try theme from settings + if (tryThemePath(config.getValue("theme", ""))) + return; + + // Try theme from branding + if (tryThemePath(branding.getValue("theme", ""))) + return; + + if (mThemePath.empty()) + mThemePath = "graphics/gui"; + + instance()->loadColors(mThemePath); + + logger->log("Selected Theme: " + mThemePath); +} + +std::string Theme::resolveThemePath(const std::string &path) +{ + // Need to strip off any dye info for the existence tests + int pos = static_cast(path.find('|')); + std::string file; + if (pos > 0) + file = path.substr(0, pos); + else + file = path; + + // Might be a valid path already + if (PHYSFS_exists(file.c_str())) + return path; + + // Try the theme + file = getThemePath() + "/" + file; + if (PHYSFS_exists(file.c_str())) + return getThemePath() + "/" + path; + + // Backup + return branding.getStringValue("guiPath") + path; +} + +Image *Theme::getImageFromTheme(const std::string &path) +{ + ResourceManager *resman = ResourceManager::getInstance(); + return resman->getImage(resolveThemePath(path)); +} + +ImageSet *Theme::getImageSetFromTheme(const std::string &path, + int w, int h) +{ + ResourceManager *resman = ResourceManager::getInstance(); + return resman->getImageSet(resolveThemePath(path), w, h); +} + +static int readColorType(const std::string &type) +{ + static std::string colors[] = + { + "TEXT", + "SHADOW", + "OUTLINE", + "PROGRESS_BAR", + "BUTTON", + "BUTTON_DISABLED", + "TAB", + "PARTY_CHAT_TAB", + "PARTY_SOCIAL_TAB", + "GUILD_CHAT_TAB", + "GUILD_SOCIAL_TAB", + "BACKGROUND", + "HIGHLIGHT", + "TAB_FLASH", + "TAB_PLAYER_FLASH", + "SHOP_WARNING", + "ITEM_EQUIPPED", + "CHAT", + "GM", + "PLAYER", + "WHISPER", + "WHISPER_OFFLINE", + "IS", + "SERVER", + "LOGGER", + "HYPERLINK", + "UNKNOWN_ITEM", + "GENERIC", + "HEAD", + "USABLE", + "TORSO", + "ONEHAND", + "LEGS", + "FEET", + "TWOHAND", + "SHIELD", + "RING", + "NECKLACE", + "ARMS", + "AMMO", + "SERVER_VERSION_NOT_SUPPORTED", + "WARNING" + }; + + if (type.empty()) + return -1; + + for (int i = 0; i < Theme::THEME_COLORS_END; i++) + { + if (compareStrI(type, colors[i]) == 0) + return i; + } + + return -1; +} + +static gcn::Color readColor(const std::string &description) +{ + int size = static_cast(description.length()); + if (size < 7 || description[0] != '#') + { + logger->log("Error, invalid theme color palette: %s", + description.c_str()); + return Palette::BLACK; + } + + int v = 0; + for (int i = 1; i < 7; ++i) + { + char c = description[i]; + int n; + + if ('0' <= c && c <= '9') + { + n = c - '0'; + } + else if ('A' <= c && c <= 'F') + { + n = c - 'A' + 10; + } + else if ('a' <= c && c <= 'f') + { + n = c - 'a' + 10; + } + else + { + logger->log("Error, invalid theme color palette: %s", + description.c_str()); + return Palette::BLACK; + } + + v = (v << 4) | n; + } + + return gcn::Color(v); +} + +static Palette::GradientType readColorGradient(const std::string &grad) +{ + static std::string grads[] = + { + "STATIC", + "PULSE", + "SPECTRUM", + "RAINBOW" + }; + + if (grad.empty()) + return Palette::STATIC; + + for (int i = 0; i < 4; i++) + { + if (compareStrI(grad, grads[i])) + return static_cast(i); + } + + return Palette::STATIC; +} + +static int readProgressType(const std::string &type) +{ + static std::string colors[] = + { + "DEFAULT", + "HP", + "MP", + "NO_MP", + "EXP", + "INVY_SLOTS", + "WEIGHT", + "JOB" + }; + + if (type.empty()) + return -1; + + for (int i = 0; i < Theme::THEME_PROG_END; i++) + { + if (compareStrI(type, colors[i]) == 0) + return i; + } + + return -1; +} + +void Theme::loadColors(std::string file) +{ +// if (file == mThemePath) +// return; // No need to reload + + if (file == "") + file = "colors.xml"; + else + file += "/colors.xml"; + + XML::Document doc(resolveThemePath(file)); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "colors")) + { + logger->log("Error loading colors file: %s", file.c_str()); + return; + } + + logger->log("Loading colors file: %s", file.c_str()); + + int type; + std::string temp; + gcn::Color color; + GradientType grad; + + for_each_xml_child_node(node, root) + { + if (xmlStrEqual(node->name, BAD_CAST "color")) + { + type = readColorType(XML::getProperty(node, "id", "")); + if (type < 0) // invalid or no type given + continue; + + temp = XML::getProperty(node, "color", ""); + if (temp.empty()) // no color set, so move on + continue; + + color = readColor(temp); + grad = readColorGradient(XML::getProperty(node, "effect", "")); + + mColors[type].set(type, color, grad, 10); + } + else if (xmlStrEqual(node->name, BAD_CAST "progressbar")) + { + type = readProgressType(XML::getProperty(node, "id", "")); + if (type < 0) // invalid or no type given + continue; + + mProgressColors[type] = new DyePalette(XML::getProperty(node, + "color", "")); + } + } +} diff --git a/src/gui/theme.h b/src/gui/theme.h new file mode 100644 index 000000000..bb7a66f85 --- /dev/null +++ b/src/gui/theme.h @@ -0,0 +1,273 @@ +/* + * Gui Skinning + * Copyright (C) 2008 The Legend of Mazzeroth Development Team + * Copyright (C) 2009 Aethyra Development Team + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SKIN_H +#define SKIN_H + +#include "configlistener.h" +#include "graphics.h" + +#include "gui/palette.h" + +#include +#include +#include + +class DyePalette; +class Image; +class ImageSet; +class ProgressBar; + +class Skin +{ + public: + Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown, + const std::string &filePath, + const std::string &name = ""); + + ~Skin(); + + /** + * Returns the skin's name. Useful for giving a human friendly skin + * name if a dialog for skin selection for a specific window type is + * done. + */ + const std::string &getName() const + { return mName; } + + /** + * Returns the skin's xml file path. + */ + const std::string &getFilePath() const + { return mFilePath; } + + /** + * Returns the background skin. + */ + const ImageRect &getBorder() const + { return mBorder; } + + /** + * Returns the image used by a close button for this skin. + */ + Image *getCloseImage() const + { return mCloseImage; } + + /** + * Returns the image used by a sticky button for this skin. + */ + Image *getStickyImage(bool state) const + { return state ? mStickyImageDown : mStickyImageUp; } + + /** + * Returns the minimum width which can be used with this skin. + */ + int getMinWidth() const; + + /** + * Returns the minimum height which can be used with this skin. + */ + int getMinHeight() const; + + /** + * Updates the alpha value of the skin + */ + void updateAlpha(float minimumOpacityAllowed = 0.0f); + + int instances; + + private: + std::string mFilePath; /**< File name path for the skin */ + std::string mName; /**< Name of the skin to use */ + ImageRect mBorder; /**< The window border and background */ + Image *mCloseImage; /**< Close Button Image */ + Image *mStickyImageUp; /**< Sticky Button Image */ + Image *mStickyImageDown; /**< Sticky Button Image */ +}; + +class Theme : public Palette, public ConfigListener +{ + public: + static Theme *instance(); + + static void deleteInstance(); + + static void prepareThemePath(); + + static void selectSkin(); + + static std::string getThemePath() + { return mThemePath; } + + static std::string getThemeName() + { return mThemeName; } + + static void fillSkinsList(std::vector &list); + + static void fillFontsList(std::vector &list); + + /** + * Returns the patch to the given gui resource relative to the theme + * or, if it isn't in the theme, relative to 'graphics/gui'. + */ + static std::string resolveThemePath(const std::string &path); + + static Image *getImageFromTheme(const std::string &path); + + static ImageSet *getImageSetFromTheme(const std::string &path, + int w, int h); + + enum ThemePalette + { + TEXT = 0, + SHADOW, + OUTLINE, + PROGRESS_BAR, + BUTTON, + BUTTON_DISABLED, + TAB, + PARTY_CHAT_TAB, + PARTY_SOCIAL_TAB, + GUILD_CHAT_TAB, + GUILD_SOCIAL_TAB, + BACKGROUND, + HIGHLIGHT, + TAB_FLASH, + TAB_PLAYER_FLASH, + SHOP_WARNING, + ITEM_EQUIPPED, + CHAT, + GM, + PLAYER, + WHISPER, + WHISPER_OFFLINE, + IS, + SERVER, + LOGGER, + HYPERLINK, + UNKNOWN_ITEM, + GENERIC, + HEAD, + USABLE, + TORSO, + ONEHAND, + LEGS, + FEET, + TWOHAND, + SHIELD, + RING, + NECKLACE, + ARMS, + AMMO, + SERVER_VERSION_NOT_SUPPORTED, + WARNING, + THEME_COLORS_END + }; + + enum ProgressPalette + { + PROG_DEFAULT = 0, + PROG_HP, + PROG_MP, + PROG_NO_MP, + PROG_EXP, + PROG_INVY_SLOTS, + PROG_WEIGHT, + PROG_JOB, + THEME_PROG_END + }; + + /** + * Gets the color associated with the type. Sets the alpha channel + * before returning. + * + * @param type the color type requested + * @param alpha alpha channel to use + * + * @return the requested color + */ + inline static const gcn::Color &getThemeColor(int type, + int alpha = 255) + { return mInstance->getColor(type, alpha); } + + const static gcn::Color &getThemeColor(char c, bool &valid) + { return mInstance->getColor(c, valid); } + + static gcn::Color getProgressColor(int type, float progress); + + /** + * Loads a skin. + */ + Skin *load(const std::string &filename, + const std::string &defaultPath = getThemePath()); + + /** + * Updates the alpha values of all of the skins. + */ + void updateAlpha(); + + /** + * Get the minimum opacity allowed to skins. + */ + float getMinimumOpacity() + { return mMinimumOpacity; } + + /** + * Set the minimum opacity allowed to skins. + * Set a negative value to free the minimum allowed. + */ + void setMinimumOpacity(float minimumOpacity); + + void optionChanged(const std::string &); + + private: + Theme(); + ~Theme(); + + Skin *readSkin(const std::string &filename0); + + // Map containing all window skins + typedef std::map Skins; + typedef Skins::iterator SkinIterator; + + Skins mSkins; + + static std::string mThemePath; + static std::string mThemeName; + static Theme *mInstance; + + static bool tryThemePath(std::string themePath); + + void loadColors(std::string file = ""); + + /** + * Tells if the current skins opacity + * should not get less than the given value + */ + float mMinimumOpacity; + + typedef std::vector ProgressColors; + ProgressColors mProgressColors; +}; + +#endif diff --git a/src/gui/trade.cpp b/src/gui/trade.cpp new file mode 100644 index 000000000..ee67dc6da --- /dev/null +++ b/src/gui/trade.cpp @@ -0,0 +1,420 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/trade.h" + +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "units.h" + +#include "gui/inventorywindow.h" +#include "gui/itemamount.h" +#include "gui/setup.h" +#include "gui/theme.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" +#include "gui/widgets/itemcontainer.h" +#include "gui/widgets/label.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/textfield.h" +#include "gui/widgets/layout.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" +#include "net/tradehandler.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#include + +#define CAPTION_PROPOSE _("Propose trade") +#define CAPTION_CONFIRMED _("Confirmed. Waiting...") +#define CAPTION_ACCEPT _("Agree trade") +#define CAPTION_ACCEPTED _("Agreed. Waiting...") + +TradeWindow::TradeWindow(): + Window(_("Trade: You")), + mMyInventory(new Inventory(Inventory::TRADE)), + mPartnerInventory(new Inventory(Inventory::TRADE)), + mStatus(PROPOSING), + mAutoAddItem(0), + mAutoAddToNick(""), + mGotMoney(0), + mAutoMoney(0) +{ + logger->log1("TradeWindow::TradeWindow nick"); + + setWindowName("Trade"); + setResizable(true); + setCloseButton(true); + setDefaultSize(386, 180, ImageRect::CENTER); + setMinWidth(386); + setMinHeight(180); + + if (setupWindow) + setupWindow->registerWindowForReset(this); + + std::string longestName = getFont()->getWidth(_("OK")) > + getFont()->getWidth(_("Trade")) ? + _("OK") : _("Trade"); + + mAddButton = new Button(_("Add"), "add", this); + mOkButton = new Button("", "", this); // Will be filled in later + + int width = std::max(mOkButton->getFont()->getWidth(CAPTION_PROPOSE), + mOkButton->getFont()->getWidth(CAPTION_CONFIRMED)); + width = std::max(width, mOkButton->getFont()->getWidth(CAPTION_ACCEPT)); + width = std::max(width, mOkButton->getFont()->getWidth(CAPTION_ACCEPTED)); + + mOkButton->setWidth(8 + width); + + mMyItemContainer = new ItemContainer(mMyInventory.get()); + mMyItemContainer->addSelectionListener(this); + + ScrollArea *myScroll = new ScrollArea(mMyItemContainer); + myScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mPartnerItemContainer = new ItemContainer(mPartnerInventory.get()); + mPartnerItemContainer->addSelectionListener(this); + + ScrollArea *partnerScroll = new ScrollArea(mPartnerItemContainer); + partnerScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mMoneyLabel = new Label(strprintf(_("You get %s"), "")); + gcn::Label *mMoneyLabel2 = new Label(_("You give:")); + + mMoneyField = new TextField; + mMoneyField->setWidth(40); + mMoneyChangeButton = new Button(_("Change"), "money", this); + + place(1, 0, mMoneyLabel); + place(0, 1, myScroll).setPadding(3); + place(1, 1, partnerScroll).setPadding(3); + ContainerPlacer place; + place = getPlacer(0, 0); + place(0, 0, mMoneyLabel2); + place(1, 0, mMoneyField, 2); + place(3, 0, mMoneyChangeButton).setHAlign(LayoutCell::LEFT); + place = getPlacer(0, 2); + place(0, 0, mAddButton); + place(1, 0, mOkButton); + Layout &layout = getLayout(); + layout.extend(0, 2, 2, 1); + layout.setRowHeight(1, Layout::AUTO_SET); + layout.setRowHeight(2, 0); + layout.setColWidth(0, Layout::AUTO_SET); + layout.setColWidth(1, Layout::AUTO_SET); + + loadWindowState(); + + reset(); +} + +TradeWindow::~TradeWindow() +{ +} + +void TradeWindow::setMoney(int amount) +{ + if (amount < mGotMoney) + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::WARNING)); + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT)); + + mGotMoney = amount; + mMoneyLabel->setCaption(strprintf(_("You get %s"), + Units::formatCurrency(amount).c_str())); + mMoneyLabel->adjustSize(); +} + +void TradeWindow::addItem(int id, bool own, int quantity, int refine) +{ + if (own) + mMyInventory->addItem(id, quantity, refine); + else + mPartnerInventory->addItem(id, quantity, refine); +} + +void TradeWindow::addItem(int id, bool own, int quantity, + int refine, bool equipment) +{ + if (own) + mMyInventory->addItem(id, quantity, refine, equipment); + else + mPartnerInventory->addItem(id, quantity, refine, equipment); +} + +void TradeWindow::changeQuantity(int index, bool own, int quantity) +{ + if (own) + { + if (mMyInventory->getItem(index)) + mMyInventory->getItem(index)->setQuantity(quantity); + } + else + { + if (mPartnerInventory->getItem(index)) + mPartnerInventory->getItem(index)->setQuantity(quantity); + } +} + +void TradeWindow::increaseQuantity(int index, bool own, int quantity) +{ + if (own) + { + if (mMyInventory->getItem(index)) + mMyInventory->getItem(index)->increaseQuantity(quantity); + } + else + { + if (mPartnerInventory->getItem(index)) + mPartnerInventory->getItem(index)->increaseQuantity(quantity); + } +} + +void TradeWindow::reset() +{ + mMyInventory->clear(); + mPartnerInventory->clear(); + mOkOther = false; + mOkMe = false; + setMoney(0); + mMoneyField->setEnabled(true); + mMoneyField->setText(""); + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT)); + mAddButton->setEnabled(true); + mMoneyChangeButton->setEnabled(true); + mGotMoney = 0; + setStatus(PREPARING); +} + +void TradeWindow::receivedOk(bool own) +{ + if (own) + mOkMe = true; + else + mOkOther = true; + + if (mOkMe && mOkOther) + { + //mOkMe = false; + //mOkOther = false; + setStatus(ACCEPTING); + } +} + +void TradeWindow::tradeItem(Item *item, int quantity) +{ + Net::getTradeHandler()->addItem(item, quantity); +} + +void TradeWindow::valueChanged(const gcn::SelectionEvent &event) +{ + if (!mMyItemContainer || !mPartnerItemContainer) + return; + + /* If an item is selected in one container, make sure no item is selected + * in the other container. + */ + if (event.getSource() == mMyItemContainer && + mMyItemContainer->getSelectedItem()) + { + mPartnerItemContainer->selectNone(); + } + else if (mPartnerItemContainer->getSelectedItem()) + { + mMyItemContainer->selectNone(); + } +} + +void TradeWindow::setStatus(Status s) +{ + if (s == mStatus) + return; + mStatus = s; + + switch (s) + { + case PREPARING: + mOkButton->setCaption(CAPTION_PROPOSE); + mOkButton->setActionEventId("ok"); + break; + case PROPOSING: + mOkButton->setCaption(CAPTION_CONFIRMED); + mOkButton->setActionEventId(""); + break; + case ACCEPTING: + mOkButton->setCaption(CAPTION_ACCEPT); + mOkButton->setActionEventId("trade"); + break; + case ACCEPTED: + mOkButton->setCaption(CAPTION_ACCEPTED); + mOkButton->setActionEventId(""); + break; + default: + break; + } + + mOkButton->setEnabled((s != PROPOSING && s != ACCEPTED)); +} + +void TradeWindow::action(const gcn::ActionEvent &event) +{ + if (!inventoryWindow) + return; + + Item *item = inventoryWindow->getSelectedItem(); + + if (event.getId() == "add") + { + if (mStatus != PREPARING) + return; + + if (!inventoryWindow->isVisible()) + { + inventoryWindow->setVisible(true); + return; + } + + if (!item) + return; + + if (mMyInventory->getFreeSlot() == -1) + return; + + if (mMyInventory->contains(item)) + { + if (localChatTab) + { + localChatTab->chatLog(_("Failed adding item. You can not " + "overlap one kind of item on the window."), BY_SERVER); + } + return; + } + + // Choose amount of items to trade + ItemAmountWindow::showWindow(ItemAmountWindow::TradeAdd, this, item); + + setStatus(PREPARING); + } + else if (event.getId() == "cancel") + { + setVisible(false); + reset(); + PlayerInfo::setTrading(false); + + Net::getTradeHandler()->cancel(); + } + else if (event.getId() == "ok") + { + mMoneyField->setEnabled(false); + mAddButton->setEnabled(false); + mMoneyChangeButton->setEnabled(false); + receivedOk(true); + setStatus(PROPOSING); + Net::getTradeHandler()->confirm(); + } + else if (event.getId() == "trade") + { + receivedOk(true); + setStatus(ACCEPTED); + Net::getTradeHandler()->finish(); + } + else if (event.getId() == "money") + { + if (mStatus != PREPARING) + return; + + int v = atoi(mMoneyField->getText().c_str()); + int curMoney = PlayerInfo::getAttribute(MONEY); + if (v > curMoney) + { + if (localChatTab) + { + localChatTab->chatLog(_("You don't have enough money."), + BY_SERVER); + } + v = curMoney; + } + Net::getTradeHandler()->setMoney(v); + mMoneyField->setText(strprintf("%d", v)); + } +} + +void TradeWindow::close() +{ + Net::getTradeHandler()->cancel(); + clear(); +} + +void TradeWindow::clear() +{ + mAutoAddItem = 0; + mAutoAddToNick = ""; + mAutoMoney = 0; + mAutoAddAmount = 0; + mGotMoney = 0; + mMoneyLabel->setForegroundColor(Theme::getThemeColor(Theme::TEXT)); +} + +void TradeWindow::addAutoItem(std::string nick, Item* item, int amount) +{ + mAutoAddToNick = nick; + mAutoAddItem = item; + mAutoAddAmount = amount; +} + +void TradeWindow::addAutoMoney(std::string nick, int money) +{ + mAutoAddToNick = nick; + mAutoMoney = money; +} + +void TradeWindow::initTrade(std::string nick) +{ + if (!player_node) + return; + + if (!mAutoAddToNick.empty() && mAutoAddToNick == nick) + { + if (mAutoAddItem && mAutoAddItem->getQuantity()) + { + Inventory *inv = PlayerInfo::getInventory(); + if (inv) + { + Item *item = inv->findItem(mAutoAddItem->getId()); + if (item) + tradeItem(item, mAutoAddItem->getQuantity()); + } + } + if (mAutoMoney) + { + Net::getTradeHandler()->setMoney(mAutoMoney); + mMoneyField->setText(strprintf("%d", mAutoMoney)); + } + } + clear(); +} diff --git a/src/gui/trade.h b/src/gui/trade.h new file mode 100644 index 000000000..1baa14fa8 --- /dev/null +++ b/src/gui/trade.h @@ -0,0 +1,170 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRADE_H +#define TRADE_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include +#include + +#include + +class Inventory; +class Item; +class ItemContainer; +class ScrollArea; + +/** + * Trade dialog. + * + * \ingroup Interface + */ +class TradeWindow : public Window, gcn::ActionListener, gcn::SelectionListener +{ + public: + /** + * Constructor. + */ + TradeWindow(); + + /** + * Destructor. + */ + ~TradeWindow(); + + /** + * Displays expected money in the trade window. + */ + void setMoney(int quantity); + + /** + * Add an item to the trade window. + */ + void addItem(int id, bool own, int quantity, int refine); + + /** + * Reset both item containers + */ + void reset(); + + /** + * Add an item to the trade window. + */ + void addItem(int id, bool own, int quantity, + int refine, bool equipment); + + /** + * Change quantity of an item. + */ + void changeQuantity(int index, bool own, int quantity); + + /** + * Increase quantity of an item. + */ + void increaseQuantity(int index, bool own, int quantity); + + /** + * Player received ok message from server + */ + void receivedOk(bool own); + + /** + * Send trade packet. + */ + void tradeItem(Item *item, int quantity); + + /** + * Updates the labels and makes sure only one item is selected in + * either my inventory or partner inventory. + */ + void valueChanged(const gcn::SelectionEvent &event); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + /** + * Closes the Trade Window, as well as telling the server that the + * window has been closed. + */ + void close(); + + /** + * Clear auto trade items. + */ + void clear(); + + /** + * Add item what will be added to trade. + */ + void addAutoItem(std::string nick, Item* item, int amount); + + void addAutoMoney(std::string nick, int money); + + void initTrade(std::string nick); + + std::string getAutoTradeNick() + { return mAutoAddToNick; } + + private: + enum Status + { + PREPARING = 0, /**< Players are adding items. (1) */ + PROPOSING, /**< Local player has confirmed the trade. (1) */ + ACCEPTING, /**< Accepting the trade. (2) */ + ACCEPTED /**< Local player has accepted the trade. */ + }; + + /** + * Sets the current status of the trade. + */ + void setStatus(Status s); + + typedef const std::auto_ptr InventoryPtr; + InventoryPtr mMyInventory; + InventoryPtr mPartnerInventory; + + ItemContainer *mMyItemContainer; + ItemContainer *mPartnerItemContainer; + + gcn::Label *mMoneyLabel; + gcn::Button *mAddButton; + gcn::Button *mOkButton; + gcn::Button *mMoneyChangeButton; + gcn::TextField *mMoneyField; + + Status mStatus; + bool mOkOther, mOkMe; + Item* mAutoAddItem; + std::string mAutoAddToNick; + int mGotMoney; + int mAutoMoney; + int mAutoAddAmount; +}; + +extern TradeWindow *tradeWindow; + +#endif diff --git a/src/gui/truetypefont.cpp b/src/gui/truetypefont.cpp new file mode 100644 index 000000000..8e77c1ded --- /dev/null +++ b/src/gui/truetypefont.cpp @@ -0,0 +1,336 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/truetypefont.h" + +#include "client.h" +#include "graphics.h" +#include "log.h" +#include "main.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +#include + +const unsigned int CACHE_SIZE = 256; +const unsigned int CACHE_SIZE_SMALL1 = 90; +const unsigned int CACHE_SIZE_SMALL2 = 150; + +char *strBuf; + +class TextChunk +{ + public: + TextChunk(const std::string &text, const gcn::Color &color) : + img(0), text(text), color(color) + { + } + + ~TextChunk() + { + delete img; + img = 0; + } + + bool operator==(const TextChunk &chunk) const + { + return (chunk.text == text && chunk.color == color); + } + + void generate(TTF_Font *font) + { + SDL_Color sdlCol; + sdlCol.b = static_cast(color.b); + sdlCol.r = static_cast(color.r); + sdlCol.g = static_cast(color.g); + + getSafeUtf8String(text, strBuf); + +// SDL_Surface *surface = TTF_RenderUTF8_Solid( + SDL_Surface *surface = TTF_RenderUTF8_Blended( + font, strBuf, sdlCol); + + if (!surface) + { + img = 0; + return; + } + + img = Image::load(surface); + + SDL_FreeSurface(surface); + } + + Image *img; + std::string text; + gcn::Color color; +}; + +typedef std::list::iterator CacheIterator; + +static int fontCounter; + +TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style) : + mCreateCounter(0), + mDeleteCounter(0) +{ + ResourceManager *resman = ResourceManager::getInstance(); + + if (fontCounter == 0 && TTF_Init() == -1) + { + throw GCN_EXCEPTION("Unable to initialize SDL_ttf: " + + std::string(TTF_GetError())); + } + + if (!fontCounter) + { + strBuf = new char[65535]; + memset(strBuf, 65535, 0); + } + + ++fontCounter; + mFont = TTF_OpenFont(resman->getPath(filename).c_str(), size); + + if (!mFont) + { + logger->log("Error finding font " + filename); + mFont = TTF_OpenFont(resman->getPath( + "fonts/dejavusans.ttf").c_str(), size); + if (!mFont) + { + throw GCN_EXCEPTION("SDLTrueTypeFont::SDLTrueTypeFont: " + + std::string(TTF_GetError())); + } + } + + TTF_SetFontStyle(mFont, style); + mCleanTime = cur_time + 120; +} + +TrueTypeFont::~TrueTypeFont() +{ + TTF_CloseFont(mFont); + mFont = 0; + --fontCounter; + + if (fontCounter == 0) + { + TTF_Quit(); + delete []strBuf; + } +} + +void TrueTypeFont::loadFont(const std::string &filename, int size, int style) +{ + ResourceManager *resman = ResourceManager::getInstance(); + + if (fontCounter == 0 && TTF_Init() == -1) + { + logger->log("Unable to initialize SDL_ttf: " + + std::string(TTF_GetError())); + return; + } + + TTF_Font *font = TTF_OpenFont(resman->getPath(filename).c_str(), size); + + if (!font) + { + logger->log("SDLTrueTypeFont::SDLTrueTypeFont: " + + std::string(TTF_GetError())); + return; + } + + if (mFont) + TTF_CloseFont(mFont); + + mFont = font; + TTF_SetFontStyle(mFont, style); + clear(); +} + +void TrueTypeFont::clear() +{ + for (unsigned short f = 0; f < (unsigned short)CACHES_NUMBER; f ++) + mCache[static_cast(f)].clear(); +} + +void TrueTypeFont::drawString(gcn::Graphics *graphics, + const std::string &text, + int x, int y) +{ + if (text.empty()) + return; + + Graphics *g = dynamic_cast(graphics); + + gcn::Color col = g->getColor(); + const float alpha = static_cast(col.a) / 255.0f; + + /* The alpha value is ignored at string generation so avoid caching the + * same text with different alpha values. + */ + col.a = 255; + + TextChunk chunk(text, col); + + unsigned char chr = text[0]; + std::list *cache = &mCache[chr]; + + bool found = false; + +#ifdef DEBUG_FONT + int cnt = 0; +#endif + + for (CacheIterator i = cache->begin(); i != cache->end(); ++i) + { + if (chunk == (*i)) + { + // Raise priority: move it to front + cache->splice(cache->begin(), *cache, i); + found = true; + break; + } +#ifdef DEBUG_FONT + cnt ++; +#endif + } +#ifdef DEBUG_FONT + logger->log("drawString: " + text + ", iterations: " + toString(cnt)); +#endif + + // Surface not found + if (!found) + { + if (cache->size() >= CACHE_SIZE) + { +#ifdef DEBUG_FONT_COUNTERS + mDeleteCounter ++; +#endif + cache->pop_back(); + } +#ifdef DEBUG_FONT_COUNTERS + mCreateCounter ++; +#endif + cache->push_front(chunk); + cache->front().generate(mFont); + + if (!mCleanTime) + { + mCleanTime = cur_time + 120; + } + else if (mCleanTime < cur_time) + { + doClean(); + mCleanTime = cur_time + 120; + } + } + + if (cache->front().img) + { + cache->front().img->setAlpha(alpha); + g->drawImage(cache->front().img, x, y); + } + +} + +void TrueTypeFont::createTextChunk(TextChunk *chunk) +{ + if (!chunk || chunk->text.empty()) + return; + + const float alpha = static_cast(chunk->color.a) / 255.0f; + chunk->color.a = 255; + chunk->generate(mFont); + if (chunk->img) + chunk->img->setAlpha(alpha); +} + +int TrueTypeFont::getWidth(const std::string &text) const +{ + if (text.empty()) + return 0; + + unsigned char chr = text[0]; + std::list *cache = &mCache[chr]; + +#ifdef DEBUG_FONT + int cnt = 0; +#endif + + for (CacheIterator i = cache->begin(); i != cache->end(); i++) + { + if (i->text == text) + { + // Raise priority: move it to front + // Assumption is that TTF::draw will be called next + cache->splice(cache->begin(), *cache, i); + if (i->img) + return i->img->getWidth(); + else + return 0; + } +#ifdef DEBUG_FONT + cnt ++; +#endif + } + +#ifdef DEBUG_FONT + logger->log("getWidth: " + text + ", iterations: " + toString(cnt)); +#endif + + int w, h; + getSafeUtf8String(text, strBuf); + TTF_SizeUTF8(mFont, strBuf, &w, &h); + return w; +} + +int TrueTypeFont::getHeight() const +{ + return TTF_FontHeight(mFont); +} + +void TrueTypeFont::doClean() +{ + for (int f = 0; f < CACHES_NUMBER; f ++) + { + std::list *cache = &mCache[f]; + if (cache->size() > CACHE_SIZE_SMALL2) + { +#ifdef DEBUG_FONT_COUNTERS + mDeleteCounter += 10; +#endif + for (int d = 0; d < 10; d ++) + cache->pop_back(); + } + else if (cache->size() > CACHE_SIZE_SMALL1) + { +#ifdef DEBUG_FONT_COUNTERS + mDeleteCounter ++; +#endif + cache->pop_back(); + } + } +} \ No newline at end of file diff --git a/src/gui/truetypefont.h b/src/gui/truetypefont.h new file mode 100644 index 000000000..7a28c3b97 --- /dev/null +++ b/src/gui/truetypefont.h @@ -0,0 +1,104 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRUETYPEFONT_H +#define TRUETYPEFONT_H + +#include + +#ifdef __APPLE__ +#include +#else +#ifdef __WIN32__ +#include +#else +#include +#endif +#endif + +#include +#include + +#define CACHES_NUMBER 256 + +class TextChunk; + +/** + * A wrapper around SDL_ttf for allowing the use of TrueType fonts. + * + * NOTE: This class initializes SDL_ttf as necessary. + */ +class TrueTypeFont : public gcn::Font +{ + public: + /** + * Constructor. + * + * @param filename Font filename. + * @param size Font size. + */ + TrueTypeFont(const std::string &filename, int size, int style = 0); + + /** + * Destructor. + */ + ~TrueTypeFont(); + + void loadFont(const std::string &filename, int size, int style = 0); + + void createTextChunk(TextChunk *chunk); + + virtual int getWidth(const std::string &text) const; + + virtual int getHeight() const; + + std::list *getCache() + { return mCache; } + + /** + * @see Font::drawString + */ + void drawString(gcn::Graphics *graphics, + const std::string &text, + int x, int y); + + void clear(); + + void doClean(); + + int getCreateCounter() const + { return mCreateCounter; } + + int getDeleteCounter() const + { return mDeleteCounter; } + + private: + TTF_Font *mFont; + unsigned mCreateCounter; + unsigned mDeleteCounter; + + // Word surfaces cache + mutable std::list mCache[CACHES_NUMBER]; + int mCleanTime; +}; + +#endif diff --git a/src/gui/unregisterdialog.cpp b/src/gui/unregisterdialog.cpp new file mode 100644 index 000000000..a226f0d84 --- /dev/null +++ b/src/gui/unregisterdialog.cpp @@ -0,0 +1,145 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/unregisterdialog.h" + +#include "client.h" +#include "log.h" + +#include "gui/okdialog.h" +#include "gui/register.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/label.h" +#include "gui/widgets/passwordfield.h" +#include "gui/widgets/textfield.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +UnRegisterDialog::UnRegisterDialog(LoginData *loginData): + Window(_("Unregister"), true), + mWrongDataNoticeListener(new WrongDataNoticeListener), + mLoginData(loginData) +{ + gcn::Label *userLabel = new Label(strprintf(_("Name: %s"), mLoginData-> + username.c_str())); + gcn::Label *passwordLabel = new Label(_("Password:")); + mPasswordField = new PasswordField(mLoginData->password); + mUnRegisterButton = new Button(_("Unregister"), "unregister", this); + mCancelButton = new Button(_("Cancel"), "cancel", this); + + const int width = 210; + const int height = 80; + setContentSize(width, height); + + userLabel->setPosition(5, 5); + userLabel->setWidth(width - 5); + mPasswordField->setPosition( + 68, userLabel->getY() + userLabel->getHeight() + 7); + mPasswordField->setWidth(130); + + passwordLabel->setPosition(5, mPasswordField->getY() + 1); + + mCancelButton->setPosition( + width - 5 - mCancelButton->getWidth(), + height - 5 - mCancelButton->getHeight()); + mUnRegisterButton->setPosition( + mCancelButton->getX() - 5 - mUnRegisterButton->getWidth(), + mCancelButton->getY()); + + add(userLabel); + add(passwordLabel); + add(mPasswordField); + add(mUnRegisterButton); + add(mCancelButton); + + center(); + setVisible(true); + mPasswordField->requestFocus(); + mPasswordField->setActionEventId("cancel"); +} + +UnRegisterDialog::~UnRegisterDialog() +{ + delete mWrongDataNoticeListener; + mWrongDataNoticeListener = 0; +} + +void UnRegisterDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + Client::setState(STATE_CHAR_SELECT); + } + else if (event.getId() == "unregister") + { + const std::string username = mLoginData->username.c_str(); + const std::string password = mPasswordField->getText(); + logger->log("UnregisterDialog::unregistered, Username is %s", + username.c_str()); + + std::stringstream errorMessage; + bool error = false; + + unsigned int min = Net::getLoginHandler()->getMinPasswordLength(); + unsigned int max = Net::getLoginHandler()->getMaxPasswordLength(); + + // Check password + if (password.length() < min) + { + // Pass too short + errorMessage << strprintf(_("The password needs to be at least %d " + "characters long."), min); + error = true; + } + else if (password.length() > max - 1) + { + // Pass too long + errorMessage << strprintf(_("The password needs to be less than " + "%d characters long."), max); + error = true; + } + + if (error) + { + mWrongDataNoticeListener->setTarget(this->mPasswordField); + + OkDialog *dlg = new OkDialog(_("Error"), errorMessage.str()); + dlg->addActionListener(mWrongDataNoticeListener); + } + else + { + // No errors detected, unregister the new user. + mUnRegisterButton->setEnabled(false); + mLoginData->password = password; + Client::setState(STATE_UNREGISTER_ATTEMPT); + } + } +} diff --git a/src/gui/unregisterdialog.h b/src/gui/unregisterdialog.h new file mode 100644 index 000000000..dd330afdc --- /dev/null +++ b/src/gui/unregisterdialog.h @@ -0,0 +1,68 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UNREGISTERDIALOG_H +#define UNREGISTERDIALOG_H + +#include "guichanfwd.h" + +#include "gui/widgets/window.h" + +#include + +class LoginData; +class OkDialog; +class WrongDataNoticeListener; + +/** + * The Unregister dialog. + * + * \ingroup Interface + */ +class UnRegisterDialog : public Window, public gcn::ActionListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + UnRegisterDialog(LoginData *loginData); + + ~UnRegisterDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + private: + gcn::TextField *mPasswordField; + + gcn::Button *mUnRegisterButton; + gcn::Button *mCancelButton; + + WrongDataNoticeListener *mWrongDataNoticeListener; + + LoginData *mLoginData; +}; + +#endif diff --git a/src/gui/updatewindow.cpp b/src/gui/updatewindow.cpp new file mode 100644 index 000000000..e199d7a72 --- /dev/null +++ b/src/gui/updatewindow.cpp @@ -0,0 +1,672 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/updatewindow.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" +#include "main.h" + +#include "gui/sdlinput.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/button.h" +#include "gui/widgets/label.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/progressbar.h" +#include "gui/widgets/scrollarea.h" + +#include "net/download.h" +#include "net/logindata.h" + +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include +#include + +#include + +const std::string xmlUpdateFile = "resources.xml"; +const std::string txtUpdateFile = "resources2.txt"; + +std::vector loadXMLFile(const std::string &fileName); +std::vector loadTxtFile(const std::string &fileName); + +/** + * Load the given file into a vector of updateFiles. + */ +std::vector loadXMLFile(const std::string &fileName) +{ + std::vector files; + XML::Document doc(fileName, false); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "updates")) + { + logger->log("Error loading update file: %s", fileName.c_str()); + return files; + } + + for_each_xml_child_node(fileNode, rootNode) + { + // Ignore all tags except for the "update" tags + if (!xmlStrEqual(fileNode->name, BAD_CAST "update")) + continue; + + updateFile file; + file.name = XML::getProperty(fileNode, "file", ""); + file.hash = XML::getProperty(fileNode, "hash", ""); + file.type = XML::getProperty(fileNode, "type", "data"); + file.desc = XML::getProperty(fileNode, "description", ""); + if (XML::getProperty(fileNode, "required", "yes") == "yes") + file.required = true; + else + file.required = false; + + files.push_back(file); + } + + return files; +} + +std::vector loadTxtFile(const std::string &fileName) +{ + std::vector files; + std::ifstream fileHandler; + fileHandler.open(fileName.c_str(), std::ios::in); + + if (fileHandler.is_open()) + { + while (fileHandler.good()) + { + char name[256], hash[50]; + fileHandler.getline(name, 256, ' '); + fileHandler.getline(hash, 50); + + updateFile thisFile; + thisFile.name = name; + thisFile.hash = hash; + thisFile.type = "data"; + thisFile.required = true; + thisFile.desc = ""; + + files.push_back(thisFile); + } + } + else + { + logger->log("Error loading update file: %s", fileName.c_str()); + } + fileHandler.close(); + + return files; +} + +UpdaterWindow::UpdaterWindow(const std::string &updateHost, + const std::string &updatesDir, + bool applyUpdates, + int updateType): + Window(_("Updating...")), + mDownloadStatus(UPDATE_NEWS), + mUpdateHost(updateHost), + mUpdatesDir(updatesDir), + mCurrentFile("news.txt"), + mDownloadProgress(0.0f), + mCurrentChecksum(0), + mStoreInMemory(true), + mDownloadComplete(true), + mUserCancel(false), + mDownloadedBytes(0), + mMemoryBuffer(NULL), + mDownload(NULL), + mUpdateIndex(0), + mLoadUpdates(applyUpdates), + mUpdateType(updateType) +{ + mBrowserBox = new BrowserBox; + mScrollArea = new ScrollArea(mBrowserBox); + mLabel = new Label(_("Connecting...")); + mProgressBar = new ProgressBar(0.0, 310, 20); + mCancelButton = new Button(_("Cancel"), "cancel", this); + mPlayButton = new Button(_("Play"), "play", this); + + mProgressBar->setSmoothProgress(false); + mBrowserBox->setOpaque(false); + mPlayButton->setEnabled(false); + + ContainerPlacer place; + place = getPlacer(0, 0); + + place(0, 0, mScrollArea, 5, 3).setPadding(3); + place(0, 3, mLabel, 5); + place(0, 4, mProgressBar, 5); + place(3, 5, mCancelButton); + place(4, 5, mPlayButton); + + reflowLayout(450, 400); + + Layout &layout = getLayout(); + layout.setRowHeight(0, Layout::AUTO_SET); + + addKeyListener(this); + + center(); + setVisible(true); + mCancelButton->requestFocus(); + + // Try to download the updates list + download(); +} + +UpdaterWindow::~UpdaterWindow() +{ + if (mLoadUpdates) + loadUpdates(); + + if (mDownload) + { + mDownload->cancel(); + + delete mDownload; + mDownload = 0; + } + free(mMemoryBuffer); +} + +void UpdaterWindow::setProgress(float p) +{ + // Do delayed progress bar update, since Guichan isn't thread-safe + MutexLocker lock(&mDownloadMutex); + mDownloadProgress = p; +} + +void UpdaterWindow::setLabel(const std::string &str) +{ + // Do delayed label text update, since Guichan isn't thread-safe + MutexLocker lock(&mDownloadMutex); + mNewLabelCaption = str; +} + +void UpdaterWindow::enable() +{ + mCancelButton->setEnabled(false); + mPlayButton->setEnabled(true); + mPlayButton->requestFocus(); + + if (mUpdateType & LoginData::Upd_Close) + Client::setState(STATE_LOAD_DATA); +} + +void UpdaterWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "cancel") + { + // Register the user cancel + mUserCancel = true; + // Skip the updating process + if (mDownloadStatus != UPDATE_COMPLETE) + { + mDownload->cancel(); + mDownloadStatus = UPDATE_ERROR; + } + } + else if (event.getId() == "play") + { + Client::setState(STATE_LOAD_DATA); + } +} + +void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + { + action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId())); + Client::setState(STATE_WORLD_SELECT); + } + else if (key.getValue() == Key::ENTER) + { + if (mDownloadStatus == UPDATE_COMPLETE || + mDownloadStatus == UPDATE_ERROR) + { + action(gcn::ActionEvent(NULL, mPlayButton->getActionEventId())); + } + else + { + action(gcn::ActionEvent(NULL, mCancelButton->getActionEventId())); + } + } +} + +void UpdaterWindow::loadNews() +{ + if (!mMemoryBuffer) + { + logger->log1("Couldn't load news"); + return; + } + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast(realloc( + mMemoryBuffer, mDownloadedBytes + 1)); + + mMemoryBuffer[mDownloadedBytes] = '\0'; + + mBrowserBox->clearRows(); + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + while (line) + { + mBrowserBox->addRow(line); + line = strtok(NULL, "\n"); + } + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = NULL; + mDownloadedBytes = 0; + + mScrollArea->setVerticalScrollAmount(0); +} + +void UpdaterWindow::loadPatch() +{ + if (!mMemoryBuffer) + { + logger->log1("Couldn't load patch"); + return; + } + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast( + realloc(mMemoryBuffer, mDownloadedBytes + 1)); + mMemoryBuffer[mDownloadedBytes] = '\0'; + + std::string version; + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + if (line) + { + version = line; + if (version > CHECK_VERSION) + { + mBrowserBox->addRow("", true); + mBrowserBox->addRow(" ##1http://tmw.cetki.com/4144/", true); + mBrowserBox->addRow("##1You can download it from", true); + mBrowserBox->addRow("##1ManaPlus updated.", true); + } + else + { + mBrowserBox->addRow("You have latest client version.", true); + } + } + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = NULL; + mDownloadedBytes = 0; + + mScrollArea->setVerticalScrollAmount(0); +} + +int UpdaterWindow::updateProgress(void *ptr, DownloadStatus status, + size_t dt, size_t dn) +{ + UpdaterWindow *uw = reinterpret_cast(ptr); + if (!uw) + return -1; + + if (status == DOWNLOAD_STATUS_COMPLETE) + { + uw->mDownloadComplete = true; + } + else if (status == DOWNLOAD_STATUS_ERROR || + status == DOWNLOAD_STATUS_CANCELLED) + { + uw->mDownloadStatus = UPDATE_ERROR; + } + + if (!dt) + dt = 1; + + float progress = static_cast(dn) / + static_cast(dt); + + if (progress != progress) + progress = 0.0f; // check for NaN + if (progress < 0.0f) + progress = 0.0f; // no idea how this could ever happen, + // but why not check for it anyway. + if (progress > 1.0f) + progress = 1.0f; + + uw->setLabel(uw->mCurrentFile + " (" + + toString(static_cast(progress * 100)) + "%)"); + + uw->setProgress(progress); + + if (Client::getState() != STATE_UPDATE + || uw->mDownloadStatus == UPDATE_ERROR) + { + // If the action was canceled return an error code to stop the mThread + return -1; + } + + return 0; +} + +size_t UpdaterWindow::memoryWrite(void *ptr, size_t size, + size_t nmemb, void *stream) +{ + UpdaterWindow *uw = reinterpret_cast(stream); + size_t totalMem = size * nmemb; + uw->mMemoryBuffer = static_cast(realloc(uw->mMemoryBuffer, + uw->mDownloadedBytes + totalMem)); + if (uw->mMemoryBuffer) + { + memcpy(&(uw->mMemoryBuffer[uw->mDownloadedBytes]), ptr, totalMem); + uw->mDownloadedBytes += static_cast(totalMem); + } + + return totalMem; +} + +void UpdaterWindow::download() +{ + if (mDownload) + { + mDownload->cancel(); + delete mDownload; + } + if (mDownloadStatus == UPDATE_PATCH) + { + mDownload = new Net::Download(this, "http://tmw.cetki.com/update/" + + mCurrentFile, updateProgress); + } + else + { + mDownload = new Net::Download(this, mUpdateHost + "/" + mCurrentFile, + updateProgress); + } + + if (mStoreInMemory) + { + mDownload->setWriteFunction(UpdaterWindow::memoryWrite); + } + else + { + if (mDownloadStatus == UPDATE_RESOURCES) + { + mDownload->setFile(mUpdatesDir + "/" + mCurrentFile, + mCurrentChecksum); + } + else + { + mDownload->setFile(mUpdatesDir + "/" + mCurrentFile); + } + } + + if (mDownloadStatus != UPDATE_RESOURCES) + mDownload->noCache(); + + setLabel(mCurrentFile + " (0%)"); + mDownloadComplete = false; + + // TODO: check return + mDownload->start(); +} + +void UpdaterWindow::loadUpdates() +{ + ResourceManager *resman = ResourceManager::getInstance(); + + if (mUpdateFiles.empty()) + { // updates not downloaded + mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); + if (!mUpdateFiles.size()) + { + logger->log("Warning this server does not have a" + " %s file falling back to %s", xmlUpdateFile.c_str(), + txtUpdateFile.c_str()); + mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); + } + } + + std::string fixPath = mUpdatesDir + "/fix"; + for (mUpdateIndex = 0; mUpdateIndex < mUpdateFiles.size(); mUpdateIndex++) + { + UpdaterWindow::addUpdateFile(resman, mUpdatesDir, fixPath, + mUpdateFiles[mUpdateIndex].name, false); + } +} + +void UpdaterWindow::loadLocalUpdates(std::string dir) +{ + ResourceManager *resman = ResourceManager::getInstance(); + + std::vector updateFiles + = loadXMLFile(dir + "/" + xmlUpdateFile); + + if (updateFiles.empty()) + { + logger->log("Warning this server does not have a" + " %s file falling back to %s", xmlUpdateFile.c_str(), + txtUpdateFile.c_str()); + updateFiles = loadTxtFile(dir + "/" + txtUpdateFile); + } + + std::string fixPath = dir + "/fix"; + for (unsigned int updateIndex = 0; + updateIndex < updateFiles.size(); updateIndex++) + { + UpdaterWindow::addUpdateFile(resman, dir, fixPath, + updateFiles[updateIndex].name, false); + } +} + +void UpdaterWindow::addUpdateFile(ResourceManager *resman, std::string path, + std::string fixPath, std::string file, + bool append) +{ + if (!append) + resman->addToSearchPath(path + "/" + file, append); + + const std::string fixFile = fixPath + "/" + file; + struct stat statbuf; + if (!stat(fixFile.c_str(), &statbuf)) + resman->addToSearchPath(fixFile, append); + + if (append) + resman->addToSearchPath(path + "/" + file, append); +} + +void UpdaterWindow::logic() +{ + // Update Scroll logic + mScrollArea->logic(); + + // Synchronize label caption when necessary + { + MutexLocker lock(&mDownloadMutex); + + if (mLabel->getCaption() != mNewLabelCaption) + { + mLabel->setCaption(mNewLabelCaption); + mLabel->adjustSize(); + } + + mProgressBar->setProgress(mDownloadProgress); + } + + std::string filename = mUpdatesDir + "/" + mCurrentFile; + + switch (mDownloadStatus) + { + case UPDATE_ERROR: + // TODO: Only send complete sentences to gettext + mBrowserBox->addRow(""); + mBrowserBox->addRow(_("##1 The update process is incomplete.")); + // TRANSLATORS: Continues "you try again later.". + mBrowserBox->addRow(_("##1 It is strongly recommended that")); + // TRANSLATORS: Begins "It is strongly recommended that". + mBrowserBox->addRow(_("##1 you try again later.")); + + mBrowserBox->addRow(mDownload->getError()); + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + mDownloadStatus = UPDATE_COMPLETE; + break; + case UPDATE_NEWS: + if (mDownloadComplete) + { + // Parse current memory buffer as news and dispose of the data + loadNews(); + + mCurrentFile = xmlUpdateFile; + mStoreInMemory = false; + mDownloadStatus = UPDATE_LIST; + download(); // download() changes mDownloadComplete to false + } + break; + case UPDATE_PATCH: + if (mDownloadComplete) + { + // Parse current memory buffer as news and dispose of the data + loadPatch(); + +/* + mCurrentFile = "news.txt"; + mStoreInMemory = true; + mDownloadStatus = UPDATE_NEWS; + download(); // download() changes mDownloadComplete to false +*/ + mDownloadStatus = UPDATE_COMPLETE; + } + break; + + case UPDATE_LIST: + if (mDownloadComplete) + { + if (mCurrentFile == xmlUpdateFile) + { + mUpdateFiles = loadXMLFile( + mUpdatesDir + "/" + xmlUpdateFile); + + if (mUpdateFiles.size() == 0) + { + logger->log("Warning this server does not have a %s" + " file falling back to %s", + xmlUpdateFile.c_str(), + txtUpdateFile.c_str()); + + // If the resources.xml file fails, + // fall back onto a older version + mCurrentFile = txtUpdateFile; + mStoreInMemory = false; + mDownloadStatus = UPDATE_LIST; + download(); + break; + } + } + else if (mCurrentFile == txtUpdateFile) + { + mUpdateFiles = loadTxtFile( + mUpdatesDir + "/" + txtUpdateFile); + } + mStoreInMemory = false; + mDownloadStatus = UPDATE_RESOURCES; + } + break; + case UPDATE_RESOURCES: + if (mDownloadComplete) + { + if (mUpdateIndex < mUpdateFiles.size()) + { + updateFile thisFile = mUpdateFiles[mUpdateIndex]; + if (!thisFile.required) + { + // This statement checks to see if the file type + // is music, and if download-music is true + // If it fails, this statement returns true, + // and results in not downloading the file + // Else it will ignore the break, + // and download the file. + + if (!(thisFile.type == "music" + && config.getBoolValue("download-music"))) + { + mUpdateIndex++; + break; + } + } + mCurrentFile = thisFile.name; + std::string checksum; + checksum = thisFile.hash; + std::stringstream ss(checksum); + ss >> std::hex >> mCurrentChecksum; + + std::ifstream temp( + (mUpdatesDir + "/" + mCurrentFile).c_str()); + + if (!temp.is_open()) + { + temp.close(); + download(); + } + else + { + temp.close(); + logger->log("%s already here", mCurrentFile.c_str()); + } + mUpdateIndex++; + } + else + { + // Download of updates completed +// mDownloadStatus = UPDATE_COMPLETE; + mCurrentFile = "latest.txt"; + mStoreInMemory = true; + mDownloadStatus = UPDATE_PATCH; + download(); // download() changes + // mDownloadComplete to false + } + } + break; + case UPDATE_COMPLETE: + enable(); + setLabel(_("Completed")); + break; + case UPDATE_IDLE: + break; + default: + logger->log("UpdaterWindow::logic unknown status: " + + toString(static_cast(mDownloadStatus))); + break; + } +} diff --git a/src/gui/updatewindow.h b/src/gui/updatewindow.h new file mode 100644 index 000000000..841af23a5 --- /dev/null +++ b/src/gui/updatewindow.h @@ -0,0 +1,210 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _UPDATERWINDOW_H +#define _UPDATERWINDOW_H + +#include "gui/widgets/window.h" + +#include "net/download.h" + +#include "utils/mutex.h" + +#include +#include + +#include +#include + +class BrowserBox; +class Button; +class ProgressBar; +class ResourceManager; +class ScrollArea; + +struct updateFile +{ + public: + std::string name; + std::string hash; + std::string type; + bool required; + std::string desc; +}; + +/** + * Update progress window GUI + * + * \ingroup GUI + */ +class UpdaterWindow : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor. + * + * @param updateHost Host where to get the updated files. + * @param updatesDir Directory where to store updates (should be absolute + * and already created). + * @param applyUpdates If true, the update window will pass the updates to teh + * resource manager + */ + UpdaterWindow(const std::string &updateHost, + const std::string &updatesDir, + bool applyUpdates, int updateType); + + /** + * Destructor + */ + ~UpdaterWindow(); + + /** + * Set's progress bar status + */ + void setProgress(float p); + + /** + * Set's label above progress + */ + void setLabel(const std::string &); + + /** + * Enables play button + */ + void enable(); + + /** + * Loads and display news. Assumes the news file contents have been loaded + * into the memory buffer. + */ + void loadNews(); + + void loadPatch(); + + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + void logic(); + + static void loadLocalUpdates(std::string dir); + + static void addUpdateFile(ResourceManager *resman, std::string path, + std::string fixPath, std::string file, + bool append); + + int updateState; + +private: + void download(); + + /** + * Loads the updates this window has gotten into the resource manager + */ + void loadUpdates(); + + + /** + * A download callback for progress updates. + */ + static int updateProgress(void *ptr, DownloadStatus status, + size_t dt, size_t dn); + + /** + * A libcurl callback for writing to memory. + */ + static size_t memoryWrite(void *ptr, size_t size, size_t nmemb, + void *stream); + + enum UpdateDownloadStatus + { + UPDATE_ERROR = 0, + UPDATE_IDLE, + UPDATE_LIST, + UPDATE_COMPLETE, + UPDATE_NEWS, + UPDATE_RESOURCES, + UPDATE_PATCH + }; + + /** Status of the current download. */ + UpdateDownloadStatus mDownloadStatus; + + /** Host where we get the updated files. */ + std::string mUpdateHost; + + /** Place where the updates are stored (absolute path). */ + std::string mUpdatesDir; + + /** The file currently downloading. */ + std::string mCurrentFile; + + /** The new label caption to be set in the logic method. */ + std::string mNewLabelCaption; + + /** The new progress value to be set in the logic method. */ + float mDownloadProgress; + + /** The mutex used to guard access to mNewLabelCaption and mDownloadProgress. */ + Mutex mDownloadMutex; + + /** The Adler32 checksum of the file currently downloading. */ + unsigned long mCurrentChecksum; + + /** A flag to indicate whether to use a memory buffer or a regular file. */ + bool mStoreInMemory; + + /** Flag that show if current download is complete. */ + bool mDownloadComplete; + + /** Flag that show if the user has canceled the update. */ + bool mUserCancel; + + /** Byte count currently downloaded in mMemoryBuffer. */ + int mDownloadedBytes; + + /** Buffer for files downloaded to memory. */ + char *mMemoryBuffer; + + /** Download handle. */ + Net::Download *mDownload; + + /** List of files to download. */ + std::vector mUpdateFiles; + + /** Index of the file to be downloaded. */ + unsigned int mUpdateIndex; + + /** Tells ~UpdaterWindow() if it should load updates */ + bool mLoadUpdates; + + int mUpdateType; + + gcn::Label *mLabel; /**< Progress bar caption. */ + Button *mCancelButton; /**< Button to stop the update process. */ + Button *mPlayButton; /**< Button to start playing. */ + ProgressBar *mProgressBar; /**< Update progress bar. */ + BrowserBox *mBrowserBox; /**< Box to display news. */ + ScrollArea *mScrollArea; /**< Used to scroll news box. */ +}; + +#endif diff --git a/src/gui/userpalette.cpp b/src/gui/userpalette.cpp new file mode 100644 index 000000000..03e5c1eed --- /dev/null +++ b/src/gui/userpalette.cpp @@ -0,0 +1,292 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "userpalette.h" + +#include "configuration.h" +#include "client.h" +#include "log.h" + +#include "gui/gui.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +const std::string ColorTypeNames[] = +{ + "ColorBeing", + "ColorFriend", + "ColorDisregarded", + "ColorIgnored", + "ColorErased", + "ColorPlayer", + "ColorSelf", + "ColorGM", + "ColorNPC", + "ColorMonster", + "ColorMonsterHp", + "ColorMonsterHp2", + "ColorParty", + "ColorGuild", + "ColorParticle", + "ColorPickupInfo", + "ColorExpInfo", + "ColorHitPlayerMonster", + "ColorHitMonsterPlayer", + "ColorHitPlayerPlayer", + "ColorHitCritical", + "ColorHitLocalPlayerMonster", + "ColorHitLocalPlayerCritical", + "ColorHitLocalPlayerMiss", + "ColorMiss", + "ColorPortalHighlight", + "ColorCollisionHighlight", + "ColorWalkableTileHighlight", + "ColorAttackRange", + "ColorAttackRangeBorder", + "ColorMonsterAttackRange", + "ColorHomePlace", + "ColorHomePlaceBorder", + "ColorRoadPoint" +}; + +std::string UserPalette::getConfigName(const std::string &typeName) +{ + std::string res = "Color" + typeName; + + int pos = 5; + for (size_t i = 0; i < typeName.length(); i++) + { + if (i == 0 || typeName[i] == '_') + { + if (i > 0) + i++; + + res[pos] = typeName[i]; + } + else + { + res[pos] = static_cast(tolower(typeName[i])); + } + pos++; + } + res.erase(pos, res.length() - pos); + + return res; +} + +UserPalette::UserPalette(): + Palette(USER_COLOR_LAST) +{ + mColors[BEING] = ColorElem(); + mColors[PC] = ColorElem(); + mColors[SELF] = ColorElem(); + mColors[GM] = ColorElem(); + mColors[NPC] = ColorElem(); + mColors[MONSTER] = ColorElem(); + + addColor(BEING, 0xffffff, STATIC, _("Being")); + addColor(FRIEND, 0xb0ffb0, STATIC, _("Friend Names")); + addColor(DISREGARDED, 0xa00000, STATIC, _("Disregarded Names")); + addColor(IGNORED, 0xff0000, STATIC, _("Ignored Names")); + addColor(ERASED, 0xff0000, STATIC, _("Erased Names")); + addColor(PC, 0xffffff, STATIC, _("Other Players' Names")); + addColor(SELF, 0xff8040, STATIC, _("Own Name")); + addColor(GM, 0x00ff00, STATIC, _("GM Names")); + addColor(NPC, 0xc8c8ff, STATIC, _("NPCs")); + addColor(MONSTER, 0xff4040, STATIC, _("Monsters")); + addColor(MONSTER_HP, 0x00ff00, STATIC, _("Monster HP bar"), 50); + addColor(MONSTER_HP2, 0xff0000, STATIC, + _("Monster HP bar (second color)"), 50); + addColor(PARTY, 0xff00d8, STATIC, _("Party Members")); + addColor(GUILD, 0xff00d8, STATIC, _("Guild Members")); + addColor(PARTICLE, 0xffffff, STATIC, _("Particle Effects")); + addColor(PICKUP_INFO, 0x28dc28, STATIC, _("Pickup Notification")); + addColor(EXP_INFO, 0xffff00, STATIC, _("Exp Notification")); + addColor(HIT_PLAYER_MONSTER, 0x0064ff, STATIC, _("Player Hits Monster")); + addColor(HIT_MONSTER_PLAYER, 0xff3232, STATIC, _("Monster Hits Player")); + addColor(HIT_PLAYER_PLAYER, 0xff5050, STATIC, + _("Other Player Hits Local Player")); + addColor(HIT_CRITICAL, 0xff0000, RAINBOW, _("Critical Hit")); + addColor(HIT_LOCAL_PLAYER_MONSTER, 0x00ff00, STATIC, + _("Local Player Hits Monster")); + addColor(HIT_LOCAL_PLAYER_CRITICAL, 0xff0000, RAINBOW, + _("Local Player Critical Hit")); + addColor(HIT_LOCAL_PLAYER_MISS, 0x00ffa6, STATIC, + _("Local Player Miss")); + addColor(MISS, 0xffff00, STATIC, _("Misses")); + addColor(PORTAL_HIGHLIGHT, 0xC80000, STATIC, _("Portal Highlight")); + addColor(COLLISION_HIGHLIGHT, 0x0000C8, STATIC, + _("Collision Highlight"), 64); + addColor(WALKABLE_HIGHLIGHT, 0x00D000, STATIC, + _("Walkable Highlight"), 255); + addColor(ATTACK_RANGE, 0xffffff, STATIC, + _("Local Player Attack Range"), 5); + addColor(ATTACK_RANGE_BORDER, 0x0, STATIC, + _("Local Player Attack Range Border"), 76); + addColor(MONSTER_ATTACK_RANGE, 0xff0000, STATIC, + _("Monster Attack Range"), 20); + addColor(HOME_PLACE, 0xffffff, STATIC, + _("Home Place"), 20); + addColor(HOME_PLACE_BORDER, 0xffff00, STATIC, + _("Home Place Border"), 200); + addColor(ROAD_POINT, 0x000000, STATIC, + _("Road Point"), 100); + commit(true); +} + +UserPalette::~UserPalette() +{ + for (Colors::iterator col = mColors.begin(), + colEnd = mColors.end(); col != colEnd; ++col) + { + const std::string &configName = ColorTypeNames[col->type]; + config.setValue(configName + "Gradient", + static_cast(col->committedGrad)); + config.setValue(configName + "Delay", col->delay); + + if (col->grad == STATIC || col->grad == PULSE) + { + char buffer[20]; + sprintf(buffer, "0x%06x", col->getRGB()); + config.setValue(configName, std::string(buffer)); + } + } +} + +void UserPalette::setColor(int type, int r, int g, int b) +{ + mColors[type].color.r = r; + mColors[type].color.g = g; + mColors[type].color.b = b; +} + +void UserPalette::setGradient(int type, GradientType grad) +{ + ColorElem *elem = &mColors[type]; + if (!elem) + return; + + if (elem->grad != STATIC && grad == STATIC) + { + for (size_t i = 0; i < mGradVector.size(); i++) + { + if (mGradVector[i] == elem) + { + mGradVector.erase(mGradVector.begin() + i); + break; + } + } + } + else if (elem->grad == STATIC && grad != STATIC) + { + mGradVector.push_back(elem); + } + + if (elem->grad != grad) + elem->grad = grad; +} + +std::string UserPalette::getElementAt(int i) +{ + if (i < 0 || i >= getNumberOfElements()) + return ""; + + return mColors[i].text; +} + +void UserPalette::commit(bool commitNonStatic) +{ + for (Colors::iterator i = mColors.begin(), iEnd = mColors.end(); + i != iEnd; ++i) + { + i->committedGrad = i->grad; + i->committedDelay = i->delay; + if (commitNonStatic || i->grad == STATIC) + i->committedColor = i->color; + else if (i->grad == PULSE) + i->committedColor = i->testColor; + } +} + +void UserPalette::rollback() +{ + for (Colors::iterator i = mColors.begin(), iEnd = mColors.end(); + i != iEnd; ++i) + { + if (i->grad != i->committedGrad) + setGradient(i->type, i->committedGrad); + + setGradientDelay(i->type, i->committedDelay); + setColor(i->type, i->committedColor.r, + i->committedColor.g, i->committedColor.b); + + if (i->grad == PULSE) + { + i->testColor.r = i->committedColor.r; + i->testColor.g = i->committedColor.g; + i->testColor.b = i->committedColor.b; + } + } +} + +int UserPalette::getColorTypeAt(int i) +{ + if (i < 0 || i >= getNumberOfElements()) + return BEING; + + return mColors[i].type; +} + +void UserPalette::addColor(unsigned type, unsigned rgb, + Palette::GradientType grad, const std::string &text, + int delay) +{ + const unsigned maxType = sizeof(ColorTypeNames) + / sizeof(ColorTypeNames[0]); + + if (type >= maxType) + return; + + const std::string &configName = ColorTypeNames[type]; + char buffer[20]; + sprintf(buffer, "0x%06x", rgb); + + const std::string rgbString = config.getValue(configName, + std::string(buffer)); + unsigned int rgbValue = 0; + if (rgbString.length() == 8 && rgbString[0] == '0' && rgbString[1] == 'x') + rgbValue = atox(rgbString); + else + rgbValue = atoi(rgbString.c_str()); + gcn::Color trueCol = rgbValue; + grad = static_cast(config.getValue(configName + "Gradient", + static_cast(grad))); + delay = config.getValueInt(configName + "Delay", delay); + mColors[type].set(type, trueCol, grad, delay); + mColors[type].text = text; + + if (grad != STATIC) + mGradVector.push_back(&mColors[type]); +} diff --git a/src/gui/userpalette.h b/src/gui/userpalette.h new file mode 100644 index 000000000..057d47113 --- /dev/null +++ b/src/gui/userpalette.h @@ -0,0 +1,222 @@ +/* + * Configurable text colors + * Copyright (C) 2008 Douglas Boffey + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef USER_PALETTE_H +#define USER_PALETTE_H + +#include "gui/palette.h" + +#include + +/** + * Class controlling the game's color palette. + */ +class UserPalette : public Palette, public gcn::ListModel +{ + public: + /** List of all colors that are configurable. */ + enum + { + BEING = 0, + FRIEND, + DISREGARDED, + IGNORED, + ERASED, + PC, + SELF, + GM, + NPC, + MONSTER, + MONSTER_HP, + MONSTER_HP2, + PARTY, + GUILD, + PARTICLE, + PICKUP_INFO, + EXP_INFO, + HIT_PLAYER_MONSTER, + HIT_MONSTER_PLAYER, + HIT_PLAYER_PLAYER, + HIT_CRITICAL, + HIT_LOCAL_PLAYER_MONSTER, + HIT_LOCAL_PLAYER_CRITICAL, + HIT_LOCAL_PLAYER_MISS, + MISS, + PORTAL_HIGHLIGHT, + COLLISION_HIGHLIGHT, + WALKABLE_HIGHLIGHT, + ATTACK_RANGE, + ATTACK_RANGE_BORDER, + MONSTER_ATTACK_RANGE, + HOME_PLACE, + HOME_PLACE_BORDER, + ROAD_POINT, + USER_COLOR_LAST + }; + + /** + * Constructor + */ + UserPalette(); + + /** + * Destructor + */ + ~UserPalette(); + + /** + * Gets the committed color associated with the specified type. + * + * @param type the color type requested + * + * @return the requested committed color + */ + inline const gcn::Color &getCommittedColor(int type) + { + return mColors[type].committedColor; + } + + /** + * Gets the test color associated with the specified type. + * + * @param type the color type requested + * + * @return the requested test color + */ + inline const gcn::Color &getTestColor(int type) + { return mColors[type].testColor; } + + /** + * Sets the test color associated with the specified type. + * + * @param type the color type requested + * @param color the color that should be tested + */ + inline void setTestColor(int type, gcn::Color color) + { mColors[type].testColor = color; } + + /** + * Sets the color for the specified type. + * + * @param type color to be set + * @param r red component + * @param g green component + * @param b blue component + */ + void setColor(int type, int r, int g, int b); + + /** + * Sets the gradient type for the specified color. + * + * @param grad gradient type to set + */ + void setGradient(int type, Palette::GradientType grad); + + /** + * Sets the gradient delay for the specified color. + * + * @param grad gradient type to set + */ + void setGradientDelay(int type, int delay) + { mColors[type].delay = delay; } + + /** + * Returns the number of colors known. + * + * @return the number of colors known + */ + inline int getNumberOfElements() + { return static_cast(mColors.size()); } + + /** + * Returns the name of the ith color. + * + * @param i index of color interested in + * + * @return the name of the color + */ + std::string getElementAt(int i); + + /** + * Commit the colors + */ + inline void commit() + { commit(false); } + + /** + * Rollback the colors + */ + void rollback(); + + /** + * Gets the ColorType used by the color for the element at index i in + * the current color model. + * + * @param i the index of the color + * + * @return the color type of the color with the given index + */ + int getColorTypeAt(int i); + + private: + /** + * Define a color replacement. + * + * @param i the index of the color to replace + * @param r red component + * @param g green component + * @param b blue component + */ + void setColorAt(int i, int r, int g, int b); + + /** + * Commit the colors. Commit the non-static color values, if + * commitNonStatic is true. Only needed in the constructor. + */ + void commit(bool commitNonStatic); + + /** + * Prefixes the given string with "Color", lowercases all letters but + * the first and all following a '_'. All '_'s will be removed. + * + * E.g.: HIT_PLAYER_MONSTER -> HitPlayerMonster + * + * @param typeName string to transform + * + * @return the transformed string + */ + static std::string getConfigName(const std::string &typeName); + + /** + * Initialise color + * + * @param c character that needs initialising + * @param rgb default color if not found in config + * @param text identifier of color + */ + void addColor(unsigned type, unsigned rgb, GradientType grad, + const std::string &text, int delay = GRADIENT_DELAY); +}; + +extern UserPalette *userPalette; + +#endif // USER_PALETTE_H diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp new file mode 100644 index 000000000..e2bab0621 --- /dev/null +++ b/src/gui/viewport.cpp @@ -0,0 +1,763 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/viewport.h" + +#include "actorsprite.h" +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "map.h" +#include "textmanager.h" + +#include "gui/beingpopup.h" +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/ministatus.h" +#include "gui/popupmenu.h" +#include "gui/statuspopup.h" +#include "gui/textpopup.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/chattab.h" + +#include "net/net.h" + +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +extern volatile int tick_time; + +Viewport::Viewport(): + mMap(0), + mMouseX(0), + mMouseY(0), + mPixelViewX(0.0f), + mPixelViewY(0.0f), +// mTileViewX(0), +// mTileViewY(0), + mShowDebugPath(false), + mCameraMode(0), + mPlayerFollowMouse(false), + mLocalWalkTime(-1), + mHoverBeing(0), + mHoverItem(0), + mHoverSign(0), + mCameraRelativeX(0), + mCameraRelativeY(0) +{ + setOpaque(false); + addMouseListener(this); + + mScrollLaziness = config.getIntValue("ScrollLaziness"); + mScrollRadius = config.getIntValue("ScrollRadius"); + mScrollCenterOffsetX = config.getIntValue("ScrollCenterOffsetX"); + mScrollCenterOffsetY = config.getIntValue("ScrollCenterOffsetY"); + + config.addListener("ScrollLaziness", this); + config.addListener("ScrollRadius", this); + + mPopupMenu = new PopupMenu; + mBeingPopup = new BeingPopup; + mTextPopup = new TextPopup; + + setFocusable(true); +} + +Viewport::~Viewport() +{ + config.removeListener("ScrollLaziness", this); + config.removeListener("ScrollRadius", this); + + delete mPopupMenu; + mPopupMenu = 0; + delete mBeingPopup; + mBeingPopup = 0; + delete mTextPopup; + mTextPopup = 0; +} + +void Viewport::setMap(Map *map) +{ + if (mMap && map) + map->setDebugFlags(mMap->getDebugFlags()); + mMap = map; +} + +extern MiniStatusWindow *miniStatusWindow; + +void Viewport::draw(gcn::Graphics *gcnGraphics) +{ + static int lastTick = tick_time; + + if (!mMap || !player_node) + { + gcnGraphics->setColor(gcn::Color(64, 64, 64)); + gcnGraphics->fillRectangle( + gcn::Rectangle(0, 0, getWidth(), getHeight())); + return; + } + + Graphics *graphics = static_cast(gcnGraphics); + + // Avoid freaking out when tick_time overflows + if (tick_time < lastTick) + lastTick = tick_time; + + // Calculate viewpoint + int midTileX = (graphics->getWidth() + mScrollCenterOffsetX) / 2; + int midTileY = (graphics->getHeight() + mScrollCenterOffsetX) / 2; + + const Vector &playerPos = player_node->getPosition(); + const int player_x = static_cast(playerPos.x) + - midTileX + mCameraRelativeX; + const int player_y = static_cast(playerPos.y) + - midTileY + mCameraRelativeY; + + if (mScrollLaziness < 1) + mScrollLaziness = 1; // Avoids division by zero + + // Apply lazy scrolling + while (lastTick < tick_time) + { + if (player_x > static_cast(mPixelViewX) + mScrollRadius) + { + mPixelViewX += static_cast(player_x + - static_cast(mPixelViewX) - mScrollRadius) / + static_cast(mScrollLaziness); + } + if (player_x < static_cast(mPixelViewX) - mScrollRadius) + { + mPixelViewX += static_cast(player_x + - static_cast(mPixelViewX) + mScrollRadius) / + static_cast(mScrollLaziness); + } + if (player_y > static_cast(mPixelViewY) + mScrollRadius) + { + mPixelViewY += static_cast(player_y + - static_cast(mPixelViewY) - mScrollRadius) / + static_cast(mScrollLaziness); + } + if (player_y < static_cast(mPixelViewY) - mScrollRadius) + { + mPixelViewY += static_cast(player_y + - static_cast(mPixelViewY) + mScrollRadius) / + static_cast(mScrollLaziness); + } + lastTick++; + } + + // Auto center when player is off screen + if (player_x - static_cast(mPixelViewX) > graphics->getWidth() / 2 + || static_cast(mPixelViewX) - player_x > graphics->getWidth() / 2 + || static_cast(mPixelViewY) - player_y + > graphics->getHeight() / 2 + || player_y - static_cast(mPixelViewY) + > graphics->getHeight() / 2) + { + mPixelViewX = static_cast(player_x); + mPixelViewY = static_cast(player_y); + }; + + // Don't move camera so that the end of the map is on screen + const int viewXmax = + mMap->getWidth() * mMap->getTileWidth() - graphics->getWidth(); + const int viewYmax = + mMap->getHeight() * mMap->getTileHeight() - graphics->getHeight(); + if (mMap) + { + if (mPixelViewX < 0) + mPixelViewX = 0; + if (mPixelViewY < 0) + mPixelViewY = 0; + if (mPixelViewX > viewXmax) + mPixelViewX = static_cast(viewXmax); + if (mPixelViewY > viewYmax) + mPixelViewY = static_cast(viewYmax); + } + + // Draw tiles and sprites + if (mMap) + { + mMap->draw(graphics, static_cast(mPixelViewX), + static_cast(mPixelViewY)); + + if (mShowDebugPath) + { + mMap->drawCollision(graphics, + static_cast(mPixelViewX), + static_cast(mPixelViewY), + mShowDebugPath); + if (mShowDebugPath == Map::MAP_DEBUG) + _drawDebugPath(graphics); + } + } + + if (player_node->getCheckNameSetting()) + { + player_node->setCheckNameSetting(false); + player_node->setName(player_node->getName()); + } + + // Draw text + if (textManager) + textManager->draw(graphics, static_cast(mPixelViewX), + static_cast(mPixelViewY)); + + // Draw player names, speech, and emotion sprite as needed + const ActorSprites &actors = actorSpriteManager->getAll(); + for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); + it != it_end; it++) + { + if ((*it)->getType() == ActorSprite::FLOOR_ITEM) + continue; + Being *b = static_cast(*it); + + b->drawSpeech(static_cast(mPixelViewX), + static_cast(mPixelViewY)); + b->drawEmotion(graphics, static_cast(mPixelViewX), + static_cast(mPixelViewY)); + } + + if (miniStatusWindow) + miniStatusWindow->drawIcons(graphics); + + // Draw contained widgets + WindowContainer::draw(gcnGraphics); +} + +void Viewport::logic() +{ + WindowContainer::logic(); + + // Make the player follow the mouse position + // if the mouse is dragged elsewhere than in a window. + _followMouse(); +} + +void Viewport::_followMouse() +{ + Uint8 button = SDL_GetMouseState(&mMouseX, &mMouseY); + // If the left button is dragged + if (mPlayerFollowMouse && button & SDL_BUTTON(1)) + { + // We create a mouse event and send it to mouseDragged. + Uint8 *keys = SDL_GetKeyState(NULL); + gcn::MouseEvent mouseEvent(NULL, + (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]), + false, + false, + false, + gcn::MouseEvent::DRAGGED, + gcn::MouseEvent::LEFT, + mMouseX, + mMouseY, + 0); + + mouseDragged(mouseEvent); + } +} + +void Viewport::_drawDebugPath(Graphics *graphics) +{ + // Get the current mouse position + SDL_GetMouseState(&mMouseX, &mMouseY); + + Path debugPath; + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + const int mouseTileX = (mMouseX + static_cast(mPixelViewX)) / 32; + const int mouseTileY = (mMouseY + static_cast(mPixelViewY)) / 32; + const Vector &playerPos = player_node->getPosition(); + + debugPath = mMap->findPath( + static_cast(playerPos.x - 16) / 32, + static_cast(playerPos.y - 32) / 32, + mouseTileX, mouseTileY, 0, 500); + + _drawPath(graphics, debugPath); + } + else if (Net::getNetworkType() == ServerInfo::MANASERV) + { + const Vector &playerPos = player_node->getPosition(); + const int playerRadius = player_node->getCollisionRadius(); + // Draw player collision rectangle + graphics->setColor(gcn::Color(128, 128, 0, 120)); + graphics->fillRectangle( + gcn::Rectangle(static_cast(playerPos.x) + - static_cast(mPixelViewX) - playerRadius, + static_cast(playerPos.y) + - static_cast(mPixelViewY) - playerRadius, + playerRadius * 2, playerRadius * 2)); + + debugPath = mMap->findPixelPath( + static_cast(playerPos.x), + static_cast(playerPos.y), + mMouseX + static_cast(mPixelViewX), + mMouseY + static_cast(mPixelViewY), + playerRadius, 0xFF); + + // We draw the path proposed by mouse + _drawPath(graphics, debugPath, gcn::Color(128, 0, 128)); + + // But also the one currently walked on. + _drawPath(graphics, player_node->getPath(), gcn::Color(0, 0, 255)); + } +} + +void Viewport::_drawPath(Graphics *graphics, const Path &path, + gcn::Color color) +{ + graphics->setColor(color); + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + for (Path::const_iterator i = path.begin(); i != path.end(); ++i) + { + int squareX = i->x * 32 - static_cast(mPixelViewX) + 12; + int squareY = i->y * 32 - static_cast(mPixelViewY) + 12; + + graphics->fillRectangle(gcn::Rectangle(squareX, squareY, 8, 8)); + if (mMap) + { + graphics->drawText( + toString(mMap->getMetaTile(i->x, i->y)->Gcost), + squareX + 4, squareY + 12, gcn::Graphics::CENTER); + } + } + } + else if (Net::getNetworkType() == ServerInfo::MANASERV) + { + for (Path::const_iterator i = path.begin(); i != path.end(); ++i) + { + int squareX = i->x - static_cast(mPixelViewX); + int squareY = i->y - static_cast(mPixelViewY); + + graphics->fillRectangle(gcn::Rectangle(squareX - 4, squareY - 4, + 8, 8)); + if (mMap) + { + graphics->drawText( + toString(mMap->getMetaTile(i->x / 32, i->y / 32)->Gcost), + squareX + 4, squareY + 12, gcn::Graphics::CENTER); + } + } + + } +} + +void Viewport::mousePressed(gcn::MouseEvent &event) +{ + if (event.getSource() != this) + return; + + // Check if we are alive and kickin' +// if (!mMap || !player_node || !player_node->isAlive()) + if (!mMap || !player_node) + return; + + // Check if we are busy + // if commented, allow context menu if npc dialog open + if (Being::isTalking()) + return; + + mPlayerFollowMouse = false; + + const int pixelX = event.getX() + static_cast(mPixelViewX); + const int pixelY = event.getY() + static_cast(mPixelViewY); + + // Right click might open a popup + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + if (mHoverBeing) + { + if (actorSpriteManager) + { + std::list beings; + const int x = getMouseX() + static_cast(mPixelViewX); + const int y = getMouseY() + static_cast(mPixelViewY); + actorSpriteManager->findBeingsByPixel(beings, x, y, true); + if (beings.size() > 1) + { + mPopupMenu->showPopup(event.getX(), event.getY(), beings); + return; + } + else + { + mPopupMenu->showPopup(event.getX(), event.getY(), + mHoverBeing); + return; + } + } + } + else if (mHoverItem) + { + mPopupMenu->showPopup(event.getX(), event.getY(), mHoverItem); + return; + } + else if (mHoverSign) + { + mPopupMenu->showPopup(event.getX(), event.getY(), mHoverSign); + return; + } + } + + // If a popup is active, just remove it + if (mPopupMenu->isVisible()) + { + mPopupMenu->setVisible(false); + return; + } + + // Left click can cause different actions + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Interact with some being + if (mHoverBeing) + { + if (!mHoverBeing->isAlive()) + return; + + if (mHoverBeing->canTalk()) + { + mHoverBeing->talkTo(); + } + else + { + if (mHoverBeing->getType() == ActorSprite::PLAYER) + { + if (actorSpriteManager) + actorSpriteManager->heal(player_node, mHoverBeing); + } + else if (player_node->withinAttackRange(mHoverBeing) || + keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->attack(mHoverBeing, + !keyboard.isKeyActive(keyboard.KEY_TARGET)); + } + else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->setGotoTarget(mHoverBeing); + } + } + // Picks up a item if we clicked on one + } + else if (mHoverItem) + { + player_node->pickUp(mHoverItem); + } + // Just walk around + else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK)) + { + player_node->stopAttack(); + player_node->cancelFollow(); + mPlayerFollowMouse = true; + + // Make the player go to the mouse position + _followMouse(); + } + } + else if (event.getButton() == gcn::MouseEvent::MIDDLE) + { + // Find the being nearest to the clicked position + if (actorSpriteManager) + { + Being *target = actorSpriteManager->findNearestLivingBeing( + pixelX, pixelY, 20, ActorSprite::MONSTER); + + if (target) + player_node->setTarget(target); + } + } +} + +void Viewport::mouseDragged(gcn::MouseEvent &event) +{ + if (!mMap || !player_node) + return; + + if (mPlayerFollowMouse && !event.isShiftPressed()) + { + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay) + { + mLocalWalkTime = tick_time; + player_node->setDestination(event.getX() + + static_cast(mPixelViewX), + event.getY() + + static_cast(mPixelViewY)); + player_node->pathSetByMouse(); + } + } + else + { + if (mLocalWalkTime != player_node->getActionTime()) + { + mLocalWalkTime = player_node->getActionTime(); + int destX = static_cast((static_cast(event.getX()) + + mPixelViewX) + / static_cast(mMap->getTileWidth())); + int destY = static_cast((static_cast(event.getY()) + + mPixelViewY) + / static_cast(mMap->getTileHeight())); + player_node->setDestination(destX, destY); + } + } + } +} + +void Viewport::mouseReleased(gcn::MouseEvent &event _UNUSED_) +{ + mPlayerFollowMouse = false; + + // Only useful for eAthena but doesn't hurt under ManaServ + mLocalWalkTime = -1; +} + +void Viewport::showPopup(Window *parent, int x, int y, Item *item, + bool isInventory) +{ + mPopupMenu->showPopup(parent, x, y, item, isInventory); +} + +void Viewport::showPopup(MapItem *item) +{ + mPopupMenu->showPopup(getMouseX(), getMouseY(), item); +} + +void Viewport::showPopup(Window *parent, Item *item, bool isInventory) +{ + mPopupMenu->showPopup(parent, getMouseX(), getMouseY(), item, isInventory); +} + +void Viewport::showItemPopup(Item *item) +{ + mPopupMenu->showItemPopup(getMouseX(), getMouseY(), item); +} + +void Viewport::showItemPopup(int itemId) +{ + mPopupMenu->showItemPopup(getMouseX(), getMouseY(), itemId); +} + +void Viewport::showDropPopup(Item *item) +{ + mPopupMenu->showDropPopup(getMouseX(), getMouseY(), item); +} + +void Viewport::showOutfitsPopup(int x, int y) +{ + mPopupMenu->showOutfitsPopup(x, y); +} + +void Viewport::showOutfitsPopup() +{ + mPopupMenu->showOutfitsPopup(getMouseX(), getMouseY()); +} + +void Viewport::showSpellPopup(TextCommand *cmd) +{ + mPopupMenu->showSpellPopup(getMouseX(), getMouseY(), cmd); +} + +void Viewport::showChatPopup(int x, int y, ChatTab *tab) +{ + mPopupMenu->showChatPopup(x, y, tab); +} + +void Viewport::showChatPopup(ChatTab *tab) +{ + mPopupMenu->showChatPopup(getMouseX(), getMouseY(), tab); +} + +void Viewport::showPopup(int x, int y, Being *being) +{ + mPopupMenu->showPopup(x, y, being); +} + +void Viewport::showPlayerPopup(std::string nick) +{ + mPopupMenu->showPlayerPopup(getMouseX(), getMouseY(), nick); +} + +void Viewport::showPopup(int x, int y, Button *button) +{ + mPopupMenu->showPopup(x, y, button); +} + +void Viewport::closePopupMenu() +{ + if (mPopupMenu) + mPopupMenu->handleLink("cancel", 0); +} + +void Viewport::optionChanged(const std::string &name _UNUSED_) +{ + mScrollLaziness = config.getIntValue("ScrollLaziness"); + mScrollRadius = config.getIntValue("ScrollRadius"); +} + +void Viewport::mouseMoved(gcn::MouseEvent &event _UNUSED_) +{ + // Check if we are on the map + if (!mMap || !player_node || !actorSpriteManager) + return; + + const int x = getMouseX() + static_cast(mPixelViewX); + const int y = getMouseY() + static_cast(mPixelViewY); + + mHoverBeing = actorSpriteManager->findBeingByPixel(x, y, true); + if (mHoverBeing && mHoverBeing->getType() == Being::PLAYER) + { + mTextPopup->setVisible(false); + mBeingPopup->show(getMouseX(), getMouseY(), mHoverBeing); + } + else + { + mBeingPopup->setVisible(false); + } + + mHoverItem = 0; + if (!mHoverBeing && actorSpriteManager) + { + mHoverItem = actorSpriteManager->findItem(x / mMap->getTileWidth(), + y / mMap->getTileHeight()); + } + if (!mHoverBeing && !mHoverItem) + { + SpecialLayer *specialLayer = mMap->getSpecialLayer(); + if (specialLayer) + { + int mouseTileX = (getMouseX() + getCameraX()) + / mMap->getTileWidth(); + int mouseTileY = (getMouseY() + getCameraY()) + / mMap->getTileHeight(); + + mHoverSign = specialLayer->getTile(mouseTileX, mouseTileY); + if (mHoverSign && mHoverSign->getType() != MapItem::EMPTY) + { + if (!mHoverSign->getComment().empty()) + { + if (mBeingPopup) + mBeingPopup->setVisible(false); + mTextPopup->show(getMouseX(), getMouseY(), + mHoverSign->getComment()); + } + else + { + if (mTextPopup->isVisible()) + mTextPopup->setVisible(false); + } + return; + } + } + } + if (mTextPopup->isVisible()) + mTextPopup->setVisible(false); + + if (mHoverBeing) + { + switch (mHoverBeing->getType()) + { + // NPCs + case ActorSprite::NPC: + gui->setCursorType(Gui::CURSOR_TALK); + break; + + // Monsters + case ActorSprite::MONSTER: + gui->setCursorType(Gui::CURSOR_FIGHT); + break; + default: + gui->setCursorType(Gui::CURSOR_POINTER); + break; + } + } + // Item mouseover + else if (mHoverItem) + { + gui->setCursorType(Gui::CURSOR_PICKUP); + } + else + { + gui->setCursorType(Gui::CURSOR_POINTER); + } +} + +void Viewport::toggleDebugPath() +{ + mShowDebugPath++; + if (mShowDebugPath > Map::MAP_BLACKWHITE) + mShowDebugPath = Map::MAP_NORMAL; + if (mMap) + mMap->setDebugFlags(mShowDebugPath); +} + +void Viewport::toggleCameraMode() +{ + mCameraMode++; + if (mCameraMode > 1) + mCameraMode = 0; + if (!mCameraMode) + { + mCameraRelativeX = 0; + mCameraRelativeY = 0; + } + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void Viewport::hideBeingPopup() +{ + if (mBeingPopup) + mBeingPopup->setVisible(false); + if (mTextPopup) + mTextPopup->setVisible(false); +} + +void Viewport::clearHover(ActorSprite *actor) +{ + if (mHoverBeing == actor) + mHoverBeing = 0; + + if (mHoverItem == actor) + mHoverItem = 0; +} + +void Viewport::cleanHoverItems() +{ + mHoverBeing = 0; + mHoverItem = 0; + mHoverSign = 0; +} + +void Viewport::moveCamera(int dx, int dy) +{ + mCameraRelativeX += dx; + mCameraRelativeY += dy; +} \ No newline at end of file diff --git a/src/gui/viewport.h b/src/gui/viewport.h new file mode 100644 index 000000000..cf5e53c4d --- /dev/null +++ b/src/gui/viewport.h @@ -0,0 +1,298 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VIEWPORT_H +#define VIEWPORT_H + +#include "actorspritemanager.h" +#include "configlistener.h" +#include "position.h" + +#include "gui/widgets/windowcontainer.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ActorSprite; +class Button; +class Being; +class BeingPopup; +class ChatTab; +class FloorItem; +class Graphics; +class ImageSet; +class Item; +class ItemShortcut; +class Map; +class PopupMenu; +class TextCommand; +class StatusPopup; +class TextPopup; +class Window; + +/** Delay between two mouse calls when dragging mouse and move the player */ +const int walkingMouseDelay = 500; + +/** + * The viewport on the map. Displays the current map and handles mouse input + * and the popup menu. + * + * TODO: This class is planned to be extended to allow floating widgets on top + * of it such as NPC messages, which are positioned using map pixel + * coordinates. + */ +class Viewport : public WindowContainer, public gcn::MouseListener, + public ConfigListener +{ + public: + /** + * Constructor. + */ + Viewport(); + + /** + * Destructor. + */ + ~Viewport(); + + /** + * Sets the map displayed by the viewport. + */ + void setMap(Map *map); + + /** + * Draws the viewport. + */ + void draw(gcn::Graphics *graphics); + + /** + * Implements player to keep following mouse. + */ + void logic(); + + /** + * Toggles whether the path debug graphics are shown. normal, debug with all images and grid, debug with out big images and with out grid. + */ + void toggleDebugPath(); + + void toggleCameraMode(); + + /** + * Handles mouse press on map. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse move on map + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse button release on map. + */ + void mouseReleased(gcn::MouseEvent &event); + + /** + * Handles mouse move on map. + */ + void mouseMoved(gcn::MouseEvent &event); + + /** + * Shows a popup for an item. + * TODO Find some way to get rid of Item here + */ + void showPopup(Window *parent, int x, int y, Item *item, + bool isInventory = true); + + /** + * Shows a popup for an item. + * TODO Find some way to get rid of Item here + */ + void showPopup(Window *parent, Item *item, bool isInventory = true); + + void showPopup(int x, int y, Button *button); + + void showPopup(MapItem *item); + + void showItemPopup(Item *item); + + void showItemPopup(int itemId); + + void showDropPopup(Item *item); + + /** + * Shows a popup for being. + */ + void showPopup(int x, int y, Being *being); + + void showPlayerPopup(std::string nick); + + void showOutfitsPopup(int x, int y); + + void showOutfitsPopup(); + + void showSpellPopup(TextCommand *cmd); + + /** + * Shows the related popup menu when right click on the chat + * at the specified mouse coordinates. + */ + void showChatPopup(int x, int y, ChatTab *tab); + + /** + * Shows the related popup menu when right click on the chat + */ + void showChatPopup(ChatTab *tab); + + /** + * Closes the popup menu. Needed for when the player dies or switching + * maps. + */ + void closePopupMenu(); + + /** + * A relevant config option changed. + */ + void optionChanged(const std::string &name); + + /** + * Returns camera x offset in pixels. + */ + int getCameraX() const + { return static_cast(mPixelViewX); } + + /** + * Returns camera y offset in pixels. + */ + int getCameraY() const + { return static_cast(mPixelViewY); } + + /** + * Returns mouse x in pixels. + */ + int getMouseX() const + { return mMouseX; } + + /** + * Returns mouse y in pixels. + */ + int getMouseY() const + { return mMouseY; } + + /** + * Changes viewpoint by relative pixel coordinates. + */ + void scrollBy(float x, float y) + { mPixelViewX += x; mPixelViewY += y; } + + /** + * Returns the current map object. + */ + Map *getCurrentMap() const + { return mMap; } + + int getDebugPath() + { return mShowDebugPath; } + + int getCameraMode() + { return mCameraMode; } + + /** + * Hides the BeingPopup. + */ + void hideBeingPopup(); + + /** + * Clear all hover item\being etc + */ + void cleanHoverItems(); + + Map *getMap() + { return mMap; } + + void moveCamera(int dx, int dy); + + int getCameraRelativeX() + { return mCameraRelativeX; } + + int getCameraRelativeY() + { return mCameraRelativeY; } + + protected: + friend class ActorSpriteManager; + + /// Clears any matching hovers + void clearHover(ActorSprite *actor); + + private: + /** + * Finds a path from the player to the mouse, and draws it. This is for + * debug purposes. + */ + void _drawDebugPath(Graphics *graphics); + + /** + * Draws the given path. + */ + void _drawPath(Graphics *graphics, const Path &path, + gcn::Color color = gcn::Color(255, 0, 0)); + + /** + * Make the player go to the mouse position. + */ + void _followMouse(); + + Map *mMap; /**< The current map. */ + + int mScrollRadius; + int mScrollLaziness; + int mScrollCenterOffsetX; + int mScrollCenterOffsetY; + int mMouseX; /**< Current mouse position in pixels. */ + int mMouseY; /**< Current mouse position in pixels. */ + float mPixelViewX; /**< Current viewpoint in pixels. */ + float mPixelViewY; /**< Current viewpoint in pixels. */ + int mShowDebugPath; /**< Show a path from player to pointer. */ + int mCameraMode; /**< Camera mode. */ + + bool mPlayerFollowMouse; + + int mLocalWalkTime; /**< Timestamp before the next walk can be sent. */ + + PopupMenu *mPopupMenu; /**< Popup menu. */ + Being *mHoverBeing; /**< Being mouse is currently over. */ + FloorItem *mHoverItem; /**< FloorItem mouse is currently over. */ + MapItem *mHoverSign; /**< Map sign mouse is currently over. */ + BeingPopup *mBeingPopup; /**< Being information popup. */ + TextPopup *mTextPopup; /**< Map Item information popup. */ + + int mCameraRelativeX; + int mCameraRelativeY; +}; + +extern Viewport *viewport; /**< The viewport. */ + +#endif diff --git a/src/gui/whoisonline.cpp b/src/gui/whoisonline.cpp new file mode 100644 index 000000000..84485b995 --- /dev/null +++ b/src/gui/whoisonline.cpp @@ -0,0 +1,550 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "whoisonline.h" + +#include +#include +#include +#include + +#include "gui/viewport.h" +#include "gui/widgets/button.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/chattab.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "localplayer.h" +#include "playerrelations.h" +#include "main.h" + +#include "gui/chat.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +// Curl should be included after Guichan to avoid Windows redefinitions +#include + +bool stringCompare(const std::string &left, const std::string &right); + +bool stringCompare(const std::string &left, const std::string &right ) +{ + for (std::string::const_iterator lit = left.begin(), + rit = right.begin(); + lit != left.end() && rit != right.end(); ++lit, ++rit) + { + if (tolower(*lit) < tolower(*rit)) + return true; + else if (tolower(*lit) > tolower(*rit)) + return false; + } + if (left.size() < right.size()) + return true; + return false; +} + +WhoIsOnline::WhoIsOnline(): + Window(_("Who Is Online - Updating")), + mThread(NULL), + mDownloadStatus(UPDATE_LIST), + mDownloadComplete(true), + mDownloadedBytes(0), + mMemoryBuffer(NULL), + mCurlError(new char[CURL_ERROR_SIZE]), + mAllowUpdate(true), + mShowLevel(false) +{ + mCurlError[0] = 0; + setWindowName("WhoIsOnline"); + + const int h = 350; + const int w = 200; + setDefaultSize(w, h, ImageRect::CENTER); +// setContentSize(w, h); + setCloseButton(true); + setResizable(true); + + mUpdateButton = new Button(_("Update"), "update", this); + mUpdateButton->setEnabled(false); + mUpdateButton->setDimension(gcn::Rectangle(5, 5, w - 10, 20 + 5)); + + mBrowserBox = new BrowserBox(); + mScrollArea = new ScrollArea(mBrowserBox); + mScrollArea->setOpaque(false); + mBrowserBox->setOpaque(false); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mScrollArea->setDimension(gcn::Rectangle(5, 20 + 10, w - 10, h - 10 - 30)); + mScrollArea->setSize(w - 10, h - 10 - 30); + mBrowserBox->setLinkHandler(this); + + add(mUpdateButton); + add(mScrollArea); + + mUpdateTimer = 0; + setLocationRelativeTo(getParent()); + + loadWindowState(); + + download(); + + config.addListener("updateOnlineList", this); + mUpdateOnlineList = config.getBoolValue("updateOnlineList"); +} + +WhoIsOnline::~WhoIsOnline() +{ + config.removeListener("updateOnlineList", this); + + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, NULL); + + free(mMemoryBuffer); + mMemoryBuffer = 0; + + // Remove possibly leftover temporary download + delete[] mCurlError; +} + +void WhoIsOnline::handleLink(const std::string& link, gcn::MouseEvent *event) +{ + if (!event || event->getButton() == gcn::MouseEvent::LEFT) + { + if (chatWindow) + { + if (config.getBoolValue("whispertab")) + chatWindow->localChatInput("/q " + link); + else + chatWindow->addInputText("/w \"" + link + "\" "); + } + } + else if (event->getButton() == gcn::MouseEvent::RIGHT) + { + if (player_node && link == player_node->getName()) + return; + + if (viewport) + { + if (actorSpriteManager) + { + Being* being = actorSpriteManager->findBeingByName( + link, Being::PLAYER); + + if (being && viewport) + { + viewport->showPopup(event->getX(), event->getY(), being); + return; + } + } + viewport->showPlayerPopup(link); + } + } +} + +void WhoIsOnline::loadList() +{ + if (!mMemoryBuffer) + return; + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast( + realloc(mMemoryBuffer, mDownloadedBytes + 1)); + mMemoryBuffer[mDownloadedBytes] = '\0'; + + mBrowserBox->clearRows(); + bool listStarted(false); + std::string lineStr; + int numOnline(0); + std::vector friends; + std::vector neutral; + std::vector disregard; + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + const std::string gmText = "(GM)"; + mOnlinePlayers.clear(); + + mShowLevel = config.getBoolValue("showlevel"); + + while (line != NULL) + { + std::string nick; + lineStr = line; + trim(lineStr); + if (listStarted == true) + { + size_t found; + found = lineStr.find(" users are online."); + if (found == std::string::npos) + { + int level = 0; + + std::string::size_type pos = 0; + if (lineStr.length() > 24) + { + nick = lineStr.substr(0, 24); + lineStr = lineStr.substr(25); + } + else + { + nick = lineStr; + lineStr = ""; + } + trim(nick); + + pos = lineStr.find(gmText, 0); + if (pos != std::string::npos) + lineStr = lineStr.substr(pos + gmText.length()); + + trim(lineStr); + pos = lineStr.find("/", 0); + + if (pos != std::string::npos) + lineStr = lineStr.substr(0, pos); + + if (!lineStr.empty()) + level = atoi(lineStr.c_str()); + + if (actorSpriteManager) + { + Being *being = actorSpriteManager->findBeingByName( + nick, Being::PLAYER); + if (being) + { + if (level > 0) + { + being->setLevel(level); + being->updateName(); + } + else + { + if (being->getLevel() > 1) + level = being->getLevel(); + } + } + } + + mOnlinePlayers.insert(nick); + + numOnline++; + switch (player_relations.getRelation(nick)) + { + case PlayerRelation::NEUTRAL: + neutral.push_back(prepareNick(nick, level, "0")); + break; + + case PlayerRelation::FRIEND: + friends.push_back(prepareNick(nick, level, "2")); + break; + + case PlayerRelation::DISREGARDED: + disregard.push_back(prepareNick(nick, level, "8")); + break; + + case PlayerRelation::IGNORED: + case PlayerRelation::ERASED: + default: + //Ignore the ignored. + break; + } + } + } + else if (lineStr.find("------------------------------") + != std::string::npos) + { + listStarted = true; + } + line = strtok(NULL, "\n"); + } + + //Set window caption + setCaption(_("Who Is Online - ") + toString(numOnline)); + + //List the online people + sort(friends.begin(), friends.end(), stringCompare); + sort(neutral.begin(), neutral.end(), stringCompare); + sort(disregard.begin(), disregard.end(), stringCompare); + bool addedFromSection(false); + for (int i = 0; i < static_cast(friends.size()); i++) + { + mBrowserBox->addRow(friends.at(i)); + addedFromSection = true; + } + if (addedFromSection == true) + { + mBrowserBox->addRow("---"); + addedFromSection = false; + } + for (int i = 0; i < static_cast(neutral.size()); i++) + { + mBrowserBox->addRow(neutral.at(i)); + addedFromSection = true; + } + if (addedFromSection == true && !disregard.empty()) + { + mBrowserBox->addRow("---"); + addedFromSection = false; + } + for (int i = 0; i < static_cast(disregard.size()); i++) + { + mBrowserBox->addRow(disregard.at(i)); + } + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = 0; + + if (mScrollArea->getVerticalMaxScroll() < + mScrollArea->getVerticalScrollAmount()) + { + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + } +} + +size_t WhoIsOnline::memoryWrite(void *ptr, size_t size, + size_t nmemb, FILE *stream) +{ + WhoIsOnline *wio = reinterpret_cast(stream); + size_t totalMem = size * nmemb; + wio->mMemoryBuffer = static_cast(realloc(wio->mMemoryBuffer, + wio->mDownloadedBytes + totalMem)); + if (wio->mMemoryBuffer) + { + memcpy(&(wio->mMemoryBuffer[wio->mDownloadedBytes]), ptr, totalMem); + wio->mDownloadedBytes += static_cast(totalMem); + } + + return totalMem; +} + +int WhoIsOnline::downloadThread(void *ptr) +{ + int attempts = 0; + WhoIsOnline *wio = reinterpret_cast(ptr); + CURL *curl; + CURLcode res; + + std::string url(Client::getServerName() + "/online.txt"); + + while (attempts < 1 && !wio->mDownloadComplete) + { + curl = curl_easy_init(); + + if (curl) + { + if (!wio->mAllowUpdate) + { + curl_easy_cleanup(curl); + break; + } + wio->mDownloadedBytes = 0; + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + WhoIsOnline::memoryWrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, + strprintf(PACKAGE_EXTENDED_VERSION, branding + .getValue("appShort", "mana").c_str()).c_str()); + + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, wio->mCurlError); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, ptr); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); + + struct curl_slist *pHeaders = 0; + // Make sure the resources2.txt and news.txt aren't cached, + // in order to always get the latest version. + pHeaders = curl_slist_append(pHeaders, "pragma: no-cache"); + pHeaders = + curl_slist_append(pHeaders, "Cache-Control: no-cache"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders); + + if ((res = curl_easy_perform(curl)) != 0) + { + wio->mDownloadStatus = UPDATE_ERROR; + switch (res) + { + case CURLE_COULDNT_CONNECT: + default: + std::cerr << "curl error " + << static_cast(res) << ": " + << wio->mCurlError << " host: " + << url.c_str() << std::endl; + break; + } + attempts++; + continue; + } + + curl_easy_cleanup(curl); + + curl_slist_free_all(pHeaders); + + // It's stored in memory, we're done + wio->mDownloadComplete = true; + } + if (!wio->mAllowUpdate) + break; + attempts++; + } + + if (!wio->mDownloadComplete) + wio->mDownloadStatus = UPDATE_ERROR; + +// wio->mThread = 0; + return 0; +} + +void WhoIsOnline::download() +{ + mDownloadComplete = true; + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, NULL); + + mDownloadComplete = false; + mThread = SDL_CreateThread(WhoIsOnline::downloadThread, this); + + if (mThread == NULL) + mDownloadStatus = UPDATE_ERROR; +} + +void WhoIsOnline::logic() +{ + // Update Scroll logic + mScrollArea->logic(); + + if (!mAllowUpdate) + return; + + if (mUpdateTimer == 0) + mUpdateTimer = cur_time; + + double timeDiff = difftime(cur_time, mUpdateTimer); + int timeLimit = isVisible() ? 20 : 120; + + if (mUpdateOnlineList && timeDiff >= timeLimit + && mDownloadStatus != UPDATE_LIST) + { + if (mDownloadComplete == true) + { + setCaption(_("Who Is Online - Updating")); + mUpdateTimer = 0; + mDownloadStatus = UPDATE_LIST; + download(); + } + } + + switch (mDownloadStatus) + { + case UPDATE_ERROR: + mBrowserBox->clearRows(); + mBrowserBox->addRow("##1Failed to fetch the online list!"); + mBrowserBox->addRow(mCurlError); + mDownloadStatus = UPDATE_COMPLETE; + setCaption(_("Who Is Online - error")); + mUpdateButton->setEnabled(true); + mUpdateTimer = cur_time + 240; + break; + case UPDATE_LIST: + if (mDownloadComplete == true) + { + loadList(); + mDownloadStatus = UPDATE_COMPLETE; + mUpdateButton->setEnabled(true); + mUpdateTimer = 0; + updateSize(); + if (mOnlinePlayers.size() > 0 && chatWindow) + chatWindow->updateOnline(mOnlinePlayers); + } + break; + default: + break; + } +} + +void WhoIsOnline::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "update") + { + if (mDownloadStatus == UPDATE_COMPLETE) + { + mUpdateTimer = cur_time - 20; + if (mUpdateButton) + mUpdateButton->setEnabled(false); + setCaption(_("Who Is Online - Update")); + if (mThread && SDL_GetThreadID(mThread)) + { + SDL_WaitThread(mThread, NULL); + mThread = NULL; + } + mDownloadComplete = true; + } + } +} + +void WhoIsOnline::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + updateSize(); +} + +void WhoIsOnline::updateSize() +{ + if (mDownloadStatus == UPDATE_COMPLETE) + { + const gcn::Rectangle area = getChildrenArea(); + if (mUpdateButton) + mUpdateButton->setWidth(area.width - 10); + + if (mScrollArea) + mScrollArea->setSize(area.width - 10, area.height - 10 - 30); + } +} + +const std::string WhoIsOnline::prepareNick(std::string nick, int level, + std::string color) const +{ + if (mShowLevel && level > 1) + { + return strprintf("@@%s|##%s%s (%d)@@", nick.c_str(), + color.c_str(), nick.c_str(), level); + } + else + { + return strprintf("@@%s|##%s%s@@", nick.c_str(), + color.c_str(), nick.c_str()); + } +} + +void WhoIsOnline::optionChanged(const std::string &name) +{ + if (name == "updateOnlineList") + mUpdateOnlineList = config.getBoolValue("updateOnlineList"); +} \ No newline at end of file diff --git a/src/gui/whoisonline.h b/src/gui/whoisonline.h new file mode 100644 index 000000000..d6ac177d6 --- /dev/null +++ b/src/gui/whoisonline.h @@ -0,0 +1,139 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _WHOISONLINE_H +#define _WHOISONLINE_H + +#include +#include + +#include "configlistener.h" + +#include "gui/widgets/linkhandler.h" +#include "gui/widgets/window.h" + +#include "../utils/mutex.h" + +#include + +class BrowserBox; +class ScrollArea; + +struct SDL_Thread; + +/** + * Update progress window GUI + * + * \ingroup GUI + */ +class WhoIsOnline : public Window, + public LinkHandler, + public gcn::ActionListener, + public ConfigListener +{ + public: + /** + * Constructor. + */ + WhoIsOnline(); + + /** + * Destructor + */ + ~WhoIsOnline(); + + /** + * Loads and display online list from the memory buffer. + */ + void loadList(); + + void handleLink(const std::string& link, gcn::MouseEvent *event); + + void logic(); + + void action(const gcn::ActionEvent &event); + + void widgetResized(const gcn::Event &event); + + std::set &getOnlinePlayers() + { return mOnlinePlayers; } + + void setAllowUpdate(bool n) + { mAllowUpdate = n; } + + void optionChanged(const std::string &name); + +private: + void download(); + + void updateSize(); + + /** + * The thread function that download the files. + */ + static int downloadThread(void *ptr); + + /** + * A libcurl callback for writing to memory. + */ + static size_t memoryWrite(void *ptr, size_t size, size_t nmemb, + FILE *stream); + + const std::string prepareNick(std::string nick, int level, + std::string color) const; + enum DownloadStatus + { + UPDATE_ERROR = 0, + UPDATE_COMPLETE, + UPDATE_LIST + }; + + /** A thread that use libcurl to download updates. */ + SDL_Thread *mThread; + + /** Status of the current download. */ + DownloadStatus mDownloadStatus; + + /** Flag that show if current download is complete. */ + bool mDownloadComplete; + + /** Byte count currently downloaded in mMemoryBuffer. */ + int mDownloadedBytes; + + /** Buffer for files downloaded to memory. */ + char *mMemoryBuffer; + + /** Buffer to handler human readable error provided by curl. */ + char *mCurlError; + + BrowserBox *mBrowserBox; + ScrollArea *mScrollArea; + time_t mUpdateTimer; + std::set mOnlinePlayers; + + gcn::Button *mUpdateButton; + bool mAllowUpdate; + bool mShowLevel; + bool mUpdateOnlineList; +}; + +#endif diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp new file mode 100644 index 000000000..d665c81ce --- /dev/null +++ b/src/gui/widgets/avatarlistbox.cpp @@ -0,0 +1,346 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/avatarlistbox.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "graphics.h" +#include "guild.h" +#include "localplayer.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/viewport.h" +#include "gui/theme.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +#include + +int AvatarListBox::instances = 0; +Image *AvatarListBox::onlineIcon = 0; +Image *AvatarListBox::offlineIcon = 0; + +AvatarListBox::AvatarListBox(AvatarListModel *model): + ListBox(model), + mShowGender(false), + mShowLevel(false) +{ + instances++; + + if (instances == 1) + { + onlineIcon = Theme::getImageFromTheme("circle-green.png"); + offlineIcon = Theme::getImageFromTheme("circle-gray.png"); + } + + setWidth(200); + + mShowGender = config.getBoolValue("showgender"); + mShowLevel = config.getBoolValue("showlevel"); + + config.addListener("showgender", this); + config.addListener("showlevel", this); +} + +AvatarListBox::~AvatarListBox() +{ + config.removeListener("showgender", this); + config.removeListener("showlevel", this); + + instances--; + + if (instances == 0) + { + if (onlineIcon) + onlineIcon->decRef(); + if (offlineIcon) + offlineIcon->decRef(); + } +} + +void AvatarListBox::draw(gcn::Graphics *gcnGraphics) +{ + if (!mListModel || !player_node) + return; + + AvatarListModel *model = static_cast(mListModel); +// Guild *guild = dynamic_cast(model); + + updateAlpha(); + + Graphics *graphics = static_cast(gcnGraphics); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + const int fontHeight = getFont()->getHeight(); + + Widget *parent = getParent(); + + const std::string name = player_node->getName(); + + // Draw the list elements + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + for (int i = 0, y = 0; + i < model->getNumberOfElements(); + ++i, y += fontHeight) + { + Avatar *a = model->getAvatarAt(i); + if (!a) + continue; + + // Draw online status + Image *icon = a->getOnline() ? onlineIcon : offlineIcon; + if (icon) + graphics->drawImage(icon, 2, y + 1); + + if (a->getDisplayBold()) + graphics->setFont(boldFont); + + std::string text; + + if (a->getMaxHp() > 0) + { + if (mShowLevel && a->getLevel() > 1) + { + text = strprintf("%s %d/%d (%d)", a->getComplexName().c_str(), + a->getHp(), a->getMaxHp(), a->getLevel()); + } + else + { + text = strprintf("%s %d/%d", a->getComplexName().c_str(), + a->getHp(), a->getMaxHp()); + } + if (parent && a->getMaxHp()) + { + gcn::Color color = Theme::getProgressColor( + Theme::PROG_HP, static_cast(a->getHp()) + / static_cast(a->getMaxHp())); + color.a = 80; + graphics->setColor(color); + + graphics->fillRectangle(gcn::Rectangle(0, y, + parent->getWidth() * a->getHp() / a->getMaxHp(), + fontHeight)); + } + } + else if (a->getDamageHp() != 0 && a->getName() != name) + { + if (mShowLevel && a->getLevel() > 1) + { + text = strprintf("%s -%d (%d)", a->getComplexName().c_str(), + a->getDamageHp(), a->getLevel()); + } + else + { + text = strprintf("%s -%d", a->getComplexName().c_str(), + a->getDamageHp()); + } + + if (parent) + { +// int diff; +// if (a->getDamageHp() > 1024) +// diff = 0; +// else +// diff = 1024 - a->getDamageHp(); + gcn::Color color = Theme::getProgressColor(Theme::PROG_HP, + 1); +// 0 / 1024); +/* + if (a->getDamageHp() >= 400) + { + } + else + { +// int intens = 1024/(400 - a->getDamageHp()); + int intens = a->getDamageHp() / 1024; + if (intens > 1) + intens = 1; + color = Theme::getProgressColor(Theme::PROG_HP, + intens); + } +*/ + + color.a = 80; + graphics->setColor(color); + graphics->fillRectangle(gcn::Rectangle(0, y, + parent->getWidth() * a->getDamageHp() / 1024, + fontHeight)); + + if (a->getLevel() > 1) + { + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + int minHp = 40 + ((a->getLevel() - 1) * 5); + if (minHp < 0) + minHp = 40; + + graphics->drawLine(parent->getWidth()*minHp / 1024, y, + parent->getWidth() * minHp / 1024, y + fontHeight); + } + } + } + else + { + if (mShowLevel && a->getLevel() > 1) + { + text = strprintf("%s (%d)", a->getComplexName().c_str(), + a->getLevel()); + } + else + { + text = a->getComplexName(); + } + } + + if (!a->getMap().empty()) + { + if (a->getX() != -1) + { + text += strprintf(" [%d,%d %s]", a->getX(), a->getY(), + a->getMap().c_str()); + } + else + { + text += strprintf(" [%s]", a->getMap().c_str()); + } + } + + if (mShowGender) + { + switch (a->getGender()) + { + case GENDER_FEMALE: + text += strprintf(" \u2640 %s", + a->getAdditionString().c_str()); + break; + case GENDER_MALE: + text += strprintf(" \u2642 %s", + a->getAdditionString().c_str()); + break; + default: + break; + } + } + else + { + text += a->getAdditionString(); + } + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + // Draw Name + graphics->drawText(text, 15, y); + + if (a->getDisplayBold()) + graphics->setFont(getFont()); + } + + setWidth(parent->getWidth() - 10); +} + +void AvatarListBox::mousePressed(gcn::MouseEvent &event) +{ + if (!actorSpriteManager || !player_node || !viewport + || !getFont()->getHeight()) + { + return; + } + + int y = event.getY() / getFont()->getHeight(); + if (!mListModel || y > mListModel->getNumberOfElements()) + return; + + setSelected(y); + distributeActionEvent(); + int selected = getSelected(); + AvatarListModel *model = static_cast(mListModel); + if (!model) + return; + Avatar *ava = model->getAvatarAt(selected); + if (!ava) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (ava->getType() == AVATAR_PLAYER) + { + Being* being = actorSpriteManager->findBeingByName(ava->getName(), + Being::PLAYER); + if (being) + actorSpriteManager->heal(player_node, being); + } + else + { + player_node->navigateTo(ava->getX(), ava->getY()); + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + if (ava->getType() == AVATAR_PLAYER) + { + Being* being = actorSpriteManager->findBeingByName( + model->getAvatarAt(selected)->getName(), Being::PLAYER); + if (being) + { + viewport->showPopup(event.getX(), event.getY(), being); + } + else + { + viewport->showPlayerPopup( + model->getAvatarAt(selected)->getName()); + } + } + else + { + Map *map = viewport->getMap(); + Avatar *ava = model->getAvatarAt(selected); + if (map && ava) + { + MapItem *mapItem = map->findPortalXY(ava->getX(), ava->getY()); + viewport->showPopup(mapItem); + } + } + } + + else if (event.getButton() == gcn::MouseEvent::MIDDLE) + { + if (ava->getType() == AVATAR_PLAYER && chatWindow) + { + chatWindow->addWhisperTab(model->getAvatarAt(selected) + ->getName(), true); + } + } +} + +void AvatarListBox::optionChanged(const std::string &value) +{ + if (value == "showgender") + mShowGender = config.getBoolValue("showgender"); + else if (value == "showlevel") + mShowLevel = config.getBoolValue("showlevel"); +} \ No newline at end of file diff --git a/src/gui/widgets/avatarlistbox.h b/src/gui/widgets/avatarlistbox.h new file mode 100644 index 000000000..c7bc11f7c --- /dev/null +++ b/src/gui/widgets/avatarlistbox.h @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_GUILDLISTBOX_H +#define GUI_GUILDLISTBOX_H + +#include "avatar.h" + +#include "configlistener.h" + +#include "gui/widgets/listbox.h" + +#include +#include +#include + +class Image; + +class AvatarListModel : public gcn::ListModel +{ +public: + virtual Avatar *getAvatarAt(int i) = 0; + + std::string getElementAt(int i) + { return getAvatarAt(i)->getName(); } +}; + +class AvatarListBox : public ListBox, public ConfigListener +{ +public: + AvatarListBox(AvatarListModel *model); + + ~AvatarListBox(); + + /** + * Draws the list box. + */ + void draw(gcn::Graphics *gcnGraphics); + + void mousePressed(gcn::MouseEvent &event); + + void optionChanged(const std::string &value); + +private: + bool mShowGender; + bool mShowLevel; + + static int instances; + static Image *onlineIcon; + static Image *offlineIcon; +}; + +#endif diff --git a/src/gui/widgets/battletab.cpp b/src/gui/widgets/battletab.cpp new file mode 100644 index 000000000..68f1a0453 --- /dev/null +++ b/src/gui/widgets/battletab.cpp @@ -0,0 +1,54 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/battletab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "net/net.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +BattleTab::BattleTab() : + ChatTab(_("Battle")) +{ + loadFromLogFile("#Battle"); +} + +BattleTab::~BattleTab() +{ +} + +void BattleTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log(std::string("#Battle"), std::string(msg)); +} diff --git a/src/gui/widgets/battletab.h b/src/gui/widgets/battletab.h new file mode 100644 index 000000000..fdfe626f0 --- /dev/null +++ b/src/gui/widgets/battletab.h @@ -0,0 +1,47 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BATTLETAB_H +#define BATTLETAB_H + +#include "gui/widgets/chattab.h" + +/** + * A tab for a party chat channel. + */ +class BattleTab : public ChatTab +{ + public: + BattleTab(); + + ~BattleTab(); + + int getType() const + { return ChatTab::TAB_BATTLE; } + + void saveToLogFile(std::string &msg); +}; + +extern BattleTab *battleChatTab; +#endif + + + diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp new file mode 100644 index 000000000..acb182c3c --- /dev/null +++ b/src/gui/widgets/browserbox.cpp @@ -0,0 +1,534 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/browserbox.h" + +#include "client.h" +#include "log.h" + +#include "utils/stringutils.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/linkhandler.h" + +#include +#include +#include + +#include + +BrowserBox::BrowserBox(unsigned int mode, bool opaque): + gcn::Widget(), + mMode(mode), mHighMode(UNDERLINE | BACKGROUND), + mOpaque(opaque), + mUseLinksAndUserColors(true), + mSelectedLink(-1), + mMaxRows(0), + mHeight(0), + mWidth(0), + mYStart(0), + mUpdateTime(-1), + mAlwaysUpdate(true) +{ + setFocusable(true); + addMouseListener(this); +} + +BrowserBox::~BrowserBox() +{ +} + +void BrowserBox::setLinkHandler(LinkHandler* linkHandler) +{ + mLinkHandler = linkHandler; +} + +void BrowserBox::setOpaque(bool opaque) +{ + mOpaque = opaque; +} + +void BrowserBox::setHighlightMode(unsigned int highMode) +{ + mHighMode = highMode; +} + +void BrowserBox::addRow(const std::string &row, bool atTop) +{ + std::string tmp = row; + std::string newRow; + std::string::size_type idx1, idx2, idx3; + gcn::Font *font = getFont(); + + // Use links and user defined colors + if (mUseLinksAndUserColors) + { + BROWSER_LINK bLink; + + // Check for links in format "@@link|Caption@@" + idx1 = tmp.find("@@"); + while (idx1 != std::string::npos) + { + idx2 = tmp.find("|", idx1); + idx3 = tmp.find("@@", idx2); + + if (idx2 == std::string::npos || idx3 == std::string::npos) + break; + bLink.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2)); + bLink.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1)); + bLink.y1 = static_cast(mTextRows.size()) * font->getHeight(); + bLink.y2 = bLink.y1 + font->getHeight(); + + newRow += tmp.substr(0, idx1); + + std::string tmp2 = newRow; + idx1 = tmp2.find("##"); + while (idx1 != std::string::npos) + { + tmp2.erase(idx1, 3); + idx1 = tmp2.find("##"); + } + bLink.x1 = font->getWidth(tmp2) - 1; + bLink.x2 = bLink.x1 + font->getWidth(bLink.caption) + 1; + + mLinks.push_back(bLink); + + newRow += "##<" + bLink.caption; + + tmp.erase(0, idx3 + 2); + if (!tmp.empty()) + newRow += "##>"; + + idx1 = tmp.find("@@"); + } + + newRow += tmp; + } + // Don't use links and user defined colors + else + { + newRow = row; + } + + if (atTop) + mTextRows.push_front(newRow); + else + mTextRows.push_back(newRow); + + //discard older rows when a row limit has been set + if (mMaxRows > 0) + { + while (mTextRows.size() > mMaxRows) + { + mTextRows.pop_front(); + for (unsigned int i = 0; i < mLinks.size(); i++) + { + mLinks[i].y1 -= font->getHeight(); + mLinks[i].y2 -= font->getHeight(); + + if (mLinks[i].y1 < 0) + mLinks.erase(mLinks.begin() + i); + } + } + } + + // Auto size mode + if (mMode == AUTO_SIZE) + { + std::string plain = newRow; + for (idx1 = plain.find("##"); + idx1 != std::string::npos; + idx1 = plain.find("##")) + { + plain.erase(idx1, 3); + } + + // Adjust the BrowserBox size + int w = font->getWidth(plain); + if (w > getWidth()) + setWidth(w); + } + + if (mMode == AUTO_WRAP) + { + unsigned int y = 0; + unsigned int nextChar; + const char *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + int x = 0; + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + std::string row = *i; + for (unsigned int j = 0; j < row.size(); j++) + { + std::string character = row.substr(j, 1); + x += font->getWidth(character); + nextChar = j + 1; + + // Wraping between words (at blank spaces) + if ((nextChar < row.size()) && (row.at(nextChar) == ' ')) + { + int nextSpacePos = static_cast( + row.find(" ", (nextChar + 1))); + if (nextSpacePos <= 0) + nextSpacePos = static_cast(row.size()) - 1; + + int nextWordWidth = font->getWidth( + row.substr(nextChar, + (nextSpacePos - nextChar))); + + if ((x + nextWordWidth + 10) > getWidth()) + { + x = 15; // Ident in new line + y += 1; + j++; + } + } + // Wrapping looong lines (brutal force) + else if ((x + 2 * hyphenWidth) > getWidth()) + { + x = 15; // Ident in new line + y += 1; + } + } + } + + setHeight(font->getHeight() * (static_cast( + mTextRows.size()) + y)); + } + else + { + setHeight(font->getHeight() * static_cast(mTextRows.size())); + } + mUpdateTime = 0; + updateHeight(); +} + +void BrowserBox::clearRows() +{ + mTextRows.clear(); + mLinks.clear(); + setWidth(0); + setHeight(0); + mSelectedLink = -1; + mUpdateTime = 0; + updateHeight(); +} + +struct MouseOverLink +{ + MouseOverLink(int x, int y) : mX(x), mY(y) + { } + + bool operator() (BROWSER_LINK &link) + { + return (mX >= link.x1 && mX < link.x2 && + mY >= link.y1 && mY < link.y2); + } + int mX, mY; +}; + +void BrowserBox::mousePressed(gcn::MouseEvent &event) +{ + if (!mLinkHandler) + return; + + LinkIterator i = find_if(mLinks.begin(), mLinks.end(), + MouseOverLink(event.getX(), event.getY())); + + if (i != mLinks.end()) + mLinkHandler->handleLink(i->link, &event); +} + +void BrowserBox::mouseMoved(gcn::MouseEvent &event) +{ + LinkIterator i = find_if(mLinks.begin(), mLinks.end(), + MouseOverLink(event.getX(), event.getY())); + + mSelectedLink = (i != mLinks.end()) + ? static_cast(i - mLinks.begin()) : -1; +} + +void BrowserBox::draw(gcn::Graphics *graphics) +{ + gcn::ClipRectangle cr = graphics->getCurrentClipArea(); + mYStart = cr.y - cr.yOffset; + int yEnd = mYStart + cr.height; + if (mYStart < 0) + mYStart = 0; + + if (getWidth() != mWidth) + updateHeight(); + + if (mOpaque) + { + graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND)); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mSelectedLink >= 0 && mSelectedLink < (signed)mLinks.size()) + { + if ((mHighMode & BACKGROUND)) + { + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT)); + graphics->fillRectangle(gcn::Rectangle( + mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y1, + mLinks[mSelectedLink].x2 - mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y2 - mLinks[mSelectedLink].y1 + )); + } + + if ((mHighMode & UNDERLINE)) + { + graphics->setColor(Theme::getThemeColor(Theme::HYPERLINK)); + graphics->drawLine( + mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y2, + mLinks[mSelectedLink].x2, + mLinks[mSelectedLink].y2); + } + } + + gcn::Font *font = getFont(); + + for (LinePartIterator i = mLineParts.begin(); + i != mLineParts.end(); + i ++) + { + const LinePart &part = *i; + if (part.mY + 50 < mYStart) + continue; + if (part.mY > yEnd) + break; + graphics->setColor(part.mColor); + font->drawString(graphics, part.mText, part.mX, part.mY); + } + + return; +} + +int BrowserBox::calcHeight() +{ + int x = 0, y = 0; + int wrappedLines = 0; + int link = 0; + gcn::Font *font = getFont(); + + int fontHeight = font->getHeight(); + int fontWidthMinus = font->getWidth("-"); + char const *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + + gcn::Color selColor = Theme::getThemeColor(Theme::TEXT); + const gcn::Color textColor = Theme::getThemeColor(Theme::TEXT); + + mLineParts.clear(); + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + const std::string row = *(i); + bool wrapped = false; + x = 0; + + // Check for separator lines + if (row.find("---", 0) == 0) + { + const int dashWidth = fontWidthMinus; + for (x = 0; x < getWidth(); x++) + { + mLineParts.push_back(LinePart(x, y, selColor, "-")); + x += dashWidth - 2; + } + + y += fontHeight; + continue; + } + + gcn::Color prevColor = selColor; + + // TODO: Check if we must take texture size limits into account here + // TODO: Check if some of the O(n) calls can be removed + for (std::string::size_type start = 0, end = std::string::npos; + start != std::string::npos; + start = end, end = std::string::npos) + { + // Wrapped line continuation shall be indented + if (wrapped) + { + y += fontHeight; + x = 15; + wrapped = false; + } + + // "Tokenize" the string at control sequences + if (mUseLinksAndUserColors) + end = row.find("##", start + 1); + + if (mUseLinksAndUserColors || + (!mUseLinksAndUserColors && (start == 0))) + { + // Check for color change in format "##x", x = [L,P,0..9] + if (row.find("##", start) == start && row.size() > start + 2) + { + const char c = row.at(start + 2); + + bool valid; + const gcn::Color col = Theme::getThemeColor(c, valid); + + if (c == '>') + { + selColor = prevColor; + } + else if (c == '<') + { +// link++; + prevColor = selColor; + selColor = col; + } + else if (valid) + { + selColor = col; + } + else + { + + switch (c) + { + case '1': selColor = RED; break; + case '2': selColor = GREEN; break; + case '3': selColor = BLUE; break; + case '4': selColor = ORANGE; break; + case '5': selColor = YELLOW; break; + case '6': selColor = PINK; break; + case '7': selColor = PURPLE; break; + case '8': selColor = GRAY; break; + case '9': selColor = BROWN; break; + case '0': + default: + selColor = textColor; + } + } + + if (c == '<' && link < (signed)mLinks.size()) + { + const int size = + font->getWidth(mLinks[link].caption) + 1; + + mLinks[link].x1 = x; + mLinks[link].y1 = y; + mLinks[link].x2 = mLinks[link].x1 + size; + mLinks[link].y2 = y + fontHeight - 1; + link++; + } + start += 3; + + if (start == row.size()) + break; + } + } + + std::string::size_type len = + end == std::string::npos ? end : end - start; + + if (start >= row.length()) + break; + + std::string part = row.substr(start, len); + + // Auto wrap mode + if (mMode == AUTO_WRAP && getWidth() > 0 + && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()) + { + bool forced = false; + + /* FIXME: This code layout makes it easy to crash remote + clients by talking garbage. Forged long utf-8 characters + will cause either a buffer underflow in substr or an + infinite loop in the main loop. */ + do + { + if (!forced) + end = row.rfind(' ', end); + + // Check if we have to (stupidly) force-wrap + if (end == std::string::npos || end <= start) + { + forced = true; + end = row.size(); + x += hyphenWidth; // Account for the wrap-notifier + continue; + } + + // Skip to the start of the current character + while ((row[end] & 192) == 128) + end--; + end--; // And then to the last byte of the previous one + + part = row.substr(start, end - start + 1); + } + while (end > start && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()); + + if (forced) + { + x -= hyphenWidth; // Remove the wrap-notifier accounting + mLineParts.push_back(LinePart(getWidth() - hyphenWidth, + y, selColor, hyphen)); + end++; // Skip to the next character + } + else + { + end += 2; // Skip to after the space + } + + wrapped = true; + wrappedLines++; + } + + mLineParts.push_back(LinePart(x, y, selColor, part.c_str())); + + if (mMode == AUTO_WRAP && font->getWidth(part) == 0) + break; + + x += font->getWidth(part); + } + y += fontHeight; + } + return (static_cast(mTextRows.size()) + wrappedLines) * fontHeight; +} + +void BrowserBox::updateHeight() +{ + if (mAlwaysUpdate || mUpdateTime != cur_time + || mTextRows.size() < 3 || !mUpdateTime) + { + mWidth = getWidth(); + mHeight = calcHeight(); + setHeight(mHeight); + mUpdateTime = cur_time; + } +} diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h new file mode 100644 index 000000000..cd9cc92de --- /dev/null +++ b/src/gui/widgets/browserbox.h @@ -0,0 +1,205 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BROWSERBOX_H +#define BROWSERBOX_H + +#include +#include + +#include +#include + +class LinkHandler; + +struct BROWSER_LINK +{ + int x1, x2, y1, y2; /**< Where link is placed */ + std::string link; + std::string caption; +}; + +class LinePart +{ + public: + LinePart(int x, int y, gcn::Color color, std::string text) : + mX(x), mY(y), mColor(color), mText(text) + { + } + + int mX, mY; + gcn::Color mColor; + std::string mText; +}; + +/** + * A simple browser box able to handle links and forward events to the + * parent conteiner. + */ +class BrowserBox : public gcn::Widget, + public gcn::MouseListener +{ + public: + /** + * Constructor. + */ + BrowserBox(unsigned int mode = AUTO_SIZE, bool opaque = true); + + /** + * Destructor. + */ + ~BrowserBox(); + + /** + * Sets the handler for links. + */ + void setLinkHandler(LinkHandler *linkHandler); + + /** + * Sets the BrowserBox opacity. + */ + void setOpaque(bool opaque); + + /** + * Sets the Highlight mode for links. + */ + void setHighlightMode(unsigned int highMode); + + /** + * Sets the maximum numbers of rows in the browser box. 0 = no limit. + */ + void setMaxRow(unsigned max) {mMaxRows = max; }; + + /** + * Disable links & user defined colors to be used in chat input. + */ +/* + void disableLinksAndUserColors(); +*/ + /** + * Adds a text row to the browser. + */ + void addRow(const std::string &row, bool atTop = false); + + /** + * Remove all rows. + */ + void clearRows(); + +// void setSize(int width, int height); + +// void widgetResized(const gcn::Event &event); + + /** + * Handles mouse actions. + */ + void mousePressed(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + /** + * Draws the browser box. + */ + void draw(gcn::Graphics *graphics); + + void updateHeight(); + +// void widgetResized(const gcn::Event &event); + + /** + * BrowserBox modes. + */ + enum + { + AUTO_SIZE = 0, + AUTO_WRAP /**< Maybe it needs a fix or to be redone. */ + }; + + /** + * BrowserBox colors. + * + * NOTES (by Javila): + * - color values is "0x" prefix followed by HTML color style. + * - we can add up to 10 different colors: [0..9]. + * - not all colors will be fine with all backgrounds due transparent + * windows and widgets. So, I think it's better keep BrowserBox + * opaque (white background) by default. + */ + enum + { + RED = 0xff0000, /**< Color 1 */ + GREEN = 0x009000, /**< Color 2 */ + BLUE = 0x0000ff, /**< Color 3 */ + ORANGE = 0xe0980e, /**< Color 4 */ + YELLOW = 0xf1dc27, /**< Color 5 */ + PINK = 0xff00d8, /**< Color 6 */ + PURPLE = 0x8415e2, /**< Color 7 */ + GRAY = 0x919191, /**< Color 8 */ + BROWN = 0x8e4c17 /**< Color 9 */ + }; + + /** + * Highlight modes for links. + * This can be used for a bitmask. + */ + enum + { + UNDERLINE = 1, + BACKGROUND = 2 + }; + + typedef std::list TextRows; + + TextRows &getRows() + { return mTextRows; } + + void setAlwaysUpdate(bool n) + { mAlwaysUpdate = n; } + + private: + int calcHeight(); + + typedef TextRows::iterator TextRowIterator; + TextRows mTextRows; + + typedef std::list LinePartList; + typedef LinePartList::iterator LinePartIterator; + LinePartList mLineParts; + + typedef std::vector Links; + typedef Links::iterator LinkIterator; + Links mLinks; + + LinkHandler *mLinkHandler; + unsigned int mMode; + unsigned int mHighMode; + bool mOpaque; + bool mUseLinksAndUserColors; + int mSelectedLink; + unsigned int mMaxRows; + int mHeight; + int mWidth; + int mYStart; + int mUpdateTime; + bool mAlwaysUpdate; +}; + +#endif diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp new file mode 100644 index 000000000..3445928a1 --- /dev/null +++ b/src/gui/widgets/button.cpp @@ -0,0 +1,227 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/button.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include +#include + +int Button::mInstances = 0; +float Button::mAlpha = 1.0; + +enum +{ + BUTTON_STANDARD = 0, // 0 + BUTTON_HIGHLIGHTED, // 1 + BUTTON_PRESSED, // 2 + BUTTON_DISABLED, // 3 + BUTTON_COUNT // 4 - Must be last. +}; + +struct ButtonData +{ + char const *file; + int gridX; + int gridY; +}; + +static ButtonData const data[BUTTON_COUNT] = +{ + { "button.png", 0, 0 }, + { "buttonhi.png", 9, 4 }, + { "buttonpress.png", 16, 19 }, + { "button_disabled.png", 25, 23 } +}; + +ImageRect Button::button[BUTTON_COUNT]; + +Button::Button(): + mDescription(""), mClickCount(0) +{ + init(); +} + +Button::Button(const std::string &caption, const std::string &actionEventId, + gcn::ActionListener *listener): + gcn::Button(caption), + mDescription(""), mClickCount(0) +{ + init(); + setActionEventId(actionEventId); + + if (listener) + addActionListener(listener); +} + +void Button::init() +{ + setFrameSize(0); + + if (mInstances == 0) + { + // Load the skin + Image *btn[BUTTON_COUNT]; + + int a, x, y, mode; + + for (mode = 0; mode < BUTTON_COUNT; mode++) + { + btn[mode] = Theme::getImageFromTheme(data[mode].file); + if (!btn[mode]) + continue; + + a = 0; + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + button[mode].grid[a] = btn[mode]->getSubImage( + data[x].gridX, data[y].gridY, + data[x + 1].gridX - data[x].gridX + 1, + data[y + 1].gridY - data[y].gridY + 1); + a++; + } + } + if (btn[mode]) + btn[mode]->decRef(); + } + updateAlpha(); + } + mInstances++; +} + +Button::~Button() +{ + mInstances--; + + if (mInstances == 0) + { + for (int mode = 0; mode < BUTTON_COUNT; mode++) + { + if (button[mode].grid) + { + for_each(button[mode].grid, + button[mode].grid + 9, dtor()); + } + } + } +} + +void Button::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + if (button[BUTTON_DISABLED].grid[a]) + button[BUTTON_DISABLED].grid[a]->setAlpha(mAlpha); + if (button[BUTTON_PRESSED].grid[a]) + button[BUTTON_PRESSED].grid[a]->setAlpha(mAlpha); + if (button[BUTTON_HIGHLIGHTED].grid[a]) + button[BUTTON_HIGHLIGHTED].grid[a]->setAlpha(mAlpha); + if (button[BUTTON_STANDARD].grid[a]) + button[BUTTON_STANDARD].grid[a]->setAlpha(mAlpha); + } + } +} + +void Button::draw(gcn::Graphics *graphics) +{ + int mode; + + if (!isEnabled()) + mode = BUTTON_DISABLED; + else if (isPressed()) + mode = BUTTON_PRESSED; + else if (mHasMouse || isFocused()) + mode = BUTTON_HIGHLIGHTED; + else + mode = BUTTON_STANDARD; + + updateAlpha(); + + static_cast(graphics)-> + drawImageRect(0, 0, getWidth(), getHeight(), button[mode]); + + if (mode == BUTTON_DISABLED) + graphics->setColor(Theme::getThemeColor(Theme::BUTTON_DISABLED)); + else + graphics->setColor(Theme::getThemeColor(Theme::BUTTON)); + + int textX; + int textY = getHeight() / 2 - getFont()->getHeight() / 2; + + switch (getAlignment()) + { + default: + case gcn::Graphics::LEFT: + textX = 4; + break; + case gcn::Graphics::CENTER: + textX = getWidth() / 2; + break; + case gcn::Graphics::RIGHT: + textX = getWidth() - 4; + break; +// throw GCN_EXCEPTION("Button::draw. Unknown alignment."); + } + + graphics->setFont(getFont()); + + if (isPressed()) + graphics->drawText(getCaption(), textX + 1, textY + 1, getAlignment()); + else + graphics->drawText(getCaption(), textX, textY, getAlignment()); +} + +void Button::mouseReleased(gcn::MouseEvent& mouseEvent) +{ + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT + && mMousePressed && mHasMouse) + { + mMousePressed = false; + mClickCount = mouseEvent.getClickCount(); + distributeActionEvent(); + mouseEvent.consume(); + } + else if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + mMousePressed = false; + mClickCount = 0; + mouseEvent.consume(); + } +} diff --git a/src/gui/widgets/button.h b/src/gui/widgets/button.h new file mode 100644 index 000000000..301d02fbe --- /dev/null +++ b/src/gui/widgets/button.h @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include +#include + +class ImageRect; + +/** + * Button widget. Same as the Guichan button but with custom look. + * + * \ingroup GUI + */ +class Button : public gcn::Button +{ + public: + /** + * Default constructor. + */ + Button(); + + /** + * Constructor, sets the caption of the button to the given string and + * adds the given action listener. + */ + Button(const std::string &caption, const std::string &actionEventId, + gcn::ActionListener *listener); + + /** + * Destructor. + */ + ~Button(); + + /** + * Draws the button. + */ + void draw(gcn::Graphics *graphics); + + /** + * Update the alpha value to the button components. + */ + void updateAlpha(); + + virtual void mouseReleased(gcn::MouseEvent& mouseEvent); + + void setDescription(std::string text) + { mDescription = text; } + + std::string getDescription() + { return mDescription; } + + unsigned getClickCount() + { return mClickCount; } + + void setTag(int tag) + { mTag = tag; } + + int getTag() + { return mTag; } + + private: + void init(); + + static ImageRect button[4]; /**< Button state graphics */ + static int mInstances; /**< Number of button instances */ + static float mAlpha; + + std::string mDescription; + unsigned mClickCount; + int mTag; +}; + +#endif diff --git a/src/gui/widgets/channeltab.cpp b/src/gui/widgets/channeltab.cpp new file mode 100644 index 000000000..a7370a4c5 --- /dev/null +++ b/src/gui/widgets/channeltab.cpp @@ -0,0 +1,132 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "channeltab.h" + +#include "channel.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "utils/gettext.h" + +ChannelTab::ChannelTab(Channel *channel) : + ChatTab(channel->getName()), + mChannel(channel) +{ + channel->setTab(this); +} + +ChannelTab::~ChannelTab() +{ +} + +void ChannelTab::handleInput(const std::string &msg) +{ + Net::getChatHandler()->sendToChannel(getChannel()->getId(), msg); +} + +void ChannelTab::showHelp() +{ + chatLog(_("/users > Lists the users in the current channel")); + chatLog(_("/topic > Set the topic of the current channel")); + chatLog(_("/quit > Leave a channel")); + chatLog(_("/op > Make a user a channel operator")); + chatLog(_("/kick > Kick a user from the channel")); +} + +bool ChannelTab::handleCommand(const std::string &type, + const std::string &args) +{ + if (type == "help") + { + if (args == "users") + { + chatLog(_("Command: /users")); + chatLog(_("This command shows the users in this channel.")); + } + else if (args == "topic") + { + chatLog(_("Command: /topic ")); + chatLog(_("This command sets the topic to .")); + } + else if (args == "quit") + { + chatLog(_("Command: /quit")); + chatLog(_("This command leaves the current channel.")); + chatLog(_("If you're the last person in the channel, " + "it will be deleted.")); + } + else if (args == "op") + { + chatLog(_("Command: /op ")); + chatLog(_("This command makes a channel operator.")); + chatLog(_("If the has spaces in it, enclose it in " + "double quotes (\").")); + chatLog(_("Channel operators can kick and op other users " + "from the channel.")); + } + else if (args == "kick") + { + chatLog(_("Command: /kick ")); + chatLog(_("This command makes leave the channel.")); + chatLog(_("If the has spaces in it, enclose it in " + "double quotes (\").")); + } + else + return false; + } + else if (type == "users") + { + Net::getChatHandler()->userList(mChannel->getName()); + } + else if (type == "topic") + { + Net::getChatHandler()->setChannelTopic(mChannel->getId(), args); + } + else if (type == "topic") + { + Net::getChatHandler()->setChannelTopic(mChannel->getId(), args); + } + else if (type == "quit") + { + Net::getChatHandler()->quitChannel(mChannel->getId()); + } + else if (type == "op") + { + // set the user mode 'o' to op a user + if (args != "") + Net::getChatHandler()->setUserMode(mChannel->getId(), args, 'o'); + else + chatLog(_("Need a user to op!"), BY_CHANNEL); + } + else if (type == "kick") + { + if (args != "") + Net::getChatHandler()->kickUser(mChannel->getId(), args); + else + chatLog(_("Need a user to kick!"), BY_CHANNEL); + } + else + return false; + + return true; +} diff --git a/src/gui/widgets/channeltab.h b/src/gui/widgets/channeltab.h new file mode 100644 index 000000000..842b80f7d --- /dev/null +++ b/src/gui/widgets/channeltab.h @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHANNELTAB_H +#define CHANNELTAB_H + +#include "chattab.h" + +class Channel; + +/** + * A tab for a chat channel. + */ +class ChannelTab : public ChatTab +{ + public: + + Channel *getChannel() const { return mChannel; } + + void showHelp(); + + bool handleCommand(const std::string &type, + const std::string &args); + + protected: + friend class Channel; + + /** + * Constructor. + */ + ChannelTab(Channel *channel); + + /** + * Destructor. + */ + ~ChannelTab(); + + void handleInput(const std::string &msg); + + private: + Channel *mChannel; +}; + +#endif // CHANNELTAB_H diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp new file mode 100644 index 000000000..06ba3d3ed --- /dev/null +++ b/src/gui/widgets/chattab.cpp @@ -0,0 +1,431 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/chattab.h" + +#include "actorspritemanager.h" +#include "chatlog.h" +#include "commandhandler.h" +#include "configuration.h" +#include "localplayer.h" +#include "log.h" +#include "sound.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/scrollarea.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/tradetab.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#define MAX_WORD_SIZE 50 + +ChatTab::ChatTab(const std::string &name) : + Tab(), + mAllowHightlight(true) +{ + setCaption(name); + + mTextOutput = new BrowserBox(BrowserBox::AUTO_WRAP); + mTextOutput->setOpaque(false); + mTextOutput->setMaxRow((int) config.getIntValue("ChatLogLength")); + if (chatWindow) + mTextOutput->setLinkHandler(chatWindow->mItemLinkHandler); + mTextOutput->setAlwaysUpdate(false); + + mScrollArea = new ScrollArea(mTextOutput); + mScrollArea->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER, + gcn::ScrollArea::SHOW_ALWAYS); + mScrollArea->setScrollAmount(0, 1); + mScrollArea->setOpaque(false); + + if (chatWindow) + chatWindow->addTab(this); +} + +ChatTab::~ChatTab() +{ + if (chatWindow) + chatWindow->removeTab(this); + + delete mTextOutput; + mTextOutput = 0; + delete mScrollArea; + mScrollArea = 0; +} + +void ChatTab::chatLog(std::string line, Own own, + bool ignoreRecord, bool tryRemoveColors) +{ + // Trim whitespace + trim(line); + + if (line.empty()) + return; + + if (tryRemoveColors && own == BY_OTHER && + config.getBoolValue("removeColors")) + { + line = removeColors(line); + if (line.empty()) + return; + } + + unsigned lineLim = config.getIntValue("chatMaxCharLimit"); + if (lineLim > 0 && line.length() > lineLim) + line = line.substr(0, lineLim); + + if (line.empty()) + return; + + CHATLOG tmp; + tmp.own = own; + tmp.nick = ""; + tmp.text = line; + + std::string::size_type pos = line.find(" : "); + if (pos != std::string::npos) + { + if (line.length() <= pos + 3) + return; + + tmp.nick = line.substr(0, pos); + tmp.text = line.substr(pos + 3); + } + else + { + // Fix the owner of welcome message. + if (line.length() > 7 && line.substr(0, 7) == "Welcome") + own = BY_SERVER; + } + + // *implements actions in a backwards compatible way* + if ((own == BY_PLAYER || own == BY_OTHER) && + tmp.text.at(0) == '*' && + tmp.text.at(tmp.text.length()-1) == '*') + { + tmp.text[0] = ' '; + tmp.text.erase(tmp.text.length() - 1); + own = ACT_IS; + } + + std::string lineColor = "##C"; + switch (own) + { + case BY_GM: + if (tmp.nick.empty()) + { + tmp.nick = std::string(_("Global announcement:")); + tmp.nick += " "; + lineColor = "##G"; + } + else + { + tmp.nick = strprintf(_("Global announcement from %s:"), + tmp.nick.c_str()); + tmp.nick += " "; + lineColor = "##1"; // Equiv. to BrowserBox::RED + } + break; + case BY_PLAYER: + tmp.nick += ": "; + lineColor = "##Y"; + break; + case BY_OTHER: + tmp.nick += ": "; + lineColor = "##C"; + break; + case BY_SERVER: + tmp.nick = _("Server:"); + tmp.nick += " "; + tmp.text = line; + lineColor = "##S"; + break; + case BY_CHANNEL: + tmp.nick = ""; + // TODO: Use a predefined color + lineColor = "##2"; // Equiv. to BrowserBox::GREEN + break; + case ACT_WHISPER: + tmp.nick = strprintf(_("%s whispers: %s"), tmp.nick.c_str(), ""); + lineColor = "##W"; + break; + case ACT_IS: + lineColor = "##I"; + break; + case BY_LOGGER: + tmp.nick = ""; + tmp.text = line; + lineColor = "##L"; + break; + default: + logger->log1("ChatTab::chatLog incorrect value in switch"); + break; + } + + if (tmp.nick == ": ") + { + tmp.nick = ""; + lineColor = "##S"; + } + + // if configured, move magic messages log to debug chat tab + if (localChatTab && this == localChatTab + && ((config.getBoolValue("showMagicInDebug") + && own == BY_PLAYER && tmp.text.length() > 1 + && tmp.text.length() > 1 && tmp.text.at(0) == '#' + && tmp.text.at(1) != '#') + || (config.getBoolValue("serverMsgInDebug") && (own == BY_SERVER + || tmp.nick.empty())))) + { + if (debugChatTab) + debugChatTab->chatLog(line, own, ignoreRecord, tryRemoveColors); + return; + } + + // Get the current system time + time_t t; + time(&t); + + // Format the time string properly + std::stringstream timeStr; + timeStr << "[" << ((((t / 60) / 60) % 24 < 10) ? "0" : "") + << static_cast(((t / 60) / 60) % 24) + << ":" << (((t / 60) % 60 < 10) ? "0" : "") + << static_cast((t / 60) % 60) + << "] "; + + line = lineColor + timeStr.str() + tmp.nick + tmp.text; + + if (config.getBoolValue("enableChatLog")) + saveToLogFile(line); + + mTextOutput->setMaxRow(config.getIntValue("chatMaxLinesLimit")); + + // We look if the Vertical Scroll Bar is set at the max before + // adding a row, otherwise the max will always be a row higher + // at comparison. + if (mScrollArea->getVerticalScrollAmount() >= + mScrollArea->getVerticalMaxScroll()) + { + addRow(line); + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + } + else + { + addRow(line); + } + + mScrollArea->logic(); + if (own != BY_PLAYER) + { + if (own == BY_SERVER && (getType() == TAB_PARTY + || getType() == TAB_GUILD)) + { + return; + } + + if (!getTabbedArea()) + return; + + if (this != getTabbedArea()->getSelectedTab()) + { + if (getFlash() == 0) + { + if (player_node) + { + std::string::size_type pos + = tmp.text.find(player_node->getName()); + if (pos != std::string::npos) + setFlash(2); + else + setFlash(1); + } + else + { + setFlash(1); + } + } + } + + if (getAllowHighlight() && (this != getTabbedArea()->getSelectedTab() + || (Client::getIsMinimized() || (!Client::getMouseFocused() + && !Client::getInputFocused())))) + { + if (own != BY_SERVER) + sound.playGuiSfx("system/newmessage.ogg"); + } + } +} + +void ChatTab::chatLog(const std::string &nick, std::string msg) +{ + Own byWho = (nick == player_node->getName() ? BY_PLAYER : BY_OTHER); + if (byWho == BY_OTHER && config.getBoolValue("removeColors")) + msg = removeColors(msg); + chatLog(nick + " : " + msg, byWho, false, false); +} + +void ChatTab::chatInput(const std::string &message) +{ + std::string msg = message; + trim(msg); + + if (msg.empty()) + return; + + // Check for item link + std::string::size_type start = msg.find('['); + while (start + 1 < msg.size() && start != std::string::npos + && msg[start + 1] != '@') + { + std::string::size_type end = msg.find(']', start); + if (start + 1 != end && end != std::string::npos) + { + // Catch multiple embeds and ignore them + // so it doesn't crash the client. + while ((msg.find('[', start + 1) != std::string::npos) && + (msg.find('[', start + 1) < end)) + { + start = msg.find('[', start + 1); + } + + std::string temp = ""; + if (start + 1 < msg.length() && end < msg.length() + && end > start + 1) + { + temp = msg.substr(start + 1, end - start - 1); + + const ItemInfo itemInfo = ItemDB::get(temp); + if (itemInfo.getId() != 0) + { + msg.insert(end, "@@"); + msg.insert(start + 1, "|"); + msg.insert(start + 1, toString(itemInfo.getId())); + msg.insert(start + 1, "@@"); + } + } + } + start = msg.find('[', start + 1); + } + + // Prepare ordinary message + if (msg[0] != '/') + handleInput(msg); + else + handleCommand(std::string(msg, 1)); +} + +void ChatTab::scroll(int amount) +{ + int range = mScrollArea->getHeight() / 8 * amount; + gcn::Rectangle scr; + scr.y = mScrollArea->getVerticalScrollAmount() + range; + scr.height = abs(range); + mTextOutput->showPart(scr); +} + +void ChatTab::clearText() +{ + mTextOutput->clearRows(); +} + +void ChatTab::handleInput(const std::string &msg) +{ + if (chatWindow) + Net::getChatHandler()->talk(chatWindow->doReplace(msg)); + else + Net::getChatHandler()->talk(msg); +} + +void ChatTab::handleCommand(const std::string &msg) +{ + if (commandHandler) + commandHandler->handleCommands(msg, this); +} + +bool ChatTab::handleCommands(const std::string &type, const std::string &args) +{ + // need split to commands and call each + + return handleCommand(type, args); +} + +void ChatTab::saveToLogFile(std::string &msg) +{ + if (getType() == TAB_INPUT && chatLogger) + chatLogger->log(msg); +} + +int ChatTab::getType() const +{ + if (getCaption() == "General" || getCaption() == _("General")) + return TAB_INPUT; + else if (getCaption() == "Debug" || getCaption() == _("Debug")) + return TAB_DEBUG; + else + return TAB_UNKNOWN; +} + +void ChatTab::addRow(std::string &line) +{ + std::string::size_type idx = 0; + + for (unsigned int f = 0; f < line.length(); f++) + { + if (line.at(f) == ' ') + { + idx = f; + } + else if (f - idx > MAX_WORD_SIZE) + { + line.insert(f, " "); + idx = f; + } + } + mTextOutput->addRow(line); +} + +void ChatTab::loadFromLogFile(std::string name) +{ + if (chatLogger) + { + std::list list; + chatLogger->loadLast(name, list, 5); + std::list::iterator i = list.begin(); + while (i != list.end()) + { + std::string line = "##9" + *i; + addRow(line); + ++i; + } + } +} diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h new file mode 100644 index 000000000..ddc36d29c --- /dev/null +++ b/src/gui/widgets/chattab.h @@ -0,0 +1,173 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHATTAB_H +#define CHATTAB_H + +#include "gui/chat.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/tab.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ScrollArea; + +/** + * A tab for the chat window. This is special to ease chat handling. + */ +class ChatTab : public Tab +{ + public: + enum Type + { + TAB_UNKNOWN = 0, + TAB_INPUT, + TAB_WHISPER, + TAB_PARTY, + TAB_GUILD, + TAB_DEBUG, + TAB_TRADE, + TAB_BATTLE + }; + + /** + * Constructor. + */ + ChatTab(const std::string &name); + ~ChatTab(); + + /** + * Adds a line of text to our message list. Parameters: + * + * @param line Text message. + * @param own Type of message (usually the owner-type). + * @param channelName which channel to send the message to. + * @param ignoreRecord should this not be recorded? + * @param removeColors try remove color if configured + */ + void chatLog(std::string line, Own own = BY_SERVER, + bool ignoreRecord = false, bool tryRemoveColors = true); + + /** + * Adds the text to the message list + * + * @param msg The message text which is to be sent. + */ + void chatLog(const std::string &nick, std::string msg); + + /** + * Determines whether the message is a command or message, then + * sends the given message to the game server to be said, or to the + * command handler + * + * @param msg The message text which is to be sent. + */ + void chatInput(const std::string &msg); + + /** + * Scrolls the chat window + * + * @param amount direction and amount to scroll. Negative numbers scroll + * up, positive numbers scroll down. The absolute amount indicates the + * amount of 1/8ths of chat window real estate that should be scrolled. + */ + void scroll(int amount); + + /** + * Clears the text from the tab + */ + void clearText(); + + /** + * Add any extra help text to the output. Allows tabs to define help + * for commands defined by the tab itself. + */ + virtual void showHelp() {} + + /** + * Handle special commands. Allows a tab to handle commands it + * defines itself. + * + * @returns true if the command was handled + * false if the command was not handled + */ + virtual bool handleCommand(const std::string &type _UNUSED_, + const std::string &args _UNUSED_) + { return false; } + + /** + * Handle special commands. Allows a tab to handle commands it + * defines itself. + * + * @returns true if the command was handled + * false if the command was not handled + */ + virtual bool handleCommands(const std::string &type, + const std::string &args); + + /** + * Returns type of the being. + */ + virtual int getType() const; + + virtual void saveToLogFile(std::string &msg); + + std::list &getRows() + { return mTextOutput->getRows(); } + + void loadFromLogFile(std::string name); + + bool getAllowHighlight() + { return mAllowHightlight; } + + void setAllowHighlight(bool n) + { mAllowHightlight = n; } + + protected: + friend class ChatWindow; + friend class WhisperWindow; + + virtual void setCurrent() + { setFlash(false); } + + virtual void handleInput(const std::string &msg); + + virtual void handleCommand(const std::string &msg); + + virtual void getAutoCompleteList(std::vector&) const + {} + + void addRow(std::string &line); + + ScrollArea *mScrollArea; + BrowserBox *mTextOutput; + bool mAllowHightlight; +}; + +extern ChatTab *localChatTab; +extern ChatTab *debugChatTab; + +#endif // CHATTAB_H diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp new file mode 100644 index 000000000..01331ddba --- /dev/null +++ b/src/gui/widgets/checkbox.cpp @@ -0,0 +1,187 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/checkbox.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include + +int CheckBox::instances = 0; +float CheckBox::mAlpha = 1.0; +Image *CheckBox::checkBoxNormal; +Image *CheckBox::checkBoxChecked; +Image *CheckBox::checkBoxDisabled; +Image *CheckBox::checkBoxDisabledChecked; +Image *CheckBox::checkBoxNormalHi; +Image *CheckBox::checkBoxCheckedHi; + +CheckBox::CheckBox(const std::string &caption, bool selected, + gcn::ActionListener* listener, std::string eventId): + gcn::CheckBox(caption, selected), + mHasMouse(false) +{ + if (instances == 0) + { + Image *checkBox = Theme::getImageFromTheme("checkbox.png"); + if (checkBox) + { + checkBoxNormal = checkBox->getSubImage(0, 0, 9, 10); + checkBoxChecked = checkBox->getSubImage(9, 0, 9, 10); + checkBoxDisabled = checkBox->getSubImage(18, 0, 9, 10); + checkBoxDisabledChecked = checkBox->getSubImage(27, 0, 9, 10); + checkBoxNormalHi = checkBox->getSubImage(36, 0, 9, 10); + checkBoxCheckedHi = checkBox->getSubImage(45, 0, 9, 10); + checkBoxNormal->setAlpha(mAlpha); + checkBoxChecked->setAlpha(mAlpha); + checkBoxDisabled->setAlpha(mAlpha); + checkBoxDisabledChecked->setAlpha(mAlpha); + checkBoxNormalHi->setAlpha(mAlpha); + checkBoxCheckedHi->setAlpha(mAlpha); + checkBox->decRef(); + } + else + { + checkBoxNormal = 0; + checkBoxChecked = 0; + checkBoxDisabled = 0; + checkBoxDisabledChecked = 0; + checkBoxNormalHi = 0; + checkBoxCheckedHi = 0; + } + } + + instances++; + + if (!eventId.empty()) + setActionEventId(eventId); + + if (listener) + addActionListener(listener); +} + +CheckBox::~CheckBox() +{ + instances--; + + if (instances == 0) + { + delete checkBoxNormal; + checkBoxNormal = 0; + delete checkBoxChecked; + checkBoxChecked = 0; + delete checkBoxDisabled; + checkBoxDisabled = 0; + delete checkBoxDisabledChecked; + checkBoxDisabledChecked = 0; + delete checkBoxNormalHi; + checkBoxNormalHi = 0; + delete checkBoxCheckedHi; + checkBoxCheckedHi = 0; + } +} + +void CheckBox::draw(gcn::Graphics* graphics) +{ + drawBox(graphics); + + graphics->setFont(getFont()); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + const int h = getHeight() + getHeight() / 2; + + graphics->drawText(getCaption(), h - 2, 0); +} + +void CheckBox::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + if (checkBoxNormal) + checkBoxNormal->setAlpha(mAlpha); + if (checkBoxChecked) + checkBoxChecked->setAlpha(mAlpha); + if (checkBoxDisabled) + checkBoxDisabled->setAlpha(mAlpha); + if (checkBoxDisabledChecked) + checkBoxDisabledChecked->setAlpha(mAlpha); + if (checkBoxNormal) + checkBoxNormal->setAlpha(mAlpha); + if (checkBoxCheckedHi) + checkBoxCheckedHi->setAlpha(mAlpha); + } +} + +void CheckBox::drawBox(gcn::Graphics* graphics) +{ + Image *box; + + if (isEnabled()) + { + if (isSelected()) + { + if (mHasMouse) + box = checkBoxCheckedHi; + else + box = checkBoxChecked; + } + else + { + if (mHasMouse) + box = checkBoxNormalHi; + else + box = checkBoxNormal; + } + } + else + { + if (isSelected()) + box = checkBoxDisabledChecked; + else + box = checkBoxDisabled; + } + + updateAlpha(); + + if (box) + static_cast(graphics)->drawImage(box, 2, 2); +} + +void CheckBox::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void CheckBox::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} diff --git a/src/gui/widgets/checkbox.h b/src/gui/widgets/checkbox.h new file mode 100644 index 000000000..b885e8922 --- /dev/null +++ b/src/gui/widgets/checkbox.h @@ -0,0 +1,92 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHECKBOX_H +#define CHECKBOX_H + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Check box widget. Same as the Guichan check box but with custom look. + * + * \ingroup GUI + */ +class CheckBox : public gcn::CheckBox +{ + public: + /** + * Constructor. + */ + CheckBox(const std::string &caption, bool selected = false, + gcn::ActionListener* listener = NULL, + std::string eventId = ""); + + /** + * Destructor. + */ + ~CheckBox(); + + /** + * Draws the caption, then calls drawBox to draw the check box. + */ + void draw(gcn::Graphics* graphics); + + /** + * Update the alpha value to the checkbox components. + */ + void updateAlpha(); + + /** + * Draws the check box, not the caption. + */ + void drawBox(gcn::Graphics* graphics); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + private: + static int instances; + static float mAlpha; + bool mHasMouse; + static Image *checkBoxNormal; + static Image *checkBoxChecked; + static Image *checkBoxDisabled; + static Image *checkBoxDisabledChecked; + static Image *checkBoxNormalHi; + static Image *checkBoxCheckedHi; +}; + +#endif diff --git a/src/gui/widgets/container.cpp b/src/gui/widgets/container.cpp new file mode 100644 index 000000000..b788b0610 --- /dev/null +++ b/src/gui/widgets/container.cpp @@ -0,0 +1,33 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/container.h" + +Container::Container() +{ + setOpaque(false); +} + +Container::~Container() +{ + while (!mWidgets.empty()) + delete mWidgets.front(); +} diff --git a/src/gui/widgets/container.h b/src/gui/widgets/container.h new file mode 100644 index 000000000..c2696a65f --- /dev/null +++ b/src/gui/widgets/container.h @@ -0,0 +1,43 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_CONTAINER_H +#define GUI_CONTAINER_H + +#include + +/** + * A widget container. + * + * The main difference between the standard Guichan container and this one is + * that childs added to this container are automatically deleted when the + * container is deleted. + * + * This container is also non-opaque by default. + */ +class Container : public gcn::Container +{ + public: + Container(); + ~Container(); +}; + +#endif diff --git a/src/gui/widgets/desktop.cpp b/src/gui/widgets/desktop.cpp new file mode 100644 index 000000000..fa5b1698a --- /dev/null +++ b/src/gui/widgets/desktop.cpp @@ -0,0 +1,157 @@ +/* + * Desktop widget + * Copyright (c) 2009-2010 The Mana World Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/desktop.h" + +#include "configuration.h" +#include "graphics.h" +#include "log.h" +#include "main.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/label.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" +#include "resources/wallpaper.h" + +#include "utils/stringutils.h" + +Desktop::Desktop() + : mWallpaper(0) +{ + addWidgetListener(this); + + Wallpaper::loadWallpapers(); + + std::string appName = branding.getValue("appName", std::string("")); + + if (appName.empty()) + mVersionLabel = new Label(FULL_VERSION); + else + mVersionLabel = new Label(strprintf("%s (Mana %s)", appName.c_str(), + FULL_VERSION)); + + mVersionLabel->setBackgroundColor(gcn::Color(255, 255, 255, 128)); + add(mVersionLabel, 25, 2); +} + +Desktop::~Desktop() +{ + if (mWallpaper) + mWallpaper->decRef(); +} + +void Desktop::reloadWallpaper() +{ + Wallpaper::loadWallpapers(); + setBestFittingWallpaper(); +} + +void Desktop::widgetResized(const gcn::Event &event _UNUSED_) +{ + setBestFittingWallpaper(); +} + +void Desktop::draw(gcn::Graphics *graphics) +{ + Graphics *g = static_cast(graphics); + + if (!mWallpaper || (getWidth() > mWallpaper->getWidth() || + getHeight() > mWallpaper->getHeight())) + { + // TODO: Color from palette + g->setColor(gcn::Color(64, 64, 64)); + g->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mWallpaper) + { + if (!mWallpaper->useOpenGL()) + { + g->drawImage(mWallpaper, + (getWidth() - mWallpaper->getWidth()) / 2, + (getHeight() - mWallpaper->getHeight()) / 2); + } + else + { + g->drawRescaledImage(mWallpaper, 0, 0, 0, 0, + mWallpaper->getWidth(), mWallpaper->getHeight(), + getWidth(), getHeight(), false); + } + } + + // Draw a thin border under the application version... + g->setColor(gcn::Color(255, 255, 255, 128)); + g->fillRectangle(gcn::Rectangle(mVersionLabel->getDimension())); + + Container::draw(graphics); +} + +void Desktop::setBestFittingWallpaper() +{ + if (!config.getBoolValue("showBackground")) + return; + + const std::string wallpaperName = + Wallpaper::getWallpaper(getWidth(), getHeight()); + + Image *nWallPaper = Theme::getImageFromTheme(wallpaperName); + + if (nWallPaper) + { + if (mWallpaper) + mWallpaper->decRef(); + + if (!nWallPaper->useOpenGL() + && (nWallPaper->getWidth() != getWidth() + || nWallPaper->getHeight() != getHeight())) + { + // We rescale to obtain a fullscreen wallpaper... + Image *newRsclWlPpr = nWallPaper->SDLgetScaledImage( + getWidth(), getHeight()); + std::string idPath = nWallPaper->getIdPath(); + + // We replace the resource in the resource manager + nWallPaper->decRef(); + if (newRsclWlPpr) + { + ResourceManager::getInstance()->addResource( + idPath, newRsclWlPpr); + + mWallpaper = newRsclWlPpr; + } + else + { + mWallpaper = nWallPaper; + } + } + else + { + mWallpaper = nWallPaper; + } + } + else + { + logger->log("Couldn't load %s as wallpaper", wallpaperName.c_str()); + } +} diff --git a/src/gui/widgets/desktop.h b/src/gui/widgets/desktop.h new file mode 100644 index 000000000..83568c66f --- /dev/null +++ b/src/gui/widgets/desktop.h @@ -0,0 +1,73 @@ +/* + * Desktop widget + * Copyright (c) 2009-2010 The Mana World Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DESKTOP_H +#define DESKTOP_H + +#include "guichanfwd.h" + +#include "gui/widgets/container.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Desktop widget, for drawing a background image and color. + * + * It picks the best fitting background image. If the image doesn't fit, a + * background color is drawn and the image is centered. + * + * When the desktop widget is resized, the background image is automatically + * updated. + * + * The desktop also displays the client version in the top-right corner. + * + * \ingroup GUI + */ +class Desktop : public Container, gcn::WidgetListener +{ + public: + Desktop(); + ~Desktop(); + + /** + * Has to be called after updates have been loaded. + */ + void reloadWallpaper(); + + void widgetResized(const gcn::Event &event); + + void draw(gcn::Graphics *graphics); + + private: + void setBestFittingWallpaper(); + + Image *mWallpaper; + gcn::Label *mVersionLabel; +}; + +#endif // DESKTOP_H diff --git a/src/gui/widgets/dropdown.cpp b/src/gui/widgets/dropdown.cpp new file mode 100644 index 000000000..b8616643b --- /dev/null +++ b/src/gui/widgets/dropdown.cpp @@ -0,0 +1,303 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/dropdown.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include + +int DropDown::instances = 0; +Image *DropDown::buttons[2][2]; +ImageRect DropDown::skin; +float DropDown::mAlpha = 1.0; + +DropDown::DropDown(gcn::ListModel *listModel): + gcn::DropDown::DropDown(listModel, + new ScrollArea, + new ListBox(listModel)) +{ + setFrameSize(2); + + // Initialize graphics + if (instances == 0) + { + // Load the background skin + + // Get the button skin + buttons[1][0] = Theme::getImageFromTheme("vscroll_up_default.png"); + buttons[0][0] = Theme::getImageFromTheme("vscroll_down_default.png"); + buttons[1][1] = Theme::getImageFromTheme("vscroll_up_pressed.png"); + buttons[0][1] = Theme::getImageFromTheme("vscroll_down_pressed.png"); + + if (buttons[0][0]) + buttons[0][0]->setAlpha(mAlpha); + if (buttons[0][1]) + buttons[0][1]->setAlpha(mAlpha); + if (buttons[1][0]) + buttons[1][0]->setAlpha(mAlpha); + if (buttons[1][1]) + buttons[1][1]->setAlpha(mAlpha); + + // get the border skin + Image *boxBorder = Theme::getImageFromTheme("deepbox.png"); + if (boxBorder) + { + int gridx[4] = {0, 3, 28, 31}; + int gridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + skin.grid[a] = boxBorder->getSubImage(gridx[x], gridy[y], + gridx[x + 1] - + gridx[x] + 1, + gridy[y + 1] - + gridy[y] + 1); + if (skin.grid[a]) + skin.grid[a]->setAlpha(mAlpha); + a++; + } + } + + boxBorder->decRef(); + } + } + + instances++; +} + +DropDown::~DropDown() +{ + instances--; + // Free images memory + if (instances == 0) + { + if (buttons[0][0]) + buttons[0][0]->decRef(); + if (buttons[0][1]) + buttons[0][1]->decRef(); + if (buttons[1][0]) + buttons[1][0]->decRef(); + if (buttons[1][1]) + buttons[1][1]->decRef(); + + for_each(skin.grid, skin.grid + 9, dtor()); + } + + delete mScrollArea; + mScrollArea = 0; +} + +void DropDown::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + + if (buttons[0][0]) + buttons[0][0]->setAlpha(mAlpha); + if (buttons[0][1]) + buttons[0][1]->setAlpha(mAlpha); + if (buttons[1][0]) + buttons[1][0]->setAlpha(mAlpha); + if (buttons[1][1]) + buttons[1][1]->setAlpha(mAlpha); + + for (int a = 0; a < 9; a++) + { + if (skin.grid[a]) + skin.grid[a]->setAlpha(mAlpha); + } + } +} + +void DropDown::draw(gcn::Graphics* graphics) +{ + int h; + + if (mDroppedDown) + h = mFoldedUpHeight; + else + h = getHeight(); + + updateAlpha(); + + const int alpha = static_cast(mAlpha * 255.0f); + gcn::Color faceColor = getBaseColor(); + faceColor.a = alpha; + const gcn::Color *highlightColor = &Theme::getThemeColor(Theme::HIGHLIGHT, + alpha); + gcn::Color shadowColor = faceColor - 0x303030; + shadowColor.a = alpha; + + if (mListBox->getListModel() && mListBox->getSelected() >= 0) + { + graphics->setFont(getFont()); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(mListBox->getListModel()->getElementAt( + mListBox->getSelected()), 1, 0); + } + + if (isFocused()) + { + if (highlightColor) + graphics->setColor(*highlightColor); + graphics->drawRectangle(gcn::Rectangle(0, 0, getWidth() - h, h)); + } + + drawButton(graphics); + + if (mDroppedDown) + { + drawChildren(graphics); + + // Draw two lines separating the ListBox with selected + // element view. + if (highlightColor) + graphics->setColor(*highlightColor); + graphics->drawLine(0, h, getWidth(), h); + graphics->setColor(shadowColor); + graphics->drawLine(0, h + 1, getWidth(), h + 1); + } +} + +void DropDown::drawFrame(gcn::Graphics *graphics) +{ + const int bs = getFrameSize(); + const int w = getWidth() + bs * 2; + const int h = getHeight() + bs * 2; + + static_cast(graphics)->drawImageRect(0, 0, w, h, skin); +} + +void DropDown::drawButton(gcn::Graphics *graphics) +{ + int height = mDroppedDown ? mFoldedUpHeight : getHeight(); + + if (buttons[mDroppedDown][mPushed]) + { + static_cast(graphics)-> + drawImage(buttons[mDroppedDown][mPushed], + getWidth() - height + 2, 1); + } +} + +// -- KeyListener notifications +void DropDown::keyPressed(gcn::KeyEvent& keyEvent) +{ + if (keyEvent.isConsumed()) + return; + + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE) + dropDown(); + else if (key.getValue() == Key::UP) + setSelected(getSelected() - 1); + else if (key.getValue() == Key::DOWN) + setSelected(getSelected() + 1); + else if (key.getValue() == Key::HOME) + setSelected(0); + else if (key.getValue() == Key::END && mListBox->getListModel()) + setSelected(mListBox->getListModel()->getNumberOfElements() - 1); + else + return; + + keyEvent.consume(); +} + +void DropDown::focusLost(const gcn::Event& event) +{ + gcn::DropDown::focusLost(event); + releaseModalMouseInputFocus(); +} + +void DropDown::mousePressed(gcn::MouseEvent& mouseEvent) +{ + gcn::DropDown::mousePressed(mouseEvent); + + if (0 <= mouseEvent.getY() && mouseEvent.getY() < getHeight() && + mouseEvent.getX() >= 0 && mouseEvent.getX() < getWidth() && + mouseEvent.getButton() == gcn::MouseEvent::LEFT && mDroppedDown && + mouseEvent.getSource() == mListBox) + { + mPushed = false; + foldUp(); + releaseModalMouseInputFocus(); + distributeActionEvent(); + } +} + +void DropDown::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent) +{ + setSelected(getSelected() - 1); + mouseEvent.consume(); +} + +void DropDown::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) +{ + setSelected(getSelected() + 1); + mouseEvent.consume(); +} + +void DropDown::setSelectedString(std::string str) +{ + gcn::ListModel *listModel = mListBox->getListModel(); + if (!listModel) + return; + + for (int f = 0; f < listModel->getNumberOfElements(); f ++) + { + if (listModel->getElementAt(f) == str) + { + setSelected(f); + break; + } + } +} + +std::string DropDown::getSelectedString() const +{ + gcn::ListModel *listModel = mListBox->getListModel(); + if (!listModel) + return ""; + + return listModel->getElementAt(getSelected()); +} diff --git a/src/gui/widgets/dropdown.h b/src/gui/widgets/dropdown.h new file mode 100644 index 000000000..f6e347b2b --- /dev/null +++ b/src/gui/widgets/dropdown.h @@ -0,0 +1,97 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DROPDOWN_H +#define DROPDOWN_H + +#include + +class Image; +class ImageRect; + +/** + * A drop down box from which you can select different values. + * + * A ListModel provides the contents of the drop down. To be able to use + * DropDown you must give DropDown an implemented ListModel which represents + * your list. + */ +class DropDown : public gcn::DropDown +{ + public: + /** + * Contructor. + * + * @param listModel the ListModel to use. + * @param scrollArea the ScrollArea to use. + * @param listBox the listBox to use. + * @see ListModel, ScrollArea, ListBox. + */ + DropDown(gcn::ListModel *listModel = 0); + + ~DropDown(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + void draw(gcn::Graphics *graphics); + + void drawFrame(gcn::Graphics *graphics); + + // Inherited from FocusListener + + void focusLost(const gcn::Event& event); + + // Inherited from KeyListener + + void keyPressed(gcn::KeyEvent& keyEvent); + + // Inherited from MouseListener + + void mousePressed(gcn::MouseEvent& mouseEvent); + + void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + void setSelectedString(std::string str); + + std::string getSelectedString() const; + + protected: + /** + * Draws the button with the little down arrow. + * + * @param graphics a Graphics object to draw with. + */ + void drawButton(gcn::Graphics *graphics); + + // Add own Images. + static int instances; + static Image *buttons[2][2]; + static ImageRect skin; + static float mAlpha; +}; + +#endif // end DROPDOWN_H + diff --git a/src/gui/widgets/dropshortcutcontainer.cpp b/src/gui/widgets/dropshortcutcontainer.cpp new file mode 100644 index 000000000..c3aaed829 --- /dev/null +++ b/src/gui/widgets/dropshortcutcontainer.cpp @@ -0,0 +1,303 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/widgets/dropshortcutcontainer.h" + +#include "gui/inventorywindow.h" +#include "gui/itempopup.h" +#include "gui/palette.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "configuration.h" +#include "dropshortcut.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +#include "utils/stringutils.h" + +DropShortcutContainer::DropShortcutContainer(): + ShortcutContainer(), + mItemClicked(false), + mItemMoved(NULL) +{ + addMouseListener(this); + addWidgetListener(this); + + mItemPopup = new ItemPopup; + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + if (dropShortcut) + mMaxItems = dropShortcut->getItemCount(); + else + mMaxItems = 0; + + if (mBackgroundImg) + { + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +DropShortcutContainer::~DropShortcutContainer() +{ + if (mBackgroundImg) + mBackgroundImg->decRef(); + delete mItemPopup; + mItemPopup = 0; +} + +void DropShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!dropShortcut) + return; + + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + if (mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + } + + Graphics *g = static_cast(graphics); + + graphics->setFont(getFont()); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int itemX = (i % mGridWidth) * mBoxWidth; + const int itemY = (i / mGridWidth) * mBoxHeight; + + if (mBackgroundImg) + g->drawImage(mBackgroundImg, itemX, itemY); + +/* // Draw item keyboard shortcut. + const char *key = SDL_GetKeyName( + (SDLKey) keyboard.getKeyValue(keyboard.KEY_SHORTCUT_1 + i)); + graphics->setColor(guiPalette->getColor(Palette::TEXT)); + g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT); +*/ + if (dropShortcut->getItem(i) < 0) + continue; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(dropShortcut->getItem(i)); + + if (item) + { + // Draw item icon. + Image* image = item->getImage(); + + if (image) + { + std::string caption; + if (item->getQuantity() > 1) + caption = toString(item->getQuantity()); + else if (item->isEquipped()) + caption = "Eq."; + + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + if (item->isEquipped()) + g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); + else + g->setColor(Theme::getThemeColor(Theme::TEXT)); + g->drawText(caption, itemX + mBoxWidth / 2, + itemY + mBoxHeight - 14, gcn::Graphics::CENTER); + } + } + } + + if (mItemMoved) + { + // Draw the item image being dragged by the cursor. + Image* image = mItemMoved->getImage(); + if (image) + { + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); + + g->drawImage(image, tPosX, tPosY); + g->drawText(toString(mItemMoved->getQuantity()), + tPosX + mBoxWidth / 2, tPosY + mBoxHeight - 14, + gcn::Graphics::CENTER); + } + } +} + +void DropShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (!dropShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mItemMoved && mItemClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = dropShortcut->getItem(index); + + if (itemId < 0) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(itemId); + + if (item) + { + mItemMoved = item; + dropShortcut->removeItem(index); + } + } + if (mItemMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void DropShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!dropShortcut || !inventoryWindow) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Stores the selected item if theirs one. + if (dropShortcut->isItemSelected() && inventoryWindow->isVisible()) + { + dropShortcut->setItem(index); + dropShortcut->setItemSelected(-1); + } + else if (dropShortcut->getItem(index)) + { + mItemClicked = true; + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(dropShortcut->getItem(index)); + + if (viewport) + viewport->showDropPopup(item); + } +} + +void DropShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!dropShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (dropShortcut->isItemSelected()) + dropShortcut->setItemSelected(-1); + + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + mItemMoved = NULL; + return; + } + if (mItemMoved) + { + dropShortcut->setItems(index, mItemMoved->getId()); + mItemMoved = NULL; + } + + if (mItemClicked) + mItemClicked = false; + } +} + +// Show ItemTooltip +void DropShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!dropShortcut) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = dropShortcut->getItem(index); + + if (itemId < 0) + return; + + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + Item *item = inv->findItem(itemId); + + if (item && viewport) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } +} + +// Hide ItemTooltip +void DropShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mItemPopup->setVisible(false); +} diff --git a/src/gui/widgets/dropshortcutcontainer.h b/src/gui/widgets/dropshortcutcontainer.h new file mode 100644 index 000000000..c072f0613 --- /dev/null +++ b/src/gui/widgets/dropshortcutcontainer.h @@ -0,0 +1,88 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DROPSHORTCUTCONTAINER_H +#define DROPSHORTCUTCONTAINER_H + +#include + +#include "gui/widgets/shortcutcontainer.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class Item; +class ItemPopup; + +/** + * An item shortcut container. Used to quickly use items. + * + * \ingroup GUI + */ +class DropShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + DropShortcutContainer(); + + /** + * Destructor. + */ + virtual ~DropShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + bool mItemClicked; + Item *mItemMoved; + + ItemPopup *mItemPopup; +}; + +#endif diff --git a/src/gui/widgets/emoteshortcutcontainer.cpp b/src/gui/widgets/emoteshortcutcontainer.cpp new file mode 100644 index 000000000..a9e435540 --- /dev/null +++ b/src/gui/widgets/emoteshortcutcontainer.cpp @@ -0,0 +1,259 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/emoteshortcutcontainer.h" + +#include "animatedsprite.h" +#include "configuration.h" +#include "emoteshortcut.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/textpopup.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "resources/emotedb.h" +#include "resources/image.h" + +#include "utils/dtor.h" + +static const int MAX_ITEMS = 42; + +EmoteShortcutContainer::EmoteShortcutContainer(): + ShortcutContainer(), + mEmoteClicked(false), + mEmoteMoved(0), + mEmotePopup(new TextPopup) +{ + addMouseListener(this); + addWidgetListener(this); + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + + if (mBackgroundImg) + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + + // Setup emote sprites + for (int i = 0; i <= EmoteDB::getLast(); i++) + { + const EmoteSprite* sprite = EmoteDB::getSprite(i, true); + if (sprite && sprite->sprite) + mEmoteImg.push_back(sprite); + } + +// mMaxItems = EmoteDB::getLast() < MAX_ITEMS ? EmoteDB::getLast() : MAX_ITEMS; + mMaxItems = MAX_ITEMS; + + if (mBackgroundImg) + { + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +EmoteShortcutContainer::~EmoteShortcutContainer() +{ + delete mEmotePopup; + + if (mBackgroundImg) + mBackgroundImg->decRef(); +} + +void EmoteShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!emoteShortcut) + return; + + mAlpha = Client::getGuiAlpha(); + if (Client::getGuiAlpha() != mAlpha && mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + + Graphics *g = static_cast(graphics); + + graphics->setFont(getFont()); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int emoteX = (i % mGridWidth) * mBoxWidth; + const int emoteY = (i / mGridWidth) * mBoxHeight; + + if (mBackgroundImg) + g->drawImage(mBackgroundImg, emoteX, emoteY); + + // Draw emote keyboard shortcut. + std::string key = keyboard.getKeyValueString( + keyboard.KEY_EMOTE_1 + i); + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + g->drawText(key, emoteX + 2, emoteY + 2, gcn::Graphics::LEFT); + +/* + if (emoteShortcut->getEmote(i) + && static_cast(emoteShortcut->getEmote(i)) - 1 + < mEmoteImg.size() + && mEmoteImg[emoteShortcut->getEmote(i) - 1]) + { + mEmoteImg[emoteShortcut->getEmote(i) - 1]->draw(g, emoteX + 2, + emoteY + 10); + } +*/ + + if (i < mEmoteImg.size() && mEmoteImg[i] && mEmoteImg[i]->sprite) + mEmoteImg[i]->sprite->draw(g, emoteX + 2, emoteY + 10); + } + + if (mEmoteMoved && mEmoteMoved < (unsigned)mEmoteImg.size() + 1 + && mEmoteMoved > 0) + { + // Draw the emote image being dragged by the cursor. + const EmoteSprite* sprite = mEmoteImg[mEmoteMoved - 1]; + if (sprite && sprite->sprite) + { + const AnimatedSprite *spr = sprite->sprite; + const int tPosX = mCursorPosX - (spr->getWidth() / 2); + const int tPosY = mCursorPosY - (spr->getHeight() / 2); + + spr->draw(g, tPosX, tPosY); + } + } +} + +void EmoteShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (!emoteShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mEmoteMoved && mEmoteClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + +// const unsigned char emoteId = emoteShortcut->getEmote(index); + const unsigned char emoteId = index + 1; + + if (emoteId) + { + mEmoteMoved = emoteId; + emoteShortcut->removeEmote(index); + } + } + if (mEmoteMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void EmoteShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!emoteShortcut) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + // Stores the selected emote if there is one. + if (emoteShortcut->isEmoteSelected()) + { + emoteShortcut->setEmote(index); + emoteShortcut->setEmoteSelected(0); + } + else if (emoteShortcut->getEmote(index)) + { + mEmoteClicked = true; + } +} + +void EmoteShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!emoteShortcut) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (emoteShortcut->isEmoteSelected()) + emoteShortcut->setEmoteSelected(0); + + if (index == -1) + { + mEmoteMoved = 0; + return; + } + + if (mEmoteMoved) + { + emoteShortcut->setEmotes(index, mEmoteMoved); + mEmoteMoved = 0; + } + else if (emoteShortcut->getEmote(index) && mEmoteClicked) + { + emoteShortcut->useEmote(index + 1); + } + + if (mEmoteClicked) + mEmoteClicked = false; + } +} + +void EmoteShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!emoteShortcut || !mEmotePopup) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + mEmotePopup->setVisible(false); + + if ((unsigned)index < mEmoteImg.size() && mEmoteImg[index]) + { + mEmotePopup->show(viewport->getMouseX(), viewport->getMouseY(), + mEmoteImg[index]->name); + } +} + +void EmoteShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mEmotePopup) + mEmotePopup->setVisible(false); +} \ No newline at end of file diff --git a/src/gui/widgets/emoteshortcutcontainer.h b/src/gui/widgets/emoteshortcutcontainer.h new file mode 100644 index 000000000..e841e6dfb --- /dev/null +++ b/src/gui/widgets/emoteshortcutcontainer.h @@ -0,0 +1,84 @@ +/* + * Extended support for activating emotes + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EMOTESHORTCUTCONTAINER_H +#define EMOTESHORTCUTCONTAINER_H + +#include "gui/widgets/shortcutcontainer.h" + +#include "resources/emotedb.h" + +#include + +class AnimatedSprite; +class Image; +class TextPopup; + +/** + * An emote shortcut container. Used to quickly use emoticons. + * + * \ingroup GUI + */ +class EmoteShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + EmoteShortcutContainer(); + + /** + * Destructor. + */ + virtual ~EmoteShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + void mouseMoved(gcn::MouseEvent &event); + + void mouseExited(gcn::MouseEvent &event); + + private: + std::vector mEmoteImg; + + bool mEmoteClicked; + unsigned char mEmoteMoved; + TextPopup *mEmotePopup; +}; + +#endif diff --git a/src/gui/widgets/flowcontainer.cpp b/src/gui/widgets/flowcontainer.cpp new file mode 100644 index 000000000..a93818abc --- /dev/null +++ b/src/gui/widgets/flowcontainer.cpp @@ -0,0 +1,88 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "flowcontainer.h" + +FlowContainer::FlowContainer(int boxWidth, int boxHeight): + mBoxWidth(boxWidth), mBoxHeight(boxHeight), + mGridWidth(1), mGridHeight(1) +{ + addWidgetListener(this); + if (!mBoxWidth) + mBoxWidth = 1; + if (!mBoxHeight) + mBoxHeight = 1; +} + +void FlowContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + if (getWidth() < mBoxWidth) + { + setWidth(mBoxWidth); + return; + } + + int itemCount = static_cast(mWidgets.size()); + + if (!mBoxWidth) + mGridWidth = getWidth(); + else + mGridWidth = getWidth() / mBoxWidth; + + if (mGridWidth < 1) + mGridWidth = 1; + + mGridHeight = itemCount / mGridWidth; + + if (itemCount % mGridWidth != 0 || mGridHeight < 1) + ++mGridHeight; + + int height = mGridHeight * mBoxHeight; + + if (getHeight() != height) + { + setHeight(height); + return; + } + + int i = 0; + height = 0; + for (WidgetList::iterator it = mWidgets.begin(); + it != mWidgets.end(); it++) + { + int x = i % mGridWidth * mBoxWidth; + (*it)->setPosition(x, height); + + i++; + + if (i % mGridWidth == 0) + height += mBoxHeight; + } +} + +void FlowContainer::add(gcn::Widget *widget) +{ + if (!widget) + return; + + Container::add(widget); + widget->setSize(mBoxWidth, mBoxHeight); + widgetResized(NULL); +} diff --git a/src/gui/widgets/flowcontainer.h b/src/gui/widgets/flowcontainer.h new file mode 100644 index 000000000..f738be209 --- /dev/null +++ b/src/gui/widgets/flowcontainer.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FLOWCONTAINER_H +#define FLOWCONTAINER_H + +#include "container.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * A container that arranges its contents like words on a page. + * + * \ingroup GUI + */ +class FlowContainer : public Container, + public gcn::WidgetListener +{ + public: + /** + * Constructor. Initializes the shortcut container. + */ + FlowContainer(int boxWidth, int boxHeight); + + /** + * Destructor. + */ + ~FlowContainer() {} + + /** + * Invoked when a widget changes its size. This is used to determine + * the new height of the container. + */ + void widgetResized(const gcn::Event &event); + + int getBoxWidth() const + { return mBoxWidth; } + + int getBoxHeight() const + { return mBoxHeight; } + + void add(gcn::Widget *widget); + + private: + int mBoxWidth; + int mBoxHeight; + int mGridWidth, mGridHeight; +}; + +#endif diff --git a/src/gui/widgets/icon.cpp b/src/gui/widgets/icon.cpp new file mode 100644 index 000000000..4e5902128 --- /dev/null +++ b/src/gui/widgets/icon.cpp @@ -0,0 +1,60 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/icon.h" + +#include "graphics.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +Icon::Icon(const std::string &file) + : mImage(0) +{ + mImage = ResourceManager::getInstance()->getImage(file); + if (mImage) + setSize(mImage->getWidth(), mImage->getHeight()); +} + +Icon::Icon(Image *image) + : mImage(image) +{ + if (mImage) + setSize(mImage->getWidth(), mImage->getHeight()); +} + +void Icon::setImage(Image *image) +{ + mImage = image; + if (mImage) + setSize(mImage->getWidth(), mImage->getHeight()); +} + +void Icon::draw(gcn::Graphics *g) +{ + if (mImage) + { + Graphics *graphics = static_cast(g); + const int x = (getWidth() - mImage->getWidth()) / 2; + const int y = (getHeight() - mImage->getHeight()) / 2; + graphics->drawImage(mImage, x, y); + } +} diff --git a/src/gui/widgets/icon.h b/src/gui/widgets/icon.h new file mode 100644 index 000000000..27ed0db8e --- /dev/null +++ b/src/gui/widgets/icon.h @@ -0,0 +1,66 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ICON_H +#define ICON_H + +#include + +class Image; + +/** + * An icon. + * + * \ingroup GUI + */ +class Icon : public gcn::Widget +{ + public: + /** + * Constructor. + */ + Icon(const std::string &filename); + + /** + * Constructor, uses an existing Image. + */ + Icon(Image *image); + + /** + * Gets the current Image. + */ + Image *getImage() const { return mImage; } + + /** + * Sets the image to display. + */ + void setImage(Image *image); + + /** + * Draws the Icon. + */ + void draw(gcn::Graphics *g); + + private: + Image *mImage; +}; + +#endif // ICON_H diff --git a/src/gui/widgets/inttextfield.cpp b/src/gui/widgets/inttextfield.cpp new file mode 100644 index 000000000..d0ffaebc5 --- /dev/null +++ b/src/gui/widgets/inttextfield.cpp @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/inttextfield.h" + +#include "gui/sdlinput.h" + +#include "utils/stringutils.h" + +IntTextField::IntTextField(int def, int min, int max, + bool enabled, int width): + TextField(toString(def)), + mDefault(def), + mValue(def) +{ + if (min != 0 || max != 0) + setRange(min, max); + + setEnabled(enabled); + if (width != 0) + setWidth(width); +} + +void IntTextField::keyPressed(gcn::KeyEvent &event) +{ + const gcn::Key &key = event.getKey(); + + if (key.getValue() == Key::BACKSPACE || + key.getValue() == Key::DELETE) + { + setText(std::string()); + event.consume(); + } + + if (!key.isNumber()) + return; + + TextField::keyPressed(event); + + std::istringstream s(getText()); + int i; + s >> i; + setValue(i); +} + +void IntTextField::setRange(int min, int max) +{ + mMin = min; + mMax = max; + + if (mValue < mMin) + mValue = mMin; + else if (mValue > mMax) + mValue = mMax; + + if (mDefault < mMin) + mDefault = mMin; + else if (mDefault > mMax) + mDefault = mMax; +} + +int IntTextField::getValue() +{ + return getText().empty() ? mMin : mValue; +} + +void IntTextField::setValue(int i) +{ + if (i < mMin) + mValue = mMin; + else if (i > mMax) + mValue = mMax; + else + mValue = i; + + const std::string valStr = toString(mValue); + setText(valStr); + setCaretPosition(static_cast(valStr.length()) + 1); +} + +void IntTextField::setDefaultValue(int value) +{ + if (value < mMin) + mDefault = mMin; + else if (value > mMax) + mDefault = mMax; + else + mDefault = value; +} + +void IntTextField::reset() +{ + setValue(mDefault); +} diff --git a/src/gui/widgets/inttextfield.h b/src/gui/widgets/inttextfield.h new file mode 100644 index 000000000..69c8a74d7 --- /dev/null +++ b/src/gui/widgets/inttextfield.h @@ -0,0 +1,76 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INTTEXTFIELD_H +#define INTTEXTFIELD_H + +#include "textfield.h" + +/** + * TextBox which only accepts numbers as input. + */ +class IntTextField : public TextField +{ + public: + /** + * Constructor, sets default value. + */ + IntTextField(int def = 0, int min = 0, int max = 0, + bool enabled = true, int width = 0); + + /** + * Sets the minimum and maximum values of the text box. + */ + void setRange(int minimum, int maximum); + + /** + * Returns the value in the text box. + */ + int getValue(); + + /** + * Reset the field to the default value. + */ + void reset(); + + /** + * Set the value of the text box to the specified value. + */ + void setValue(int value); + + /** + * Set the default value of the text box to the specified value. + */ + void setDefaultValue(int value); + + /** + * Responds to key presses. + */ + void keyPressed(gcn::KeyEvent &event); + + private: + int mMin; /**< Minimum value */ + int mMax; /**< Maximum value */ + int mDefault; /**< Default value */ + int mValue; /**< Current value */ +}; + +#endif diff --git a/src/gui/widgets/itemcontainer.cpp b/src/gui/widgets/itemcontainer.cpp new file mode 100644 index 000000000..8b5b1914a --- /dev/null +++ b/src/gui/widgets/itemcontainer.cpp @@ -0,0 +1,475 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/itemcontainer.h" + +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "dropshortcut.h" +#include "log.h" + +#include "gui/chat.h" +#include "gui/itempopup.h" +#include "gui/outfitwindow.h" +#include "gui/palette.h" +#include "gui/shopwindow.h" +#include "gui/shortcutwindow.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "net/net.h" +#include "net/inventoryhandler.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" + +#include "utils/stringutils.h" + +#include +#include + +// TODO: Add support for adding items to the item shortcut window (global +// itemShortcut). + +static const int BOX_WIDTH = 35; +static const int BOX_HEIGHT = 43; + +ItemContainer::ItemContainer(Inventory *inventory, bool forceQuantity): + mInventory(inventory), + mGridColumns(1), + mGridRows(1), + mSelectedIndex(-1), + mHighlightedIndex(-1), + mLastUsedSlot(-1), + mSelectionStatus(SEL_NONE), + mForceQuantity(forceQuantity), + mSwapItems(false), + mDescItems(false) +{ + mItemPopup = new ItemPopup; + setFocusable(true); + + mSelImg = Theme::getImageFromTheme("selection.png"); + if (!mSelImg) + logger->log1("Error: Unable to load selection.png"); + + addKeyListener(this); + addMouseListener(this); + addWidgetListener(this); +} + +ItemContainer::~ItemContainer() +{ + if (mSelImg) + { + mSelImg->decRef(); + mSelImg = 0; + } + delete mItemPopup; + mItemPopup = 0; +} + +void ItemContainer::logic() +{ + gcn::Widget::logic(); + + if (!mInventory) + return; + + const int lastUsedSlot = mInventory->getLastUsedSlot(); + + if (lastUsedSlot != mLastUsedSlot) + { + mLastUsedSlot = lastUsedSlot; + adjustHeight(); + } +} + +void ItemContainer::draw(gcn::Graphics *graphics) +{ + if (!mInventory) + return; + + Graphics *g = static_cast(graphics); + + g->setFont(getFont()); + + for (int i = 0; i < mGridColumns; i++) + { + for (int j = 0; j < mGridRows; j++) + { + int itemX = i * BOX_WIDTH; + int itemY = j * BOX_HEIGHT; + int itemIndex = (j * mGridColumns) + i; + Item *item = mInventory->getItem(itemIndex); + + if (!item || item->getId() == 0) + continue; + + Image *image = item->getImage(); + if (image) + { + if (itemIndex == mSelectedIndex) + { + if (mSelectionStatus == SEL_DRAGGING) + { + // Reposition the coords to that of the cursor. + itemX = mDragPosX - (BOX_WIDTH / 2); + itemY = mDragPosY - (BOX_HEIGHT / 2); + } + else + { + // Draw selection border image. + if (mSelImg) + g->drawImage(mSelImg, itemX, itemY); + } + } + image->setAlpha(1.0f); // ensure the image if fully drawn... + g->drawImage(image, itemX, itemY); + } + // Draw item caption + std::string caption; + if (item->getQuantity() > 1 || mForceQuantity) + caption = toString(item->getQuantity()); + else if (item->isEquipped()) + caption = "Eq."; + + if (item->isEquipped()) + g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); + else + g->setColor(gcn::Color(0, 0, 0)); + + g->drawText(caption, itemX + BOX_WIDTH / 2, + itemY + BOX_HEIGHT - 14, gcn::Graphics::CENTER); + } + } + + // Draw an orange box around the selected item + if (isFocused() && mHighlightedIndex != -1 && mGridColumns) + { + const int itemX = (mHighlightedIndex % mGridColumns) * BOX_WIDTH; + const int itemY = (mHighlightedIndex / mGridColumns) * BOX_HEIGHT; + g->setColor(gcn::Color(255, 128, 0)); + g->drawRectangle(gcn::Rectangle(itemX, itemY, BOX_WIDTH, BOX_HEIGHT)); + } +} + +void ItemContainer::selectNone() +{ + setSelectedIndex(-1); + mSelectionStatus = SEL_NONE; + if (outfitWindow) + outfitWindow->setItemSelected(-1); + if (shopWindow) + shopWindow->setItemSelected(-1); +} + +void ItemContainer::setSelectedIndex(int newIndex) +{ + if (mSelectedIndex != newIndex) + { + mSelectedIndex = newIndex; + distributeValueChangedEvent(); + } +} + +Item *ItemContainer::getSelectedItem() const +{ + if (mInventory) + return mInventory->getItem(mSelectedIndex); + else + return 0; +} + +void ItemContainer::distributeValueChangedEvent() +{ + SelectionListenerIterator i, i_end; + + for (i = mSelectionListeners.begin(), i_end = mSelectionListeners.end(); + i != i_end; ++i) + { + if (*i) + { + gcn::SelectionEvent event(this); + (*i)->valueChanged(event); + } + } +} + +void ItemContainer::keyPressed(gcn::KeyEvent &event _UNUSED_) +{ + /*switch (event.getKey().getValue()) + { + case Key::LEFT: + moveHighlight(Left); + break; + case Key::RIGHT: + moveHighlight(Right); + break; + case Key::UP: + moveHighlight(Up); + break; + case Key::DOWN: + moveHighlight(Down); + break; + case Key::SPACE: + keyAction(); + break; + case Key::LEFT_ALT: + case Key::RIGHT_ALT: + mSwapItems = true; + break; + case Key::RIGHT_CONTROL: + mDescItems = true; + break; + }*/ +} + +void ItemContainer::keyReleased(gcn::KeyEvent &event _UNUSED_) +{ + /*switch (event.getKey().getValue()) + { + case Key::LEFT_ALT: + case Key::RIGHT_ALT: + mSwapItems = false; + break; + case Key::RIGHT_CONTROL: + mDescItems = false; + break; + }*/ +} + +void ItemContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!mInventory) + return; + + const int button = event.getButton(); + if (button == gcn::MouseEvent::LEFT || button == gcn::MouseEvent::RIGHT) + { + const int index = getSlotIndex(event.getX(), event.getY()); + if (index == Inventory::NO_SLOT_INDEX) + return; + + Item *item = mInventory->getItem(index); + + // put item name into chat window + if (item && mDescItems && chatWindow) + chatWindow->addItemText(item->getInfo().getName()); + + if (mSelectedIndex == index) + { + mSelectionStatus = SEL_DESELECTING; + } + else if (item && item->getId()) + { + setSelectedIndex(index); + mSelectionStatus = SEL_SELECTING; + + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS) + { + if (itemShortcut[num]) + itemShortcut[num]->setItemSelected(item->getId()); + } + if (dropShortcut) + dropShortcut->setItemSelected(item->getId()); + if (item->isEquipment() && outfitWindow) + outfitWindow->setItemSelected(item->getId()); + if (shopWindow) + shopWindow->setItemSelected(item->getId()); + } + else + { + selectNone(); + } + } +} + +void ItemContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (mSelectionStatus != SEL_NONE) + { + mSelectionStatus = SEL_DRAGGING; + mDragPosX = event.getX(); + mDragPosY = event.getY(); + } +} + +void ItemContainer::mouseReleased(gcn::MouseEvent &event) +{ + switch (mSelectionStatus) + { + case SEL_SELECTING: + mSelectionStatus = SEL_SELECTED; + return; + case SEL_DESELECTING: + selectNone(); + return; + case SEL_DRAGGING: + mSelectionStatus = SEL_SELECTED; + break; + default: + return; + }; + + int index = getSlotIndex(event.getX(), event.getY()); + if (index == Inventory::NO_SLOT_INDEX) + return; + if (index == mSelectedIndex || mSelectedIndex == -1) + return; + Net::getInventoryHandler()->moveItem(mSelectedIndex, index); + selectNone(); +} + + +// Show ItemTooltip +void ItemContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!mInventory) + return; + + Item *item = mInventory->getItem(getSlotIndex(event.getX(), event.getY())); + + if (item && viewport) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } +} + +// Hide ItemTooltip +void ItemContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mItemPopup->setVisible(false); +} + +void ItemContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + mGridColumns = std::max(1, getWidth() / BOX_WIDTH); + adjustHeight(); +} + +void ItemContainer::adjustHeight() +{ + if (!mGridColumns) + return; + + mGridRows = (mLastUsedSlot + 1) / mGridColumns; + if (mGridRows == 0 || (mLastUsedSlot + 1) % mGridColumns > 0) + ++mGridRows; + + setHeight(mGridRows * BOX_HEIGHT); +} + +int ItemContainer::getSlotIndex(int x, int y) const +{ + if (x < getWidth() && y < getHeight()) + return (y / BOX_HEIGHT) * mGridColumns + (x / BOX_WIDTH); + + return Inventory::NO_SLOT_INDEX; +} + +void ItemContainer::keyAction() +{ + // If there is no highlight then return. + if (mHighlightedIndex == -1) + return; + + // If the highlight is on the selected item, then deselect it. + if (mHighlightedIndex == mSelectedIndex) + { + selectNone(); + } + // Check and swap items if necessary. + else if (mSwapItems && mSelectedIndex != -1 && mHighlightedIndex != -1) + { + Net::getInventoryHandler()->moveItem( + mSelectedIndex, mHighlightedIndex); + setSelectedIndex(mHighlightedIndex); + } + // If the highlight is on an item then select it. + else if (mHighlightedIndex != -1) + { + setSelectedIndex(mHighlightedIndex); + mSelectionStatus = SEL_SELECTED; + } + // If the highlight is on a blank space then move it. + else if (mSelectedIndex != -1) + { + Net::getInventoryHandler()->moveItem( + mSelectedIndex, mHighlightedIndex); + selectNone(); + } +} + +void ItemContainer::moveHighlight(Direction direction) +{ + if (mHighlightedIndex == -1) + { + if (mSelectedIndex != -1) + mHighlightedIndex = mSelectedIndex; + else + mHighlightedIndex = 0; + return; + } + + switch (direction) + { + case Left: + if (mHighlightedIndex % mGridColumns == 0) + mHighlightedIndex += mGridColumns; + mHighlightedIndex--; + break; + case Right: + if ((mHighlightedIndex % mGridColumns) == + (mGridColumns - 1)) + { + mHighlightedIndex -= mGridColumns; + } + mHighlightedIndex++; + break; + case Up: + if (mHighlightedIndex / mGridColumns == 0) + mHighlightedIndex += (mGridColumns * mGridRows); + mHighlightedIndex -= mGridColumns; + break; + case Down: + if ((mHighlightedIndex / mGridColumns) == + (mGridRows - 1)) + { + mHighlightedIndex -= (mGridColumns * mGridRows); + } + mHighlightedIndex += mGridColumns; + break; + default: + logger->log("warning moveHighlight unknown direction:" + + toString(static_cast(direction))); + break; + } +} diff --git a/src/gui/widgets/itemcontainer.h b/src/gui/widgets/itemcontainer.h new file mode 100644 index 000000000..8aaa236b4 --- /dev/null +++ b/src/gui/widgets/itemcontainer.h @@ -0,0 +1,195 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEMCONTAINER_H +#define ITEMCONTAINER_H + +#include +#include +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class Inventory; +class Item; +class ItemPopup; + +namespace gcn +{ + class SelectionListener; +} + +/** + * An item container. Used to show items in inventory and trade dialog. + * + * \ingroup GUI + */ +class ItemContainer : public gcn::Widget, + public gcn::KeyListener, + public gcn::MouseListener, + public gcn::WidgetListener +{ + public: + /** + * Constructor. Initializes the graphic. + * + * @param inventory + * @param gridColumns Amount of columns in grid. + * @param gridRows Amount of rows in grid. + * @param offset Index offset + */ + ItemContainer(Inventory *inventory, bool forceQuantity = false); + + /** + * Destructor. + */ + virtual ~ItemContainer(); + + /** + * Necessary for checking how full the inventory is. + */ + void logic(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + // KeyListener + void keyPressed(gcn::KeyEvent &event); + void keyReleased(gcn::KeyEvent &event); + + // MouseListener + void mousePressed(gcn::MouseEvent &event); + void mouseDragged(gcn::MouseEvent &event); + void mouseReleased(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + void mouseExited(gcn::MouseEvent &event); + + // WidgetListener + void widgetResized(const gcn::Event &event); + + /** + * Returns the selected item. + */ + Item *getSelectedItem() const; + + /** + * Sets selected item to NULL. + */ + void selectNone(); + + /** + * Adds a listener to the list that's notified each time a change to + * the selection occurs. + */ + void addSelectionListener(gcn::SelectionListener *listener) + { mSelectionListeners.push_back(listener); } + + /** + * Removes a listener from the list that's notified each time a change + * to the selection occurs. + */ + void removeSelectionListener(gcn::SelectionListener *listener) + { mSelectionListeners.remove(listener); } + + private: + enum Direction + { + Left = 0, + Right, + Up, + Down + }; + + enum SelectionState + { + SEL_NONE = 0, + SEL_SELECTED, + SEL_SELECTING, + SEL_DESELECTING, + SEL_DRAGGING + }; + + /** + * Execute all the functionality associated with the action key. + */ + void keyAction(); + + /** + * Moves the highlight in the direction specified. + * + * @param direction The move direction of the highlighter. + */ + void moveHighlight(Direction direction); + + /** + * Sets the currently selected item. + */ + void setSelectedIndex(int index); + + /** + * Determine and set the height of the container. + */ + void adjustHeight(); + + /** + * Sends out selection events to the list of selection listeners. + */ + void distributeValueChangedEvent(); + + /** + * Gets the slot index based on the cursor position. + * + * @param x The X coordinate position. + * @param y The Y coordinate position. + * @return The slot index on success, -1 on failure. + */ + int getSlotIndex(int x, int y) const; + + Inventory *mInventory; + int mGridColumns, mGridRows; + Image *mSelImg; + int mSelectedIndex, mHighlightedIndex; + int mLastUsedSlot; + SelectionState mSelectionStatus; + bool mForceQuantity; + bool mSwapItems; + bool mDescItems; + int mDragPosX, mDragPosY; + + ItemPopup *mItemPopup; + + typedef std::list SelectionListenerList; + typedef SelectionListenerList::iterator SelectionListenerIterator; + + SelectionListenerList mSelectionListeners; +}; + +#endif diff --git a/src/gui/widgets/itemlinkhandler.cpp b/src/gui/widgets/itemlinkhandler.cpp new file mode 100644 index 000000000..7c0a65bcb --- /dev/null +++ b/src/gui/widgets/itemlinkhandler.cpp @@ -0,0 +1,66 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "item.h" + +#include "gui/itempopup.h" +#include "gui/viewport.h" + +#include "gui/widgets/itemlinkhandler.h" + +#include "resources/itemdb.h" + +ItemLinkHandler::ItemLinkHandler() +{ + mItemPopup = new ItemPopup; +} + +ItemLinkHandler::~ItemLinkHandler() +{ + delete mItemPopup; + mItemPopup = 0; +} + +void ItemLinkHandler::handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_) +{ + if (!mItemPopup) + return; + + int id = 0; + std::stringstream stream; + stream << link; + stream >> id; + + if (id > 0) + { + const ItemInfo &itemInfo = ItemDB::get(id); + mItemPopup->setItem(itemInfo, true); + + if (mItemPopup->isVisible()) + mItemPopup->setVisible(false); + else if (viewport) + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } +} diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h new file mode 100644 index 000000000..57e135fed --- /dev/null +++ b/src/gui/widgets/itemlinkhandler.h @@ -0,0 +1,47 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEM_LINK_HANDLER_H +#define ITEM_LINK_HANDLER_H + +#include "gui/widgets/linkhandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ItemPopup; + +class ItemLinkHandler : public LinkHandler +{ + public: + ItemLinkHandler(); + ~ItemLinkHandler(); + void handleLink(const std::string &link, + gcn::MouseEvent *event _UNUSED_); + + private: + ItemPopup *mItemPopup; +}; + +#endif diff --git a/src/gui/widgets/itemshortcutcontainer.cpp b/src/gui/widgets/itemshortcutcontainer.cpp new file mode 100644 index 000000000..2ab4a7e19 --- /dev/null +++ b/src/gui/widgets/itemshortcutcontainer.cpp @@ -0,0 +1,375 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/itemshortcutcontainer.h" + +#include "configuration.h" +#include "graphics.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "spellshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "spellmanager.h" +#include "textcommand.h" + +#include "gui/inventorywindow.h" +#include "gui/itempopup.h" +#include "gui/palette.h" +#include "gui/spellpopup.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" + +#include "utils/stringutils.h" + +ItemShortcutContainer::ItemShortcutContainer(unsigned number): + ShortcutContainer(), + mItemClicked(false), + mItemMoved(NULL), + mNumber(number) +{ + addMouseListener(this); + addWidgetListener(this); + + mItemPopup = new ItemPopup; + mSpellPopup = new SpellPopup; + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + if (itemShortcut[mNumber]) + mMaxItems = itemShortcut[mNumber]->getItemCount(); + else + mMaxItems = 0; + + if (mBackgroundImg) + { + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +ItemShortcutContainer::~ItemShortcutContainer() +{ + if (mBackgroundImg) + { + mBackgroundImg->decRef(); + mBackgroundImg = 0; + } + delete mItemPopup; + mItemPopup = 0; + delete mSpellPopup; + mSpellPopup = 0; +} + +void ItemShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!itemShortcut[mNumber]) + return; + + mAlpha = Client::getGuiAlpha(); + if (Client::getGuiAlpha() != mAlpha) + { + if (mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + } + + Graphics *g = static_cast(graphics); + + graphics->setFont(getFont()); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int itemX = (i % mGridWidth) * mBoxWidth; + const int itemY = (i / mGridWidth) * mBoxHeight; + + if (mBackgroundImg) + g->drawImage(mBackgroundImg, itemX, itemY); + + // Draw item keyboard shortcut. + std::string key = keyboard.getKeyValueString( + keyboard.KEY_SHORTCUT_1 + i); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT); + + int itemId = itemShortcut[mNumber]->getItem(i); + + if (itemId < 0) + continue; + + // this is item + if (itemId < SPELL_MIN_ID) + { + if (!PlayerInfo::getInventory()) + continue; + + Item *item = PlayerInfo::getInventory()->findItem(itemId); + + if (item) + { + // Draw item icon. + Image* image = item->getImage(); + + if (image) + { + std::string caption; + if (item->getQuantity() > 1) + caption = toString(item->getQuantity()); + else if (item->isEquipped()) + caption = "Eq."; + + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + if (item->isEquipped()) + { + g->setColor(Theme::getThemeColor( + Theme::ITEM_EQUIPPED)); + } + else + { + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + } + g->drawText(caption, itemX + mBoxWidth / 2, + itemY + mBoxHeight - 14, gcn::Graphics::CENTER); + } + } + } + else if (spellManager) // this is magic shortcut + { + TextCommand *spell = spellManager->getSpellByItem(itemId); + if (spell) + { + if (!spell->isEmpty()) + { + Image* image = spell->getImage(); + + if (image) + { + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + } + } + + g->drawText(spell->getSymbol(), itemX + 2, + itemY + mBoxHeight / 2, gcn::Graphics::LEFT); + } + } + + } + + if (mItemMoved) + { + // Draw the item image being dragged by the cursor. + Image* image = mItemMoved->getImage(); + if (image) + { + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); + + g->drawImage(image, tPosX, tPosY); + g->drawText(toString(mItemMoved->getQuantity()), + tPosX + mBoxWidth / 2, tPosY + mBoxHeight - 14, + gcn::Graphics::CENTER); + } + } +} + +void ItemShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mItemMoved && mItemClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = itemShortcut[mNumber]->getItem(index); + + if (itemId < 0) + return; + + if (itemId < SPELL_MIN_ID) + { + if (!PlayerInfo::getInventory()) + return; + + Item *item = PlayerInfo::getInventory()->findItem(itemId); + + if (item) + { + mItemMoved = item; + itemShortcut[mNumber]->removeItem(index); + } + } + else if (spellManager) + { + TextCommand *spell = spellManager->getSpellByItem(itemId); + if (spell) + itemShortcut[mNumber]->removeItem(index); + } + } + if (mItemMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void ItemShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Stores the selected item if theirs one. + if (itemShortcut[mNumber]->isItemSelected() && + (inventoryWindow && (inventoryWindow->isVisible() || + itemShortcut[mNumber]->getSelectedItem() >= SPELL_MIN_ID))) + { + itemShortcut[mNumber]->setItem(index); + itemShortcut[mNumber]->setItemSelected(-1); + if (spellShortcut) + spellShortcut->setItemSelected(-1); + } + else if (itemShortcut[mNumber]->getItem(index)) + { + mItemClicked = true; + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { +// Item *item = PlayerInfo::getInventory()->findItem(id); + + if (viewport && itemShortcut[mNumber]) + viewport->showItemPopup(itemShortcut[mNumber]->getItem(index)); + } +} + +void ItemShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (itemShortcut[mNumber]->isItemSelected()) + itemShortcut[mNumber]->setItemSelected(-1); + + const int index = getIndexFromGrid(event.getX(), event.getY()); + if (index == -1) + { + mItemMoved = NULL; + return; + } + if (mItemMoved) + { + itemShortcut[mNumber]->setItems(index, mItemMoved->getId()); + mItemMoved = NULL; + } + else if (itemShortcut[mNumber]->getItem(index) && mItemClicked) + { + itemShortcut[mNumber]->useItem(index); + } + + if (mItemClicked) + mItemClicked = false; + } +} + +// Show ItemTooltip +void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!itemShortcut[mNumber]) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = itemShortcut[mNumber]->getItem(index); + + if (itemId < 0) + return; + + if (itemId < SPELL_MIN_ID) + { + mSpellPopup->setVisible(false); + + if (!PlayerInfo::getInventory()) + return; + + Item *item = PlayerInfo::getInventory()->findItem(itemId); + + if (item && viewport) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } + } + else if (spellManager) + { + mItemPopup->setVisible(false); + TextCommand *spell = spellManager->getSpellByItem(itemId); + if (spell && viewport) + { + mSpellPopup->setItem(spell); + mSpellPopup->view(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mSpellPopup->setVisible(false); + } + } +} + +// Hide ItemTooltip +void ItemShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mItemPopup->setVisible(false); + mSpellPopup->setVisible(false); +} diff --git a/src/gui/widgets/itemshortcutcontainer.h b/src/gui/widgets/itemshortcutcontainer.h new file mode 100644 index 000000000..0f7067e38 --- /dev/null +++ b/src/gui/widgets/itemshortcutcontainer.h @@ -0,0 +1,93 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEMSHORTCUTCONTAINER_H +#define ITEMSHORTCUTCONTAINER_H + +#include "spellmanager.h" + +#include "gui/widgets/shortcutcontainer.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class Item; +class ItemPopup; +class SpellPopup; + +/** + * An item shortcut container. Used to quickly use items. + * + * \ingroup GUI + */ +class ItemShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + ItemShortcutContainer(unsigned number); + + /** + * Destructor. + */ + virtual ~ItemShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + bool mItemClicked; + Item *mItemMoved; + unsigned mNumber; + + ItemPopup *mItemPopup; + SpellPopup *mSpellPopup; +}; + +//extern SpellManager *spellManager; +#endif diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp new file mode 100644 index 000000000..e9b4cb0f6 --- /dev/null +++ b/src/gui/widgets/label.cpp @@ -0,0 +1,38 @@ +/* + * The Mana Client + * Copyright (c) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/label.h" + +#include "gui/theme.h" + +Label::Label() +{ +} + +Label::Label(const std::string &caption) : + gcn::Label(caption) +{ + setForegroundColor(Theme::getThemeColor(Theme::TEXT)); +} + +void Label::draw(gcn::Graphics *graphics) +{ + gcn::Label::draw(static_cast(graphics)); +} diff --git a/src/gui/widgets/label.h b/src/gui/widgets/label.h new file mode 100644 index 000000000..2dfa254c4 --- /dev/null +++ b/src/gui/widgets/label.h @@ -0,0 +1,52 @@ +/* + * The Mana Client + * Copyright (c) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LABEL_H +#define LABEL_H + +#include + +/** + * Label widget. Same as the Guichan label but modified to use the palette + * system. + * + * \ingroup GUI + */ +class Label : public gcn::Label +{ + public: + /** + * Constructor. + */ + Label(); + + /** + * Constructor. This version of the constructor sets the label with an + * inintialization string. + */ + Label(const std::string &caption); + + /** + * Draws the label. + */ + void draw(gcn::Graphics *graphics); +}; + +#endif diff --git a/src/gui/widgets/layout.cpp b/src/gui/widgets/layout.cpp new file mode 100644 index 000000000..38c6bb471 --- /dev/null +++ b/src/gui/widgets/layout.cpp @@ -0,0 +1,362 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/layout.h" + +#include "log.h" + +#include + +ContainerPlacer ContainerPlacer::at(int x, int y) +{ + return ContainerPlacer(mContainer, &mCell->at(x, y)); +} + +LayoutCell &ContainerPlacer::operator() + (int x, int y, gcn::Widget *wg, int w, int h) +{ + mContainer->add(wg); + return mCell->place(wg, x, y, w, h); +} + +LayoutCell::~LayoutCell() +{ + if (mType == ARRAY) + { + delete mArray; + mArray = 0; + } +} + +LayoutArray &LayoutCell::getArray() +{ + assert(mType != WIDGET); + if (mType == ARRAY) + return *mArray; + + mArray = new LayoutArray; + mType = ARRAY; + mExtent[0] = 1; + mExtent[1] = 1; + mPadding = 0; + mAlign[0] = FILL; + mAlign[1] = FILL; + return *mArray; +} + +void LayoutCell::reflow(int nx, int ny, int nw, int nh) +{ + assert(mType != NONE); + nx += mPadding; + ny += mPadding; + nw -= 2 * mPadding; + nh -= 2 * mPadding; + if (mType == ARRAY) + mArray->reflow(nx, ny, nw, nh); + else + mWidget->setDimension(gcn::Rectangle(nx, ny, nw, nh)); +} + +void LayoutCell::computeSizes() +{ + assert(mType == ARRAY); + + std::vector< std::vector< LayoutCell * > >::iterator + i = mArray->mCells.begin(); + + while (i != mArray->mCells.end()) + { + std::vector< LayoutCell * >::iterator j = i->begin(); + while (j != i->end()) + { + LayoutCell *cell = *j; + if (cell && cell->mType == ARRAY) + cell->computeSizes(); + + ++j; + } + ++i; + } + + mSize[0] = mArray->getSize(0); + mSize[1] = mArray->getSize(1); +} + +LayoutArray::LayoutArray(): mSpacing(4) +{ +} + +LayoutArray::~LayoutArray() +{ + std::vector< std::vector< LayoutCell * > >::iterator i = mCells.begin(); + while (i != mCells.end()) + { + std::vector< LayoutCell * >::iterator j = i->begin(); + while (j != i->end()) + { + delete *j; + ++j; + } + ++i; + } +} + +LayoutCell &LayoutArray::at(int x, int y, int w, int h) +{ + resizeGrid(x + w, y + h); + LayoutCell *&cell = mCells[y][x]; + if (!cell) + cell = new LayoutCell; + return *cell; +} + +void LayoutArray::resizeGrid(int w, int h) +{ + bool extW = w && w > static_cast(mSizes[0].size()), + extH = h && h > static_cast(mSizes[1].size()); + + if (!extW && !extH) + return; + + if (extH) + { + mSizes[1].resize(h, Layout::AUTO_DEF); + mCells.resize(h); + if (!extW) + w = static_cast(mSizes[0].size()); + } + + if (extW) + mSizes[0].resize(w, Layout::AUTO_DEF); + + std::vector< std::vector< LayoutCell * > >::iterator i = mCells.begin(); + while (i != mCells.end()) + { + i->resize(w, 0); + ++i; + } +} + +void LayoutArray::setColWidth(int n, int w) +{ + resizeGrid(n + 1, 0); + mSizes[0][n] = w; +} + +void LayoutArray::setRowHeight(int n, int h) +{ + resizeGrid(0, n + 1); + mSizes[1][n] = h; +} + +void LayoutArray::matchColWidth(int n1, int n2) +{ + resizeGrid(std::max(n1, n2) + 1, 0); + std::vector widths = getSizes(0, Layout::AUTO_DEF); + int s = std::max(widths[n1], widths[n2]); + mSizes[0][n1] = s; + mSizes[0][n2] = s; +} + +void LayoutArray::extend(int x, int y, int w, int h) +{ + LayoutCell &cell = at(x, y, w, h); + cell.mExtent[0] = w; + cell.mExtent[1] = h; +} + +LayoutCell &LayoutArray::place(gcn::Widget *widget, int x, int y, int w, int h) +{ + LayoutCell &cell = at(x, y, w, h); + assert(cell.mType == LayoutCell::NONE); + cell.mType = LayoutCell::WIDGET; + cell.mWidget = widget; + if (widget) + { + cell.mSize[0] = w == 1 ? widget->getWidth() : 0; + cell.mSize[1] = h == 1 ? widget->getHeight() : 0; + } + else + { + cell.mSize[0] = 1; + cell.mSize[1] = 1; + } + cell.mExtent[0] = w; + cell.mExtent[1] = h; + cell.mPadding = 0; + cell.mAlign[0] = LayoutCell::FILL; + cell.mAlign[1] = LayoutCell::FILL; + int &cs = mSizes[0][x], &rs = mSizes[1][y]; + if (cs == Layout::AUTO_DEF && w == 1) + cs = 0; + if (rs == Layout::AUTO_DEF && h == 1) + rs = 0; + return cell; +} + +void LayoutArray::align(int &pos, int &size, int dim, + LayoutCell const &cell, int *sizes) const +{ + int size_max = sizes[0]; + for (int i = 1; i < cell.mExtent[dim]; ++i) + size_max += sizes[i] + mSpacing; + size = std::min(cell.mSize[dim], size_max); + + switch (cell.mAlign[dim]) + { + case LayoutCell::LEFT: + return; + case LayoutCell::RIGHT: + pos += size_max - size; + return; + case LayoutCell::CENTER: + pos += (size_max - size) / 2; + return; + case LayoutCell::FILL: + size = size_max; + return; + default: + logger->log1("LayoutArray::align unknown layout"); + return; + } +} + +std::vector LayoutArray::getSizes(int dim, int upp) const +{ + int gridW = static_cast(mSizes[0].size()), + gridH = static_cast(mSizes[1].size()); + std::vector sizes = mSizes[dim]; + + // Compute minimum sizes. + for (int gridY = 0; gridY < gridH; ++gridY) + { + for (int gridX = 0; gridX < gridW; ++gridX) + { + LayoutCell const *cell = mCells[gridY][gridX]; + if (!cell || cell->mType == LayoutCell::NONE) + continue; + + if (cell->mExtent[dim] == 1) + { + int n = (dim == 0 ? gridX : gridY); + int s = cell->mSize[dim] + cell->mPadding * 2; + if (s > sizes[n]) sizes[n] = s; + } + } + } + + if (upp == Layout::AUTO_DEF) return sizes; + + // Compute the FILL sizes. + int nb = static_cast(sizes.size()); + int nbFill = 0; + for (int i = 0; i < nb; ++i) + { + if (mSizes[dim][i] <= Layout::AUTO_DEF) + { + ++nbFill; + if (mSizes[dim][i] == Layout::AUTO_SET || + sizes[i] <= Layout::AUTO_DEF) + { + sizes[i] = 0; + } + } + upp -= sizes[i] + mSpacing; + } + upp = upp + mSpacing; + + if (nbFill == 0) + return sizes; + + for (int i = 0; i < nb; ++i) + { + if (mSizes[dim][i] > Layout::AUTO_DEF) + continue; + + int s = upp / nbFill; + sizes[i] += s; + upp -= s; + --nbFill; + } + + return sizes; +} + +int LayoutArray::getSize(int dim) const +{ + std::vector sizes = getSizes(dim, Layout::AUTO_DEF); + int size = 0; + int nb = static_cast(sizes.size()); + for (int i = 0; i < nb; ++i) + { + if (sizes[i] > Layout::AUTO_DEF) + size += sizes[i]; + size += mSpacing; + } + return size - mSpacing; +} + +void LayoutArray::reflow(int nx, int ny, int nw, int nh) +{ + int gridW = static_cast(mSizes[0].size()), + gridH = static_cast(mSizes[1].size()); + + std::vector widths = getSizes(0, nw); + std::vector heights = getSizes(1, nh); + + int y = ny; + for (int gridY = 0; gridY < gridH; ++gridY) + { + int x = nx; + for (int gridX = 0; gridX < gridW; ++gridX) + { + LayoutCell *cell = mCells[gridY][gridX]; + if (cell && cell->mType != LayoutCell::NONE) + { + int dx = x, dy = y, dw, dh; + align(dx, dw, 0, *cell, &widths[gridX]); + align(dy, dh, 1, *cell, &heights[gridY]); + cell->reflow(dx, dy, dw, dh); + } + x += widths[gridX] + mSpacing; + } + y += heights[gridY] + mSpacing; + } +} + +Layout::Layout(): mComputed(false) +{ + getArray(); + setPadding(6); +} + +void Layout::reflow(int &nw, int &nh) +{ + if (!mComputed) + { + computeSizes(); + mComputed = true; + } + + nw = (nw == 0 ? mSize[0] + 2 * mPadding : nw); + nh = (nh == 0 ? mSize[1] + 2 * mPadding : nh); + LayoutCell::reflow(0, 0, nw, nh); +} diff --git a/src/gui/widgets/layout.h b/src/gui/widgets/layout.h new file mode 100644 index 000000000..4c1e40bb9 --- /dev/null +++ b/src/gui/widgets/layout.h @@ -0,0 +1,319 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WIDGET_LAYOUT_H +#define WIDGET_LAYOUT_H + +#include + +#include + +class LayoutCell; + +/** + * This class is a helper for adding widgets to nested tables in a window. + */ +class ContainerPlacer +{ + public: + ContainerPlacer(gcn::Container *c = NULL, LayoutCell *l = NULL): + mContainer(c), mCell(l) + {} + + /** + * Gets the pointed cell. + */ + LayoutCell &getCell() + { return *mCell; } + + /** + * Returns a placer for the same container but to an inner cell. + */ + ContainerPlacer at(int x, int y); + + /** + * Adds the given widget to the container and places it in the layout. + * @see LayoutArray::place + */ + LayoutCell &operator() + (int x, int y, gcn::Widget *, int w = 1, int h = 1); + + private: + gcn::Container *mContainer; + LayoutCell *mCell; +}; + +/** + * This class contains a rectangular array of cells. + */ +class LayoutArray +{ + friend class LayoutCell; + + public: + + LayoutArray(); + + ~LayoutArray(); + + /** + * Returns a reference on the cell at given position. + */ + LayoutCell &at(int x, int y, int w = 1, int h = 1); + + /** + * Places a widget in a given cell. + * @param w number of columns the widget spawns. + * @param h number of rows the widget spawns. + * @note When @a w is 1, the width of column @a x is reset to zero if + * it was AUTO_DEF. Similarly for @a h. + */ + LayoutCell &place(gcn::Widget *, int x, int y, int w = 1, int h = 1); + + /** + * Sets the minimum width of a column. + */ + void setColWidth(int n, int w); + + /** + * Sets the minimum height of a row. + */ + void setRowHeight(int n, int h); + + /** + * Sets the widths of two columns to the maximum of their widths. + */ + void matchColWidth(int n1, int n2); + + /** + * Spawns a cell over several columns/rows. + */ + void extend(int x, int y, int w, int h); + + /** + * Computes and sets the positions of all the widgets. + * @param nW width of the array, used to resize the AUTO_ columns. + * @param nH height of the array, used to resize the AUTO_ rows. + */ + void reflow(int nX, int nY, int nW, int nH); + + private: + + // Copy not allowed, as the array owns all its cells. + LayoutArray(LayoutArray const &); + LayoutArray &operator=(LayoutArray const &); + + /** + * Gets the position and size of a widget along a given axis + */ + void align(int &pos, int &size, int dim, + LayoutCell const &cell, int *sizes) const; + + /** + * Ensures the private vectors are large enough. + */ + void resizeGrid(int w, int h); + + /** + * Gets the column/row sizes along a given axis. + * @param upp target size for the array. Ignored if AUTO_DEF. + */ + std::vector getSizes(int dim, int upp) const; + + /** + * Gets the total size along a given axis. + */ + int getSize(int dim) const; + + std::vector mSizes[2]; + std::vector< std::vector < LayoutCell * > > mCells; + + int mSpacing; +}; + +/** + * This class describes the formatting of a widget in the cell of a layout + * table. Horizontally, a widget can either fill the width of the cell (minus + * the cell padding), or it can retain its size and be flushed left, or flush + * right, or centered in the cell. The process is similar for the vertical + * alignment, except that top is represented by LEFT and bottom by RIGHT. + */ +class LayoutCell +{ + friend class Layout; + friend class LayoutArray; + + public: + enum Alignment + { + LEFT = 0, + RIGHT, + CENTER, + FILL + }; + + LayoutCell(): mType(NONE) {} + + ~LayoutCell(); + + /** + * Sets the padding around the cell content. + */ + LayoutCell &setPadding(int p) + { mPadding = p; return *this; } + + /** + * Sets the horizontal alignment of the cell content. + */ + LayoutCell &setHAlign(Alignment a) + { mAlign[0] = a; return *this; } + + /** + * Sets the vertical alignment of the cell content. + */ + LayoutCell &setVAlign(Alignment a) + { mAlign[1] = a; return *this; } + + /** + * @see LayoutArray::at + */ + LayoutCell &at(int x, int y) + { return getArray().at(x, y); } + + /** + * @see LayoutArray::place + */ + LayoutCell &place(gcn::Widget *wg, int x, int y, int w = 1, int h = 1) + { return getArray().place(wg, x, y, w, h); } + + /** + * @see LayoutArray::matchColWidth + */ + void matchColWidth(int n1, int n2) + { getArray().matchColWidth(n1, n2); } + + /** + * @see LayoutArray::setColWidth + */ + void setColWidth(int n, int w) + { getArray().setColWidth(n, w); } + + /** + * @see LayoutArray::setRowHeight + */ + void setRowHeight(int n, int h) + { getArray().setRowHeight(n, h); } + + /** + * @see LayoutArray::extend. + */ + void extend(int x, int y, int w, int h) + { getArray().extend(x, y, w, h); } + + /** + * Sets the minimum widths and heights of this cell and of all the + * inner cells. + */ + void computeSizes(); + + private: + // Copy not allowed, as the cell may own an array. + LayoutCell(LayoutCell const &); + LayoutCell &operator=(LayoutCell const &); + + union + { + gcn::Widget *mWidget; + LayoutArray *mArray; + }; + + enum + { + NONE = 0, + WIDGET, + ARRAY + }; + + /** + * Returns the embedded array. Creates it if the cell does not contain + * anything yet. Aborts if it contains a widget. + */ + LayoutArray &getArray(); + + /** + * @see LayoutArray::reflow + */ + void reflow(int nx, int ny, int nw, int nh); + + int mSize[2]; + int mPadding; + int mExtent[2]; + int mAlign[2]; + int mNbFill[2]; + int mType; +}; + +/** + * This class is an helper for setting the position of widgets. They are + * positioned along the cells of some rectangular tables. The layout may either + * be a single table or a tree of nested tables. + * + * The size of a given table column can either be set manually or be chosen + * from the widest widget of the column. An empty column has a AUTO_DEF width, + * which means it will be extended so that the layout fits its minimum width. + * + * The process is similar for table rows. By default, there is a spacing of 4 + * pixels between rows and between columns, and a margin of 6 pixels around the + * whole layout. + */ +class Layout : public LayoutCell +{ + public: + Layout(); + + /** + * Sets the margin around the layout. + */ + void setMargin(int m) + { setPadding(m); } + + /** + * Sets the positions of all the widgets. + * @see LayoutArray::reflow + */ + void reflow(int &nW, int &nH); + + /** + * When the minimum size of the layout is less than the available size, + * the remaining pixels are equally split amongst the FILL items. + */ + enum + { + AUTO_DEF = -42, /**< Default value, behaves like AUTO_ADD. */ + AUTO_SET = -43, /**< Uses the share as the new size. */ + AUTO_ADD = -44 /**< Adds the share to the current size. */ + }; + + private: + bool mComputed; +}; + +#endif // WIDGET_LAYOUT_H diff --git a/src/gui/widgets/layouthelper.cpp b/src/gui/widgets/layouthelper.cpp new file mode 100644 index 000000000..a12de9bff --- /dev/null +++ b/src/gui/widgets/layouthelper.cpp @@ -0,0 +1,63 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/layouthelper.h" + +LayoutHelper::LayoutHelper(gcn::Container *container): + mContainer(container) +{ + mContainer->addWidgetListener(this); +} + +LayoutHelper::~LayoutHelper() +{ + mContainer->removeWidgetListener(this); +} + +Layout &LayoutHelper::getLayout() +{ + return mLayout; +} + +LayoutCell &LayoutHelper::place(int x, int y, gcn::Widget *wg, int w, int h) +{ + mContainer->add(wg); + return mLayout.place(wg, x, y, w, h); +} + +ContainerPlacer LayoutHelper::getPlacer(int x, int y) +{ + return ContainerPlacer(mContainer, &mLayout.at(x, y)); +} + +void LayoutHelper::reflowLayout(int w, int h) +{ + mLayout.reflow(w, h); + mContainer->setSize(w, h); +} + +void LayoutHelper::widgetResized(const gcn::Event &event _UNUSED_) +{ + const gcn::Rectangle area = mContainer->getChildrenArea(); + int w = area.width; + int h = area.height; + mLayout.reflow(w, h); +} diff --git a/src/gui/widgets/layouthelper.h b/src/gui/widgets/layouthelper.h new file mode 100644 index 000000000..033457429 --- /dev/null +++ b/src/gui/widgets/layouthelper.h @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LAYOUTHELPER_H +#define LAYOUTHELPER_H + +#include "gui/widgets/layout.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * A helper class for adding a layout to a Guichan container widget. The layout + * will register itself as a widget listener and relayout the widgets in the + * container dynamically on resize. + */ +class LayoutHelper : public gcn::WidgetListener +{ + public: + /** + * Constructor. + */ + LayoutHelper(gcn::Container *container); + + /** + * Destructor. + */ + ~LayoutHelper(); + + /** + * Gets the layout handler. + */ + Layout &getLayout(); + + /** + * Computes the position of the widgets according to the current + * layout. Resizes the managed container so that the layout fits. + * + * @note This function is meant to be called with fixed-size + * containers. + * + * @param w if non-zero, force the container to this width. + * @param h if non-zero, force the container to this height. + */ + void reflowLayout(int w = 0, int h = 0); + + /** + * Adds a widget to the container and sets it at given cell. + */ + LayoutCell &place(int x, int y, gcn::Widget *, int w = 1, int h = 1); + + /** + * Returns a proxy for adding widgets in an inner table of the layout. + */ + ContainerPlacer getPlacer(int x, int y); + + /** + * Called whenever the managed container changes size. + */ + void widgetResized(const gcn::Event &event); + + private: + Layout mLayout; /**< Layout handler */ + gcn::Container *mContainer; /**< Managed container */ +}; + +#endif // LAYOUTHELPER_H diff --git a/src/gui/widgets/linkhandler.h b/src/gui/widgets/linkhandler.h new file mode 100644 index 000000000..d9d0f1161 --- /dev/null +++ b/src/gui/widgets/linkhandler.h @@ -0,0 +1,42 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LINK_HANDLER_H +#define LINK_HANDLER_H + +#include + +#include + +/** + * A simple interface to windows that need to handle links from BrowserBox + * widget. + */ +class LinkHandler +{ + public: + virtual ~LinkHandler() { } + + virtual void handleLink(const std::string &link, + gcn::MouseEvent *event) = 0; +}; + +#endif diff --git a/src/gui/widgets/listbox.cpp b/src/gui/widgets/listbox.cpp new file mode 100644 index 000000000..8dcc4ae67 --- /dev/null +++ b/src/gui/widgets/listbox.cpp @@ -0,0 +1,146 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/listbox.h" + +#include "client.h" +#include "configuration.h" + +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include +#include +#include +#include + +float ListBox::mAlpha = 1.0; + +ListBox::ListBox(gcn::ListModel *listModel): + gcn::ListBox(listModel) +{ +} + +ListBox::~ListBox() +{ +} + +void ListBox::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + mAlpha = alpha; +} + +void ListBox::draw(gcn::Graphics *graphics) +{ + if (!mListModel) + return; + + updateAlpha(); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast(mAlpha * 255.0f))); + graphics->setFont(getFont()); + + const int height = getRowHeight(); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + graphics->fillRectangle(gcn::Rectangle(0, height * mSelected, + getWidth(), height)); + } + + // Draw the list elements + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + for (int i = 0, y = 0; i < mListModel->getNumberOfElements(); + ++i, y += height) + { + graphics->drawText(mListModel->getElementAt(i), 1, y); + } +} + +void ListBox::keyPressed(gcn::KeyEvent& keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE) + { + distributeActionEvent(); + keyEvent.consume(); + } + else if (key.getValue() == Key::UP) + { + if (getSelected() > 0) + setSelected(mSelected - 1); + else if (getSelected() == 0 && mWrappingEnabled && getListModel()) + setSelected(getListModel()->getNumberOfElements() - 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::DOWN) + { + if (getSelected() < (getListModel()->getNumberOfElements() - 1)) + { + setSelected(mSelected + 1); + } + else if (getSelected() == (getListModel()->getNumberOfElements() - 1) + && mWrappingEnabled) + { + setSelected(0); + } + keyEvent.consume(); + } + else if (key.getValue() == Key::HOME) + { + setSelected(0); + keyEvent.consume(); + } + else if (key.getValue() == Key::END && getListModel()) + { + setSelected(getListModel()->getNumberOfElements() - 1); + keyEvent.consume(); + } +} + +// Don't do anything on scrollwheel. ScrollArea will deal with that. + +void ListBox::mouseWheelMovedUp(gcn::MouseEvent &mouseEvent _UNUSED_) +{ +} + +void ListBox::mouseWheelMovedDown(gcn::MouseEvent &mouseEvent _UNUSED_) +{ +} + +void ListBox::mouseDragged(gcn::MouseEvent &event) +{ + if (event.getButton() != gcn::MouseEvent::LEFT || getRowHeight() == 0) + return; + + // Make list selection update on drag, but guard against negative y + int y = std::max(0, event.getY()); + if (getRowHeight()) + setSelected(y / getRowHeight()); +} diff --git a/src/gui/widgets/listbox.h b/src/gui/widgets/listbox.h new file mode 100644 index 000000000..721e1bd36 --- /dev/null +++ b/src/gui/widgets/listbox.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LISTBOX_H +#define LISTBOX_H + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class SelectionListener; + +/** + * A list box, meant to be used inside a scroll area. Same as the Guichan list + * box except this one doesn't have a background, instead completely relying + * on the scroll area. It also adds selection listener functionality. + * + * \ingroup GUI + */ +class ListBox : public gcn::ListBox +{ + public: + /** + * Constructor. + */ + ListBox(gcn::ListModel *listModel); + + ~ListBox(); + + /** + * Draws the list box. + */ + void draw(gcn::Graphics *graphics); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + // Inherited from KeyListener + + void keyPressed(gcn::KeyEvent& keyEvent); + + // Inherited from MouseListener + + void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + void mouseDragged(gcn::MouseEvent &event); + + protected: + static float mAlpha; +}; + +#endif diff --git a/src/gui/widgets/passwordfield.cpp b/src/gui/widgets/passwordfield.cpp new file mode 100644 index 000000000..14b924bbd --- /dev/null +++ b/src/gui/widgets/passwordfield.cpp @@ -0,0 +1,36 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "passwordfield.h" + +PasswordField::PasswordField(const std::string &text): + TextField(text) +{ +} + +void PasswordField::draw(gcn::Graphics *graphics) +{ + // std::string uses cow, thus cheap copy + const std::string original = mText; + mText.assign(mText.length(), '*'); + TextField::draw(graphics); + mText = original; +} diff --git a/src/gui/widgets/passwordfield.h b/src/gui/widgets/passwordfield.h new file mode 100644 index 000000000..619cd8420 --- /dev/null +++ b/src/gui/widgets/passwordfield.h @@ -0,0 +1,46 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PASSWORDFIELD_H +#define PASSWORDFIELD_H + +#include "textfield.h" + +/** + * A password field. + * + * \ingroup GUI + */ +class PasswordField : public TextField +{ + public: + /** + * Constructor, initializes the password field with the given string. + */ + PasswordField(const std::string &text = ""); + + /** + * Draws the password field. + */ + void draw(gcn::Graphics *graphics); +}; + +#endif diff --git a/src/gui/widgets/playerbox.cpp b/src/gui/widgets/playerbox.cpp new file mode 100644 index 000000000..e3a660120 --- /dev/null +++ b/src/gui/widgets/playerbox.cpp @@ -0,0 +1,120 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/playerbox.h" + +#include "animatedsprite.h" +#include "being.h" +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +int PlayerBox::instances = 0; +float PlayerBox::mAlpha = 1.0; +ImageRect PlayerBox::background; + +PlayerBox::PlayerBox(const Being *being): + mBeing(being) +{ + setFrameSize(2); + + if (instances == 0) + { + // Load the background skin + Image *textbox = Theme::getImageFromTheme("deepbox.png"); + int bggridx[4] = {0, 3, 28, 31}; + int bggridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (textbox) + { + background.grid[a] = textbox->getSubImage( + bggridx[x], bggridy[y], + bggridx[x + 1] - bggridx[x] + 1, + bggridy[y + 1] - bggridy[y] + 1); + if (background.grid[a]) + background.grid[a]->setAlpha(Client::getGuiAlpha()); + } + else + { + background.grid[a] = 0; + } + a++; + } + } + + if (textbox) + textbox->decRef(); + } + + instances++; +} + +PlayerBox::~PlayerBox() +{ + instances--; + + mBeing = 0; + + if (instances == 0) + for_each(background.grid, background.grid + 9, dtor()); +} + +void PlayerBox::draw(gcn::Graphics *graphics) +{ + if (mBeing) + { + // Draw character + const int bs = getFrameSize(); + const int x = getWidth() / 2 + bs - 16; + const int y = getHeight() - bs - 32; + mBeing->drawSpriteAt(static_cast(graphics), x, y); + } + + if (Client::getGuiAlpha() != mAlpha) + { + for (int a = 0; a < 9; a++) + { + if (background.grid[a]) + background.grid[a]->setAlpha(Client::getGuiAlpha()); + } + } +} + +void PlayerBox::drawFrame(gcn::Graphics *graphics) +{ + int w, h, bs; + bs = getFrameSize(); + w = getWidth() + bs * 2; + h = getHeight() + bs * 2; + + static_cast(graphics)->drawImageRect(0, 0, w, h, background); +} diff --git a/src/gui/widgets/playerbox.h b/src/gui/widgets/playerbox.h new file mode 100644 index 000000000..4505367f8 --- /dev/null +++ b/src/gui/widgets/playerbox.h @@ -0,0 +1,74 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLAYERBOX_H +#define PLAYERBOX_H + +#include + +class Being; +class ImageRect; + +/** + * A box showing a player character. + * + * \ingroup GUI + */ +class PlayerBox : public gcn::ScrollArea +{ + public: + /** + * Constructor. Takes the initial player character that this box should + * display, which defaults to NULL. + */ + PlayerBox(const Being *being = 0); + + /** + * Destructor. + */ + ~PlayerBox(); + + /** + * Sets a new player character to be displayed by this box. Setting the + * player to NULL causes the box not to draw any + * character. + */ + void setPlayer(const Being *being) { mBeing = being; } + + /** + * Draws the scroll area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Draws the background and border of the scroll area. + */ + void drawFrame(gcn::Graphics *graphics); + + private: + const Being *mBeing; /**< The character used for display */ + + static float mAlpha; + static int instances; + static ImageRect background; +}; + +#endif diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp new file mode 100644 index 000000000..fa955c8f3 --- /dev/null +++ b/src/gui/widgets/popup.cpp @@ -0,0 +1,174 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/popup.h" + +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/windowcontainer.h" + +#include "resources/image.h" + +#include + +Popup::Popup(const std::string &name, const std::string &skin): + mPopupName(name), + mMinWidth(100), + mMinHeight(40), + mMaxWidth(graphics->getWidth()), + mMaxHeight(graphics->getHeight()) +{ + logger->log("Popup::Popup(\"%s\")", name.c_str()); + + if (!windowContainer) + throw GCN_EXCEPTION("Popup::Popup(): no windowContainer set"); + + setPadding(3); + + // Loads the skin + mSkin = Theme::instance()->load(skin); + + // Add this window to the window container + windowContainer->add(this); + + // Popups are invisible by default + setVisible(false); +} + +Popup::~Popup() +{ + logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str()); + + if (mSkin) + mSkin->instances--; +} + +void Popup::setWindowContainer(WindowContainer *wc) +{ + windowContainer = wc; +} + +void Popup::draw(gcn::Graphics *graphics) +{ + Graphics *g = static_cast(graphics); + + g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + + drawChildren(graphics); +} + +gcn::Rectangle Popup::getChildrenArea() +{ + return gcn::Rectangle(getPadding(), 0, getWidth() - getPadding() * 2, + getHeight() - getPadding() * 2); +} + +void Popup::setContentSize(int width, int height) +{ + width += 2 * getPadding(); + height += 2 * getPadding(); + + if (getMinWidth() > width) + width = getMinWidth(); + else if (getMaxWidth() < width) + width = getMaxWidth(); + if (getMinHeight() > height) + height = getMinHeight(); + else if (getMaxHeight() < height) + height = getMaxHeight(); + + setSize(width, height); +} + +void Popup::setLocationRelativeTo(gcn::Widget *widget) +{ + if (!widget) + return; + + int wx, wy; + int x, y; + + widget->getAbsolutePosition(wx, wy); + getAbsolutePosition(x, y); + + setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), + getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y)); +} + +void Popup::setMinWidth(int width) +{ + mMinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth(); +} + +void Popup::setMinHeight(int height) +{ + mMinHeight = height > mSkin->getMinHeight() ? + height : mSkin->getMinHeight(); +} + +void Popup::setMaxWidth(int width) +{ + mMaxWidth = width; +} + +void Popup::setMaxHeight(int height) +{ + mMaxHeight = height; +} + +void Popup::scheduleDelete() +{ + windowContainer->scheduleDelete(this); +} + +void Popup::position(int x, int y) +{ + const int distance = 20; + + int posX = std::max(0, x - getWidth() / 2); + int posY = y + distance; + + if (posX + getWidth() > graphics->getWidth()) + posX = graphics->getWidth() - getWidth(); + if (posY + getHeight() > graphics->getHeight()) + posY = y - getHeight() - distance; + + setPosition(posX, posY); + setVisible(true); + requestMoveToTop(); +} + +void Popup::mouseMoved(gcn::MouseEvent &event _UNUSED_) +{ + if (viewport) + viewport->hideBeingPopup(); +} + +void Popup::hide() +{ + setVisible(false); +} \ No newline at end of file diff --git a/src/gui/widgets/popup.h b/src/gui/widgets/popup.h new file mode 100644 index 000000000..c83368e52 --- /dev/null +++ b/src/gui/widgets/popup.h @@ -0,0 +1,174 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef POPUP_H +#define POPUP_H + +#include "configuration.h" +#include "guichanfwd.h" + +#include "gui/widgets/container.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Skin; +class WindowContainer; + +/** + * A light version of the Window class. Particularly suited for popup type + * functionality that doesn't need to be resized or moved around by the mouse + * once created, but only needs to display some simple content, like a static + * message. + * + * Popups, in general, shouldn't also need to update their content once + * created, although this is not an explicit requirement to use the popup + * class. + * + * \ingroup GUI + */ +class Popup : public Container, public gcn::MouseListener +{ + public: + /** + * Constructor. Initializes the title to the given text and hooks + * itself into the popup container. + * + * @param name A human readable name for the popup. Only useful for + * debugging purposes. + * @param skin The location where the Popup's skin XML can be found. + */ + Popup(const std::string &name = "", + const std::string &skin = "window.xml"); + + /** + * Destructor. Deletes all the added widgets. + */ + ~Popup(); + + /** + * Sets the window container to be used by new popups. + */ + static void setWindowContainer(WindowContainer *windowContainer); + + /** + * Draws the popup. + */ + void draw(gcn::Graphics *graphics); + + /** + * Sets the size of this popup. + */ + void setContentSize(int width, int height); + + /** + * Sets the location relative to the given widget. + */ + void setLocationRelativeTo(gcn::Widget *widget); + + void mouseMoved(gcn::MouseEvent &event); + + /** + * Sets the minimum width of the popup. + */ + void setMinWidth(int width); + + int getMinWidth() const { return mMinWidth; } + + /** + * Sets the minimum height of the popup. + */ + void setMinHeight(int height); + + int getMinHeight() const { return mMinHeight; } + + /** + * Sets the maximum width of the popup. + */ + void setMaxWidth(int width); + + int getMaxWidth() const { return mMaxWidth; } + + /** + * Sets the minimum height of the popup. + */ + void setMaxHeight(int height); + + int getMaxHeight() const { return mMaxHeight; } + + /** + * Gets the padding of the popup. The padding is the distance between + * the popup border and the content. + * + * @return The padding of the popup. + * @see setPadding + */ + int getPadding() const { return mPadding; } + + void setPadding(int padding) { mPadding = padding; } + + /** + * Sets the name of the popup. This is only useful for debug purposes. + */ + void setPopupName(const std::string &name) + { mPopupName = name; } + + const std::string &getPopupName() const + { return mPopupName; } + + /** + * Schedule this popup for deletion. It will be deleted at the start + * of the next logic update. + */ + void scheduleDelete(); + + // Inherited from BasicContainer + + virtual gcn::Rectangle getChildrenArea(); + + /** + * Sets the location to display the popup. Tries to horizontally center + * the popup and provide a vertical buffer between the given point and + * the popup. Prevents the popup from extending off-screen, if + * possible. + */ + void position(int x, int y); + + void hide(); + + private: + std::string mPopupName; /**< Name of the popup */ + int mMinWidth; /**< Minimum popup width */ + int mMinHeight; /**< Minimum popup height */ + int mMaxWidth; /**< Maximum popup width */ + int mMaxHeight; /**< Maximum popup height */ + int mPadding; /**< Holds the padding of the popup. */ + + Skin *mSkin; /**< Skin in use by this popup */ +}; + +#endif diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp new file mode 100644 index 000000000..5275aacf6 --- /dev/null +++ b/src/gui/widgets/progressbar.cpp @@ -0,0 +1,225 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/progressbar.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "textrenderer.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include + +ImageRect ProgressBar::mBorder; +int ProgressBar::mInstances = 0; +float ProgressBar::mAlpha = 1.0; + +ProgressBar::ProgressBar(float progress, + int width, int height, + int color): + gcn::Widget(), + mSmoothProgress(true), + mProgressPalette(color), + mSmoothColorChange(true) +{ + // The progress value is directly set at load time: + if (progress > 1.0f || progress < 0.0f) + progress = 1.0f; + + mProgress = progress; + mProgressToGo = progress; + + mColor = Theme::getProgressColor(color >= 0 ? color : 0, mProgress); + mColorToGo = mColor; + + setSize(width, height); + + if (mInstances == 0) + { + Image *dBorders = Theme::getImageFromTheme("vscroll_grey.png"); + if (dBorders) + { + mBorder.grid[0] = dBorders->getSubImage(0, 0, 4, 4); + mBorder.grid[1] = dBorders->getSubImage(4, 0, 3, 4); + mBorder.grid[2] = dBorders->getSubImage(7, 0, 4, 4); + mBorder.grid[3] = dBorders->getSubImage(0, 4, 4, 10); + mBorder.grid[4] = dBorders->getSubImage(4, 4, 3, 10); + mBorder.grid[5] = dBorders->getSubImage(7, 4, 4, 10); + mBorder.grid[6] = dBorders->getSubImage(0, 15, 4, 4); + mBorder.grid[7] = dBorders->getSubImage(4, 15, 3, 4); + mBorder.grid[8] = dBorders->getSubImage(7, 15, 4, 4); + + for (int i = 0; i < 9; i++) + mBorder.grid[i]->setAlpha(mAlpha); + + dBorders->decRef(); + } + else + { + for (int f = 0; f < 9; f ++) + mBorder.grid[f] = 0; + } + + } + + mInstances++; +} + +ProgressBar::~ProgressBar() +{ + mInstances--; + + if (mInstances == 0) + for_each(mBorder.grid, mBorder.grid + 9, dtor()); +} + +void ProgressBar::logic() +{ + if (mSmoothColorChange && mColorToGo != mColor) + { + // Smoothly changing the color for a nicer effect. + if (mColorToGo.r > mColor.r) + mColor.r++; + if (mColorToGo.g > mColor.g) + mColor.g++; + if (mColorToGo.b > mColor.b) + mColor.b++; + if (mColorToGo.r < mColor.r) + mColor.r--; + if (mColorToGo.g < mColor.g) + mColor.g--; + if (mColorToGo.b < mColor.b) + mColor.b--; + } + + if (mSmoothProgress && mProgressToGo != mProgress) + { + // Smoothly showing the progressbar changes. + if (mProgressToGo > mProgress) + mProgress = std::min(1.0f, mProgress + 0.005f); + if (mProgressToGo < mProgress) + mProgress = std::max(0.0f, mProgress - 0.005f); + } +} + +void ProgressBar::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (mAlpha != alpha) + { + mAlpha = alpha; + for (int i = 0; i < 9; i++) + { + if (mBorder.grid[i]) + mBorder.grid[i]->setAlpha(mAlpha); + } + } + +} + +void ProgressBar::draw(gcn::Graphics *graphics) +{ + updateAlpha(); + + mColor.a = static_cast(mAlpha * 255); + + gcn::Rectangle rect = getDimension(); + rect.x = 0; + rect.y = 0; + + render(static_cast(graphics), rect, mColor, + mProgress, mText); +} + +void ProgressBar::setProgress(float progress) +{ + const float p = std::min(1.0f, std::max(0.0f, progress)); + mProgressToGo = p; + + if (!mSmoothProgress) + mProgress = p; + + if (mProgressPalette >= 0) + mColorToGo = Theme::getProgressColor(mProgressPalette, progress); +} + +void ProgressBar::setProgressPalette(int progressPalette) +{ + int oldPalette = mProgressPalette; + mProgressPalette = progressPalette; + + if (mProgressPalette != oldPalette && mProgressPalette >= 0) + mColorToGo = Theme::getProgressColor(mProgressPalette, mProgressToGo); +} + +void ProgressBar::setColor(const gcn::Color &color) +{ + mColorToGo = color; + + if (!mSmoothColorChange) + mColor = color; +} + +void ProgressBar::render(Graphics *graphics, const gcn::Rectangle &area, + const gcn::Color &color, float progress, + const std::string &text) +{ + gcn::Font *oldFont = graphics->getFont(); + gcn::Color oldColor = graphics->getColor(); + + graphics->drawImageRect(area, mBorder); + + // The bar + if (progress > 0) + { + graphics->setColor(color); + graphics->fillRectangle(gcn::Rectangle(static_cast(area.x + 4), + static_cast(area.y + 4), + static_cast(static_cast(progress) + * static_cast(area.width - 8)), + static_cast(area.height - 8))); + } + + // The label + if (!text.empty()) + { + const int textX = area.x + area.width / 2; + const int textY = area.y + (area.height - boldFont->getHeight()) / 2; + + TextRenderer::renderText(graphics, text, textX, textY, + gcn::Graphics::CENTER, + Theme::getThemeColor(Theme::PROGRESS_BAR), + gui->getFont(), true, false); + } + + graphics->setFont(oldFont); + graphics->setColor(oldColor); +} diff --git a/src/gui/widgets/progressbar.h b/src/gui/widgets/progressbar.h new file mode 100644 index 000000000..56bcb0a0f --- /dev/null +++ b/src/gui/widgets/progressbar.h @@ -0,0 +1,139 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PROGRESSBAR_H +#define PROGRESSBAR_H + +#include + +#include + +class Graphics; +class ImageRect; + +/** + * A progress bar. + * + * \ingroup GUI + */ +class ProgressBar : public gcn::Widget +{ + public: + /** + * Constructor, initializes the progress with the given value. + */ + ProgressBar(float progress = 0.0f, + int width = 40, int height = 7, + int color = -1); + + ~ProgressBar(); + + /** + * Performs progress bar logic (fading colors) + */ + void logic(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the progress bar. + */ + void draw(gcn::Graphics *graphics); + + /** + * Sets the current progress. + */ + void setProgress(float progress); + + /** + * Returns the current progress. + */ + float getProgress() const { return mProgress; } + + /** + * Change the ProgressPalette for this ProgressBar to follow or -1 to + * disable this and manage color manually. + */ + void setProgressPalette(int progressPalette); + + /** + * Change the color of the progress bar. + */ + void setColor(const gcn::Color &color); + + /** + * Returns the color of the progress bar. + */ + const gcn::Color &getColor() const { return mColor; } + + /** + * Sets the text shown on the progress bar. + */ + void setText(const std::string &text) + { mText = text; } + + /** + * Returns the text shown on the progress bar. + */ + const std::string &text() const + { return mText; } + + /** + * Set whether the progress is moved smoothly. + */ + void setSmoothProgress(bool smoothProgress) + { mSmoothProgress = smoothProgress; } + + /** + * Set whether the color changing is made smoothly. + */ + void setSmoothColorChange(bool smoothColorChange) + { mSmoothColorChange = smoothColorChange; } + + /** + * Renders a progressbar with the given properties. + */ + static void render(Graphics *graphics, const gcn::Rectangle &area, + const gcn::Color &color, float progress, + const std::string &text = ""); + + private: + float mProgress, mProgressToGo; + bool mSmoothProgress; + + int mProgressPalette; /** < Entry in ProgressPalette or -1 for none. */ + gcn::Color mColor; + gcn::Color mColorToGo; + bool mSmoothColorChange; + + std::string mText; + + static ImageRect mBorder; + static int mInstances; + static float mAlpha; + + static const gcn::Color TEXT_COLOR; +}; + +#endif diff --git a/src/gui/widgets/progressindicator.cpp b/src/gui/widgets/progressindicator.cpp new file mode 100644 index 000000000..9f1a8f032 --- /dev/null +++ b/src/gui/widgets/progressindicator.cpp @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "progressindicator.h" + +#include "graphics.h" +#include "simpleanimation.h" + +#include "gui/theme.h" + +#include "resources/animation.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include + +ProgressIndicator::ProgressIndicator() +{ + ImageSet *images = Theme::getImageSetFromTheme("progress-indicator.png", + 32, 32); + + Animation *anim = new Animation; + if (images) + { + for (ImageSet::size_type i = 0; i < images->size(); ++i) + anim->addFrame(images->get(i), 100, 0, 0); + + mIndicator = new SimpleAnimation(anim); + + images->decRef(); + } + else + { + mIndicator = 0; + } + + setSize(32, 32); +} + +ProgressIndicator::~ProgressIndicator() +{ + delete mIndicator; + mIndicator = 0; +} + +void ProgressIndicator::logic() +{ + if (mIndicator) + mIndicator->update(10); +} + +void ProgressIndicator::draw(gcn::Graphics *graphics) +{ + if (mIndicator) + { + // Draw the indicator centered on the widget + const int x = (getWidth() - 32) / 2; + const int y = (getHeight() - 32) / 2; + mIndicator->draw(static_cast(graphics), x, y); + } +} diff --git a/src/gui/widgets/progressindicator.h b/src/gui/widgets/progressindicator.h new file mode 100644 index 000000000..b990d8bef --- /dev/null +++ b/src/gui/widgets/progressindicator.h @@ -0,0 +1,45 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PROGRESSINDICATOR_H +#define PROGRESSINDICATOR_H + +#include + +class SimpleAnimation; + +/** + * A widget that indicates progress. Suitable to use instead of a progress bar + * in cases where it is unknown how long something is going to take. + */ +class ProgressIndicator : public gcn::Widget +{ +public: + ProgressIndicator(); + ~ProgressIndicator(); + + void logic(); + void draw(gcn::Graphics *graphics); + +private: + SimpleAnimation *mIndicator; +}; + +#endif // PROGRESSINDICATOR_H diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp new file mode 100644 index 000000000..c9738e3cd --- /dev/null +++ b/src/gui/widgets/radiobutton.cpp @@ -0,0 +1,163 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/radiobutton.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +int RadioButton::instances = 0; +float RadioButton::mAlpha = 1.0; +Image *RadioButton::radioNormal; +Image *RadioButton::radioChecked; +Image *RadioButton::radioDisabled; +Image *RadioButton::radioDisabledChecked; +Image *RadioButton::radioNormalHi; +Image *RadioButton::radioCheckedHi; + +RadioButton::RadioButton(const std::string &caption, const std::string &group, + bool marked): + gcn::RadioButton(caption, group, marked), + mHasMouse(false) +{ + if (instances == 0) + { + radioNormal = Theme::getImageFromTheme("radioout.png"); + radioChecked = Theme::getImageFromTheme("radioin.png"); + radioDisabled = Theme::getImageFromTheme("radioout.png"); + radioDisabledChecked = Theme::getImageFromTheme("radioin.png"); + radioNormalHi = Theme::getImageFromTheme("radioout_highlight.png"); + radioCheckedHi = Theme::getImageFromTheme("radioin_highlight.png"); + if (radioNormal) + radioNormal->setAlpha(mAlpha); + if (radioChecked) + radioChecked->setAlpha(mAlpha); + if (radioDisabled) + radioDisabled->setAlpha(mAlpha); + if (radioDisabledChecked) + radioDisabledChecked->setAlpha(mAlpha); + if (radioNormalHi) + radioNormalHi->setAlpha(mAlpha); + if (radioCheckedHi) + radioCheckedHi->setAlpha(mAlpha); + } + + instances++; +} + +RadioButton::~RadioButton() +{ + instances--; + + if (instances == 0) + { + if (radioNormal) + radioNormal->decRef(); + if (radioChecked) + radioChecked->decRef(); + if (radioDisabled) + radioDisabled->decRef(); + if (radioDisabledChecked) + radioDisabledChecked->decRef(); + if (radioNormalHi) + radioNormalHi->decRef(); + if (radioCheckedHi) + radioCheckedHi->decRef(); + } +} + +void RadioButton::drawBox(gcn::Graphics* graphics) +{ + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + if (radioNormal) + radioNormal->setAlpha(mAlpha); + if (radioChecked) + radioChecked->setAlpha(mAlpha); + if (radioDisabled) + radioDisabled->setAlpha(mAlpha); + if (radioDisabledChecked) + radioDisabledChecked->setAlpha(mAlpha); + if (radioNormalHi) + radioNormalHi->setAlpha(mAlpha); + if (radioCheckedHi) + radioCheckedHi->setAlpha(mAlpha); + } + + Image *box = NULL; + + if (isEnabled()) + { + if (isSelected()) + if (mHasMouse) + box = radioCheckedHi; + else + box = radioChecked; + else + if (mHasMouse) + box = radioNormalHi; + else + box = radioNormal; + } + else + { + if (isSelected()) + box = radioDisabledChecked; + else + box = radioDisabled; + } + + if (box) + static_cast(graphics)->drawImage(box, 2, 2); +} + +void RadioButton::draw(gcn::Graphics* graphics) +{ + graphics->pushClipArea(gcn::Rectangle(1, 1, getWidth() - 1, + getHeight() - 1)); + + drawBox(graphics); + + graphics->popClipArea(); + + graphics->setFont(getFont()); + graphics->setColor(getForegroundColor()); + + int h = getHeight() + getHeight() / 2; + graphics->drawText(getCaption(), h - 2, 0); +} + +void RadioButton::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void RadioButton::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} + diff --git a/src/gui/widgets/radiobutton.h b/src/gui/widgets/radiobutton.h new file mode 100644 index 000000000..b7383aa7c --- /dev/null +++ b/src/gui/widgets/radiobutton.h @@ -0,0 +1,85 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RADIOBUTTON_H +#define RADIOBUTTON_H + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Guichan based RadioButton with custom look + */ +class RadioButton : public gcn::RadioButton +{ + public: + /** + * Constructor. + */ + RadioButton(const std::string &caption, const std::string &group, + bool marked = false); + + /** + * Destructor. + */ + ~RadioButton(); + + /** + * Draws the radiobutton, not the caption. + */ + void drawBox(gcn::Graphics* graphics); + + /** + * Implementation of the draw methods. + * Thus, avoiding the rhomb around the radio button. + */ + void draw(gcn::Graphics* graphics); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + private: + static int instances; + static float mAlpha; + bool mHasMouse; + static Image *radioNormal; + static Image *radioChecked; + static Image *radioDisabled; + static Image *radioDisabledChecked; + static Image *radioNormalHi; + static Image *radioCheckedHi; +}; + +#endif // RADIOBUTTON_H diff --git a/src/gui/widgets/resizegrip.cpp b/src/gui/widgets/resizegrip.cpp new file mode 100644 index 000000000..e8ccd0740 --- /dev/null +++ b/src/gui/widgets/resizegrip.cpp @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/resizegrip.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +#include + +Image *ResizeGrip::gripImage = 0; +int ResizeGrip::mInstances = 0; +float ResizeGrip::mAlpha = 1.0; + +ResizeGrip::ResizeGrip(const std::string &image) +{ + if (mInstances == 0) + { + // Load the grip image + gripImage = Theme::getImageFromTheme(image); + if (gripImage) + gripImage->setAlpha(mAlpha); + } + + mInstances++; + + if (gripImage) + { + setWidth(gripImage->getWidth() + 2); + setHeight(gripImage->getHeight() + 2); + } + else + { + setWidth(2); + setHeight(2); + } +} + +ResizeGrip::~ResizeGrip() +{ + mInstances--; + + if (mInstances == 0 && gripImage) + gripImage->decRef(); +} + +void ResizeGrip::draw(gcn::Graphics *graphics) +{ + if (!gripImage) + return; + + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + gripImage->setAlpha(mAlpha); + } + + static_cast(graphics)->drawImage(gripImage, 0, 0); +} diff --git a/src/gui/widgets/resizegrip.h b/src/gui/widgets/resizegrip.h new file mode 100644 index 000000000..5ef93f297 --- /dev/null +++ b/src/gui/widgets/resizegrip.h @@ -0,0 +1,60 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RESIZEGRIP_H +#define RESIZEGRIP_H + +#include + +class Image; + +/** + * Resize grip. The resize grip is part of a resizable Window. It relies on the + * fact that uncaught mouse events are automatically routed to the parent + * window. + * + * \ingroup GUI + */ +class ResizeGrip : public gcn::Widget +{ + public: + /** + * Constructor. + */ + ResizeGrip(const std::string &image = "resize.png"); + + /** + * Destructor. + */ + ~ResizeGrip(); + + /** + * Draws the resize grip. + */ + void draw(gcn::Graphics *graphics); + + private: + static Image *gripImage; /**< Resize grip image */ + static int mInstances; /**< Number of resize grip instances */ + static float mAlpha; +}; + +#endif diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp new file mode 100644 index 000000000..187794b1d --- /dev/null +++ b/src/gui/widgets/scrollarea.cpp @@ -0,0 +1,445 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/scrollarea.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +int ScrollArea::instances = 0; +float ScrollArea::mAlpha = 1.0; +ImageRect ScrollArea::background; +ImageRect ScrollArea::vMarker; +ImageRect ScrollArea::vMarkerHi; +Image *ScrollArea::buttons[4][2]; + +ScrollArea::ScrollArea(): + gcn::ScrollArea(), + mX(0), + mY(0), + mHasMouse(false), + mOpaque(true) +{ + addWidgetListener(this); + init(); +} + +ScrollArea::ScrollArea(gcn::Widget *widget): + gcn::ScrollArea(widget), + mX(0), + mY(0), + mHasMouse(false), + mOpaque(true) +{ + init(); +} + +ScrollArea::~ScrollArea() +{ + // Garbage collection + delete getContent(); + + instances--; + + if (instances == 0) + { + for_each(background.grid, background.grid + 9, dtor()); + for_each(vMarker.grid, vMarker.grid + 9, dtor()); + for_each(vMarkerHi.grid, vMarkerHi.grid + 9, dtor()); + + if (buttons[UP][0]) + buttons[UP][0]->decRef(); + if (buttons[UP][1]) + buttons[UP][1]->decRef(); + if (buttons[DOWN][0]) + buttons[DOWN][0]->decRef(); + if (buttons[DOWN][1]) + buttons[DOWN][1]->decRef(); + if (buttons[LEFT][0]) + buttons[LEFT][0]->decRef(); + if (buttons[LEFT][1]) + buttons[LEFT][1]->decRef(); + if (buttons[RIGHT][0]) + buttons[RIGHT][0]->decRef(); + if (buttons[RIGHT][1]) + buttons[RIGHT][1]->decRef(); + } +} + +void ScrollArea::init() +{ + // Draw background by default + setOpaque(true); + + setUpButtonScrollAmount(2); + setDownButtonScrollAmount(2); + setLeftButtonScrollAmount(2); + setRightButtonScrollAmount(2); + + if (instances == 0) + { + // Load the background skin + Image *textbox = Theme::getImageFromTheme("deepbox.png"); + const int bggridx[4] = {0, 3, 28, 31}; + const int bggridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (textbox) + { + background.grid[a] = textbox->getSubImage( + bggridx[x], bggridy[y], + bggridx[x + 1] - bggridx[x] + 1, + bggridy[y + 1] - bggridy[y] + 1); + background.grid[a]->setAlpha( + Client::getGuiAlpha()); + } + else + { + background.grid[a] = 0; + } + a++; + } + } + + textbox->decRef(); + + // Load vertical scrollbar skin + Image *vscroll = Theme::getImageFromTheme("vscroll_grey.png"); + Image *vscrollHi = Theme::getImageFromTheme("vscroll_highlight.png"); + + int vsgridx[4] = {0, 4, 7, 11}; + int vsgridy[4] = {0, 4, 15, 19}; + a = 0; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (vscroll) + { + vMarker.grid[a] = vscroll->getSubImage( + vsgridx[x], vsgridy[y], + vsgridx[x + 1] - vsgridx[x], + vsgridy[y + 1] - vsgridy[y]); + vMarker.grid[a]->setAlpha( + Client::getGuiAlpha()); + } + else + { + vMarker.grid[a] = 0; + } + if (vscrollHi) + { + vMarkerHi.grid[a] = vscrollHi->getSubImage( + vsgridx[x], vsgridy[y], + vsgridx[x + 1] - vsgridx[x], + vsgridy[y + 1] - vsgridy[y]); + vMarkerHi.grid[a]->setAlpha( + Client::getGuiAlpha()); + } + else + { + vMarkerHi.grid[a] = 0; + } + a++; + } + } + + if (vscroll) + vscroll->decRef(); + if (vscrollHi) + vscrollHi->decRef(); + + buttons[UP][0] = + Theme::getImageFromTheme("vscroll_up_default.png"); + buttons[DOWN][0] = + Theme::getImageFromTheme("vscroll_down_default.png"); + buttons[LEFT][0] = + Theme::getImageFromTheme("hscroll_left_default.png"); + buttons[RIGHT][0] = + Theme::getImageFromTheme("hscroll_right_default.png"); + buttons[UP][1] = + Theme::getImageFromTheme("vscroll_up_pressed.png"); + buttons[DOWN][1] = + Theme::getImageFromTheme("vscroll_down_pressed.png"); + buttons[LEFT][1] = + Theme::getImageFromTheme("hscroll_left_pressed.png"); + buttons[RIGHT][1] = + Theme::getImageFromTheme("hscroll_right_pressed.png"); + } + + instances++; +} + +void ScrollArea::logic() +{ + if (!isVisible()) + return; + + gcn::ScrollArea::logic(); + gcn::Widget *content = getContent(); + + // When no scrollbar in a certain direction, adapt content size to match + // the content dimension exactly. + if (content) + { + if (getHorizontalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER) + { + content->setWidth(getChildrenArea().width - + 2 * content->getFrameSize()); + } + if (getVerticalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER) + { + content->setHeight(getChildrenArea().height - + 2 * content->getFrameSize()); + } + } + + if (mUpButtonPressed) + { + setVerticalScrollAmount(getVerticalScrollAmount() - + mUpButtonScrollAmount); + } + else if (mDownButtonPressed) + { + setVerticalScrollAmount(getVerticalScrollAmount() + + mDownButtonScrollAmount); + } + else if (mLeftButtonPressed) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() - + mLeftButtonScrollAmount); + } + else if (mRightButtonPressed) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + + mRightButtonScrollAmount); + } +} + +void ScrollArea::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (alpha != mAlpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + if (background.grid[a]) + background.grid[a]->setAlpha(mAlpha); + if (vMarker.grid[a]) + vMarker.grid[a]->setAlpha(mAlpha); + if (vMarkerHi.grid[a]) + vMarkerHi.grid[a]->setAlpha(mAlpha); + } + } +} + +void ScrollArea::draw(gcn::Graphics *graphics) +{ + if (mVBarVisible) + { + drawUpButton(graphics); + drawDownButton(graphics); + drawVBar(graphics); + drawVMarker(graphics); + } + + if (mHBarVisible) + { + drawLeftButton(graphics); + drawRightButton(graphics); + drawHBar(graphics); + drawHMarker(graphics); + } + + if (mHBarVisible && mVBarVisible) + { + graphics->setColor(getBaseColor()); + graphics->fillRectangle(gcn::Rectangle(getWidth() - mScrollbarWidth, + getHeight() - mScrollbarWidth, + mScrollbarWidth, + mScrollbarWidth)); + } + + updateAlpha(); + + drawChildren(graphics); +} + +void ScrollArea::drawFrame(gcn::Graphics *graphics) +{ + if (mOpaque) + { + const int bs = getFrameSize(); + const int w = getWidth() + bs * 2; + const int h = getHeight() + bs * 2; + + static_cast(graphics)-> + drawImageRect(0, 0, w, h, background); + } +} + +void ScrollArea::setOpaque(bool opaque) +{ + mOpaque = opaque; + setFrameSize(mOpaque ? 2 : 0); +} + +void ScrollArea::drawButton(gcn::Graphics *graphics, BUTTON_DIR dir) +{ + int state = 0; + gcn::Rectangle dim; + + switch (dir) + { + case UP: + state = mUpButtonPressed ? 1 : 0; + dim = getUpButtonDimension(); + break; + case DOWN: + state = mDownButtonPressed ? 1 : 0; + dim = getDownButtonDimension(); + break; + case LEFT: + state = mLeftButtonPressed ? 1 : 0; + dim = getLeftButtonDimension(); + break; + case RIGHT: + state = mRightButtonPressed ? 1 : 0; + dim = getRightButtonDimension(); + break; + default: + logger->log("ScrollArea::drawButton unknown dir: " + + toString(static_cast(dir))); + break; + } + + if (buttons[dir][state]) + { + static_cast(graphics)-> + drawImage(buttons[dir][state], dim.x, dim.y); + } +} + +void ScrollArea::drawUpButton(gcn::Graphics *graphics) +{ + drawButton(graphics, UP); +} + +void ScrollArea::drawDownButton(gcn::Graphics *graphics) +{ + drawButton(graphics, DOWN); +} + +void ScrollArea::drawLeftButton(gcn::Graphics *graphics) +{ + drawButton(graphics, LEFT); +} + +void ScrollArea::drawRightButton(gcn::Graphics *graphics) +{ + drawButton(graphics, RIGHT); +} + +void ScrollArea::drawVBar(gcn::Graphics *graphics) +{ + const gcn::Rectangle dim = getVerticalBarDimension(); + graphics->setColor(gcn::Color(0, 0, 0, 32)); + graphics->fillRectangle(dim); + graphics->setColor(gcn::Color(255, 255, 255)); +} + +void ScrollArea::drawHBar(gcn::Graphics *graphics) +{ + const gcn::Rectangle dim = getHorizontalBarDimension(); + graphics->setColor(gcn::Color(0, 0, 0, 32)); + graphics->fillRectangle(dim); + graphics->setColor(gcn::Color(255, 255, 255)); +} + +void ScrollArea::drawVMarker(gcn::Graphics *graphics) +{ + gcn::Rectangle dim = getVerticalMarkerDimension(); + + if ((mHasMouse) && (mX > (getWidth() - getScrollbarWidth()))) + { + static_cast(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi); + } + else + { + static_cast(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker); + } +} + +void ScrollArea::drawHMarker(gcn::Graphics *graphics) +{ + gcn::Rectangle dim = getHorizontalMarkerDimension(); + + if ((mHasMouse) && (mY > (getHeight() - getScrollbarWidth()))) + { + static_cast(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi); + } + else + { + static_cast(graphics)-> + drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker); + } +} + +void ScrollArea::mouseMoved(gcn::MouseEvent& event) +{ + mX = event.getX(); + mY = event.getY(); +} + +void ScrollArea::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void ScrollArea::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} + +void ScrollArea::widgetResized(const gcn::Event &event _UNUSED_) +{ + getContent()->setSize(getWidth() - 2 * getFrameSize(), + getHeight() - 2 * getFrameSize()); +} diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h new file mode 100644 index 000000000..4f6a07f82 --- /dev/null +++ b/src/gui/widgets/scrollarea.h @@ -0,0 +1,151 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCROLLAREA_H +#define SCROLLAREA_H + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class ImageRect; + +/** + * A scroll area. + * + * Contrary to Guichan's scroll area, this scroll area takes ownership over its + * content. However, it won't delete a previously set content widget when + * setContent is called! + * + * \ingroup GUI + */ +class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener +{ + public: + /** + * Constructor that takes no content. Needed for use with the DropDown + * class. + */ + ScrollArea(); + + /** + * Constructor. + * + * @param content the initial content to show in the scroll area + */ + ScrollArea(gcn::Widget *content); + + /** + * Destructor. Also deletes the content. + */ + ~ScrollArea(); + + /** + * Logic function optionally adapts width or height of contents. This + * depends on the scrollbar settings. + */ + void logic(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the scroll area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Draws the background and border of the scroll area. + */ + void drawFrame(gcn::Graphics *graphics); + + /** + * Sets whether the widget should draw its background or not. + */ + void setOpaque(bool opaque); + + /** + * Returns whether the widget draws its background or not. + */ + bool isOpaque() const { return mOpaque; } + + /** + * Called when the mouse moves in the widget area. + */ + void mouseMoved(gcn::MouseEvent& event); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + void widgetResized(const gcn::Event &event); + + protected: + enum BUTTON_DIR + { + UP = 0, + DOWN, + LEFT, + RIGHT + }; + + /** + * Initializes the scroll area. + */ + void init(); + + void drawButton(gcn::Graphics *graphics, BUTTON_DIR dir); + void drawUpButton(gcn::Graphics *graphics); + void drawDownButton(gcn::Graphics *graphics); + void drawLeftButton(gcn::Graphics *graphics); + void drawRightButton(gcn::Graphics *graphics); + void drawVBar(gcn::Graphics *graphics); + void drawHBar(gcn::Graphics *graphics); + void drawVMarker(gcn::Graphics *graphics); + void drawHMarker(gcn::Graphics *graphics); + + static int instances; + static float mAlpha; + static ImageRect background; + static ImageRect vMarker; + static ImageRect vMarkerHi; + static Image *buttons[4][2]; + + int mX, mY; + bool mHasMouse; + bool mOpaque; +}; + +#endif diff --git a/src/gui/widgets/setuptab.cpp b/src/gui/widgets/setuptab.cpp new file mode 100644 index 000000000..3c10e6d93 --- /dev/null +++ b/src/gui/widgets/setuptab.cpp @@ -0,0 +1,31 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/setuptab.h" + +SetupTab::SetupTab() +{ + setOpaque(false); +} + +void SetupTab::externalUpdated() +{ +} \ No newline at end of file diff --git a/src/gui/widgets/setuptab.h b/src/gui/widgets/setuptab.h new file mode 100644 index 000000000..2d8742123 --- /dev/null +++ b/src/gui/widgets/setuptab.h @@ -0,0 +1,64 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_SETUPTAB_H +#define GUI_SETUPTAB_H + +#include "gui/widgets/container.h" + +#include + +/** + * A container for the contents of a tab in the setup window. + */ +class SetupTab : public Container +{ +public: + SetupTab(); + + const std::string &getName() const + { return mName; } + + /** + * Called when the Apply button is pressed in the setup window. + */ + virtual void apply() = 0; + + /** + * Called when the Cancel button is pressed in the setup window. + */ + virtual void cancel() = 0; + + virtual void externalUpdated(); + +protected: + /** + * Sets the name displayed on the tab. Should be set in the + * constructor of a subclass. + */ + void setName(const std::string &name) + { mName = name; } + +private: + std::string mName; +}; + +#endif diff --git a/src/gui/widgets/shopitems.cpp b/src/gui/widgets/shopitems.cpp new file mode 100644 index 000000000..0aff3d5b9 --- /dev/null +++ b/src/gui/widgets/shopitems.cpp @@ -0,0 +1,118 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/shopitems.h" + +#include "shopitem.h" + +#include "utils/dtor.h" + +ShopItems::ShopItems(bool mergeDuplicates) : + mMergeDuplicates(mergeDuplicates) +{ +} + +ShopItems::~ShopItems() +{ + clear(); +} + +int ShopItems::getNumberOfElements() +{ + return static_cast(mShopItems.size()); +} + +std::string ShopItems::getElementAt(int i) +{ + if (i < 0 || (unsigned)i >= mShopItems.size() || !mShopItems.at(i)) + return ""; + + return mShopItems.at(i)->getDisplayName(); +} + +void ShopItems::addItem(int id, int amount, int price) +{ + mShopItems.push_back(new ShopItem(-1, id, amount, price)); +} + +void ShopItems::addItemNoDup(int id, int amount, int price) +{ + ShopItem *item = findItem(id); + if (!item) + mShopItems.push_back(new ShopItem(-1, id, amount, price)); +} + +void ShopItems::addItem(int inventoryIndex, int id, int quantity, int price) +{ + ShopItem *item = 0; + if (mMergeDuplicates) + item = findItem(id); + + if (item) + { + item->addDuplicate (inventoryIndex, quantity); + } + else + { + item = new ShopItem(inventoryIndex, id, quantity, price); + mShopItems.push_back(item); + } +} + +ShopItem *ShopItems::at(unsigned int i) const +{ + if (i >= mShopItems.size()) + return 0; + + return mShopItems.at(i); +} + +void ShopItems::erase(unsigned int i) +{ + if (i >= mShopItems.size()) + return; + + mShopItems.erase(mShopItems.begin() + i); +} + +void ShopItems::clear() +{ + delete_all(mShopItems); + mShopItems.clear(); +} + +ShopItem *ShopItems::findItem(int id) +{ + ShopItem *item; + + std::vector::iterator it = mShopItems.begin(); + std::vector::iterator e = mShopItems.end(); + while (it != e) + { + item = *(it); + if (item->getId() == id) + return item; + + ++it; + } + + return 0; +} diff --git a/src/gui/widgets/shopitems.h b/src/gui/widgets/shopitems.h new file mode 100644 index 000000000..ba325bfa5 --- /dev/null +++ b/src/gui/widgets/shopitems.h @@ -0,0 +1,120 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SHOPITEMS_H +#define SHOPITEMS_H + +#include + +#include +#include + +class ShopItem; + +/** + * This class handles the list of items available in a shop. + * + * The addItem routine can automatically check, if an item already exists and + * only adds duplicates to the old item, if one is found. The original + * distribution of the duplicates can be retrieved from the item. + * + * This functionality can be enabled in the constructor. + */ +class ShopItems : public gcn::ListModel +{ + public: + /** + * Constructor. + * + * @param mergeDuplicates lets the Shop look for duplicate entries and + * merges them to one item. + */ + ShopItems(bool mergeDuplicates = false); + + ~ShopItems(); + + /** + * Adds an item to the list. + */ + void addItem(int id, int amount, int price); + + /** + * Adds an item to the list (used by sell dialog). Looks for + * duplicate entries, if mergeDuplicates was turned on. + * + * @param inventoryIndex the inventory index of the item + * @param id the id of the item + * @param quantity number of available copies of the item + * @param price price of the item + */ + void addItem(int inventoryIndex, int id, int amount, int price); + + void addItemNoDup(int id, int amount, int price); + + /** + * Returns the number of items in the shop. + */ + int getNumberOfElements(); + + /** + * Returns the name of item number i in the shop. + * + * @param i the index to retrieve + */ + std::string getElementAt(int i); + + /** + * Returns the item number i in the shop. + */ + ShopItem *at(unsigned int i) const; + + /** + * Removes an element from the shop. + * + * @param i index to remove + */ + void erase(unsigned int i); + + /** + * Clears the list of items in the shop. + */ + void clear(); + + std::vector &items() + { return mShopItems; } + + private: + /** + * Searches the current items in the shop for the specified + * id and returns the item if found, or 0 else. + * + * @return the item found or 0 + */ + ShopItem *findItem(int id); + + /** The list of items in the shop. */ + std::vector mShopItems; + + /** Look for duplicate entries on addition. */ + bool mMergeDuplicates; +}; + +#endif // SHOPITEMS_H diff --git a/src/gui/widgets/shoplistbox.cpp b/src/gui/widgets/shoplistbox.cpp new file mode 100644 index 000000000..a0577db03 --- /dev/null +++ b/src/gui/widgets/shoplistbox.cpp @@ -0,0 +1,185 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/shoplistbox.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "shopitem.h" + +#include "gui/itempopup.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/shopitems.h" + +#include "resources/image.h" + +#include +#include + +const int ITEM_ICON_SIZE = 32; + +float ShopListBox::mAlpha = 1.0; + +ShopListBox::ShopListBox(gcn::ListModel *listModel): + ListBox(listModel), + mPlayerMoney(0), + mShopItems(0) +{ + mRowHeight = getFont()->getHeight(); + mPriceCheck = true; + + mItemPopup = new ItemPopup; +} + +ShopListBox::ShopListBox(gcn::ListModel *listModel, ShopItems *shopListModel): + ListBox(listModel), + mPlayerMoney(0), + mShopItems(shopListModel) +{ + mRowHeight = std::max(getFont()->getHeight(), ITEM_ICON_SIZE); + mPriceCheck = true; + + mItemPopup = new ItemPopup; +} + +void ShopListBox::setPlayersMoney(int money) +{ + mPlayerMoney = money; +} + +void ShopListBox::draw(gcn::Graphics *gcnGraphics) +{ + if (!mListModel || !mShopItems) + return; + + if (Client::getGuiAlpha() != mAlpha) + mAlpha = Client::getGuiAlpha(); + + int alpha = static_cast(mAlpha * 255.0f); + const gcn::Color* highlightColor = + &Theme::getThemeColor(Theme::HIGHLIGHT, alpha); + + Graphics *graphics = static_cast(gcnGraphics); + + graphics->setFont(getFont()); + + // Draw the list elements + for (int i = 0, y = 0; + i < mListModel->getNumberOfElements(); + ++i, y += mRowHeight) + { + gcn::Color temp; + const gcn::Color* backgroundColor = + &Theme::getThemeColor(Theme::BACKGROUND, alpha); + + if (mShopItems && mShopItems->at(i) && + mPlayerMoney < mShopItems->at(i)->getPrice() && mPriceCheck) + { + if (i != mSelected) + { + backgroundColor = &Theme::getThemeColor(Theme::SHOP_WARNING, + alpha); + } + else + { + temp = Theme::getThemeColor(Theme::SHOP_WARNING, alpha); + temp.r = (temp.r + highlightColor->r) / 2; + temp.g = (temp.g + highlightColor->g) / 2; + temp.b = (temp.g + highlightColor->b) / 2; + backgroundColor = &temp; + } + } + else if (i == mSelected) + { + backgroundColor = highlightColor; + } + + graphics->setColor(*backgroundColor); + graphics->fillRectangle(gcn::Rectangle(0, y, getWidth(), mRowHeight)); + + if (mShopItems) + { + Image *icon = mShopItems->at(i)->getImage(); + if (icon) + { + icon->setAlpha(1.0f); + graphics->drawImage(icon, 1, y); + } + } + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(mListModel->getElementAt(i), ITEM_ICON_SIZE + 5, + y + (ITEM_ICON_SIZE - getFont()->getHeight()) / 2); + } +} + +void ShopListBox::adjustSize() +{ + if (mListModel) + setHeight(mRowHeight * mListModel->getNumberOfElements()); +} + +void ShopListBox::setPriceCheck(bool check) +{ + mPriceCheck = check; +} + +void ShopListBox::mouseMoved(gcn::MouseEvent &event) +{ + if (!mItemPopup) + return; + + if (!mShopItems) + { + mItemPopup->hide(); + return; + } + + int index = event.getY() / mRowHeight; + + if (index < 0 || index >= mShopItems->getNumberOfElements()) + { + mItemPopup->hide(); + } + else + { + Item *item = mShopItems->at(index); + if (item) + { + mItemPopup->setItem(item); + mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mItemPopup->setVisible(false); + } + } +} + +void ShopListBox::mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_) +{ + if (!mItemPopup) + return; + + mItemPopup->hide(); +} \ No newline at end of file diff --git a/src/gui/widgets/shoplistbox.h b/src/gui/widgets/shoplistbox.h new file mode 100644 index 000000000..ab77c5969 --- /dev/null +++ b/src/gui/widgets/shoplistbox.h @@ -0,0 +1,104 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SHOPLISTBOX_H +#define SHOPLISTBOX_H + +#include "gui/widgets/listbox.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ShopItems; +class ItemPopup; + +/** + * A list box, meant to be used inside a scroll area. Same as the Guichan list + * box except this one doesn't have a background, instead completely relying + * on the scroll area. It also adds selection listener functionality. + * + * \ingroup GUI + */ +class ShopListBox : public ListBox +{ + public: + /** + * Constructor. + */ + ShopListBox(gcn::ListModel *listModel); + + /** + * Constructor with shopitems + */ + ShopListBox(gcn::ListModel *listModel, ShopItems *shopListModel); + + /** + * Draws the list box. + */ + void draw(gcn::Graphics *graphics); + + /** + * Returns the height of a row. + */ + unsigned int getRowHeight() const { return mRowHeight; } + + /** + * gives information about the current player's money + */ + void setPlayersMoney(int money); + + /** + * Adjust List draw size + */ + void adjustSize(); + + /** + * Set on/off the disabling of too expensive items. + * (Good for selling mode.) + */ + void setPriceCheck(bool check); + + void mouseMoved(gcn::MouseEvent &event); + + void mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_); + + private: + int mPlayerMoney; + + /** + * Keeps another pointer to the same listModel, permitting to + * use the ShopItems specific functions. + */ + ShopItems *mShopItems; + + ItemPopup *mItemPopup; + + unsigned int mRowHeight; /**< Row Height */ + + static float mAlpha; + + bool mPriceCheck; +}; + +#endif // SHOPLISTBOX_H diff --git a/src/gui/widgets/shortcutcontainer.cpp b/src/gui/widgets/shortcutcontainer.cpp new file mode 100644 index 000000000..167296410 --- /dev/null +++ b/src/gui/widgets/shortcutcontainer.cpp @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/shortcutcontainer.h" + +#include "configuration.h" + +#include "resources/image.h" + +#include "utils/stringutils.h" + +float ShortcutContainer::mAlpha = 1.0; + +ShortcutContainer::ShortcutContainer(): + mGridWidth(1), + mGridHeight(1) +{ +} + +void ShortcutContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + mGridWidth = getWidth() / mBoxWidth; + + if (mGridWidth < 1) + mGridWidth = 1; + + mGridHeight = mMaxItems / mGridWidth; + + if (mMaxItems % mGridWidth != 0 || mGridHeight < 1) + ++mGridHeight; + + setHeight(mGridHeight * mBoxHeight); +} + +int ShortcutContainer::getIndexFromGrid(int pointX, int pointY) const +{ + const gcn::Rectangle tRect = gcn::Rectangle(0, 0, mGridWidth * mBoxWidth, + mGridHeight * mBoxHeight); + + int index = ((pointY / mBoxHeight) * mGridWidth) + pointX / mBoxWidth; + + if (!tRect.isPointInRect(pointX, pointY) || + index >= (int)mMaxItems || index < 0) + { + index = -1; + } + + return index; +} diff --git a/src/gui/widgets/shortcutcontainer.h b/src/gui/widgets/shortcutcontainer.h new file mode 100644 index 000000000..85d08d0b4 --- /dev/null +++ b/src/gui/widgets/shortcutcontainer.h @@ -0,0 +1,115 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SHORTCUTCONTAINER_H +#define SHORTCUTCONTAINER_H + +#include +#include +#include + +#include "gui/widgets/tab.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * A generic shortcut container. + * + * \ingroup GUI + */ +class ShortcutContainer : public gcn::Widget, + public gcn::WidgetListener, + public gcn::MouseListener +{ + public: + /** + * Constructor. Initializes the shortcut container. + */ + ShortcutContainer(); + + /** + * Destructor. + */ + ~ShortcutContainer() {} + + /** + * Draws the shortcuts + */ + virtual void draw(gcn::Graphics *graphics) = 0; + + /** + * Invoked when a widget changes its size. This is used to determine + * the new height of the container. + */ + virtual void widgetResized(const gcn::Event &event); + + /** + * Handles mouse when dragged. + */ + virtual void mouseDragged(gcn::MouseEvent &event) = 0; + + /** + * Handles mouse when pressed. + */ + virtual void mousePressed(gcn::MouseEvent &event) = 0; + + /** + * Handles mouse release. + */ + virtual void mouseReleased(gcn::MouseEvent &event) = 0; + + int getMaxItems() const + { return mMaxItems; } + + int getBoxWidth() const + { return mBoxWidth; } + + int getBoxHeight() const + { return mBoxHeight; } + + protected: + /** + * Gets the index from the grid provided the point is in an item box. + * + * @param pointX X coordinate of the point. + * @param pointY Y coordinate of the point. + * @return index on success, -1 on failure. + */ + int getIndexFromGrid(int pointX, int pointY) const; + + Image *mBackgroundImg; + + static float mAlpha; + + unsigned mMaxItems; + int mBoxWidth; + int mBoxHeight; + int mCursorPosX, mCursorPosY; + int mGridWidth, mGridHeight; +}; + +#endif diff --git a/src/gui/widgets/slider.cpp b/src/gui/widgets/slider.cpp new file mode 100644 index 000000000..9513d5308 --- /dev/null +++ b/src/gui/widgets/slider.cpp @@ -0,0 +1,298 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/slider.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" + +#include "gui/theme.h" + +#include "resources/image.h" + +Image *Slider::hStart, *Slider::hMid, *Slider::hEnd, *Slider::hGrip; +Image *Slider::vStart, *Slider::vMid, *Slider::vEnd, *Slider::vGrip; +Image *Slider::hStartHi, *Slider::hMidHi, *Slider::hEndHi, *Slider::hGripHi; +Image *Slider::vStartHi, *Slider::vMidHi, *Slider::vEndHi, *Slider::vGripHi; +float Slider::mAlpha = 1.0; +int Slider::mInstances = 0; + +Slider::Slider(double scaleEnd): + gcn::Slider(scaleEnd), + mHasMouse(false) +{ + init(); +} + +Slider::Slider(double scaleStart, double scaleEnd): + gcn::Slider(scaleStart, scaleEnd), + mHasMouse(false) +{ + init(); +} + +Slider::~Slider() +{ + mInstances--; + + if (mInstances == 0) + { + delete hStart; + delete hMid; + delete hEnd; + delete hGrip; + delete vStart; + delete vMid; + delete vEnd; + delete vGrip; + delete hStartHi; + delete hMidHi; + delete hEndHi; + delete hGripHi; + delete vStartHi; + delete vMidHi; + delete vEndHi; + delete vGripHi; + } +} + +void Slider::init() +{ + setFrameSize(0); + + // Load resources + if (mInstances == 0) + { + int x, y, w, h, o1, o2; + + Image *slider = Theme::getImageFromTheme("slider.png"); + Image *sliderHi = Theme::getImageFromTheme("slider_hilight.png"); + + x = 0; y = 0; + w = 15; h = 6; + o1 = 4; o2 = 11; + if (slider) + { + hStart = slider->getSubImage(x, y, o1 - x, h); + hMid = slider->getSubImage(o1, y, o2 - o1, h); + hEnd = slider->getSubImage(o2, y, w - o2 + x, h); + } + else + { + hStart = 0; + hMid = 0; + hEnd = 0; + } + if (sliderHi) + { + hStartHi = sliderHi->getSubImage(x, y, o1 - x, h); + hMidHi = sliderHi->getSubImage(o1, y, o2 - o1, h); + hEndHi = sliderHi->getSubImage(o2, y, w - o2 + x, h); + } + else + { + hStartHi = 0; + hMidHi = 0; + hEndHi = 0; + } + + x = 6; y = 8; + w = 9; h = 10; + if (slider) + hGrip = slider->getSubImage(x, y, w, h); + else + hGrip = 0; + if (sliderHi) + hGripHi = sliderHi->getSubImage(x, y, w, h); + else + hGripHi = 0; + + x = 0; y = 6; + w = 6; h = 21; + o1 = 10; o2 = 18; + if (slider) + { + vStart = slider->getSubImage(x, y, w, o1 - y); + vMid = slider->getSubImage(x, o1, w, o2 - o1); + vEnd = slider->getSubImage(x, o2, w, h - o2 + y); + } + else + { + vStart = 0; + vMid = 0; + vEnd = 0; + } + if (sliderHi) + { + vStartHi = sliderHi->getSubImage(x, y, w, o1 - y); + vMidHi = sliderHi->getSubImage(x, o1, w, o2 - o1); + vEndHi = sliderHi->getSubImage(x, o2, w, h - o2 + y); + } + else + { + vStartHi = 0; + vMidHi = 0; + vEndHi = 0; + } + + x = 6; y = 8; + w = 9; h = 10; + if (slider) + vGrip = slider->getSubImage(x, y, w, h); + else + vGrip = 0; + + if (sliderHi) + vGripHi = sliderHi->getSubImage(x, y, w, h); + else + vGripHi = 0; + + if (slider) + slider->decRef(); + if (sliderHi) + sliderHi->decRef(); + } + + mInstances++; + + if (hGrip) + setMarkerLength(hGrip->getWidth()); +} + +void Slider::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (alpha != mAlpha) + { + mAlpha = alpha; + if (hStart) + hStart->setAlpha(mAlpha); + if (hMid) + hMid->setAlpha(mAlpha); + if (hEnd) + hEnd->setAlpha(mAlpha); + if (hGrip) + hGrip->setAlpha(mAlpha); + if (hStartHi) + hStartHi->setAlpha(mAlpha); + if (hMidHi) + hMidHi->setAlpha(mAlpha); + if (hEndHi) + hEndHi->setAlpha(mAlpha); + if (hGripHi) + hGripHi->setAlpha(mAlpha); + + if (vStart) + vStart->setAlpha(mAlpha); + if (vMid) + vMid->setAlpha(mAlpha); + if (vEnd) + vEnd->setAlpha(mAlpha); + if (vGrip) + vGrip->setAlpha(mAlpha); + if (vStartHi) + vStartHi->setAlpha(mAlpha); + if (vMidHi) + vMidHi->setAlpha(mAlpha); + if (vEndHi) + vEndHi->setAlpha(mAlpha); + if (vGripHi) + vGripHi->setAlpha(mAlpha); + } + +} + +void Slider::draw(gcn::Graphics *graphics) +{ + if (!hStart || !hStartHi) + return; + + int w = getWidth(); + int h = getHeight(); + int x = 0; + int y = mHasMouse ? (h - hStartHi->getHeight()) / 2 : + (h - hStart->getHeight()) / 2; + + updateAlpha(); + + if (!mHasMouse) + { + static_cast(graphics)->drawImage(hStart, x, y); + + w -= hStart->getWidth() + hEnd->getWidth(); + x += hStart->getWidth(); + + if (hMid) + { + static_cast(graphics)-> + drawImagePattern(hMid, x, y, w, hMid->getHeight()); + } + + x += w; + if (hEnd) + static_cast(graphics)->drawImage(hEnd, x, y); + } + else + { + static_cast(graphics)->drawImage(hStartHi, x, y); + + w -= hStartHi->getWidth(); + if (hEndHi) + w -= hEndHi->getWidth(); + x += hStartHi->getWidth(); + + if (hMidHi) + { + static_cast(graphics)-> + drawImagePattern(hMidHi, x, y, w, hMidHi->getHeight()); + } + + x += w; + if (hEndHi) + static_cast(graphics)->drawImage(hEndHi, x, y); + } + + drawMarker(graphics); +} + +void Slider::drawMarker(gcn::Graphics *graphics) +{ + if (!(mHasMouse?hGripHi:hGrip)) + return; + + static_cast(graphics)-> + drawImage(mHasMouse?hGripHi:hGrip, getMarkerPosition(), + (getHeight() - (mHasMouse?hGripHi:hGrip)->getHeight()) / 2); +} + +void Slider::mouseEntered(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = true; +} + +void Slider::mouseExited(gcn::MouseEvent& event _UNUSED_) +{ + mHasMouse = false; +} + diff --git a/src/gui/widgets/slider.h b/src/gui/widgets/slider.h new file mode 100644 index 000000000..be27b73f1 --- /dev/null +++ b/src/gui/widgets/slider.h @@ -0,0 +1,98 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SLIDER_H +#define SLIDER_H + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; + +/** + * Slider widget. Same as the Guichan slider but with custom look. + * + * \ingroup GUI + */ +class Slider : public gcn::Slider +{ + public: + /** + * Constructor with scale start equal to 0. + */ + Slider(double scaleEnd = 1.0); + + /** + * Constructor. + */ + Slider(double scaleStart, double scaleEnd); + + /** + * Destructor. + */ + ~Slider(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the slider. + */ + void draw(gcn::Graphics *graphics); + + /** + * Draws the marker. + */ + void drawMarker(gcn::Graphics *graphics); + + /** + * Called when the mouse enteres the widget area. + */ + void mouseEntered(gcn::MouseEvent& event); + + /** + * Called when the mouse leaves the widget area. + */ + void mouseExited(gcn::MouseEvent& event); + + private: + /** + * Used to initialize instances. + */ + void init(); + + static Image *hStart, *hMid, *hEnd, *hGrip; + static Image *vStart, *vMid, *vEnd, *vGrip; + static Image *hStartHi, *hMidHi, *hEndHi, *hGripHi; + static Image *vStartHi, *vMidHi, *vEndHi, *vGripHi; + bool mHasMouse; + static float mAlpha; + static int mInstances; +}; + +#endif diff --git a/src/gui/widgets/spellshortcutcontainer.cpp b/src/gui/widgets/spellshortcutcontainer.cpp new file mode 100644 index 000000000..18482369d --- /dev/null +++ b/src/gui/widgets/spellshortcutcontainer.cpp @@ -0,0 +1,285 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "gui/widgets/spellshortcutcontainer.h" + +#include "gui/inventorywindow.h" +#include "gui/okdialog.h" +#include "gui/palette.h" +#include "gui/shortcutwindow.h" +#include "gui/spellpopup.h" +#include "gui/viewport.h" +#include "gui/textcommandeditor.h" +#include "gui/theme.h" + +#include "configuration.h" +#include "graphics.h" +#include "inventory.h" +#include "spellshortcut.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "spellmanager.h" +#include "log.h" + +#include "resources/image.h" +#include "textcommand.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +SpellShortcutContainer::SpellShortcutContainer(): + ShortcutContainer(), + mSpellClicked(false), + mSpellMoved(NULL) +{ + mBoxWidth = mBoxWidth; + + addMouseListener(this); + addWidgetListener(this); + + mSpellPopup = new SpellPopup; + + mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); + if (spellShortcut) + mMaxItems = spellShortcut->getSpellsCount(); + else + mMaxItems = 0; + + if (mBackgroundImg) + { + mBackgroundImg->setAlpha(Client::getGuiAlpha()); + mBoxHeight = mBackgroundImg->getHeight(); + mBoxWidth = mBackgroundImg->getWidth(); + } + else + { + mBoxHeight = 1; + mBoxWidth = 1; + } +} + +SpellShortcutContainer::~SpellShortcutContainer() +{ + if (mBackgroundImg) + mBackgroundImg->decRef(); + mBackgroundImg = 0; + delete mSpellPopup; + mSpellPopup = 0; +} + +void SpellShortcutContainer::draw(gcn::Graphics *graphics) +{ + if (!spellShortcut) + return; + + if (Client::getGuiAlpha() != mAlpha) + { + mAlpha = Client::getGuiAlpha(); + if (mBackgroundImg) + mBackgroundImg->setAlpha(mAlpha); + } + + Graphics *g = static_cast(graphics); + + graphics->setColor(gcn::Color(0, 0, 0, 255)); + graphics->setFont(getFont()); + + int selectedId = spellShortcut->getSelectedItem(); + g->setColor(Theme::getThemeColor(Theme::TEXT)); + + for (unsigned i = 0; i < mMaxItems; i++) + { + const int itemX = (i % mGridWidth) * mBoxWidth; + const int itemY = (i / mGridWidth) * mBoxHeight; + + g->drawImage(mBackgroundImg, itemX, itemY); + + int itemId = spellShortcut->getItem(i); + if (selectedId >= 0 && itemId == selectedId) + { + g->drawRectangle(gcn::Rectangle( + itemX + 1, itemY + 1, + mBoxWidth - 1, mBoxHeight - 1)); + } + + if (!spellManager) + continue; + + TextCommand *spell = spellManager->getSpell(itemId); + if (spell) + { + if (!spell->isEmpty()) + { + Image* image = spell->getImage(); + + if (image) + { + image->setAlpha(1.0f); + g->drawImage(image, itemX, itemY); + } + } + + g->drawText(spell->getSymbol(), itemX + 2, + itemY + mBoxHeight / 2, gcn::Graphics::LEFT); + } + } + + if (mSpellMoved) + { + // Draw the item image being dragged by the cursor. + } + +} + +void SpellShortcutContainer::mouseDragged(gcn::MouseEvent &event) +{ + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (!mSpellMoved && mSpellClicked) + { + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = spellShortcut->getItem(index); + + if (itemId < 0) + return; + } + if (mSpellMoved) + { + mCursorPosX = event.getX(); + mCursorPosY = event.getY(); + } + } +} + +void SpellShortcutContainer::mousePressed(gcn::MouseEvent &event) +{ + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + // Stores the selected item if theirs one. + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + } + else if (event.getButton() == gcn::MouseEvent::MIDDLE) + { + if (!spellShortcut || !spellManager) + return; + + const int itemId = spellShortcut->getItem(index); + spellManager->invoke(itemId); + } +} + +void SpellShortcutContainer::mouseReleased(gcn::MouseEvent &event) +{ + if (!spellShortcut || !spellManager) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = spellShortcut->getItem(index); + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + if (itemId < 0) + return; + + const int selectedId = spellShortcut->getSelectedItem(); + + if (selectedId != itemId) + { + TextCommand *spell = spellManager->getSpell(itemId); + if (spell && !spell->isEmpty()) + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num]) + { + itemShortcut[num]->setItemSelected( + spell->getId() + SPELL_MIN_ID); + } + spellShortcut->setItemSelected(spell->getId()); + } + } + else + { + int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < SHORTCUT_TABS && itemShortcut[num]) + itemShortcut[num]->setItemSelected(-1); + spellShortcut->setItemSelected(-1); + } + } + else if (event.getButton() == gcn::MouseEvent::RIGHT) + { + TextCommand *spell = NULL; + if (itemId >= 0) + spell = spellManager->getSpell(itemId); + + if (spell && viewport) + viewport->showSpellPopup(spell); + } +} + +// Show ItemTooltip +void SpellShortcutContainer::mouseMoved(gcn::MouseEvent &event) +{ + if (!mSpellPopup || !spellShortcut || !spellManager) + return; + + const int index = getIndexFromGrid(event.getX(), event.getY()); + + if (index == -1) + return; + + const int itemId = spellShortcut->getItem(index); + + mSpellPopup->setVisible(false); + TextCommand *spell = spellManager->getSpell(itemId); + if (spell && !spell->isEmpty()) + { + mSpellPopup->setItem(spell); + mSpellPopup->view(viewport->getMouseX(), viewport->getMouseY()); + } + else + { + mSpellPopup->setVisible(false); + } +} + +// Hide SpellTooltip +void SpellShortcutContainer::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + mSpellPopup->setVisible(false); +} diff --git a/src/gui/widgets/spellshortcutcontainer.h b/src/gui/widgets/spellshortcutcontainer.h new file mode 100644 index 000000000..8f1c4b221 --- /dev/null +++ b/src/gui/widgets/spellshortcutcontainer.h @@ -0,0 +1,88 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPELLSHORTCUTCONTAINER_H +#define SPELLSHORTCUTCONTAINER_H + +#include + +#include "gui/widgets/shortcutcontainer.h" +//#include "textcommand.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Image; +class SpellPopup; +class TextCommand; + +/** + * An item shortcut container. Used to quickly use items. + * + * \ingroup GUI + */ +class SpellShortcutContainer : public ShortcutContainer +{ + public: + /** + * Constructor. Initializes the graphic. + */ + SpellShortcutContainer(); + + /** + * Destructor. + */ + virtual ~SpellShortcutContainer(); + + /** + * Draws the items. + */ + void draw(gcn::Graphics *graphics); + + /** + * Handles mouse when dragged. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Handles mouse when pressed. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Handles mouse release. + */ + void mouseReleased(gcn::MouseEvent &event); + + private: + void mouseExited(gcn::MouseEvent &event); + void mouseMoved(gcn::MouseEvent &event); + + bool mSpellClicked; + TextCommand *mSpellMoved; + SpellPopup *mSpellPopup; +}; + +#endif diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp new file mode 100644 index 000000000..43b1d154e --- /dev/null +++ b/src/gui/widgets/tab.cpp @@ -0,0 +1,196 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/tab.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/tabbedarea.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +#include + +int Tab::mInstances = 0; +float Tab::mAlpha = 1.0; + +enum +{ + TAB_STANDARD = 0, // 0 + TAB_HIGHLIGHTED, // 1 + TAB_SELECTED, // 2 + TAB_UNUSED, // 3 + TAB_COUNT // 4 - Must be last. +}; + +struct TabData +{ + char const *file; + int gridX; + int gridY; +}; + +static TabData const data[TAB_COUNT] = +{ + { "tab.png", 0, 0 }, + { "tab_hilight.png", 9, 4 }, + { "tabselected.png", 16, 19 }, + { "tab.png", 25, 23 } +}; + +ImageRect Tab::tabImg[TAB_COUNT]; + +Tab::Tab() : gcn::Tab(), + mTabColor(&Theme::getThemeColor(Theme::TAB)) +{ + init(); +} + +Tab::~Tab() +{ + mInstances--; + + if (mInstances == 0) + { + for (int mode = 0; mode < TAB_COUNT; mode++) + for_each(tabImg[mode].grid, tabImg[mode].grid + 9, dtor()); + } +} + +void Tab::init() +{ + setFocusable(false); + setFrameSize(0); + mFlash = 0; + + if (mInstances == 0) + { + // Load the skin + Image *tab[TAB_COUNT]; + + int a, x, y, mode; + + for (mode = 0; mode < TAB_COUNT; mode++) + { + tab[mode] = Theme::getImageFromTheme(data[mode].file); + a = 0; + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + tabImg[mode].grid[a] = tab[mode]->getSubImage( + data[x].gridX, data[y].gridY, + data[x + 1].gridX - data[x].gridX + 1, + data[y + 1].gridY - data[y].gridY + 1); + if (tabImg[mode].grid[a]) + tabImg[mode].grid[a]->setAlpha(mAlpha); + a++; + } + } + if (tab[mode]) + tab[mode]->decRef(); + } + } + mInstances++; +} + +void Tab::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + // TODO We don't need to do this for every tab on every draw + // Maybe use a config listener to do it as the value changes. + if (alpha != mAlpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + for (int t = 0; t < TAB_COUNT; t++) + { + if (tabImg[t].grid[a]) + tabImg[t].grid[a]->setAlpha(mAlpha); + } + } + } +} + +void Tab::draw(gcn::Graphics *graphics) +{ + int mode = TAB_STANDARD; + + // check which type of tab to draw + if (mTabbedArea) + { + mLabel->setForegroundColor(*mTabColor); + if (mTabbedArea->isTabSelected(this)) + { + mode = TAB_SELECTED; + // if tab is selected, it doesnt need to highlight activity + mFlash = 0; + } + else if (mHasMouse) + { + mode = TAB_HIGHLIGHTED; + } + + switch (mFlash) + { + case 1: + mLabel->setForegroundColor(Theme::getThemeColor( + Theme::TAB_FLASH)); + break; + case 2: + mLabel->setForegroundColor(Theme::getThemeColor( + Theme::TAB_PLAYER_FLASH)); + break; + default: + break; + } + } + + updateAlpha(); + + // draw tab + static_cast(graphics)-> + drawImageRect(0, 0, getWidth(), getHeight(), tabImg[mode]); + + // draw label + drawChildren(graphics); +} + +void Tab::setTabColor(const gcn::Color *color) +{ + mTabColor = color; +} + +void Tab::setFlash(int flash) +{ + mFlash = flash; +} diff --git a/src/gui/widgets/tab.h b/src/gui/widgets/tab.h new file mode 100644 index 000000000..b76717bcd --- /dev/null +++ b/src/gui/widgets/tab.h @@ -0,0 +1,80 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TAB_H +#define TAB_H + +#include + +class ImageRect; +class TabbedArea; + +/** + * A tab, the same as the Guichan tab in 0.8, but extended to allow + * transparency. + */ +class Tab : public gcn::Tab +{ + public: + Tab(); + ~Tab(); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draw the tabbed area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Set the normal color fo the tab's text. + */ + void setTabColor(const gcn::Color *color); + + /** + * Set tab flashing state + */ + void setFlash(int flash); + + int getFlash() + { return mFlash; } + + protected: + friend class TabbedArea; + virtual void setCurrent() + { } + + private: + /** Load images if no other instances exist yet */ + void init(); + + static ImageRect tabImg[4]; /**< Tab state graphics */ + static int mInstances; /**< Number of tab instances */ + static float mAlpha; + + const gcn::Color *mTabColor; + int mFlash; +}; + +#endif diff --git a/src/gui/widgets/tabbedarea.cpp b/src/gui/widgets/tabbedarea.cpp new file mode 100644 index 000000000..232664860 --- /dev/null +++ b/src/gui/widgets/tabbedarea.cpp @@ -0,0 +1,221 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/tabbedarea.h" + +#include "gui/widgets/tab.h" + +#include "log.h" + +#include + +TabbedArea::TabbedArea() : gcn::TabbedArea() +{ + mWidgetContainer->setOpaque(false); + addWidgetListener(this); + + widgetResized(NULL); +} + +int TabbedArea::getNumberOfTabs() const +{ + return static_cast(mTabs.size()); +} + +Tab *TabbedArea::getTab(const std::string &name) const +{ + TabContainer::const_iterator itr = mTabs.begin(), itr_end = mTabs.end(); + while (itr != itr_end) + { + if ((*itr).first->getCaption() == name) + return static_cast((*itr).first); + + ++itr; + } + return NULL; +} + +void TabbedArea::draw(gcn::Graphics *graphics) +{ + if (mTabs.empty()) + return; + + drawChildren(graphics); +} + +gcn::Widget *TabbedArea::getWidget(const std::string &name) const +{ + TabContainer::const_iterator itr = mTabs.begin(), itr_end = mTabs.end(); + while (itr != itr_end) + { + if ((*itr).first->getCaption() == name) + return (*itr).second; + + ++itr; + } + + return NULL; +} + +gcn::Widget *TabbedArea::getCurrentWidget() +{ + gcn::Tab *tab = getSelectedTab(); + + if (tab) + return getWidget(tab->getCaption()); + else + return NULL; +} + +void TabbedArea::addTab(gcn::Tab* tab, gcn::Widget* widget) +{ + if (!tab || !widget) + return; + + gcn::TabbedArea::addTab(tab, widget); + + int width = getWidth() - 2 * getFrameSize(); + int height = getHeight() - 2 * getFrameSize() - mTabContainer->getHeight(); + widget->setSize(width, height); +} + +void TabbedArea::addTab(const std::string &caption, gcn::Widget *widget) +{ + Tab *tab = new Tab; + tab->setCaption(caption); + mTabsToDelete.push_back(tab); + + addTab(tab, widget); +} + +void TabbedArea::removeTab(Tab *tab) +{ + int tabIndexToBeSelected = -1; + + if (tab == mSelectedTab) + { + int index = getSelectedTabIndex(); + + if (index == static_cast(mTabs.size()) - 1 && mTabs.size() == 1) + tabIndexToBeSelected = -1; + else + tabIndexToBeSelected = index - 1; + } + + TabContainer::iterator iter; + for (iter = mTabs.begin(); iter != mTabs.end(); iter++) + { + if (iter->first == tab) + { + mTabContainer->remove(tab); + mTabs.erase(iter); + break; + } + } + + std::vector::iterator iter2; + for (iter2 = mTabsToDelete.begin(); iter2 != mTabsToDelete.end(); iter2++) + { + if (*iter2 == tab) + { + mTabsToDelete.erase(iter2); + delete tab; + break; + } + } + + if (tabIndexToBeSelected >= (signed)mTabs.size()) + tabIndexToBeSelected = mTabs.size() - 1; + if (tabIndexToBeSelected < -1) + tabIndexToBeSelected = -1; + + if (tabIndexToBeSelected == -1) + { + mSelectedTab = 0; + mWidgetContainer->clear(); + } + else + { + setSelectedTab(tabIndexToBeSelected); + } + + adjustSize(); + adjustTabPositions(); +} + +void TabbedArea::logic() +{ + logicChildren(); +} + +void TabbedArea::mousePressed(gcn::MouseEvent &mouseEvent) +{ + if (mouseEvent.isConsumed()) + return; + + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + gcn::Widget *widget = mTabContainer->getWidgetAt(mouseEvent.getX(), + mouseEvent.getY()); + gcn::Tab *tab = dynamic_cast(widget); + + if (tab) + { + setSelectedTab(tab); + requestFocus(); + } + } +} + +void TabbedArea::setSelectedTab(gcn::Tab *tab) +{ + gcn::TabbedArea::setSelectedTab(tab); + + Tab *newTab = dynamic_cast(tab); + + if (newTab) + newTab->setCurrent(); + + widgetResized(NULL); +} + +void TabbedArea::widgetResized(const gcn::Event &event _UNUSED_) +{ + int width = getWidth() - 2 * getFrameSize() + - 2 * mWidgetContainer->getFrameSize(); + int height = getHeight() - 2 * getFrameSize() - mWidgetContainer->getY() + - 2 * mWidgetContainer->getFrameSize(); + mWidgetContainer->setSize(width, height); + + gcn::Widget *w = getCurrentWidget(); + if (w) + w->setSize(width, height); +} + +/* +void TabbedArea::moveLeft(gcn::Tab *tab) +{ +} + +void TabbedArea::moveRight(gcn::Tab *tab) +{ +} +*/ diff --git a/src/gui/widgets/tabbedarea.h b/src/gui/widgets/tabbedarea.h new file mode 100644 index 000000000..de2ae4b0a --- /dev/null +++ b/src/gui/widgets/tabbedarea.h @@ -0,0 +1,129 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TABBEDAREA_H +#define TABBEDAREA_H + +#include +#include +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Tab; + +/** + * A tabbed area, the same as the guichan tabbed area in 0.8, but extended + */ +class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener +{ + public: + /** + * Constructor. + */ + TabbedArea(); + + /** + * Draw the tabbed area. + */ + void draw(gcn::Graphics *graphics); + + /** + * Return how many tabs have been created. + * + * @todo Remove this method when upgrading to Guichan 0.9.0 + */ + int getNumberOfTabs() const; + + /** + * Return tab with specified name as caption. + */ + Tab *getTab(const std::string &name) const; + + /** + * Returns the widget with the tab that has specified caption + */ + gcn::Widget *getWidget(const std::string &name) const; + + /** + * Returns the widget for the current tab + */ + gcn::Widget *getCurrentWidget(); + + using gcn::TabbedArea::addTab; + + /** + * Add a tab. Overridden since it needs to size the widget. + * + * @param tab The tab widget for the tab. + * @param widget The widget to view when the tab is selected. + */ + void addTab(gcn::Tab* tab, gcn::Widget* widget); + + /** + * Add a tab. Overridden since it needs to create an instance of Tab + * instead of gcn::Tab. + * + * @param caption The Caption to display + * @param widget The widget to show when tab is selected + */ + void addTab(const std::string &caption, gcn::Widget *widget); + + /** + * Overload the remove tab function as it's broken in guichan 0.8. + */ + void removeTab(Tab *tab); + + /** + * Overload the logic function since it's broken in guichan 0.8. + */ + void logic(); + + int getContainerHeight() const + { return mWidgetContainer->getHeight(); } + + using gcn::TabbedArea::setSelectedTab; + + void setSelectedTab(gcn::Tab *tab); + + void widgetResized(const gcn::Event &event); + +/* + void moveLeft(gcn::Tab *tab); + + void moveRight(gcn::Tab *tab); +*/ + // Inherited from MouseListener + + void mousePressed(gcn::MouseEvent &mouseEvent); + + private: + typedef std::vector< std::pair > TabContainer; +}; + +#endif diff --git a/src/gui/widgets/table.cpp b/src/gui/widgets/table.cpp new file mode 100644 index 000000000..39ef719ef --- /dev/null +++ b/src/gui/widgets/table.cpp @@ -0,0 +1,585 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/table.h" + +#include "client.h" +#include "configuration.h" + +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "utils/dtor.h" + +#include +#include +#include + +float GuiTable::mAlpha = 1.0; + +class GuiTableActionListener : public gcn::ActionListener +{ +public: + GuiTableActionListener(GuiTable *_table, gcn::Widget *_widget, + int _row, int _column); + + virtual ~GuiTableActionListener(); + + virtual void action(const gcn::ActionEvent& actionEvent); + +protected: + GuiTable *mTable; + int mRow; + int mColumn; + gcn::Widget *mWidget; +}; + + +GuiTableActionListener::GuiTableActionListener(GuiTable *table, + gcn::Widget *widget, int row, + int column) : + mTable(table), + mRow(row), + mColumn(column), + mWidget(widget) +{ + if (widget) + { + widget->addActionListener(this); + widget->_setParent(table); + } +} + +GuiTableActionListener::~GuiTableActionListener() +{ + if (mWidget) + { + mWidget->removeActionListener(this); + mWidget->_setParent(NULL); + } +} + +void GuiTableActionListener::action(const gcn::ActionEvent + &actionEvent _UNUSED_) +{ + mTable->setSelected(mRow, mColumn); + mTable->distributeActionEvent(); +} + + +GuiTable::GuiTable(TableModel *initial_model, gcn::Color background, + bool opacity) : + mLinewiseMode(false), + mWrappingEnabled(false), + mOpaque(opacity), + mBackgroundColor(background), + mModel(NULL), + mSelectedRow(0), + mSelectedColumn(0), + mTopWidget(NULL) +{ + setModel(initial_model); + setFocusable(true); + + addMouseListener(this); + addKeyListener(this); +} + +GuiTable::~GuiTable() +{ + uninstallActionListeners(); + delete mModel; + mModel = 0; +} + +TableModel *GuiTable::getModel() const +{ + return mModel; +} + +void GuiTable::setModel(TableModel *new_model) +{ + if (mModel) + { + uninstallActionListeners(); + mModel->removeListener(this); + } + + mModel = new_model; + installActionListeners(); + + if (new_model) + { + new_model->installListener(this); + recomputeDimensions(); + } +} + +void GuiTable::recomputeDimensions() +{ + if (!mModel) + return; + + int rows_nr = mModel->getRows(); + int columns_nr = mModel->getColumns(); + int width = 0; + int height = 0; + + if (mSelectedRow >= rows_nr) + mSelectedRow = rows_nr - 1; + + if (mSelectedColumn >= columns_nr) + mSelectedColumn = columns_nr - 1; + + for (int i = 0; i < columns_nr; i++) + width += getColumnWidth(i); + + height = getRowHeight() * rows_nr; + + setWidth(width); + setHeight(height); +} + +void GuiTable::setSelected(int row, int column) +{ + mSelectedColumn = column; + mSelectedRow = row; +} + +int GuiTable::getSelectedRow() const +{ + return mSelectedRow; +} + +int GuiTable::getSelectedColumn() const +{ + return mSelectedColumn; +} + +void GuiTable::setLinewiseSelection(bool linewise) +{ + mLinewiseMode = linewise; +} + +int GuiTable::getRowHeight() const +{ + if (mModel) + return mModel->getRowHeight() + 1; // border + else + return 0; +} + +int GuiTable::getColumnWidth(int i) const +{ + if (mModel) + return mModel->getColumnWidth(i) + 1; // border + else + return 0; +} + +void GuiTable::setSelectedRow(int selected) +{ + if (!mModel) + { + mSelectedRow = -1; + } + else + { + if (selected < 0 && !mWrappingEnabled) + { + mSelectedRow = -1; + } + else if (selected >= mModel->getRows() && mWrappingEnabled) + { + mSelectedRow = 0; + } + else if ((selected >= mModel->getRows() && !mWrappingEnabled) || + (selected < 0 && mWrappingEnabled)) + { + mSelectedRow = mModel->getRows() - 1; + } + else + { + mSelectedRow = selected; + } + } +} + +void GuiTable::setSelectedColumn(int selected) +{ + if (!mModel) + { + mSelectedColumn = -1; + } + else + { + if ((selected >= mModel->getColumns() && mWrappingEnabled) || + (selected < 0 && !mWrappingEnabled)) + { + mSelectedColumn = 0; + } + else if ((selected >= mModel->getColumns() && !mWrappingEnabled) || + (selected < 0 && mWrappingEnabled)) + { + mSelectedColumn = mModel->getColumns() - 1; + } + else + { + mSelectedColumn = selected; + } + } +} + +void GuiTable::uninstallActionListeners() +{ + delete_all(mActionListeners); + mActionListeners.clear(); +} + +void GuiTable::installActionListeners() +{ + if (!mModel) + return; + + int rows = mModel->getRows(); + int columns = mModel->getColumns(); + + for (int row = 0; row < rows; ++row) + { + for (int column = 0; column < columns; ++column) + { + gcn::Widget *widget = mModel->getElementAt(row, column); + if (widget) + { + mActionListeners.push_back(new GuiTableActionListener( + this, widget, row, column)); + } + } + } + + _setFocusHandler(_getFocusHandler()); // propagate focus handler to widgets +} + +// -- widget ops +void GuiTable::draw(gcn::Graphics* graphics) +{ + if (!mModel || !getRowHeight()) + return; + + if (Client::getGuiAlpha() != mAlpha) + mAlpha = Client::getGuiAlpha(); + + if (mOpaque) + { + graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND, + static_cast(mAlpha * 255.0f))); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + // First, determine how many rows we need to draw, and where we should start. + int first_row = -(getY() / getRowHeight()); + + if (first_row < 0) + first_row = 0; + + int rows_nr = 1 + (getHeight() / getRowHeight()); // May overestimate + // by one. + + int max_rows_nr = mModel->getRows() - first_row; // clip if neccessary: + if (max_rows_nr < rows_nr) + rows_nr = max_rows_nr; + + // Now determine the first and last column + // Take the easy way out; these are usually bounded and all visible. + int first_column = 0; + int last_column1 = mModel->getColumns(); + + // Set up everything for drawing + int height = getRowHeight(); + int y_offset = first_row * height; + + for (int r = first_row; r < first_row + rows_nr; ++r) + { + int x_offset = 0; + + for (int c = first_column; c + 1 <= last_column1; ++c) + { + gcn::Widget *widget = mModel->getElementAt(r, c); + int width = getColumnWidth(c); + if (widget) + { + gcn::Rectangle bounds(x_offset, y_offset, width, height); + + if (widget == mTopWidget) + { + bounds.height = widget->getHeight(); + bounds.width = widget->getWidth(); + } + + widget->setDimension(bounds); + + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, + static_cast(mAlpha * 255.0f))); + + if (mLinewiseMode && r == mSelectedRow && c == 0) + { + graphics->fillRectangle(gcn::Rectangle(0, y_offset, + getWidth(), height)); + } + else if (!mLinewiseMode && + c == mSelectedColumn && r == mSelectedRow) + { + graphics->fillRectangle(gcn::Rectangle(x_offset, y_offset, + width, height)); + } + + graphics->pushClipArea(bounds); + widget->draw(graphics); + graphics->popClipArea(); + } + + x_offset += width; + } + + y_offset += height; + } + + if (mTopWidget) + { + gcn::Rectangle bounds = mTopWidget->getDimension(); + graphics->pushClipArea(bounds); + mTopWidget->draw(graphics); + graphics->popClipArea(); + } +} + +void GuiTable::moveToTop(gcn::Widget *widget) +{ + gcn::Widget::moveToTop(widget); + mTopWidget = widget; +} + +void GuiTable::moveToBottom(gcn::Widget *widget) +{ + gcn::Widget::moveToBottom(widget); + if (widget == mTopWidget) + mTopWidget = NULL; +} + +gcn::Rectangle GuiTable::getChildrenArea() const +{ + return gcn::Rectangle(0, 0, getWidth(), getHeight()); +} + +// -- KeyListener notifications +void GuiTable::keyPressed(gcn::KeyEvent& keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ENTER || key.getValue() == Key::SPACE) + { + distributeActionEvent(); + keyEvent.consume(); + } + else if (key.getValue() == Key::UP) + { + setSelectedRow(mSelectedRow - 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::DOWN) + { + setSelectedRow(mSelectedRow + 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::LEFT) + { + setSelectedColumn(mSelectedColumn - 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::RIGHT) + { + setSelectedColumn(mSelectedColumn + 1); + keyEvent.consume(); + } + else if (key.getValue() == Key::HOME) + { + setSelectedRow(0); + setSelectedColumn(0); + keyEvent.consume(); + } + else if (key.getValue() == Key::END && mModel) + { + setSelectedRow(mModel->getRows() - 1); + setSelectedColumn(mModel->getColumns() - 1); + keyEvent.consume(); + } +} + +// -- MouseListener notifications +void GuiTable::mousePressed(gcn::MouseEvent& mouseEvent) +{ + if (!mModel) + return; + + if (mouseEvent.getButton() == gcn::MouseEvent::LEFT) + { + int row = getRowForY(mouseEvent.getY()); + int column = getColumnForX(mouseEvent.getX()); + + if (row > -1 && column > -1 && + row < mModel->getRows() && column < mModel->getColumns()) + { + mSelectedColumn = column; + mSelectedRow = row; + } + + distributeActionEvent(); + } +} + +void GuiTable::mouseWheelMovedUp(gcn::MouseEvent& mouseEvent) +{ + if (isFocused()) + { + if (getSelectedRow() > 0 || (getSelectedRow() == 0 + && mWrappingEnabled)) + { + setSelectedRow(getSelectedRow() - 1); + } + + mouseEvent.consume(); + } +} + +void GuiTable::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) +{ + if (isFocused()) + { + setSelectedRow(getSelectedRow() + 1); + + mouseEvent.consume(); + } +} + +void GuiTable::mouseDragged(gcn::MouseEvent& mouseEvent) +{ + if (mouseEvent.getButton() != gcn::MouseEvent::LEFT) + return; + + // Make table selection update on drag + const int x = std::max(0, mouseEvent.getX()); + const int y = std::max(0, mouseEvent.getY()); + + setSelectedRow(getRowForY(y)); + setSelectedColumn(getColumnForX(x)); +} + +// -- TableModelListener notifications +void GuiTable::modelUpdated(bool completed) +{ + if (completed) + { + recomputeDimensions(); + installActionListeners(); + } + else + { // before the update? + mTopWidget = NULL; // No longer valid in general + uninstallActionListeners(); + } +} + +gcn::Widget *GuiTable::getWidgetAt(int x, int y) const +{ + int row = getRowForY(y); + int column = getColumnForX(x); + + if (mTopWidget && mTopWidget->getDimension().isPointInRect(x, y)) + return mTopWidget; + + if (mModel && row > -1 && column > -1) + { + gcn::Widget *w = mModel->getElementAt(row, column); + if (w && w->isFocusable()) + return w; + else + return NULL; // Grab the event locally + } + else + return NULL; +} + +int GuiTable::getRowForY(int y) const +{ + int row = -1; + + if (getRowHeight() > 0) + row = y / getRowHeight(); + + if (!mModel || row < 0 || row >= mModel->getRows()) + return -1; + else + return row; +} + +int GuiTable::getColumnForX(int x) const +{ + if (!mModel) + return -1; + + int column; + int delta = 0; + + for (column = 0; column < mModel->getColumns(); column++) + { + delta += getColumnWidth(column); + if (x <= delta) + break; + } + + if (column < 0 || column >= mModel->getColumns()) + return -1; + else + return column; +} + +void GuiTable::_setFocusHandler(gcn::FocusHandler* focusHandler) +{ +// add check for focusHandler. may be need remove it? + + if (!mModel || !focusHandler) + return; + + gcn::Widget::_setFocusHandler(focusHandler); + + if (mModel) + { + for (int r = 0; r < mModel->getRows(); ++r) + { + for (int c = 0; c < mModel->getColumns(); ++c) + { + gcn::Widget *w = mModel->getElementAt(r, c); + if (w) + w->_setFocusHandler(focusHandler); + } + } + } +} diff --git a/src/gui/widgets/table.h b/src/gui/widgets/table.h new file mode 100644 index 000000000..61c7302b2 --- /dev/null +++ b/src/gui/widgets/table.h @@ -0,0 +1,195 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TABLE_H +#define TABLE_H + +#include "tablemodel.h" + +#include +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class GuiTableActionListener; + +/** + * A table, with rows and columns made out of sub-widgets. Largely inspired by + * (and can be thought of as a generalisation of) the guichan listbox + * implementation. + * + * Normally you want this within a ScrollArea. + * + * \ingroup GUI + */ +class GuiTable : public gcn::Widget, + public gcn::MouseListener, + public gcn::KeyListener, + public TableModelListener +{ + // so that the action listener can call distributeActionEvent + friend class GuiTableActionListener; + +public: + GuiTable(TableModel * initial_model = NULL, + gcn::Color background = 0xffffff, + bool opacity = true); + + virtual ~GuiTable(); + + /** + * Retrieves the active table model + */ + TableModel *getModel() const; + + /** + * Sets the table model + * + * Note that actions issued by widgets returned from the model will update + * the table selection, but only AFTER any event handlers installed within + * the widget have been triggered. To be notified after such an update, add + * an action listener to the table instead. + */ + void setModel(TableModel *m); + + void setSelected(int row, int column); + + int getSelectedRow() const; + + int getSelectedColumn() const; + + void setSelectedRow(int selected); + + void setSelectedColumn(int selected); + + bool isWrappingEnabled() const + { return mWrappingEnabled; } + + void setWrappingEnabled(bool wrappingEnabled) + { mWrappingEnabled = wrappingEnabled; } + + gcn::Rectangle getChildrenArea() const; + + /** + * Toggle whether to use linewise selection mode, in which the table selects + * an entire line at a time, rather than a single cell. + * + * Note that column information is tracked even in linewise selection mode; + * this mode therefore only affects visualisation. + * + * Disabled by default. + * + * \param linewise: Whether to enable linewise selection mode + */ + void setLinewiseSelection(bool linewise); + + // Inherited from Widget + virtual void draw(gcn::Graphics* graphics); + + virtual gcn::Widget *getWidgetAt(int x, int y) const; + + virtual void moveToTop(gcn::Widget *child); + + virtual void moveToBottom(gcn::Widget *child); + + virtual void _setFocusHandler(gcn::FocusHandler* focusHandler); + + // Inherited from KeyListener + virtual void keyPressed(gcn::KeyEvent& keyEvent); + + /** + * Sets the table to be opaque, that is sets the table + * to display its background. + * + * @param opaque True if the table should be opaque, false otherwise. + */ + virtual void setOpaque(bool opaque) + { mOpaque = opaque; } + + /** + * Checks if the table is opaque, that is if the table area displays its + * background. + * + * @return True if the table is opaque, false otherwise. + */ + virtual bool isOpaque() const + { return mOpaque; } + + // Inherited from MouseListener + virtual void mousePressed(gcn::MouseEvent& mouseEvent); + + virtual void mouseWheelMovedUp(gcn::MouseEvent& mouseEvent); + + virtual void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent); + + virtual void mouseDragged(gcn::MouseEvent& mouseEvent); + + // Constraints inherited from TableModelListener + virtual void modelUpdated(bool); + +protected: + /** Frees all action listeners on inner widgets. */ + virtual void uninstallActionListeners(); + /** Installs all action listeners on inner widgets. */ + virtual void installActionListeners(); + + virtual int getRowHeight() const; + virtual int getColumnWidth(int i) const; + +private: + int getRowForY(int y) const; // -1 on error + int getColumnForX(int x) const; // -1 on error + void recomputeDimensions(); + bool mLinewiseMode; + bool mWrappingEnabled; + bool mOpaque; + + static float mAlpha; + + /** + * Holds the background color of the table. + */ + gcn::Color mBackgroundColor; + + TableModel *mModel; + + int mSelectedRow; + int mSelectedColumn; + + /** Number of frames to skip upwards when drawing the selected widget. */ + int mPopFramesNr; + + /** If someone moves a fresh widget to the top, we must display it. */ + gcn::Widget *mTopWidget; + + /** Vector for compactness; used as a list in practice. */ + std::vector mActionListeners; +}; + + +#endif // TABLE_H diff --git a/src/gui/widgets/tablemodel.cpp b/src/gui/widgets/tablemodel.cpp new file mode 100644 index 000000000..f1d583ef6 --- /dev/null +++ b/src/gui/widgets/tablemodel.cpp @@ -0,0 +1,173 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/tablemodel.h" + +#include "utils/dtor.h" + +#include + +void TableModel::installListener(TableModelListener *listener) +{ + if (listener) + listeners.insert(listener); +} + +void TableModel::removeListener(TableModelListener *listener) +{ + if (listener) + listeners.erase(listener); +} + +void TableModel::signalBeforeUpdate() +{ + for (std::set::const_iterator it = listeners.begin(); + it != listeners.end(); it++) + { + (*it)->modelUpdated(false); + } +} + +void TableModel::signalAfterUpdate() +{ + for (std::set::const_iterator it = listeners.begin(); + it != listeners.end(); it++) + { + if (*it) + (*it)->modelUpdated(true); + } +} + + +#define WIDGET_AT(row, column) (((row) * mColumns) + (column)) +#define DYN_SIZE(h) ((h) >= 0) + +StaticTableModel::StaticTableModel(int row, int column) : + mRows(row), + mColumns(column), + mHeight(1) +{ + mTableModel.resize(row * column, NULL); + mWidths.resize(column, 1); +} + +StaticTableModel::~StaticTableModel() +{ + delete_all(mTableModel); + mTableModel.clear(); +} + +void StaticTableModel::resize() +{ + mRows = getRows(); + mColumns = getColumns(); + mTableModel.resize(mRows * mColumns, NULL); +} + +void StaticTableModel::set(int row, int column, gcn::Widget *widget) +{ + if (!widget || row >= mRows || row < 0 + || column >= mColumns || column < 0) + { + // raise exn? + return; + } + + if (DYN_SIZE(mHeight) + && widget->getHeight() > mHeight) + { + mHeight = widget->getHeight(); + } + + if (DYN_SIZE(mWidths[column]) + && widget->getWidth() > mWidths[column]) + { + mWidths[column] = widget->getWidth(); + } + + signalBeforeUpdate(); + + delete mTableModel[WIDGET_AT(row, column)]; + + mTableModel[WIDGET_AT(row, column)] = widget; + + signalAfterUpdate(); +} + +gcn::Widget *StaticTableModel::getElementAt(int row, int column) const +{ + return mTableModel[WIDGET_AT(row, column)]; +} + +void StaticTableModel::fixColumnWidth(int column, int width) +{ + if (width < 0 || column < 0 || column >= mColumns) + return; + + mWidths[column] = -width; // Negate to tag as fixed +} + +void StaticTableModel::fixRowHeight(int height) +{ + if (height < 0) + return; + + mHeight = -height; +} + +int StaticTableModel::getRowHeight() const +{ + return abs(mHeight); +} + +int StaticTableModel::getColumnWidth(int column) const +{ + if (column < 0 || column >= mColumns) + return 0; + + return abs(mWidths[column]); +} + +int StaticTableModel::getRows() const +{ + return mRows; +} + +int StaticTableModel::getColumns() const +{ + return mColumns; +} + +int StaticTableModel::getWidth() const +{ + int width = 0; + + for (unsigned int i = 0; i < mWidths.size(); i++) + width += mWidths[i]; + + return width; +} + +int StaticTableModel::getHeight() const +{ + return mColumns * mHeight; +} + diff --git a/src/gui/widgets/tablemodel.h b/src/gui/widgets/tablemodel.h new file mode 100644 index 000000000..2b8729341 --- /dev/null +++ b/src/gui/widgets/tablemodel.h @@ -0,0 +1,149 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TABLE_MODEL_H +#define TABLE_MODEL_H + +#include + +#include +#include + +class TableModelListener +{ +public: + /** + * Must be invoked by the TableModel whenever a global change is about to + * occur or has occurred (e.g., when a row or column is being removed or + * added). + * + * This method is triggered twice, once before and once after the update. + * + * \param completed whether we are signalling the end of the update + */ + virtual void modelUpdated(bool completed) = 0; + + virtual ~TableModelListener() {} +}; + +/** + * A model for a regular table of widgets. + */ +class TableModel +{ +public: + virtual ~TableModel() + { } + + /** + * Determines the number of rows (lines) in the table + */ + virtual int getRows() const = 0; + + /** + * Determines the number of columns in each row + */ + virtual int getColumns() const = 0; + + /** + * Determines the height for each row + */ + virtual int getRowHeight() const = 0; + + /** + * Determines the width of each individual column + */ + virtual int getColumnWidth(int index) const = 0; + + /** + * Retrieves the widget stored at the specified location within the table. + */ + virtual gcn::Widget *getElementAt(int row, int column) const = 0; + + virtual void installListener(TableModelListener *listener); + + virtual void removeListener(TableModelListener *listener); + +protected: + /** + * Tells all listeners that the table is about to see an update + */ + virtual void signalBeforeUpdate(); + + /** + * Tells all listeners that the table has seen an update + */ + virtual void signalAfterUpdate(); + +private: + std::set listeners; +}; + + +class StaticTableModel : public TableModel +{ +public: + StaticTableModel(int width, int height); + virtual ~StaticTableModel(); + + /** + * Inserts a widget into the table model. + * The model is resized to accomodate the widget's width and height, + * unless column width / row height have been fixed. + */ + virtual void set(int row, int column, gcn::Widget *widget); + + /** + * Fixes the column width for a given column; this overrides dynamic width + * inference. + * + * Semantics are undefined for width 0. + */ + virtual void fixColumnWidth(int column, int width); + + /** + * Fixes the row height; this overrides dynamic height inference. + * + * Semantics are undefined for width 0. + */ + virtual void fixRowHeight(int height); + + /** + * Resizes the table model + */ + virtual void resize(); + + virtual int getRows() const; + virtual int getColumns() const; + virtual int getRowHeight() const; + virtual int getWidth() const; + virtual int getHeight() const; + virtual int getColumnWidth(int index) const; + virtual gcn::Widget *getElementAt(int row, int column) const; + +protected: + int mRows, mColumns; + int mHeight; + std::vector mTableModel; + std::vector mWidths; +}; + +#endif // TABLE_MODEL_H diff --git a/src/gui/widgets/textbox.cpp b/src/gui/widgets/textbox.cpp new file mode 100644 index 000000000..f248f35d2 --- /dev/null +++ b/src/gui/widgets/textbox.cpp @@ -0,0 +1,149 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/textbox.h" + +#include "gui/theme.h" + +#include + +#include + +TextBox::TextBox() : + mTextColor(&Theme::getThemeColor(Theme::TEXT)) +{ + setOpaque(false); + setFrameSize(0); + mMinWidth = getWidth(); +} + +void TextBox::setTextWrapped(const std::string &text, int minDimension) +{ + // Make sure parent scroll area sets width of this widget + if (getParent()) + getParent()->logic(); + + // Take the supplied minimum dimension as a starting point and try to beat it + mMinWidth = minDimension; + + std::stringstream wrappedStream; + std::string::size_type spacePos, newlinePos, lastNewlinePos = 0; + int minWidth = 0; + int xpos; + + spacePos = text.rfind(" ", text.size()); + + if (spacePos != std::string::npos) + { + const std::string word = text.substr(spacePos + 1); + const int length = getFont()->getWidth(word); + + if (length > mMinWidth) + mMinWidth = length; + } + + do + { + // Determine next piece of string to wrap + newlinePos = text.find("\n", lastNewlinePos); + + if (newlinePos == std::string::npos) + newlinePos = text.size(); + + std::string line = + text.substr(lastNewlinePos, newlinePos - lastNewlinePos); + std::string::size_type lastSpacePos = 0; + xpos = 0; + + do + { + spacePos = line.find(" ", lastSpacePos); + + if (spacePos == std::string::npos) + spacePos = line.size(); + + std::string word = + line.substr(lastSpacePos, spacePos - lastSpacePos); + + int width = getFont()->getWidth(word); + + if (xpos == 0 && width > mMinWidth) + { + mMinWidth = width; + xpos = width; + wrappedStream << word; + } + else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <= + mMinWidth) + { + xpos += getFont()->getWidth(" ") + width; + wrappedStream << " " << word; + } + else if (lastSpacePos == 0) + { + xpos += width; + wrappedStream << word; + } + else + { + if (xpos > minWidth) + minWidth = xpos; + + // The window wasn't big enough. Resize it and try again. + if (minWidth > mMinWidth) + { + mMinWidth = minWidth; + wrappedStream.clear(); + wrappedStream.str(""); + spacePos = 0; + lastNewlinePos = 0; + newlinePos = text.find("\n", lastNewlinePos); + if (newlinePos == std::string::npos) + newlinePos = text.size(); + line = text.substr(lastNewlinePos, newlinePos - + lastNewlinePos); + width = 0; + break; + } + else + { + wrappedStream << "\n" << word; + } + xpos = width; + } + lastSpacePos = spacePos + 1; + } + while (spacePos != line.size()); + + if (text.find("\n", lastNewlinePos) != std::string::npos) + wrappedStream << "\n"; + + lastNewlinePos = newlinePos + 1; + } + while (newlinePos != text.size()); + + if (xpos > minWidth) + minWidth = xpos; + + mMinWidth = minWidth; + + gcn::TextBox::setText(wrappedStream.str()); +} diff --git a/src/gui/widgets/textbox.h b/src/gui/widgets/textbox.h new file mode 100644 index 000000000..dffaf2736 --- /dev/null +++ b/src/gui/widgets/textbox.h @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXTBOX_H +#define TEXTBOX_H + +#include + +/** + * A text box, meant to be used inside a scroll area. Same as the Guichan text + * box except this one doesn't have a background or border, instead completely + * relying on the scroll area. + * + * \ingroup GUI + */ +class TextBox : public gcn::TextBox +{ + public: + /** + * Constructor. + */ + TextBox(); + + inline void setTextColor(const gcn::Color *color) + { mTextColor = color; } + + /** + * Sets the text after wrapping it to the current width of the widget. + */ + void setTextWrapped(const std::string &text, int minDimension); + + /** + * Get the minimum text width for the text box. + */ + int getMinWidth() const + { return mMinWidth; } + + /** + * Draws the text. + */ + inline void draw(gcn::Graphics *graphics) + { + setForegroundColor(*mTextColor); + gcn::TextBox::draw(graphics); + } + + private: + int mMinWidth; + const gcn::Color *mTextColor; +}; + +#endif diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp new file mode 100644 index 000000000..9a5b2de33 --- /dev/null +++ b/src/gui/widgets/textfield.cpp @@ -0,0 +1,306 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/textfield.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" + +#include "gui/palette.h" +#include "gui/sdlinput.h" +#include "gui/theme.h" + +#include "resources/image.h" + +#include "utils/copynpaste.h" +#include "utils/dtor.h" + +#include + +#undef DELETE //Win32 compatibility hack + +int TextField::instances = 0; +float TextField::mAlpha = 1.0; +ImageRect TextField::skin; + +TextField::TextField(const std::string &text, bool loseFocusOnTab, + gcn::ActionListener* listener, std::string eventId): + gcn::TextField(text), + mNumeric(false) +{ + setFrameSize(2); + + mLoseFocusOnTab = loseFocusOnTab; + + if (instances == 0) + { + // Load the skin + Image *textbox = Theme::getImageFromTheme("deepbox.png"); + int gridx[4] = {0, 3, 28, 31}; + int gridy[4] = {0, 3, 28, 31}; + int a = 0, x, y; + + for (y = 0; y < 3; y++) + { + for (x = 0; x < 3; x++) + { + if (textbox) + { + skin.grid[a] = textbox->getSubImage( + gridx[x], gridy[y], + gridx[x + 1] - gridx[x] + 1, + gridy[y + 1] - gridy[y] + 1); + if (skin.grid[a]) + skin.grid[a]->setAlpha(Client::getGuiAlpha()); + } + else + { + skin.grid[a] = 0; + } + a++; + } + } + + if (textbox) + textbox->decRef(); + } + + instances++; + + if (!eventId.empty()) + setActionEventId(eventId); + + if (listener) + addActionListener(listener); +} + +TextField::~TextField() +{ + instances--; + + if (instances == 0) + for_each(skin.grid, skin.grid + 9, dtor()); +} + +void TextField::updateAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + + if (alpha != mAlpha) + { + mAlpha = alpha; + for (int a = 0; a < 9; a++) + { + if (skin.grid[a]) + skin.grid[a]->setAlpha(mAlpha); + } + } +} + +void TextField::draw(gcn::Graphics *graphics) +{ + updateAlpha(); + + if (isFocused()) + { + drawCaret(graphics, + getFont()->getWidth(mText.substr(0, mCaretPosition)) - + mXScroll); + } + + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->setFont(getFont()); + graphics->drawText(mText, 1 - mXScroll, 1); +} + +void TextField::drawFrame(gcn::Graphics *graphics) +{ + //updateAlpha(); -> Not useful... + + int w, h, bs; + bs = getFrameSize(); + w = getWidth() + bs * 2; + h = getHeight() + bs * 2; + + static_cast(graphics)->drawImageRect(0, 0, w, h, skin); +} + +void TextField::setNumeric(bool numeric) +{ + mNumeric = numeric; + if (!numeric) + return; + + const char *text = mText.c_str(); + for (const char *textPtr = text; *textPtr; ++textPtr) + { + if (*textPtr < '0' || *textPtr > '9') + { + setText(mText.substr(0, textPtr - text)); + return; + } + } +} + +int TextField::getValue() const +{ + if (!mNumeric) + return 0; + + int value = atoi(mText.c_str()); + if (value < mMinimum) + return mMinimum; + + if (value > mMaximum) + return mMaximum; + + return value; +} + +void TextField::keyPressed(gcn::KeyEvent &keyEvent) +{ + int val = keyEvent.getKey().getValue(); + + if (val >= 32) + { + int l; + if (val < 128) + l = 1; // 0xxxxxxx + else if (val < 0x800) + l = 2; // 110xxxxx 10xxxxxx + else if (val < 0x10000) + l = 3; // 1110xxxx 10xxxxxx 10xxxxxx + else + l = 4; // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + + char buf[4]; + for (int i = 0; i < l; ++i) + { + buf[i] = static_cast(val >> (6 * (l - i - 1))); + if (i > 0) + buf[i] = static_cast((buf[i] & 63) | 128); + } + + if (l > 1) + buf[0] |= static_cast(255 << (8 - l)); + + mText.insert(mCaretPosition, std::string(buf, buf + l)); + mCaretPosition += l; + } + + /* In UTF-8, 10xxxxxx is only used for inner parts of characters. So skip + them when processing key presses. */ + + switch (val) + { + case Key::LEFT: + { + while (mCaretPosition > 0) + { + --mCaretPosition; + if ((mText[mCaretPosition] & 192) != 128) + break; + } + } break; + + case Key::RIGHT: + { + unsigned sz = static_cast(mText.size()); + while (mCaretPosition < sz) + { + ++mCaretPosition; + if (mCaretPosition == sz || + (mText[mCaretPosition] & 192) != 128) + { + break; + } + } + } break; + + case Key::DELETE: + { + unsigned sz = static_cast(mText.size()); + while (mCaretPosition < sz) + { + --sz; + mText.erase(mCaretPosition, 1); + if (mCaretPosition == sz || + (mText[mCaretPosition] & 192) != 128) + { + break; + } + } + } break; + + case Key::BACKSPACE: + { + while (mCaretPosition > 0) + { + --mCaretPosition; + int v = mText[mCaretPosition]; + mText.erase(mCaretPosition, 1); + if ((v & 192) != 128) + break; + } + } break; + + case Key::ENTER: + distributeActionEvent(); + break; + + case Key::HOME: + mCaretPosition = 0; + break; + + case Key::END: + mCaretPosition = static_cast(mText.size()); + break; + + case Key::TAB: + if (mLoseFocusOnTab) + return; + break; + + case 22: // Control code 22, SYNCHRONOUS IDLE, sent on Ctrl+v + handlePaste(); + break; + default: + break; + } + + keyEvent.consume(); + fixScroll(); +} + +void TextField::handlePaste() +{ + std::string text = getText(); + std::string::size_type caretPos = getCaretPosition(); + + if (RetrieveBuffer(text, caretPos)) + { + setText(text); + setCaretPosition(static_cast(caretPos)); + } +} diff --git a/src/gui/widgets/textfield.h b/src/gui/widgets/textfield.h new file mode 100644 index 000000000..b894fdc85 --- /dev/null +++ b/src/gui/widgets/textfield.h @@ -0,0 +1,110 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXTFIELD_H +#define TEXTFIELD_H + +#include + +class ImageRect; +class TextField; + +/** + * A text field. + * + * \ingroup GUI + */ +class TextField : public gcn::TextField +{ + public: + /** + * Constructor, initializes the text field with the given string. + */ + TextField(const std::string &text = "", bool loseFocusOnTab = true, + gcn::ActionListener* listener = NULL, + std::string eventId = ""); + + ~TextField(); + + /** + * Draws the text field. + */ + virtual void draw(gcn::Graphics *graphics); + + /** + * Update the alpha value to the graphic components. + */ + void updateAlpha(); + + /** + * Draws the background and border. + */ + void drawFrame(gcn::Graphics *graphics); + + /** + * Determine whether the field should be numeric or not + */ + void setNumeric(bool numeric); + + /** + * Set the range on the field if it is numeric + */ + void setRange(int min, int max) + { + mMinimum = min; + mMaximum = max; + } + + /** + * Processes one keypress. + */ + void keyPressed(gcn::KeyEvent &keyEvent); + + /** + * Set the minimum value for a range + */ + void setMinimum(int min) + { mMinimum = min; } + + /** + * Set the maximum value for a range + */ + void setMaximum(int max) + { mMaximum = max; } + + /** + * Return the value for a numeric field + */ + int getValue() const; + + private: + void handlePaste(); + + static int instances; + static float mAlpha; + static ImageRect skin; + bool mNumeric; + int mMinimum; + int mMaximum; + bool mLoseFocusOnTab; +}; + +#endif diff --git a/src/gui/widgets/textpreview.cpp b/src/gui/widgets/textpreview.cpp new file mode 100644 index 000000000..bd38d8a80 --- /dev/null +++ b/src/gui/widgets/textpreview.cpp @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/textpreview.h" + +#include "client.h" +#include "configuration.h" +#include "textrenderer.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/truetypefont.h" + +#include + +float TextPreview::mAlpha = 1.0; + +TextPreview::TextPreview(const std::string &text): + mText(text) +{ + mTextAlpha = false; + mFont = gui->getFont(); + mTextColor = &Theme::getThemeColor(Theme::TEXT); + mTextBGColor = NULL; + mBGColor = &Theme::getThemeColor(Theme::BACKGROUND); + mOpaque = false; +} + +void TextPreview::draw(gcn::Graphics* graphics) +{ + if (Client::getGuiAlpha() != mAlpha) + mAlpha = Client::getGuiAlpha(); + + int alpha = static_cast(mAlpha * 255.0f); + + if (!mTextAlpha) + alpha = 255; + + if (mOpaque) + { + graphics->setColor(gcn::Color(static_cast(mBGColor->r), + static_cast(mBGColor->g), + static_cast(mBGColor->b), + static_cast(mAlpha * 255.0f))); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mTextBGColor && typeid(*mFont) == typeid(TrueTypeFont)) + { + TrueTypeFont *font = static_cast(mFont); + int x = font->getWidth(mText) + 1 + 2 * ((mOutline || mShadow) ? 1 :0); + int y = font->getHeight() + 1 + 2 * ((mOutline || mShadow) ? 1 : 0); + graphics->setColor(gcn::Color(static_cast(mTextBGColor->r), + static_cast(mTextBGColor->g), + static_cast(mTextBGColor->b), + static_cast(mAlpha * 255.0f))); + graphics->fillRectangle(gcn::Rectangle(1, 1, x, y)); + } + + TextRenderer::renderText(graphics, mText, 2, 2, gcn::Graphics::LEFT, + gcn::Color(mTextColor->r, mTextColor->g, + mTextColor->b, alpha), + mFont, mOutline, mShadow); +} diff --git a/src/gui/widgets/textpreview.h b/src/gui/widgets/textpreview.h new file mode 100644 index 000000000..a34ab3853 --- /dev/null +++ b/src/gui/widgets/textpreview.h @@ -0,0 +1,130 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXTPREVIEW_H +#define TEXTPREVIEW_H + +#include +#include +#include + +/** + * Preview widget for particle colors, etc. + */ +class TextPreview : public gcn::Widget +{ + public: + TextPreview(const std::string &text); + + /** + * Sets the color the text is printed in. + * + * @param color the color to set + */ + inline void setTextColor(const gcn::Color *color) + { mTextColor = color; } + + /** + * Sets the text to use the set alpha value. + * + * @param alpha whether to use alpha values for the text or not + */ + inline void useTextAlpha(bool alpha) + { mTextAlpha = alpha; } + + /** + * Sets the color the text background is drawn in. This is only the + * rectangle directly behind the text, not to full widget. + * + * @param color the color to set + */ + inline void setTextBGColor(const gcn::Color *color) + { mTextBGColor = color; } + + /** + * Sets the background color of the widget. + * + * @param color the color to set + */ + inline void setBGColor(const gcn::Color *color) + { mBGColor = color; } + + /** + * Sets the font to render the text in. + * + * @param font the font to use. + */ + inline void setFont(gcn::Font *font) + { mFont = font; } + + /** + * Sets whether to use a shadow while rendering. + * + * @param shadow true, if a shadow is wanted, false else + */ + inline void setShadow(bool shadow) + { mShadow = shadow; } + + /** + * Sets whether to use an outline while rendering. + * + * @param outline true, if an outline is wanted, false else + */ + inline void setOutline(bool outline) + { mOutline = outline; } + + /** + * Widget's draw method. Does the actual job. + * + * @param graphics graphics to draw into + */ + void draw(gcn::Graphics *graphics); + + /** + * Set opacity for this widget (whether or not to show the background + * color) + * + * @param opaque Whether the widget should be opaque or not + */ + void setOpaque(bool opaque) + { mOpaque = opaque; } + + /** + * Gets opacity for this widget (whether or not the background color + * is shown below the widget) + */ + bool isOpaque() const + { return mOpaque; } + + private: + gcn::Font *mFont; + std::string mText; + const gcn::Color *mTextColor; + const gcn::Color *mBGColor; + const gcn::Color *mTextBGColor; + static float mAlpha; + bool mTextAlpha; + bool mOpaque; + bool mShadow; + bool mOutline; +}; + +#endif diff --git a/src/gui/widgets/tradetab.cpp b/src/gui/widgets/tradetab.cpp new file mode 100644 index 000000000..fb4a57fd5 --- /dev/null +++ b/src/gui/widgets/tradetab.cpp @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/tradetab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "net/net.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +TradeTab::TradeTab() : + ChatTab(_("Trade")) +{ +} + +TradeTab::~TradeTab() +{ +} + +void TradeTab::handleInput(const std::string &msg) +{ + std::string str = "\302\202" + msg; + ChatTab::handleInput(str); +} + +void TradeTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log(std::string("#Trade"), std::string(msg)); +} diff --git a/src/gui/widgets/tradetab.h b/src/gui/widgets/tradetab.h new file mode 100644 index 000000000..fceeb1e40 --- /dev/null +++ b/src/gui/widgets/tradetab.h @@ -0,0 +1,50 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRADETAB_H +#define TRADETAB_H + +#include "gui/widgets/chattab.h" + +/** + * A tab for a party chat channel. + */ +class TradeTab : public ChatTab +{ + public: + TradeTab(); + + ~TradeTab(); + + int getType() const + { return ChatTab::TAB_TRADE; } + + void saveToLogFile(std::string &msg); + + protected: + void handleInput(const std::string &msg); +}; + +extern TradeTab *tradeChatTab; +#endif + + + diff --git a/src/gui/widgets/vertcontainer.cpp b/src/gui/widgets/vertcontainer.cpp new file mode 100644 index 000000000..4dac2a617 --- /dev/null +++ b/src/gui/widgets/vertcontainer.cpp @@ -0,0 +1,53 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/vertcontainer.h" + +VertContainer::VertContainer(int spacing): + mSpacing(spacing), + mCount(0) +{ + addWidgetListener(this); +} + +void VertContainer::add(gcn::Widget *widget) +{ + if (!widget) + return; + + Container::add(widget); + widget->setPosition(0, mCount * mSpacing); + widget->setSize(getWidth(), mSpacing); + mCount++; + setHeight(mCount * mSpacing); +} + +void VertContainer::clear() +{ + Container::clear(); + + mCount = 0; +} + +void VertContainer::widgetResized(const gcn::Event &event _UNUSED_) +{ + for (WidgetListIterator it = mWidgets.begin(); it != mWidgets.end(); it++) + (*it)->setWidth(getWidth()); +} diff --git a/src/gui/widgets/vertcontainer.h b/src/gui/widgets/vertcontainer.h new file mode 100644 index 000000000..fe62d8c0e --- /dev/null +++ b/src/gui/widgets/vertcontainer.h @@ -0,0 +1,52 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUI_VERTCONTAINER_H +#define GUI_VERTCONTAINER_H + +#include "gui/widgets/container.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +/** + * A widget container. + * + * This container places it's contents veritcally. + */ +class VertContainer : public Container, public gcn::WidgetListener +{ + public: + VertContainer(int spacing); + virtual void add(gcn::Widget *widget); + virtual void clear(); + void widgetResized(const gcn::Event &event); + + private: + int mSpacing; + int mCount; +}; + +#endif diff --git a/src/gui/widgets/whispertab.cpp b/src/gui/widgets/whispertab.cpp new file mode 100644 index 000000000..f0c347b59 --- /dev/null +++ b/src/gui/widgets/whispertab.cpp @@ -0,0 +1,164 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "whispertab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/theme.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +WhisperTab::WhisperTab(const std::string &nick) : + ChatTab(nick), + mNick(nick) +{ + setTabColor(&Theme::getThemeColor(Theme::WHISPER)); +} + +WhisperTab::~WhisperTab() +{ + if (chatWindow) + chatWindow->removeWhisper(mNick); +} + +void WhisperTab::handleInput(const std::string &msg) +{ +// if (msg.empty()) +// { +// chatLog(_("Cannot send empty chat!"), BY_SERVER, false); +// return; +// } + + if (chatWindow) + { + Net::getChatHandler()->privateMessage(mNick, + chatWindow->doReplace(msg)); + } + else + { + Net::getChatHandler()->privateMessage(mNick, msg); + } + + if (player_node) + chatLog(player_node->getName(), msg); + else + chatLog("?", msg); +} + +void WhisperTab::handleCommand(const std::string &msg) +{ + if (msg == "close") + { + delete this; + return; + } + + std::string::size_type pos = msg.find(' '); + std::string type(msg, 0, pos); + std::string args(msg, pos == std::string::npos + ? msg.size() : pos + 1); + + if (type == "me") + { + std::string str = strprintf("*%s*", args.c_str()); + Net::getChatHandler()->privateMessage(mNick, str); + if (player_node) + chatLog(player_node->getName(), str); + else + chatLog("?", str); + } + else + { + ChatTab::handleCommand(msg); + } +} + +void WhisperTab::showHelp() +{ + chatLog(_("/ignore > Ignore the other player")); + chatLog(_("/unignore > Stop ignoring the other player")); + chatLog(_("/close > Close the whisper tab")); +} + +bool WhisperTab::handleCommand(const std::string &type, + const std::string &args) +{ + if (type == "help") + { + if (args == "close") + { + chatLog(_("Command: /close")); + chatLog(_("This command closes the current whisper tab.")); + } + else if (args == "ignore") + { + chatLog(_("Command: /ignore")); + chatLog(_("This command ignores the other player regardless of " + "current relations.")); + } + else if (args == "unignore") + { + chatLog(_("Command: /unignore ")); + chatLog(_("This command stops ignoring the other player if they " + "are being ignored.")); + } + else + { + return false; + } + } + else if (type == "close") + { + delete this; + if (chatWindow) + chatWindow->defaultTab(); + } + else if (type == "ignore") + { + if (commandHandler) + commandHandler->handleIgnore(mNick, this); + } + else if (type == "unignore") + { + if (commandHandler) + commandHandler->handleUnignore(mNick, this); + } + else + { + return false; + } + + return true; +} + +void WhisperTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log(getNick(), msg); +} diff --git a/src/gui/widgets/whispertab.h b/src/gui/widgets/whispertab.h new file mode 100644 index 000000000..89e70695b --- /dev/null +++ b/src/gui/widgets/whispertab.h @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WHISPERTAB_H +#define WHISPERTAB_H + +#include "chattab.h" + +class Channel; + +/** + * A tab for whispers from a single player. + */ +class WhisperTab : public ChatTab +{ + public: + const std::string &getNick() const { return mNick; } + + void showHelp(); + + bool handleCommand(const std::string &type, + const std::string &args); + + int getType() const + { return ChatTab::TAB_WHISPER; } + + void saveToLogFile(std::string &msg); + + protected: + friend class ChatWindow; + + /** + * Constructor. + * + * @param nick the name of the player this tab is whispering to + */ + WhisperTab(const std::string &nick); + + ~WhisperTab(); + + void handleInput(const std::string &msg); + + void handleCommand(const std::string &msg); + + private: + std::string mNick; +}; + +#endif // CHANNELTAB_H diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp new file mode 100644 index 000000000..6564a8d3f --- /dev/null +++ b/src/gui/widgets/window.cpp @@ -0,0 +1,924 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/window.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/theme.h" +#include "gui/viewport.h" + +#include "gui/widgets/layout.h" +#include "gui/widgets/resizegrip.h" +#include "gui/widgets/windowcontainer.h" + +#include "resources/image.h" + +#include +#include + +int Window::instances = 0; +int Window::mouseResize = 0; + +Window::Window(const std::string &caption, bool modal, Window *parent, + const std::string &skin): + gcn::Window(caption), + mGrip(0), + mParent(parent), + mLayout(NULL), + mWindowName("window"), + mShowTitle(true), + mModal(modal), + mCloseButton(false), + mDefaultVisible(false), + mSaveVisible(false), + mStickyButton(false), + mSticky(false), + mMinWinWidth(100), + mMinWinHeight(40), + mMaxWinWidth(graphics->getWidth()), + mMaxWinHeight(graphics->getHeight()) +{ + logger->log("Window::Window(\"%s\")", caption.c_str()); + + if (!windowContainer) + throw GCN_EXCEPTION("Window::Window(): no windowContainer set"); + + instances++; + + setFrameSize(0); + setPadding(3); + setTitleBarHeight(20); + + // Loads the skin + mSkin = Theme::instance()->load(skin); + + // Add this window to the window container + windowContainer->add(this); + + if (mModal) + { + gui->setCursorType(Gui::CURSOR_POINTER); + requestModalFocus(); + } + + // Windows are invisible by default + setVisible(false); + + addWidgetListener(this); +} + +Window::~Window() +{ + logger->log("Window::~Window(\"%s\")", getCaption().c_str()); + + saveWindowState(); + + delete mLayout; + mLayout = 0; + + while (!mWidgets.empty()) + delete mWidgets.front(); + +// need mWidgets.clean ? + + removeWidgetListener(this); + + instances--; + + if (mSkin) + mSkin->instances--; +} + +void Window::setWindowContainer(WindowContainer *wc) +{ + windowContainer = wc; +} + +void Window::draw(gcn::Graphics *graphics) +{ + if (!mSkin) + return; + + Graphics *g = static_cast(graphics); + + g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + + // Draw title + if (mShowTitle) + { + g->setColor(Theme::getThemeColor(Theme::TEXT)); + g->setFont(getFont()); + g->drawText(getCaption(), 7, 5, gcn::Graphics::LEFT); + } + + // Draw Close Button + if (mCloseButton && mSkin->getCloseImage()) + { + g->drawImage(mSkin->getCloseImage(), + getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(), + getPadding()); + } + + // Draw Sticky Button + if (mStickyButton) + { + Image *button = mSkin->getStickyImage(mSticky); + if (button) + { + int x = getWidth() - button->getWidth() - getPadding(); + if (mCloseButton && mSkin->getCloseImage()) + x -= mSkin->getCloseImage()->getWidth(); + + g->drawImage(button, x, getPadding()); + } + } + + drawChildren(graphics); +} + +void Window::setContentSize(int width, int height) +{ + width = width + 2 * getPadding(); + height = height + getPadding() + getTitleBarHeight(); + + if (getMinWidth() > width) + width = getMinWidth(); + else if (getMaxWidth() < width) + width = getMaxWidth(); + if (getMinHeight() > height) + height = getMinHeight(); + else if (getMaxHeight() < height) + height = getMaxHeight(); + + setSize(width, height); +} + +void Window::setLocationRelativeTo(gcn::Widget *widget) +{ + if (!widget) + return; + + int wx, wy; + int x, y; + + widget->getAbsolutePosition(wx, wy); + getAbsolutePosition(x, y); + + setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), + getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y)); +} + +void Window::setLocationHorisontallyRelativeTo(gcn::Widget *widget) +{ + if (!widget) + return; + + int wx, wy; + int x, y; + + widget->getAbsolutePosition(wx, wy); + getAbsolutePosition(x, y); + + setPosition(getX() + (wx + (widget->getWidth() - getWidth()) / 2 - x), 0); +} + +void Window::setLocationRelativeTo(ImageRect::ImagePosition position, + int offsetX, int offsetY) +{ + if (position == ImageRect::UPPER_LEFT) + { + } + else if (position == ImageRect::UPPER_CENTER) + { + offsetX += (graphics->getWidth() - getWidth()) / 2; + } + else if (position == ImageRect::UPPER_RIGHT) + { + offsetX += graphics->getWidth() - getWidth(); + } + else if (position == ImageRect::LEFT) + { + offsetY += (graphics->getHeight() - getHeight()) / 2; + } + else if (position == ImageRect::CENTER) + { + offsetX += (graphics->getWidth() - getWidth()) / 2; + offsetY += (graphics->getHeight() - getHeight()) / 2; + } + else if (position == ImageRect::RIGHT) + { + offsetX += graphics->getWidth() - getWidth(); + offsetY += (graphics->getHeight() - getHeight()) / 2; + } + else if (position == ImageRect::LOWER_LEFT) + { + offsetY += graphics->getHeight() - getHeight(); + } + else if (position == ImageRect::LOWER_CENTER) + { + offsetX += (graphics->getWidth() - getWidth()) / 2; + offsetY += graphics->getHeight() - getHeight(); + } + else if (position == ImageRect::LOWER_RIGHT) + { + offsetX += graphics->getWidth() - getWidth(); + offsetY += graphics->getHeight() - getHeight(); + } + + setPosition(offsetX, offsetY); +} + +void Window::setMinWidth(int width) +{ + mMinWinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth(); +} + +void Window::setMinHeight(int height) +{ + mMinWinHeight = height > mSkin->getMinHeight() ? + height : mSkin->getMinHeight(); +} + +void Window::setMaxWidth(int width) +{ + mMaxWinWidth = width; +} + +void Window::setMaxHeight(int height) +{ + mMaxWinHeight = height; +} + +void Window::setResizable(bool r) +{ + if (static_cast(mGrip) == r) + return; + + if (r) + { + mGrip = new ResizeGrip; + mGrip->setX(getWidth() - mGrip->getWidth() - getChildrenArea().x); + mGrip->setY(getHeight() - mGrip->getHeight() - getChildrenArea().y); + add(mGrip); + } + else + { + remove(mGrip); + delete mGrip; + mGrip = 0; + } +} + +void Window::widgetResized(const gcn::Event &event _UNUSED_) +{ + const gcn::Rectangle area = getChildrenArea(); + + if (mGrip) + { + mGrip->setPosition(getWidth() - mGrip->getWidth() - area.x, + getHeight() - mGrip->getHeight() - area.y); + } + + if (mLayout) + { + int w = area.width; + int h = area.height; + mLayout->reflow(w, h); + } +} + +void Window::widgetHidden(const gcn::Event &event _UNUSED_) +{ + if (gui) + gui->setCursorType(Gui::CURSOR_POINTER); + + WidgetListIterator it; + + if (!mFocusHandler) + return; + + for (it = mWidgets.begin(); it != mWidgets.end(); it++) + { + if (mFocusHandler->isFocused(*it)) + mFocusHandler->focusNone(); + } +} + +void Window::setCloseButton(bool flag) +{ + mCloseButton = flag; +} + +bool Window::isResizable() const +{ + return mGrip; +} + +void Window::setStickyButton(bool flag) +{ + mStickyButton = flag; +} + +void Window::setSticky(bool sticky) +{ + mSticky = sticky; +} + +void Window::setVisible(bool visible) +{ + setVisible(visible, false); +} + +void Window::setVisible(bool visible, bool forceSticky) +{ + if (visible == isVisible()) + return; // Nothing to do + + // Check if the window is off screen... + if (visible) + checkIfIsOffScreen(); + + gcn::Window::setVisible((!forceSticky && isSticky()) || visible); +} + +void Window::scheduleDelete() +{ + windowContainer->scheduleDelete(this); +} + +void Window::mousePressed(gcn::MouseEvent &event) +{ + // Let Guichan move window to top and figure out title bar drag + gcn::Window::mousePressed(event); + + if (event.getButton() == gcn::MouseEvent::LEFT) + { + const int x = event.getX(); + const int y = event.getY(); + + // Handle close button + if (mCloseButton) + { + Image *img = mSkin->getCloseImage(); + if (img) + { + gcn::Rectangle closeButtonRect( + getWidth() - img->getWidth() + - getPadding(), getPadding(), + img->getWidth(), img->getHeight()); + + if (closeButtonRect.isPointInRect(x, y)) + { + mouseResize = 0; + mMoved = 0; + close(); + return; + } + } + } + + // Handle sticky button + if (mStickyButton) + { + Image *button = mSkin->getStickyImage(mSticky); + if (button) + { + int rx = getWidth() - button->getWidth() - getPadding(); + if (mCloseButton) + { + Image *img = mSkin->getCloseImage(); + if (img) + rx -= img->getWidth(); + } + gcn::Rectangle stickyButtonRect(rx, getPadding(), + button->getWidth(), button->getHeight()); + if (stickyButtonRect.isPointInRect(x, y)) + { + setSticky(!isSticky()); + mouseResize = 0; + mMoved = 0; + return; + } + } + } + + // Handle window resizing + mouseResize = getResizeHandles(event); + mMoved = !mouseResize; + } +} + +void Window::close() +{ + setVisible(false); +} + +void Window::mouseReleased(gcn::MouseEvent &event _UNUSED_) +{ + if (mGrip && mouseResize) + { + mouseResize = 0; + if (gui) + gui->setCursorType(Gui::CURSOR_POINTER); + } + + // This should be the responsibility of Guichan (and is from 0.8.0 on) + mMoved = false; +} + +void Window::mouseExited(gcn::MouseEvent &event _UNUSED_) +{ + if (mGrip && !mouseResize && gui) + gui->setCursorType(Gui::CURSOR_POINTER); +} + +void Window::mouseMoved(gcn::MouseEvent &event) +{ + if (!gui) + return; + + int resizeHandles = getResizeHandles(event); + + // Changes the custom mouse cursor based on it's current position. + switch (resizeHandles) + { + case BOTTOM | RIGHT: + case TOP | LEFT: + gui->setCursorType(Gui::CURSOR_RESIZE_DOWN_RIGHT); + break; + case TOP | RIGHT: + case BOTTOM | LEFT: + gui->setCursorType(Gui::CURSOR_RESIZE_DOWN_LEFT); + break; + case BOTTOM: + case TOP: + gui->setCursorType(Gui::CURSOR_RESIZE_DOWN); + break; + case RIGHT: + case LEFT: + gui->setCursorType(Gui::CURSOR_RESIZE_ACROSS); + break; + default: + gui->setCursorType(Gui::CURSOR_POINTER); + } + + if (viewport) + viewport->hideBeingPopup(); +} + +void Window::mouseDragged(gcn::MouseEvent &event) +{ + // Let Guichan handle title bar drag + gcn::Window::mouseDragged(event); + + // Keep guichan window inside screen when it may be moved + if (isMovable() && mMoved) + { + int newX = std::max(0, getX()); + int newY = std::max(0, getY()); + newX = std::min(graphics->getWidth() - getWidth(), newX); + newY = std::min(graphics->getHeight() - getHeight(), newY); + setPosition(newX, newY); + } + + if (mouseResize && !mMoved) + { + const int dx = event.getX() - mDragOffsetX; + const int dy = event.getY() - mDragOffsetY; + gcn::Rectangle newDim = getDimension(); + + if (mouseResize & (TOP | BOTTOM)) + { + int newHeight = newDim.height + ((mouseResize & TOP) ? -dy : dy); + newDim.height = std::min(mMaxWinHeight, + std::max(mMinWinHeight, newHeight)); + + if (mouseResize & TOP) + newDim.y -= newDim.height - getHeight(); + } + + if (mouseResize & (LEFT | RIGHT)) + { + int newWidth = newDim.width + ((mouseResize & LEFT) ? -dx : dx); + newDim.width = std::min(mMaxWinWidth, + std::max(mMinWinWidth, newWidth)); + + if (mouseResize & LEFT) + newDim.x -= newDim.width - getWidth(); + } + + // Keep guichan window inside screen (supports resizing any side) + if (newDim.x < 0) + { + newDim.width += newDim.x; + newDim.x = 0; + } + if (newDim.y < 0) + { + newDim.height += newDim.y; + newDim.y = 0; + } + if (newDim.x + newDim.width > graphics->getWidth()) + { + newDim.width = graphics->getWidth() - newDim.x; + } + if (newDim.y + newDim.height > graphics->getHeight()) + { + newDim.height = graphics->getHeight() - newDim.y; + } + + // Update mouse offset when dragging bottom or right border + if (mouseResize & BOTTOM) + mDragOffsetY += newDim.height - getHeight(); + + if (mouseResize & RIGHT) + mDragOffsetX += newDim.width - getWidth(); + + // Set the new window and content dimensions + setDimension(newDim); + } +} + +void Window::setModal(bool modal) +{ + if (mModal != modal) + { + mModal = modal; + if (mModal) + { + if (gui) + gui->setCursorType(Gui::CURSOR_POINTER); + requestModalFocus(); + } + else + { + releaseModalFocus(); + } + } +} + +void Window::loadWindowState() +{ + const std::string &name = mWindowName; + assert(!name.empty()); + + setPosition(config.getValueInt(name + "WinX", mDefaultX), + config.getValueInt(name + "WinY", mDefaultY)); + + if (mSaveVisible) + { + setVisible(config.getValueBool(name + + "Visible", mDefaultVisible)); + } + + if (mStickyButton) + { + setSticky(config.getValueBool(name + + "Sticky", isSticky())); + } + + if (mGrip) + { + int width = config.getValueInt(name + "WinWidth", mDefaultWidth); + int height = config.getValueInt(name + "WinHeight", mDefaultHeight); + + if (getMinWidth() > width) + width = getMinWidth(); + else if (getMaxWidth() < width) + width = getMaxWidth(); + if (getMinHeight() > height) + height = getMinHeight(); + else if (getMaxHeight() < height) + height = getMaxHeight(); + + setSize(width, height); + } + else + { + setSize(mDefaultWidth, mDefaultHeight); + } + + // Check if the window is off screen... + checkIfIsOffScreen(); +} + +void Window::saveWindowState() +{ + // Saving X, Y and Width and Height for resizables in the config + if (!mWindowName.empty() && mWindowName != "window") + { + config.setValue(mWindowName + "WinX", getX()); + config.setValue(mWindowName + "WinY", getY()); + + if (mSaveVisible) + config.setValue(mWindowName + "Visible", isVisible()); + + if (mStickyButton) + config.setValue(mWindowName + "Sticky", isSticky()); + + if (mGrip) + { + if (getMinWidth() > getWidth()) + setWidth(getMinWidth()); + else if (getMaxWidth() < getWidth()) + setWidth(getMaxWidth()); + if (getMinHeight() > getHeight()) + setHeight(getMinHeight()); + else if (getMaxHeight() < getHeight()) + setHeight(getMaxHeight()); + + config.setValue(mWindowName + "WinWidth", getWidth()); + config.setValue(mWindowName + "WinHeight", getHeight()); + } + } +} + +void Window::setDefaultSize(int defaultX, int defaultY, + int defaultWidth, int defaultHeight) +{ + if (getMinWidth() > defaultWidth) + defaultWidth = getMinWidth(); + else if (getMaxWidth() < defaultWidth) + defaultWidth = getMaxWidth(); + if (getMinHeight() > defaultHeight) + defaultHeight = getMinHeight(); + else if (getMaxHeight() < defaultHeight) + defaultHeight = getMaxHeight(); + + mDefaultX = defaultX; + mDefaultY = defaultY; + mDefaultWidth = defaultWidth; + mDefaultHeight = defaultHeight; +} + +void Window::setDefaultSize() +{ + mDefaultX = getX(); + mDefaultY = getY(); + mDefaultWidth = getWidth(); + mDefaultHeight = getHeight(); +} + +void Window::setDefaultSize(int defaultWidth, int defaultHeight, + ImageRect::ImagePosition position, + int offsetX, int offsetY) +{ + int x = 0, y = 0; + + if (position == ImageRect::UPPER_LEFT) + { + } + else if (position == ImageRect::UPPER_CENTER) + { + x = (graphics->getWidth() - defaultWidth) / 2; + } + else if (position == ImageRect::UPPER_RIGHT) + { + x = graphics->getWidth() - defaultWidth; + } + else if (position == ImageRect::LEFT) + { + y = (graphics->getHeight() - defaultHeight) / 2; + } + else if (position == ImageRect::CENTER) + { + x = (graphics->getWidth() - defaultWidth) / 2; + y = (graphics->getHeight() - defaultHeight) / 2; + } + else if (position == ImageRect::RIGHT) + { + x = graphics->getWidth() - defaultWidth; + y = (graphics->getHeight() - defaultHeight) / 2; + } + else if (position == ImageRect::LOWER_LEFT) + { + y = graphics->getHeight() - defaultHeight; + } + else if (position == ImageRect::LOWER_CENTER) + { + x = (graphics->getWidth() - defaultWidth) / 2; + y = graphics->getHeight() - defaultHeight; + } + else if (position == ImageRect::LOWER_RIGHT) + { + x = graphics->getWidth() - defaultWidth; + y = graphics->getHeight() - defaultHeight; + } + + mDefaultX = x - offsetX; + mDefaultY = y - offsetY; + mDefaultWidth = defaultWidth; + mDefaultHeight = defaultHeight; +} + +void Window::resetToDefaultSize() +{ + setPosition(mDefaultX, mDefaultY); + setSize(mDefaultWidth, mDefaultHeight); + saveWindowState(); +} + +int Window::getResizeHandles(gcn::MouseEvent &event) +{ + int resizeHandles = 0; + const int y = event.getY(); + + if (mGrip && (y > static_cast(mTitleBarHeight) + || (y < (int)getPadding() && mTitleBarHeight > getPadding()))) + { + const int x = event.getX(); + + if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this) + { + resizeHandles |= (x > getWidth() - resizeBorderWidth) ? RIGHT : + (x < resizeBorderWidth) ? LEFT : 0; + resizeHandles |= (y > getHeight() - resizeBorderWidth) ? BOTTOM : + (y < resizeBorderWidth) ? TOP : 0; + } + + if (event.getSource() == mGrip) + { + mDragOffsetX = x; + mDragOffsetY = y; + resizeHandles |= BOTTOM | RIGHT; + } + } + + return resizeHandles; +} + +bool Window::isResizeAllowed(gcn::MouseEvent &event) +{ + const int y = event.getY(); + + if (mGrip && (y > static_cast(mTitleBarHeight) + || y < (int)getPadding())) + { + const int x = event.getX(); + + if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this) + return true; + + if (event.getSource() == mGrip) + return true; + } + + return false; +} + +int Window::getGuiAlpha() +{ + float alpha = std::max(Client::getGuiAlpha(), + Theme::instance()->getMinimumOpacity()); + return static_cast(alpha * 255.0f); +} + +Layout &Window::getLayout() +{ + if (!mLayout) + mLayout = new Layout; + return *mLayout; +} + +void Window::clearLayout() +{ + clear(); + + // Restore the resize grip + if (mGrip) + add(mGrip); + + // Recreate layout instance when one is present + if (mLayout) + { + delete mLayout; + mLayout = new Layout; + } +} + +LayoutCell &Window::place(int x, int y, gcn::Widget *wg, int w, int h) +{ + add(wg); + return getLayout().place(wg, x, y, w, h); +} + +ContainerPlacer Window::getPlacer(int x, int y) +{ + return ContainerPlacer(this, &getLayout().at(x, y)); +} + +void Window::reflowLayout(int w, int h) +{ + if (!mLayout) + return; + + mLayout->reflow(w, h); + delete mLayout; + mLayout = 0; + setContentSize(w, h); +} + +void Window::redraw() +{ + if (mLayout) + { + const gcn::Rectangle area = getChildrenArea(); + int w = area.width; + int h = area.height; + mLayout->reflow(w, h); + } +} + +void Window::center() +{ + setLocationRelativeTo(getParent()); +} + +void Window::centerHorisontally() +{ + setLocationHorisontallyRelativeTo(getParent()); +} + +void Window::checkIfIsOffScreen(bool partially, bool entirely) +{ + // Move the window onto screen if it has become off screen + // For instance, because of resolution change... + + // First of all, don't deal when a window hasn't got + // any size initialized yet... + if (getWidth() == 0 && getHeight() == 0) + return; + + // Made partially the default behaviour + if (!partially && !entirely) + partially = true; + + // Keep guichan window inside screen (supports resizing any side) + + gcn::Rectangle winDimension = getDimension(); + + if (winDimension.x < 0) + { + winDimension.width += winDimension.x; + winDimension.x = 0; + } + if (winDimension.y < 0) + { + winDimension.height += winDimension.y; + winDimension.y = 0; + } + + // Look if the window is partially off-screen limits... + if (partially) + { + if (winDimension.x + winDimension.width > graphics->getWidth()) + winDimension.x = graphics->getWidth() - winDimension.width; + + if (winDimension.y + winDimension.height > graphics->getHeight()) + winDimension.y = graphics->getHeight() - winDimension.height; + + setDimension(winDimension); + return; + } + + if (entirely) + { + if (winDimension.x > graphics->getWidth()) + winDimension.x = graphics->getWidth() - winDimension.width; + + if (winDimension.y > graphics->getHeight()) + winDimension.y = graphics->getHeight() - winDimension.height; + } + setDimension(winDimension); +} + +gcn::Rectangle Window::getWindowArea() +{ + return gcn::Rectangle(getPadding(), + getPadding(), + getWidth() - getPadding() * 2, + getHeight() - getPadding() * 2); +} \ No newline at end of file diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h new file mode 100644 index 000000000..09e15d3f4 --- /dev/null +++ b/src/gui/widgets/window.h @@ -0,0 +1,441 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WINDOW_H +#define WINDOW_H + +#include "graphics.h" +#include "guichanfwd.h" + +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ContainerPlacer; +class Layout; +class LayoutCell; +class ResizeGrip; +class Skin; +class WindowContainer; + +/** + * A window. This window can be dragged around and has a title bar. Windows are + * invisible by default. + * + * \ingroup GUI + */ +class Window : public gcn::Window, gcn::WidgetListener +{ + public: + /** + * Constructor. Initializes the title to the given text and hooks + * itself into the window container. + * + * @param caption The initial window title, "Window" by default. + * @param modal Block input to other windows. + * @param parent The parent window. This is the window standing above + * this one in the window hiearchy. When reordering, + * a window will never go below its parent window. + * @param skin The location where the window's skin XML can be found. + */ + Window(const std::string &caption = "Window", bool modal = false, + Window *parent = NULL, const std::string &skin = "window.xml"); + + /** + * Destructor. Deletes all the added widgets. + */ + ~Window(); + + /** + * Sets the window container to be used by new windows. + */ + static void setWindowContainer(WindowContainer *windowContainer); + + /** + * Draws the window. + */ + void draw(gcn::Graphics *graphics); + + /** + * Sets the size of this window. + */ + void setContentSize(int width, int height); + + /** + * Sets the location relative to the given widget. + */ + void setLocationRelativeTo(gcn::Widget *widget); + + /** + * Sets the location relative to the given widget (only horisontally) + */ + void setLocationHorisontallyRelativeTo(gcn::Widget *widget); + + /** + * Sets the location relative to the given enumerated position. + */ + void setLocationRelativeTo(ImageRect::ImagePosition position, + int offsetX = 0, int offsetY = 0); + + /** + * Sets whether or not the window can be resized. + */ + void setResizable(bool resize); + + void redraw(); + + /** + * Called whenever the widget changes size. + */ + void widgetResized(const gcn::Event &event); + + /** + * Called whenever the widget is hidden. + */ + virtual void widgetHidden(const gcn::Event& event); + + /** + * Sets whether or not the window has a close button. + */ + void setCloseButton(bool flag); + + /** + * Returns whether the window can be resized. + */ + bool isResizable() const; + + /** + * Sets the minimum width of the window. + */ + void setMinWidth(int width); + + int getMinWidth() const + { return mMinWinWidth; } + + /** + * Sets the minimum height of the window. + */ + void setMinHeight(int height); + + int getMinHeight() const + { return mMinWinHeight; } + + /** + * Sets the maximum width of the window. + */ + void setMaxWidth(int width); + + int getMaxWidth() const + { return mMaxWinWidth; } + + /** + * Sets the minimum height of the window. + */ + void setMaxHeight(int height); + + int getMaxHeight() const + { return mMaxWinHeight; } + + /** + * Sets flag to show a title or not. + */ + void setShowTitle(bool flag) + { mShowTitle = flag; } + + /** + * Sets whether or not the window has a sticky button. + */ + void setStickyButton(bool flag); + + /** + * Sets whether the window is sticky. A sticky window will not have + * its visibility set to false on a general setVisible(false) call. + * Use this to set the default before you call loadWindowState(). + */ + void setSticky(bool sticky); + + /** + * Returns whether the window is sticky. + */ + bool isSticky() const + { return mSticky; } + + /** + * Overloads window setVisible by Guichan to allow sticky window + * handling. + */ + virtual void setVisible(bool visible); + + /** + * Overloads window setVisible by Guichan to allow sticky window + * handling, or not, if you force the sticky state. + */ + void setVisible(bool visible, bool forceSticky); + + /** + * Returns whether the window is visible by default. + */ + bool isDefaultVisible() const + { return mDefaultVisible; } + + /** + * Sets whether the window is visible by default. + */ + void setDefaultVisible(bool save) + { mDefaultVisible = save; } + + /** + * Returns whether the window will save it's visibility. + */ + bool willSaveVisible() const + { return mSaveVisible; } + + /** + * Sets whether the window will save it's visibility. + */ + void setSaveVisible(bool save) + { mSaveVisible = save; } + + /** + * Returns the parent window. + * + * @return The parent window or NULL if there is none. + */ + Window *getParentWindow() const + { return mParent; } + + /** + * Schedule this window for deletion. It will be deleted at the start + * of the next logic update. + */ + void scheduleDelete(); + + /** + * Starts window resizing when appropriate. + */ + void mousePressed(gcn::MouseEvent &event); + + /** + * Implements window resizing and makes sure the window is not + * dragged/resized outside of the screen. + */ + void mouseDragged(gcn::MouseEvent &event); + + /** + * Implements custom cursor image changing context, based on mouse + * relative position. + */ + void mouseMoved(gcn::MouseEvent &event); + + /** + * When the mouse button has been let go, this ensures that the mouse + * custom cursor is restored back to it's standard image. + */ + void mouseReleased(gcn::MouseEvent &event); + + /** + * When the mouse leaves the window this ensures that the custom cursor + * is restored back to it's standard image. + */ + void mouseExited(gcn::MouseEvent &event); + + /** + * Sets the name of the window. This is not the window title. + */ + void setWindowName(const std::string &name) + { mWindowName = name; } + + /** + * Returns the name of the window. This is not the window title. + */ + const std::string &getWindowName() const + { return mWindowName; } + + /** + * Reads the position (and the size for resizable windows) in the + * configuration based on the given string. + * Uses the default values when config values are missing. + * Don't forget to set these default values and resizable before + * calling this function. + */ + void loadWindowState(); + + /** + * Saves the window state so that when the window is reloaded, it'll + * maintain its previous state and location. + */ + void saveWindowState(); + + /** + * Set the default win pos and size. + * (which can be different of the actual ones.) + */ + void setDefaultSize(int defaultX, int defaultY, + int defaultWidth, int defaultHeight); + + /** + * Set the default win pos and size to the current ones. + */ + void setDefaultSize(); + + /** + * Set the default win pos and size. + * (which can be different of the actual ones.) + * This version of setDefaultSize sets the window's position based + * on a relative enumerated position, rather than a coordinate position. + */ + void setDefaultSize(int defaultWidth, int defaultHeight, + ImageRect::ImagePosition position, + int offsetx = 0, int offsetY = 0); + + /** + * Reset the win pos and size to default. Don't forget to set defaults + * first. + */ + virtual void resetToDefaultSize(); + + /** + * Gets the layout handler for this window. + */ + Layout &getLayout(); + + /** + * Clears the window's layout (useful for redesigning the window). Does + * not delete the widgets! + */ + void clearLayout(); + + /** + * Computes the position of the widgets according to the current + * layout. Resizes the window so that the layout fits. Deletes the + * layout. + * @param w if non-zero, force the window to this width. + * @param h if non-zero, force the window to this height. + * @note This function is meant to be called with fixed-size windows. + */ + void reflowLayout(int w = 0, int h = 0); + + /** + * Adds a widget to the window and sets it at given cell. + */ + LayoutCell &place(int x, int y, gcn::Widget *, int w = 1, int h = 1); + + /** + * Returns a proxy for adding widgets in an inner table of the layout. + */ + ContainerPlacer getPlacer(int x, int y); + + /** + * Positions the window in the center of it's parent. + */ + void center(); + + /** + * Positions the window in the horisontal center of it's parent. + */ + void centerHorisontally(); + + /** + * Overrideable functionality for when the window is to close. This + * allows for class implementations to clean up or do certain actions + * on window close they couldn't do otherwise. + */ + virtual void close(); + + /** + * Allows the windows modal status to change + */ + void setModal(bool modal); + + /** + * Gets the alpha value used by the window, in a GUIChan usable format. + */ + int getGuiAlpha(); + + gcn::Rectangle getWindowArea(); + + bool isResizeAllowed(gcn::MouseEvent &event); + + private: + enum ResizeHandles + { + TOP = 0x01, + RIGHT = 0x02, + BOTTOM = 0x04, + LEFT = 0x08 + }; + + /** + * Check if the window is off-screen and then move it to be visible + * again. This is internally used by loadWindowState + * and setVisible(true) members. + */ + void checkIfIsOffScreen(bool partially = true, bool entirely = true); + + /** + * Determines if the mouse is in a resize area and returns appropriate + * resize handles. Also initializes drag offset in case the resize + * grip is used. + * + * @see ResizeHandles + */ + int getResizeHandles(gcn::MouseEvent &event); + + ResizeGrip *mGrip; /**< Resize grip */ + Window *mParent; /**< The parent window */ + Layout *mLayout; /**< Layout handler */ + std::string mWindowName; /**< Name of the window */ + bool mShowTitle; /**< Window has a title bar */ + bool mModal; /**< Window is modal */ + bool mCloseButton; /**< Window has a close button */ + bool mDefaultVisible; /**< Window's default visibility */ + bool mSaveVisible; /**< Window will save visibility */ + bool mStickyButton; /**< Window has a sticky button */ + bool mSticky; /**< Window resists hiding*/ + int mMinWinWidth; /**< Minimum window width */ + int mMinWinHeight; /**< Minimum window height */ + int mMaxWinWidth; /**< Maximum window width */ + int mMaxWinHeight; /**< Maximum window height */ + int mDefaultX; /**< Default window X position */ + int mDefaultY; /**< Default window Y position */ + int mDefaultWidth; /**< Default window width */ + int mDefaultHeight; /**< Default window height */ + + static int mouseResize; /**< Active resize handles */ + static int instances; /**< Number of Window instances */ + + Skin *mSkin; /**< Skin in use by this window */ + + /** + * The width of the resize border. Is independent of the actual window + * border width, and determines mostly the size of the corner area + * where two borders are moved at the same time. + */ + static const int resizeBorderWidth = 10; +}; + +#endif diff --git a/src/gui/widgets/windowcontainer.cpp b/src/gui/widgets/windowcontainer.cpp new file mode 100644 index 000000000..0fff491e4 --- /dev/null +++ b/src/gui/widgets/windowcontainer.cpp @@ -0,0 +1,40 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/widgets/windowcontainer.h" + +#include "utils/dtor.h" + +WindowContainer *windowContainer = NULL; + +void WindowContainer::logic() +{ + delete_all(mDeathList); + mDeathList.clear(); + + gcn::Container::logic(); +} + +void WindowContainer::scheduleDelete(gcn::Widget *widget) +{ + if (widget) + mDeathList.push_back(widget); +} diff --git a/src/gui/widgets/windowcontainer.h b/src/gui/widgets/windowcontainer.h new file mode 100644 index 000000000..2ec65d15a --- /dev/null +++ b/src/gui/widgets/windowcontainer.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WINDOWCONTAINER_H +#define WINDOWCONTAINER_H + +#include "gui/widgets/container.h" + +/** + * A window container. This container adds functionality for more convenient + * widget (windows in particular) destruction. + * + * \ingroup GUI + */ +class WindowContainer : public Container +{ + public: + /** + * Do GUI logic. This functions adds automatic deletion of objects that + * volunteered to be deleted. + */ + void logic(); + + /** + * Schedule a widget for deletion. It will be deleted at the start of + * the next logic update. + */ + void scheduleDelete(gcn::Widget *widget); + + private: + /** + * List of widgets that are scheduled to be deleted. + */ + typedef std::list Widgets; + typedef Widgets::iterator WidgetIterator; + Widgets mDeathList; +}; + +extern WindowContainer *windowContainer; + +#endif diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp new file mode 100644 index 000000000..eb146f700 --- /dev/null +++ b/src/gui/windowmenu.cpp @@ -0,0 +1,285 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/windowmenu.h" + +#include "emoteshortcut.h" +#include "graphics.h" +#include "keyboardconfig.h" + +#include "gui/emotepopup.h" +#include "gui/skilldialog.h" +#include "gui/specialswindow.h" +#include "gui/textpopup.h" +#include "gui/viewport.h" + +#include "gui/widgets/window.h" +#include "gui/widgets/windowcontainer.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "utils/gettext.h" + +#include + +extern Window *equipmentWindow; +extern Window *inventoryWindow; +extern Window *itemShortcutWindow; +extern Window *dropShortcutWindow; +extern Window *setupWindow; +extern Window *statusWindow; +extern Window *whoIsOnline; +extern Window *killStats; +extern Window *spellShortcutWindow; +extern Window *botCheckerWindow; +extern Window *socialWindow; + +WindowMenu::WindowMenu(): + mEmotePopup(0) +{ + int x = 0, h = 0; + + addButton(N_("BC"), _("Bot checker"), x, h, + KeyboardConfig::KEY_WINDOW_BOT_CHECKER); + addButton(N_("ONL"), _("Who is online"), x, h, + KeyboardConfig::KEY_NO_VALUE); + addButton(N_("KS"), _("Kill stats"), x, h, + KeyboardConfig::KEY_WINDOW_KILLS); + addButton(":-)", _("Smiles"), x, h, + KeyboardConfig::KEY_WINDOW_EMOTE_SHORTCUT); + addButton(N_("STA"), _("Status"), x, h, KeyboardConfig::KEY_WINDOW_STATUS); + addButton(N_("EQU"), _("Equipment"), x, h, + KeyboardConfig::KEY_WINDOW_EQUIPMENT); + addButton(N_("INV"), _("Inventory"), x, h, + KeyboardConfig::KEY_WINDOW_INVENTORY); + + if (skillDialog->hasSkills()) + { + addButton(N_("SKI"), _("Skills"), x, h, + KeyboardConfig::KEY_WINDOW_SKILL); + } + + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + addButton(N_("SPE"), _("Specials"), x, h, + KeyboardConfig::KEY_NO_VALUE); + } + + addButton(N_("SOC"), _("Social"), x, h, KeyboardConfig::KEY_WINDOW_SOCIAL); + addButton(N_("SH"), _("Shortcuts"), x, h, + KeyboardConfig::KEY_WINDOW_SHORTCUT); + addButton(N_("SP"), _("Spells"), x, h, KeyboardConfig::KEY_WINDOW_SPELLS); + addButton(N_("DR"), _("Drop"), x, h, KeyboardConfig::KEY_WINDOW_DROP); + addButton(N_("SET"), _("Setup"), x, h, KeyboardConfig::KEY_WINDOW_SETUP); + + mTextPopup = new TextPopup; + setDimension(gcn::Rectangle(graphics->getWidth() - x - 3, 3, + x - 3, h)); + + addMouseListener(this); + setVisible(true); +} + +WindowMenu::~WindowMenu() +{ + delete mTextPopup; + mTextPopup = 0; + mButtonNames.clear(); +} + +void WindowMenu::action(const gcn::ActionEvent &event) +{ + Window *window = 0; + + if (event.getId() == ":-)") + { + if (!mEmotePopup) + { + const gcn::Widget *s = event.getSource(); + if (s) + { + const gcn::Rectangle &r = s->getDimension(); + const int parentX = s->getParent()->getX(); + + mEmotePopup = new EmotePopup; + const int offset = (r.width - mEmotePopup->getWidth()) / 2; + mEmotePopup->setPosition(parentX + r.x + offset, + r.y + r.height + 5); + + mEmotePopup->addSelectionListener(this); + } + else + { + mEmotePopup = 0; + } + } + else + { + if (windowContainer) + windowContainer->scheduleDelete(mEmotePopup); + mEmotePopup = 0; + } + } + else if (event.getId() == "STA") + { + window = statusWindow; + } + else if (event.getId() == "EQU") + { + window = equipmentWindow; + } + else if (event.getId() == "INV") + { + window = inventoryWindow; + } + else if (event.getId() == "SKI") + { + window = skillDialog; + } + else if (event.getId() == "SPE") + { + window = specialsWindow; + } + else if (event.getId() == "SH") + { + window = itemShortcutWindow; + } + else if (event.getId() == "SOC") + { + window = socialWindow; + } + else if (event.getId() == "DR") + { + window = dropShortcutWindow; + } + else if (event.getId() == "SET") + { + window = setupWindow; + } + else if (event.getId() == "ONL") + { + window = whoIsOnline; + } + else if (event.getId() == "KS") + { + window = killStats; + } + else if (event.getId() == "BC") + { + window = botCheckerWindow; + } + else if (event.getId() == "SP") + { + window = spellShortcutWindow; + } + + if (window) + { + window->setVisible(!window->isVisible()); + if (window->isVisible()) + window->requestMoveToTop(); + } +} + +void WindowMenu::valueChanged(const gcn::SelectionEvent &event) +{ + if (event.getSource() == mEmotePopup) + { + int emote = mEmotePopup->getSelectedEmote(); + if (emote && emoteShortcut) + emoteShortcut->useEmote(emote); + + if (windowContainer) + windowContainer->scheduleDelete(mEmotePopup); + mEmotePopup = 0; + } +} + +void WindowMenu::addButton(const char* text, std::string description, + int &x, int &h, int key) +{ + Button *btn = new Button(gettext(text), text, this); + btn->setPosition(x, 0); + btn->setDescription(description); + btn->setTag(key); + add(btn); + mButtons.push_back(btn); + x += btn->getWidth() + 3; + h = btn->getHeight(); + mButtonNames[text] = btn; +} + +void WindowMenu::mousePressed(gcn::MouseEvent &event) +{ + if (!viewport) + return; + + if (event.getButton() == gcn::MouseEvent::RIGHT) + { + Button *btn = dynamic_cast(event.getSource()); + if (!btn) + return; + if (viewport) + viewport->showPopup(event.getX(), event.getY(), btn); + } +} + +void WindowMenu::mouseMoved(gcn::MouseEvent &event) +{ + if (!mTextPopup) + return; + + if (event.getSource() == this) + { + mTextPopup->hide(); + return; + } + + Button *btn = dynamic_cast(event.getSource()); + + if (!btn) + { + mTextPopup->hide(); + return; + } + + const int x = event.getX(); + const int y = event.getY(); + int key = btn->getTag(); + if (key != KeyboardConfig::KEY_NO_VALUE) + { + mTextPopup->show(x + getX(), y + getY(), btn->getDescription(), + "Key: " + keyboard.getKeyValueString(key)); + } + else + { + mTextPopup->show(x + getX(), y + getY(), btn->getDescription()); + } +} + +void WindowMenu::mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_) +{ + if (!mTextPopup) + return; + + mTextPopup->hide(); +} \ No newline at end of file diff --git a/src/gui/windowmenu.h b/src/gui/windowmenu.h new file mode 100644 index 000000000..f619a161a --- /dev/null +++ b/src/gui/windowmenu.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WINDOWMENU_H +#define WINDOWMENU_H + +#include "gui/widgets/container.h" +#include "gui/widgets/button.h" + +#include +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class EmotePopup; +class TextPopup; + +/** + * The window menu. Allows showing and hiding many of the different windows + * used in the game. + * + * \ingroup Interface + */ +class WindowMenu : public Container, + public gcn::ActionListener, + public gcn::SelectionListener, + public gcn::MouseListener +{ + public: + WindowMenu(); + ~WindowMenu(); + + void action(const gcn::ActionEvent &event); + + void valueChanged(const gcn::SelectionEvent &event); + + void mousePressed(gcn::MouseEvent &event); + + void mouseMoved(gcn::MouseEvent &event); + + void mouseExited(gcn::MouseEvent& mouseEvent _UNUSED_); + + std::map &getButtonNames() + { return mButtonNames; } + + private: + inline void addButton(const char* text, std::string description, + int &x, int &h, int key); + + EmotePopup *mEmotePopup; + TextPopup *mTextPopup; + std::list mButtons; + std::map mButtonNames; +}; + +extern WindowMenu *windowMenu; + +#endif diff --git a/src/gui/worldselectdialog.cpp b/src/gui/worldselectdialog.cpp new file mode 100644 index 000000000..f00871bd8 --- /dev/null +++ b/src/gui/worldselectdialog.cpp @@ -0,0 +1,139 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/worldselectdialog.h" + +#include "client.h" + +#include "gui/sdlinput.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" +#include "net/worldinfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern WorldInfo **server_info; + +/** + * The list model for the server list. + */ +class WorldListModel : public gcn::ListModel +{ + public: + WorldListModel(Worlds worlds): + mWorlds(worlds) + { + } + + virtual ~WorldListModel() {} + + int getNumberOfElements() + { + return static_cast(mWorlds.size()); + } + + std::string getElementAt(int i) + { + const WorldInfo *si = mWorlds[i]; + if (si) + return si->name + " (" + toString(si->online_users) + ")"; + else + return "???"; + } + private: + Worlds mWorlds; +}; + +WorldSelectDialog::WorldSelectDialog(Worlds worlds): + Window(_("Select World")) +{ + mWorldListModel = new WorldListModel(worlds); + mWorldList = new ListBox(mWorldListModel); + ScrollArea *worldsScroll = new ScrollArea(mWorldList); + mChangeLoginButton = new Button(_("Change Login"), "login", this); + mChooseWorld = new Button(_("Choose World"), "world", this); + + worldsScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + place(0, 0, worldsScroll, 3, 5).setPadding(2); + place(1, 5, mChangeLoginButton); + place(2, 5, mChooseWorld); + + // Make sure the list has enough height + getLayout().setRowHeight(0, 60); + + reflowLayout(0, 0); + + if (worlds.size() == 0) + // Disable Ok button + mChooseWorld->setEnabled(false); + else + // Select first server + mWorldList->setSelected(0); + + addKeyListener(this); + + center(); + setVisible(true); + mChooseWorld->requestFocus(); +} + +WorldSelectDialog::~WorldSelectDialog() +{ + delete mWorldListModel; + mWorldListModel = 0; +} + +void WorldSelectDialog::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "world") + { + mChangeLoginButton->setEnabled(false); + mChooseWorld->setEnabled(false); + Net::getLoginHandler()->chooseServer(mWorldList->getSelected()); + + // Check in case netcode moves us forward + if (Client::getState() == STATE_WORLD_SELECT) + Client::setState(STATE_WORLD_SELECT_ATTEMPT); + } + else if (event.getId() == "login") + { + Client::setState(STATE_LOGIN); + } +} + +void WorldSelectDialog::keyPressed(gcn::KeyEvent &keyEvent) +{ + gcn::Key key = keyEvent.getKey(); + + if (key.getValue() == Key::ESCAPE) + action(gcn::ActionEvent(NULL, mChangeLoginButton->getActionEventId())); + else if (key.getValue() == Key::ENTER) + action(gcn::ActionEvent(NULL, mChooseWorld->getActionEventId())); +} diff --git a/src/gui/worldselectdialog.h b/src/gui/worldselectdialog.h new file mode 100644 index 000000000..e22438714 --- /dev/null +++ b/src/gui/worldselectdialog.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WORLD_SELECT_DIALOG_H +#define WORLD_SELECT_DIALOG_H + +#include "gui/widgets/window.h" + +#include "net/worldinfo.h" + +#include +#include +#include + +#include + +class LoginData; +class WorldListModel; + +/** + * The server select dialog. + * + * \ingroup Interface + */ +class WorldSelectDialog : public Window, public gcn::ActionListener, + public gcn::KeyListener +{ + public: + /** + * Constructor + * + * @see Window::Window + */ + WorldSelectDialog(Worlds worlds); + + /** + * Destructor. + */ + ~WorldSelectDialog(); + + /** + * Called when receiving actions from the widgets. + */ + void action(const gcn::ActionEvent &event); + + void keyPressed(gcn::KeyEvent &keyEvent); + + private: + WorldListModel *mWorldListModel; + gcn::ListBox *mWorldList; + gcn::Button *mChangeLoginButton; + gcn::Button *mChooseWorld; +}; + +#endif // WORLD_SELECT_DIALOG_H diff --git a/src/guichanfwd.h b/src/guichanfwd.h new file mode 100644 index 000000000..42141028e --- /dev/null +++ b/src/guichanfwd.h @@ -0,0 +1,101 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUICHANFWD_H +#define GUICHANFWD_H + +namespace gcn +{ + class ActionEvent; + class ActionListener; + class AllegroFont; + class AllegroGraphics; + class AllegroImage; + class AllegroImageLoader; + class AllegroInput; + class BasicContainer; + class Button; + class CheckBox; + class ClipRectangle; + class Color; + class Container; + class DefaultFont; + class DropDown; + class Event; + class Exception; + class FocusHandler; + class FocusListener; + class Font; + class GenericInput; + class Graphics; + class Gui; + class HGEGraphics; + class HGEImage; + class HGEImageFont; + class HGEImageLoader; + class HGEInput; + class Icon; + class Image; + class ImageButton; + class InputEvent; + class ImageFont; + class ImageLoader; + class Input; + class Key; + class KeyEvent; + class KeyInput; + class KeyListener; + class Label; + class ListBox; + class ListModel; + class MouseEvent; + class MouseInput; + class MouseListener; + class OpenGLAllegroImageLoader; + class OpenGLGraphics; + class OpenGLImage; + class OpenGLSDLImageLoader; + class OpenLayerFont; + class OpenLayerGraphics; + class OpenLayerImage; + class OpenLayerImageLoader; + class OpenLayerInput; + class RadioButton; + class Rectangle; + class ScrollArea; + class SDLGraphics; + class SDLImage; + class SDLImageLoader; + class SDLInput; + class SDLPixel; + class SelectionEvent; + class SelectionListener; + class Slider; + class Tab; + class TabbedArea; + class TextBox; + class TextField; + class Widget; + class WidgetListener; + class Window; +} + +#endif diff --git a/src/guild.cpp b/src/guild.cpp new file mode 100644 index 000000000..c79c574b9 --- /dev/null +++ b/src/guild.cpp @@ -0,0 +1,296 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "guild.h" + +#include "actorspritemanager.h" + +class SortGuildFunctor +{ + public: + bool operator() (GuildMember* m1, GuildMember* m2) + { + if (!m1 || !m2) + return false; + if (m1->getPos() != m2->getPos()) + return m1->getPos() < m2->getPos(); + + return m1->getName() < m2->getName(); + } +} guildSorter; + +GuildMember::GuildMember(Guild *guild, int accountId, int charId, + const std::string &name): + Avatar(name), mGuild(guild), mPos(0) +{ + mId = accountId; + mCharId = charId; +} + +GuildMember::GuildMember(Guild *guild, const std::string &name): + Avatar(name), mGuild(guild), mPos(0) +{ +} + +std::string GuildMember::getAdditionString() const +{ + if (!mGuild) + return ""; + + return " - " + mGuild->getPos(mPos); +} + +Guild::GuildMap Guild::guilds; + +Guild::Guild(short id): + mId(id), + mCanInviteUsers(false), + mEmblemId(0) +{ + guilds[id] = this; +} + +GuildMember *Guild::addMember(int accountId, int charId, + const std::string &name) +{ + GuildMember *m; + if ((m = getMember(accountId, charId))) + return m; + + m = new GuildMember(this, accountId, charId, name); + + mMembers.push_back(m); + + return m; +} + +GuildMember *Guild::addMember(const std::string &name) +{ + GuildMember *m; + if ((m = getMember(name))) + return m; + + m = new GuildMember(this, name); + + mMembers.push_back(m); + + return m; +} + +GuildMember *Guild::getMember(int id) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == id) + return (*itr); + ++itr; + } + + return NULL; +} + +GuildMember *Guild::getMember(int accountId, int charId) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == accountId && (*itr)->mCharId == charId) + return (*itr); + ++itr; + } + + return NULL; +} + +GuildMember *Guild::getMember(const std::string &name) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->getName() == name) + return (*itr); + ++itr; + } + + return NULL; +} + +void Guild::removeMember(GuildMember *member) +{ + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == member->mId && + (*itr)->mCharId == member->mCharId && + (*itr)->getName() == member->getName()) + { + mMembers.erase(itr); + delete *itr; + } + ++itr; + } +} + +void Guild::removeMember(int id) +{ + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == id) + mMembers.erase(itr); + ++itr; + } +} + +void Guild::removeMember(const std::string &name) +{ + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->getName() == name) + mMembers.erase(itr); + ++itr; + } +} + +void Guild::removeFromMembers() +{ + if (!actorSpriteManager) + return; + + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + Being *b = actorSpriteManager->findBeing((*itr)->getID()); + if (b) + b->removeGuild(getId()); + ++itr; + } +} + +Avatar *Guild::getAvatarAt(int index) +{ + return mMembers[index]; +} + +void Guild::setRights(short rights) +{ + // to invite, rights must be greater than 0 + if (rights > 0) + mCanInviteUsers = true; +} + +bool Guild::isMember(GuildMember *member) const +{ + if (member->mGuild > 0 && member->mGuild != this) + return false; + + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == member->mId && + (*itr)->getName() == member->getName()) + { + return true; + } + ++itr; + } + + return false; +} + +bool Guild::isMember(int id) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == id) + return true; + ++itr; + } + + return false; +} + +bool Guild::isMember(const std::string &name) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->getName() == name) + return true; + ++itr; + } + + return false; +} + +void Guild::getNames(std::vector &names) const +{ + names.clear(); + MemberList::const_iterator it = mMembers.begin(), + it_end = mMembers.end(); + + while (it != it_end) + { + names.push_back((*it)->getName()); + ++it; + } +} + +void Guild::addPos(int id, std::string name) +{ + mPositions[id] = name; +} + +Guild *Guild::getGuild(short id) +{ + GuildMap::iterator it = guilds.find(id); + if (it != guilds.end()) + return it->second; + + return new Guild(id); +} + +std::string Guild::getPos(int id) const +{ + PositionsMap::const_iterator it = mPositions.find(id); + if (it == mPositions.end()) + return ""; + else + return it->second; +} + +void Guild::sort() +{ + std::sort(mMembers.begin(), mMembers.end(), guildSorter); +} \ No newline at end of file diff --git a/src/guild.h b/src/guild.h new file mode 100644 index 000000000..2d3cb646a --- /dev/null +++ b/src/guild.h @@ -0,0 +1,199 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUILD_H +#define GUILD_H + +#include "avatar.h" + +#include "gui/widgets/avatarlistbox.h" + +#include "utils/dtor.h" + +#include +#include +#include + +class Guild; + +typedef std::map PositionsMap; + +class GuildMember : public Avatar +{ +public: + Guild *getGuild() const + { return mGuild; } + + int getPos() + { return mPos; } + + void setPos(int pos) + { mPos = pos; } + + std::string getAdditionString() const; + +protected: + friend class Guild; + + GuildMember(Guild *guild, int id, int accountId, const std::string &name); + + GuildMember(Guild *guild, const std::string &name); + + Guild *mGuild; + int mPos; +}; + +class Guild : public AvatarListModel +{ +public: + /** + * Set the guild's name. + */ + void setName(const std::string &name) + { mName = name; } + + /** + * Adds member to the list. + */ + GuildMember *addMember(int accountId, int charId, const std::string &name); + + /** + * Adds member to the list. + */ + GuildMember *addMember(const std::string &name); + + /** + * Find a member by ID. + * + * @return the member with the given ID, or NULL if they don't exist. + */ + GuildMember *getMember(int id) const; + + /** + * Find a member by account ID and char ID. + * + * @return the member with the given ID, or NULL if they don't exist. + */ + GuildMember *getMember(int accountId, int charId) const; + + /** + * Find a member by name. + * + * @return the member with the given name, or NULL if they don't exist. + */ + GuildMember *getMember(const std::string &name) const; + + /** + * Get the name of the guild. + * @return returns name of the guild + */ + const std::string &getName() const + { return mName; } + + /** + * Get the id of the guild. + * @return Returns the id of the guild + */ + short getId() const + { return mId; } + + /** + * Removes a member from the guild. + */ + void removeMember(GuildMember *member); + + /** + * Removes a member from the guild. + */ + void removeMember(int id); + + /** + * Removes a member from the guild. + */ + void removeMember(const std::string &name); + + void removeFromMembers(); + + void clearMembers() + { delete_all(mMembers); mMembers.clear(); } + + /** + * Get size of members list. + * @return Returns the number of members in the guild. + */ + int getNumberOfElements() + { return static_cast(mMembers.size()); } + + Avatar *getAvatarAt(int i); + + /** + * Get whether user can invite users to this guild. + * @return Returns true if user can invite users + */ + bool getInviteRights() const + { return mCanInviteUsers; } + + void setRights(short rights); + + bool isMember(GuildMember *member) const; + + bool isMember(int id) const; + + bool isMember(const std::string &name) const; + + void getNames(std::vector &names) const; + + void addPos(int id, std::string name); + + void sort(); + + std::string getPos(int id) const; + + static Guild *getGuild(short id); + + const PositionsMap &getPositions() const + { return mPositions; } + + void setEmblemId(int id) + { mEmblemId = id; } + + int getEmblemId() + { return mEmblemId; } + +private: + typedef std::map GuildMap; + static GuildMap guilds; + + /** + * Constructor with guild id passed to it. + */ + Guild(short id); + + typedef std::vector MemberList; + MemberList mMembers; + std::string mName; + short mId; + bool mCanInviteUsers; + int mEmblemId; + PositionsMap mPositions; +}; + +#endif // GUILD_H diff --git a/src/imageparticle.cpp b/src/imageparticle.cpp new file mode 100644 index 000000000..d2195a533 --- /dev/null +++ b/src/imageparticle.cpp @@ -0,0 +1,91 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "imageparticle.h" + +#include "graphics.h" + +#include "resources/image.h" + +std::map ImageParticle::imageParticleCountByName; + +ImageParticle::ImageParticle(Map *map, Image *image): + Particle(map), + mImage(image) +{ + if (mImage) + { + mImage->incRef(); + + std::string name = mImage->getIdPath(); + if (ImageParticle::imageParticleCountByName.find(name) + == ImageParticle::imageParticleCountByName.end()) + { + ImageParticle::imageParticleCountByName[name] = 1; + } + else + { + ImageParticle::imageParticleCountByName[name] ++; + } + } +} + +ImageParticle::~ImageParticle() +{ + if (mImage) + { + std::string name = mImage->getIdPath(); + if (ImageParticle::imageParticleCountByName[name] > 0) + ImageParticle::imageParticleCountByName[name] --; + + mImage->decRef(); + } +} + +bool ImageParticle::draw(Graphics *graphics, int offsetX, int offsetY) const +{ + if (!isAlive() || !mImage) + return false; + + int screenX = (int) mPos.x + offsetX - mImage->getWidth() / 2; + int screenY = (int) mPos.y - (int)mPos.z + + offsetY - mImage->getHeight() / 2; + + // Check if on screen + if (screenX + mImage->getWidth() < 0 || + screenX > graphics->getWidth() || + screenY + mImage->getHeight() < 0 || + screenY > graphics->getHeight()) + { + return false; + } + + float alphafactor = mAlpha; + + if (mFadeOut && mLifetimeLeft > -1 && mLifetimeLeft < mFadeOut) + alphafactor *= (float) mLifetimeLeft / (float) mFadeOut; + + if (mFadeIn && mLifetimePast < mFadeIn) + alphafactor *= (float) mLifetimePast / (float) mFadeIn; + + mImage->setAlpha(alphafactor); + return graphics->drawImage(mImage, screenX, screenY); +} diff --git a/src/imageparticle.h b/src/imageparticle.h new file mode 100644 index 000000000..792bd4933 --- /dev/null +++ b/src/imageparticle.h @@ -0,0 +1,61 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGEPARTICLE_H +#define IMAGEPARTICLE_H + +#include "particle.h" + +#include + +class Image; +class Map; + +/** + * A particle that uses an image for its visualization. + */ +class ImageParticle : public Particle +{ + public: + /** + * Constructor. The image is reference counted by this particle. + * + * @param map the map this particle appears on + * @param image an Image instance, may not be NULL + */ + ImageParticle(Map *map, Image *image); + + /** + * Destructor. + */ + ~ImageParticle(); + + /** + * Draws the particle image + */ + virtual bool draw(Graphics *graphics, int offsetX, int offsetY) const; + + static std::map imageParticleCountByName; + protected: + Image *mImage; /**< The image used for this particle. */ +}; + +#endif diff --git a/src/imagesprite.cpp b/src/imagesprite.cpp new file mode 100644 index 000000000..7db3c96d1 --- /dev/null +++ b/src/imagesprite.cpp @@ -0,0 +1,54 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "imagesprite.h" + +#include "graphics.h" + +ImageSprite::ImageSprite(Image *image): + mImage(image) +{ + if (mImage) + { + mAlpha = mImage->getAlpha(); + mImage->incRef(); + } + else + { + mAlpha = 1; + } +} + +ImageSprite::~ImageSprite() +{ + if (mImage) + mImage->decRef(); +} + +bool ImageSprite::draw(Graphics* graphics, int posX, int posY) const +{ + if (!mImage) + return false; + + if (mImage->getAlpha() != mAlpha) + mImage->setAlpha(mAlpha); + + return graphics->drawImage(mImage, posX, posY); +} diff --git a/src/imagesprite.h b/src/imagesprite.h new file mode 100644 index 000000000..b59cd18d0 --- /dev/null +++ b/src/imagesprite.h @@ -0,0 +1,79 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGESPRITE_H +#define IMAGESPRITE_H + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +#include "sprite.h" + +#include "resources/image.h" + +class Graphics; + +class ImageSprite : public Sprite +{ +public: + ImageSprite(Image *image); + + ~ImageSprite(); + + bool reset() + { return false; } + + bool play(std::string action _UNUSED_) + { return false; } + + bool update(int time _UNUSED_) + { return false; } + + bool draw(Graphics* graphics, int posX, int posY) const; + + int getWidth() const + { return mImage ? mImage->getWidth() : 0; } + + int getHeight() const + { return mImage ? mImage->getHeight() : 0; } + + const Image* getImage() const + { return mImage; } + + virtual bool setDirection(SpriteDirection direction _UNUSED_) + { return false; } + + int getNumberOfLayers() + { return 1; } + + unsigned int getCurrentFrame() const + { return 0; } + + unsigned int getFrameCount() const + { return 1; } + +private: + Image *mImage; +}; + +#endif // IMAGESPRITE_H diff --git a/src/inventory.cpp b/src/inventory.cpp new file mode 100644 index 000000000..23387cadb --- /dev/null +++ b/src/inventory.cpp @@ -0,0 +1,225 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "inventory.h" +#include "item.h" +#include "log.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "resources/iteminfo.h" + +#include +#include + +struct SlotUsed : public std::unary_function +{ + bool operator()(const Item *item) const + { + return item && item->getId() >= 0 && item->getQuantity() > 0; + } +}; + +Inventory::Inventory(int type, int size): + mType(type), + mSize(size == -1 ? Net::getInventoryHandler()->getSize(type) + : static_cast(size)), + mUsed(0) +{ + mItems = new Item*[mSize]; + std::fill_n(mItems, mSize, (Item*) 0); +} + +Inventory::~Inventory() +{ + for (unsigned i = 0; i < mSize; i++) + delete mItems[i]; + + delete [] mItems; + mItems = 0; +} + +Item *Inventory::getItem(int index) const +{ + if (index < 0 || index >= static_cast(mSize) || !mItems[index] + || mItems[index]->getQuantity() <= 0) + { + return 0; + } + + return mItems[index]; +} + +Item *Inventory::findItem(int itemId) const +{ + for (unsigned i = 0; i < mSize; i++) + { + if (mItems[i] && mItems[i]->getId() == itemId) + return mItems[i]; + } + + return 0; +} + +void Inventory::addItem(int id, int quantity, int refine, bool equipment) +{ + setItem(getFreeSlot(), id, quantity, refine, equipment); +} + +void Inventory::setItem(int index, int id, int quantity, + int refine, bool equipment) +{ + if (index < 0 || index >= static_cast(mSize)) + { + logger->log("Warning: invalid inventory index: %d", index); + return; + } + + if (!mItems[index] && id > 0) + { + Item *item = new Item(id, quantity, refine, equipment); + item->setInvIndex(index); + mItems[index] = item; + mUsed++; + distributeSlotsChangedEvent(); + } + else if (id > 0 && mItems[index]) + { + mItems[index]->setId(id); + mItems[index]->setQuantity(quantity); + mItems[index]->setRefine(refine); + mItems[index]->setEquipment(equipment); + } + else if (mItems[index]) + { + removeItemAt(index); + } +} + +void Inventory::clear() +{ + for (unsigned i = 0; i < mSize; i++) + removeItemAt(i); +} + +void Inventory::removeItem(int id) +{ + for (unsigned i = 0; i < mSize; i++) + { + if (mItems[i] && mItems[i]->getId() == id) + removeItemAt(i); + } +} + +void Inventory::removeItemAt(int index) +{ + delete mItems[index]; + mItems[index] = 0; + mUsed--; + if (mUsed < 0) // Already at 0, no need to distribute event + mUsed = 0; + else + distributeSlotsChangedEvent(); +} + +bool Inventory::contains(Item *item) const +{ + for (unsigned i = 0; i < mSize; i++) + { + if (mItems[i] && mItems[i]->getId() == item->getId()) + return true; + } + + return false; +} + +int Inventory::getFreeSlot() const +{ + Item **i = std::find_if(mItems, mItems + mSize, + std::not1(SlotUsed())); + return (i == mItems + static_cast(mSize)) ? -1 : (i - mItems); +} + +int Inventory::getLastUsedSlot() const +{ + for (int i = mSize - 1; i >= 0; i--) + { + if (SlotUsed()(mItems[i])) + return i; + } + + return -1; +} + +void Inventory::addInventoyListener(InventoryListener* listener) +{ + mInventoryListeners.push_back(listener); +} + +void Inventory::removeInventoyListener(InventoryListener* listener) +{ + mInventoryListeners.remove(listener); +} + +void Inventory::distributeSlotsChangedEvent() +{ + InventoryListenerList::const_iterator i = mInventoryListeners.begin(); + InventoryListenerList::const_iterator i_end = mInventoryListeners.end(); + for (; i != i_end; i++) + (*i)->slotsChanged(this); +} + +Item *Inventory::findItemBySprite(std::string spritePath, Gender gender) +{ + spritePath = removeSpriteIndex(spritePath); +// logger->log1("Inventory::FindItemBySprite sprite: " + spritePath); + + std::string spritePathShort = extractNameFromSprite(spritePath); +// logger->log1("Inventory::FindItemBySprite spriteShort: " + spritePathShort); + int partialIndex = -1; + + for (unsigned i = 0; i < mSize; i++) + { + if (mItems[i]) + { + std::string path = mItems[i]->getInfo().getSprite(gender); + if (!path.empty()) + { + path = removeSpriteIndex(path); + +// logger->log("Inventory::FindItemBySprite normal: " + path); + + if (spritePath == path) + return mItems[i]; + + path = extractNameFromSprite(path); +// logger->log("Inventory::FindItemBySprite short: " + path); + if (spritePathShort == path) + partialIndex = i; + } + } + } + if (partialIndex != -1) + return mItems[partialIndex]; + + return 0; +} diff --git a/src/inventory.h b/src/inventory.h new file mode 100644 index 000000000..c09492e22 --- /dev/null +++ b/src/inventory.h @@ -0,0 +1,160 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INVENTORY_H +#define INVENTORY_H + +#include "being.h" + +#include +#include + +class Inventory; +class Item; + +class InventoryListener +{ +public: + virtual ~InventoryListener() {} + + virtual void slotsChanged(Inventory* inventory) = 0; + +protected: + InventoryListener() {} +}; + +class Inventory +{ + public: + static const int NO_SLOT_INDEX = -1; /**< Slot has no index. */ + + enum + { + INVENTORY = 0, + STORAGE, + TRADE, + TYPE_END + }; + + + /** + * Constructor. + * + * @param size the number of items that fit in the inventory + */ + Inventory(int type, int size = -1); + + /** + * Destructor. + */ + ~Inventory(); + + /** + * Returns the size that this instance is configured for. + */ + unsigned getSize() const + { return mSize; } + + /** + * Returns the item at the specified index. + */ + Item *getItem(int index) const; + + /** + * Searches for the specified item by it's id. + * + * @param itemId The id of the item to be searched. + * @return Item found on success, NULL on failure. + */ + Item *findItem(int itemId) const; + + /** + * Adds a new item in a free slot. + */ + void addItem(int id, int quantity, int refine, bool equipment = false); + + /** + * Sets the item at the given position. + */ + void setItem(int index, int id, int quantity, int refine, + bool equipment = false); + + /** + * Remove a item from the inventory. + */ + void removeItem(int id); + + /** + * Remove the item at the specified index from the inventory. + */ + void removeItemAt(int index); + + /** + * Checks if the given item is in the inventory. + */ + bool contains(Item *item) const; + + /** + * Returns id of next free slot or -1 if all occupied. + */ + int getFreeSlot() const; + + /** + * Reset all item slots. + */ + void clear(); + + /** + * Get the number of slots filled with an item + */ + int getNumberOfSlotsUsed() const + { return mUsed; } + + /** + * Returns the index of the last occupied slot or 0 if none occupied. + */ + int getLastUsedSlot() const; + + void addInventoyListener(InventoryListener* listener); + + void removeInventoyListener(InventoryListener* listener); + + int getType() const + { return mType; } + + bool isMainInventory() const + { return mType == INVENTORY; } + + Item *findItemBySprite(std::string spritePath, Gender gender); + + protected: + typedef std::list InventoryListenerList; + InventoryListenerList mInventoryListeners; + + void distributeSlotsChangedEvent(); + + int mType; + Item **mItems; /**< The holder of items */ + unsigned mSize; /**< The max number of inventory items */ + int mUsed; /**< THe number of slots in use */ +}; + +#endif diff --git a/src/item.cpp b/src/item.cpp new file mode 100644 index 000000000..94eaf383e --- /dev/null +++ b/src/item.cpp @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "item.h" + +#include "gui/theme.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" +#include "configuration.h" + +Item::Item(int id, int quantity, int refine, bool equipment, bool equipped): + mImage(0), + mDrawImage(0), + mQuantity(quantity), + mEquipment(equipment), + mEquipped(equipped), + mInEquipment(false), + mRefine(refine), + mInvIndex(0) +{ + setId(id); +} + +Item::~Item() +{ + if (mImage) + mImage->decRef(); +} + +void Item::setId(int id) +{ + mId = id; + + // Types 0 and 1 are not equippable items. + mEquipment = id && getInfo().getType() >= 2; + + // Load the associated image + if (mImage) + mImage->decRef(); + + if (mDrawImage) + mDrawImage->decRef(); + + ResourceManager *resman = ResourceManager::getInstance(); + SpriteDisplay display = getInfo().getDisplay(); + std::string imagePath = paths.getStringValue("itemIcons") + + display.image; + mImage = resman->getImage(imagePath); + mDrawImage = resman->getImage(imagePath); + + if (!mImage) + { + mImage = Theme::getImageFromTheme(paths.getValue("unknownItemFile", + "unknown-item.png")); + } + + if (!mDrawImage) + { + mDrawImage = Theme::getImageFromTheme( + paths.getValue("unknownItemFile", "unknown-item.png")); + } +} + +Image *Item::getImage(int id) +{ + ResourceManager *resman = ResourceManager::getInstance(); + SpriteDisplay display = ItemDB::get(id).getDisplay(); + std::string imagePath = "graphics/items/" + display.image; + Image *image = resman->getImage(imagePath); + + if (!image) + image = Theme::getImageFromTheme("unknown-item.png"); + return image; +} diff --git a/src/item.h b/src/item.h new file mode 100644 index 000000000..520028f8f --- /dev/null +++ b/src/item.h @@ -0,0 +1,167 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEM_H +#define ITEM_H + +#include "resources/itemdb.h" + +class Image; + +/** + * Represents one or more instances of a certain item type. + */ +class Item +{ + public: + /** + * Constructor. + */ + Item(int id = -1, int quantity = 0, int refine = 0, + bool equipment = false, bool equipped = false); + + /** + * Destructor. + */ + virtual ~Item(); + + /** + * Sets the item id, identifying the item type. + */ + void setId(int id); + + /** + * Returns the item id. + */ + int getId() const + { return mId; } + + /** + * Returns the item image. + */ + Image *getImage() const + { return mImage; } + + /** + * Returns the item image. + */ + Image *getDrawImage() const + { return mDrawImage; } + + /** + * Sets the number of items. + */ + void setQuantity(int quantity) + { mQuantity = quantity; } + + /** + * Increases the number of items by the given amount. + */ + void increaseQuantity(int amount) + { mQuantity += amount; } + + /** + * Returns the number of items. + */ + int getQuantity() const + { return mQuantity; } + + /** + * Sets whether this item is considered equipment. + */ + void setEquipment(bool equipment) + { mEquipment = equipment; } + + /** + * Returns whether this item is considered equipment. + */ + bool isEquipment() const + { return mEquipment; } + + /** + * Sets whether this item is equipped. + */ + void setEquipped(bool equipped) + { mEquipped = equipped; } + + /** + * Returns whether this item is equipped. + */ + bool isEquipped() const + { return mEquipped; } + + /** + * Sets this item refine level. + */ + void setRefine(int refine) + { mRefine = refine; } + + /** + * Returns this item refine level. + */ + int getRefine() const + { return mRefine; } + + /** + * Sets whether this item is in equipment. + */ + void setInEquipment(bool inEquipment) + { mInEquipment = inEquipment; } + + /** + * Returns whether this item is in equipment. + */ + bool isInEquipment() const + { return mInEquipment; } + + /** + * Sets the inventory index of this item. + */ + void setInvIndex(int index) + { mInvIndex = index; } + + /** + * Returns the inventory index of this item. + */ + int getInvIndex() const + { return mInvIndex; } + + /** + * Returns information about this item type. + */ + const ItemInfo &getInfo() const + { return ItemDB::get(mId); } + + static Image *getImage(int id); + + protected: + int mId; /**< Item type id. */ + Image *mImage; /**< Item image. */ + Image *mDrawImage; /**< Draw image. */ + int mQuantity; /**< Number of items. */ + bool mEquipment; /**< Item is equipment. */ + bool mEquipped; /**< Item is equipped. */ + bool mInEquipment; /**< Item is in equipment */ + int mRefine; /**< Item refine level. */ + int mInvIndex; /**< Inventory index. */ +}; + +#endif diff --git a/src/itemshortcut.cpp b/src/itemshortcut.cpp new file mode 100644 index 000000000..bef6ba5e2 --- /dev/null +++ b/src/itemshortcut.cpp @@ -0,0 +1,155 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "configuration.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "spellmanager.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "utils/stringutils.h" + +ItemShortcut *itemShortcut[SHORTCUT_TABS]; + +ItemShortcut::ItemShortcut(int number): + mItemSelected(-1), + mNumber(number) +{ + load(); +} + +ItemShortcut::~ItemShortcut() +{ + logger->log1("ItemShortcut::~ItemShortcut"); +} + +void ItemShortcut::load(bool oldConfig) +{ + std::string name; + Configuration *cfg; + if (oldConfig) + cfg = &config; + else + cfg = &serverConfig; + + if (mNumber) + name = "shortcut" + toString(mNumber) + "_"; + else + name = "shortcut"; + for (int i = 0; i < SHORTCUT_ITEMS; i++) + { + int itemId = (int) cfg->getValue(name + toString(i), -1); + + mItems[i] = itemId; + } +} + +void ItemShortcut::save() +{ + std::string name; + if (mNumber) + name = "shortcut" + toString(mNumber) + "_"; + else + name = "shortcut"; + + logger->log("save %s", name.c_str()); + + for (int i = 0; i < SHORTCUT_ITEMS; i++) + { + const int itemId = mItems[i] ? mItems[i] : -1; + serverConfig.setValue(name + toString(i), itemId); + } +} + +void ItemShortcut::useItem(int index) +{ + if (!PlayerInfo::getInventory()) + return; + + int itemId = mItems[index]; + if (itemId >= 0) + { + if (itemId < SPELL_MIN_ID) + { + Item *item = PlayerInfo::getInventory()->findItem(itemId); + if (item && item->getQuantity()) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + else + Net::getInventoryHandler()->equipItem(item); + } + else + { + Net::getInventoryHandler()->useItem(item); + } + } + } + else if (spellManager) + { + spellManager->useItem(itemId); + } + } +} + +void ItemShortcut::equipItem(int index) +{ + if (!PlayerInfo::getInventory()) + return; + + if (mItems[index]) + { + Item *item = PlayerInfo::getInventory()->findItem(mItems[index]); + if (item && item->getQuantity()) + { + if (item->isEquipment()) + { + if (!item->isEquipped()) + Net::getInventoryHandler()->equipItem(item); + } + } + } +} +void ItemShortcut::unequipItem(int index) +{ + if (!PlayerInfo::getInventory()) + return; + + if (mItems[index]) + { + Item *item = PlayerInfo::getInventory()->findItem(mItems[index]); + if (item && item->getQuantity()) + { + if (item->isEquipment()) + { + if (item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + } + } + } +} diff --git a/src/itemshortcut.h b/src/itemshortcut.h new file mode 100644 index 000000000..e4797e134 --- /dev/null +++ b/src/itemshortcut.h @@ -0,0 +1,146 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEMSHORTCUT_H +#define ITEMSHORTCUT_H + +#define SHORTCUT_ITEMS 20 + +#define SHORTCUT_TABS 3 + +class Item; + +/** + * The class which keeps track of the item shortcuts. + */ +class ItemShortcut +{ + public: + /** + * Constructor. + */ + ItemShortcut(int number); + + /** + * Destructor. + */ + ~ItemShortcut(); + + /** + * Load the configuration information. + */ + void load(bool oldConfig = 0); + + /** + * Save the configuration information. + */ + void save(); + + /** + * Returns the shortcut item ID specified by the index. + * + * @param index Index of the shortcut item. + */ + int getItem(int index) const + { return mItems[index]; } + + /** + * Returns the amount of shortcut items. + */ + int getItemCount() const + { return SHORTCUT_ITEMS; } + + /** + * Returns the item ID that is currently selected. + */ + int getItemSelected() const + { return mItemSelected; } + + /** + * Adds the selected item ID to the items specified by the index. + * + * @param index Index of the items. + */ + void setItem(int index) + { mItems[index] = mItemSelected; save(); } + + /** + * Adds an item to the items store specified by the index. + * + * @param index Index of the item. + * @param itemId ID of the item. + */ + void setItems(int index, int itemId) + { mItems[index] = itemId; save(); } + + /** + * Set the item that is selected. + * + * @param itemId The ID of the item that is to be assigned. + */ + void setItemSelected(int itemId) + { mItemSelected = itemId; } + + /** + * Returns selected shortcut item ID. + */ + int getSelectedItem() const + { return mItemSelected; } + + /** + * A flag to check if the item is selected. + */ + bool isItemSelected() const + { return mItemSelected > -1; } + + /** + * Remove a item from the shortcut. + */ + void removeItem(int index) + { mItems[index] = -1; save(); } + + /** + * Try to use the item specified by the index. + * + * @param index Index of the item shortcut. + */ + void useItem(int index); + + /** + * Equip a item from the shortcut. + */ + void equipItem(int index); + + /** + * UnEquip a item from the shortcut. + */ + void unequipItem(int index); + + private: + + int mItems[SHORTCUT_ITEMS]; /**< The items stored. */ + int mItemSelected; /**< The item held by cursor. */ + int mNumber; +}; + +extern ItemShortcut *itemShortcut[SHORTCUT_TABS]; + +#endif diff --git a/src/joystick.cpp b/src/joystick.cpp new file mode 100644 index 000000000..8d59d2783 --- /dev/null +++ b/src/joystick.cpp @@ -0,0 +1,150 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "configuration.h" +#include "joystick.h" +#include "log.h" + +#include + +int Joystick::joystickCount = 0; + +void Joystick::init() +{ + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + + // Have SDL call SDL_JoystickUpdate() automatically + SDL_JoystickEventState(SDL_ENABLE); + + joystickCount = SDL_NumJoysticks(); + logger->log("%i joysticks/gamepads found", joystickCount); + for (int i = 0; i < joystickCount; i++) + logger->log("- %s", SDL_JoystickName(i)); +} + +Joystick::Joystick(int no): + mDirection(0), + mCalibrating(false), + mEnabled(false) +{ + assert(no < joystickCount); + + mJoystick = SDL_JoystickOpen(no); + + // TODO Bail out! + if (!mJoystick) + { + logger->log("Couldn't open joystick: %s", SDL_GetError()); + return; + } + + logger->log("Axes: %i ", SDL_JoystickNumAxes(mJoystick)); + logger->log("Balls: %i", SDL_JoystickNumBalls(mJoystick)); + logger->log("Hats: %i", SDL_JoystickNumHats(mJoystick)); + logger->log("Buttons: %i", SDL_JoystickNumButtons(mJoystick)); + + mEnabled = config.getBoolValue("joystickEnabled"); + mUpTolerance = config.getIntValue("upTolerance"); + mDownTolerance = config.getIntValue("downTolerance"); + mLeftTolerance = config.getIntValue("leftTolerance"); + mRightTolerance = config.getIntValue("rightTolerance"); + + for (int i = 0; i < MAX_BUTTONS; i++) + mButtons[i] = false; +} + +Joystick::~Joystick() +{ + SDL_JoystickClose(mJoystick); +} + +void Joystick::update() +{ + mDirection = 0; + + // When calibrating, don't bother the outside with our state + if (mCalibrating) + { + doCalibration(); + return; + }; + + if (!mEnabled) + return; + + // X-Axis + int position = SDL_JoystickGetAxis(mJoystick, 0); + if (position >= mRightTolerance) + mDirection |= RIGHT; + else if (position <= mLeftTolerance) + mDirection |= LEFT; + + // Y-Axis + position = SDL_JoystickGetAxis(mJoystick, 1); + if (position <= mUpTolerance) + mDirection |= UP; + else if (position >= mDownTolerance) + mDirection |= DOWN; + + // Buttons + for (int i = 0; i < MAX_BUTTONS; i++) + mButtons[i] = (SDL_JoystickGetButton(mJoystick, i) == 1); +} + +void Joystick::startCalibration() +{ + mUpTolerance = 0; + mDownTolerance = 0; + mLeftTolerance = 0; + mRightTolerance = 0; + mCalibrating = true; +} + +void Joystick::doCalibration() +{ + // X-Axis + int position = SDL_JoystickGetAxis(mJoystick, 0); + if (position > mRightTolerance) + mRightTolerance = position; + else if (position < mLeftTolerance) + mLeftTolerance = position; + + // Y-Axis + position = SDL_JoystickGetAxis(mJoystick, 1); + if (position > mDownTolerance) + mDownTolerance = position; + else if (position < mUpTolerance) + mUpTolerance = position; +} + +void Joystick::finishCalibration() +{ + config.setValue("leftTolerance", mLeftTolerance); + config.setValue("rightTolerance", mRightTolerance); + config.setValue("upTolerance", mUpTolerance); + config.setValue("downTolerance", mDownTolerance); + mCalibrating = false; +} + +bool Joystick::buttonPressed(unsigned char no) const +{ + return (mEnabled && no < MAX_BUTTONS) ? mButtons[no] : false; +} diff --git a/src/joystick.h b/src/joystick.h new file mode 100644 index 000000000..da60686c7 --- /dev/null +++ b/src/joystick.h @@ -0,0 +1,114 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef JOYSTICK_H +#define JOYSTICK_H + +#include + +class Joystick +{ + public: + /** + * Number of buttons we can handle. + */ + enum + { + MAX_BUTTONS = 6 + }; + + /** + * Directions, to be used as bitmask values. + */ + enum + { + UP = 1, + DOWN = 2, + LEFT = 4, + RIGHT = 8 + }; + + /** + * Initializes the joystick subsystem. + */ + static void init(); + + /** + * Returns the number of available joysticks. + */ + static int getNumberOfJoysticks() + { return joystickCount; } + + /** + * Constructor, pass the number of the joystick the new object + * should access. + */ + Joystick(int no); + + ~Joystick(); + + bool isEnabled() const + { return mEnabled; } + + void setEnabled(bool enabled) + { mEnabled = enabled; } + + /** + * Updates the direction and button information. + */ + void update(); + + void startCalibration(); + + void finishCalibration(); + + bool isCalibrating() const + { return mCalibrating; } + + bool buttonPressed(unsigned char no) const; + + bool isUp() const + { return mEnabled && (mDirection & UP); }; + + bool isDown() const + { return mEnabled && (mDirection & DOWN); }; + + bool isLeft() const + { return mEnabled && (mDirection & LEFT); }; + + bool isRight() const + { return mEnabled && (mDirection & RIGHT); }; + + protected: + unsigned char mDirection; + bool mButtons[MAX_BUTTONS]; + SDL_Joystick *mJoystick; + + int mUpTolerance, mDownTolerance, mLeftTolerance, mRightTolerance; + bool mCalibrating; + bool mEnabled; + + static int joystickCount; + + void doCalibration(); +}; + +#endif // JOYSTICK_H diff --git a/src/keyboardconfig.cpp b/src/keyboardconfig.cpp new file mode 100644 index 000000000..fd3c3aa09 --- /dev/null +++ b/src/keyboardconfig.cpp @@ -0,0 +1,449 @@ +/* + * Custom keyboard shortcuts configuration + * Copyright (C) 2007 Joshua Langley + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "configuration.h" +#include "keyboardconfig.h" +#include "log.h" + +#include "gui/sdlinput.h" +#include "gui/setup_keyboard.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +struct KeyData +{ + const char *configField; + int defaultValue; + std::string caption; + int grp; +}; + +// keyData must be in same order as enum keyAction. +static KeyData const keyData[KeyboardConfig::KEY_TOTAL] = { + {"keyMoveUp", SDLK_UP, _("Move Up"), KeyboardConfig::GRP_DEFAULT}, + {"keyMoveDown", SDLK_DOWN, _("Move Down"), KeyboardConfig::GRP_DEFAULT}, + {"keyMoveLeft", SDLK_LEFT, _("Move Left"), KeyboardConfig::GRP_DEFAULT}, + {"keyMoveRight", SDLK_RIGHT, _("Move Right"), KeyboardConfig::GRP_DEFAULT}, + {"keyAttack", SDLK_LCTRL, _("Attack"), KeyboardConfig::GRP_DEFAULT}, + {"keyTargetAttack", SDLK_x, _("Target & Attack"), + KeyboardConfig::GRP_DEFAULT}, + {"keyMoveToTarget", SDLK_v, _("Move to Target"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChangeMoveToTarget", SDLK_PERIOD, _("Change Move to Target type"), + KeyboardConfig::GRP_DEFAULT}, + {"keyMoveToHome", SDLK_d, _("Move to Home location"), + KeyboardConfig::GRP_DEFAULT}, + {"keySetHome", SDLK_KP5, _("Set home location"), + KeyboardConfig::GRP_DEFAULT}, + {"keyMoveToPoint", SDLK_RSHIFT, _("Move to navigation point"), + KeyboardConfig::GRP_DEFAULT}, + {"keySmilie", SDLK_LALT, _("Smilie"), KeyboardConfig::GRP_DEFAULT}, + {"keyTalk", SDLK_t, _("Talk"), KeyboardConfig::GRP_DEFAULT}, + {"keyTarget", SDLK_LSHIFT, _("Stop Attack"), KeyboardConfig::GRP_DEFAULT}, + {"keyTargetClosest", SDLK_a, _("Target Closest"), + KeyboardConfig::GRP_DEFAULT}, + {"keyTargetNPC", SDLK_n, _("Target NPC"), KeyboardConfig::GRP_DEFAULT}, + {"keyTargetPlayer", SDLK_q, _("Target Player"), + KeyboardConfig::GRP_DEFAULT}, + {"keyPickup", SDLK_z, _("Pickup"), KeyboardConfig::GRP_DEFAULT}, + {"keyChangePickupType", SDLK_o, _("Change Pickup Type"), + KeyboardConfig::GRP_DEFAULT}, + {"keyHideWindows", SDLK_h, _("Hide Windows"), KeyboardConfig::GRP_DEFAULT}, + {"keyBeingSit", SDLK_s, _("Sit"), KeyboardConfig::GRP_DEFAULT}, + {"keyScreenshot", SDLK_p, _("Screenshot"), KeyboardConfig::GRP_DEFAULT}, + {"keyTrade", SDLK_r, _("Enable/Disable Trading"), + KeyboardConfig::GRP_DEFAULT}, + {"keyPathfind", SDLK_f, _("Change Map View Mode"), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcutsKey", SDLK_MENU, _("Item Shortcuts Key"), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut1", SDLK_1, strprintf(_("Item Shortcut %d"), 1), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut2", SDLK_2, strprintf(_("Item Shortcut %d"), 2), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut3", SDLK_3, strprintf(_("Item Shortcut %d"), 3), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut4", SDLK_4, strprintf(_("Item Shortcut %d"), 4), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut5", SDLK_5, strprintf(_("Item Shortcut %d"), 5), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut6", SDLK_6, strprintf(_("Item Shortcut %d"), 6), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut7", SDLK_7, strprintf(_("Item Shortcut %d"), 7), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut8", SDLK_8, strprintf(_("Item Shortcut %d"), 8), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut9", SDLK_9, strprintf(_("Item Shortcut %d"), 9), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut10", SDLK_0, strprintf(_("Item Shortcut %d"), 10), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut11", SDLK_MINUS, strprintf(_("Item Shortcut %d"), 11), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut12", SDLK_EQUALS, strprintf(_("Item Shortcut %d"), 12), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut13", SDLK_BACKSPACE, strprintf(_("Item Shortcut %d"), 13), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut14", SDLK_INSERT, strprintf(_("Item Shortcut %d"), 14), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut15", SDLK_HOME, strprintf(_("Item Shortcut %d"), 15), + KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut16", KeyboardConfig::KEY_NO_VALUE, + strprintf(_("Item Shortcut %d"), 16), KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut17", KeyboardConfig::KEY_NO_VALUE, + strprintf(_("Item Shortcut %d"), 17), KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut18", KeyboardConfig::KEY_NO_VALUE, + strprintf(_("Item Shortcut %d"), 18), KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut19", KeyboardConfig::KEY_NO_VALUE, + strprintf(_("Item Shortcut %d"), 19), KeyboardConfig::GRP_DEFAULT}, + {"keyShortcut20", KeyboardConfig::KEY_NO_VALUE, + strprintf(_("Item Shortcut %d"), 20), KeyboardConfig::GRP_DEFAULT}, + {"keyWindowHelp", SDLK_F1, _("Help Window"), KeyboardConfig::GRP_DEFAULT}, + {"keyWindowStatus", SDLK_F2, _("Status Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowInventory", SDLK_F3, _("Inventory Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowEquipment", SDLK_F4, _("Equipment Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowSkill", SDLK_F5, _("Skill Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowMinimap", SDLK_F6, _("Minimap Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowChat", SDLK_F7, _("Chat Window"), KeyboardConfig::GRP_DEFAULT}, + {"keyWindowShortcut", SDLK_F8, _("Item Shortcut Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowSetup", SDLK_F9, _("Setup Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowDebug", SDLK_F10, _("Debug Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowSocial", SDLK_F11, _("Social Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowEmoteBar", SDLK_F12, _("Emote Shortcut Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowOutfit", SDLK_BACKQUOTE, _("Outfits Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowShop", -1, _("Shop Window"), KeyboardConfig::GRP_DEFAULT}, + {"keyWindowDrop", SDLK_w, _("Quick drop Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowKills", SDLK_e, _("Kills Stats Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowSpells", SDLK_j, _("Commands Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowBotChecker", SDLK_LEFTBRACKET, _("Bot Checker Window"), + KeyboardConfig::GRP_DEFAULT}, + {"keyWindowOnline", KeyboardConfig::KEY_NO_VALUE, + _("Who Is Online Window"), KeyboardConfig::GRP_DEFAULT}, + {"keySocialPrevTab", KeyboardConfig::KEY_NO_VALUE, + _("Previous Social Tab"), KeyboardConfig::GRP_DEFAULT}, + {"keySocialNextTab", KeyboardConfig::KEY_NO_VALUE, _("Next Social Tab"), + KeyboardConfig::GRP_DEFAULT}, + {"keyEmoteShortcut1", SDLK_1, strprintf(_("Emote Shortcut %d"), 1), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut2", SDLK_2, strprintf(_("Emote Shortcut %d"), 2), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut3", SDLK_3, strprintf(_("Emote Shortcut %d"), 3), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut4", SDLK_4, strprintf(_("Emote Shortcut %d"), 4), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut5", SDLK_5, strprintf(_("Emote Shortcut %d"), 5), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut6", SDLK_6, strprintf(_("Emote Shortcut %d"), 6), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut7", SDLK_7, strprintf(_("Emote Shortcut %d"), 7), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut8", SDLK_8, strprintf(_("Emote Shortcut %d"), 8), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut9", SDLK_9, strprintf(_("Emote Shortcut %d"), 9), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut10", SDLK_0, strprintf(_("Emote Shortcut %d"), 10), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut11", SDLK_MINUS, strprintf(_("Emote Shortcut %d"), 11), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut12", SDLK_EQUALS, strprintf(_("Emote Shortcut %d"), 12), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut13", SDLK_BACKSPACE, + strprintf(_("Emote Shortcut %d"), 13), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut14", SDLK_INSERT, strprintf(_("Emote Shortcut %d"), 14), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut15", SDLK_HOME, strprintf(_("Emote Shortcut %d"), 15), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut16", SDLK_q, strprintf(_("Emote Shortcut %d"), 16), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut17", SDLK_w, strprintf(_("Emote Shortcut %d"), 17), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut18", SDLK_e, strprintf(_("Emote Shortcut %d"), 18), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut19", SDLK_r, strprintf(_("Emote Shortcut %d"), 19), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut20", SDLK_t, strprintf(_("Emote Shortcut %d"), 20), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut21", SDLK_y, strprintf(_("Emote Shortcut %d"), 21), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut22", SDLK_u, strprintf(_("Emote Shortcut %d"), 22), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut23", SDLK_i, strprintf(_("Emote Shortcut %d"), 23), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut24", SDLK_o, strprintf(_("Emote Shortcut %d"), 24), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut25", SDLK_p, strprintf(_("Emote Shortcut %d"), 25), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut26", SDLK_LEFTBRACKET, + strprintf(_("Emote Shortcut %d"), 26), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut27", SDLK_RIGHTBRACKET, + strprintf(_("Emote Shortcut %d"), 27), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut28", SDLK_BACKSLASH, + strprintf(_("Emote Shortcut %d"), 28), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut29", SDLK_a, strprintf(_("Emote Shortcut %d"), 29), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut30", SDLK_s, strprintf(_("Emote Shortcut %d"), 30), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut31", SDLK_d, strprintf(_("Emote Shortcut %d"), 31), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut32", SDLK_f, strprintf(_("Emote Shortcut %d"), 32), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut33", SDLK_g, strprintf(_("Emote Shortcut %d"), 33), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut34", SDLK_h, strprintf(_("Emote Shortcut %d"), 34), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut35", SDLK_j, strprintf(_("Emote Shortcut %d"), 35), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut36", SDLK_k, strprintf(_("Emote Shortcut %d"), 36), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut37", SDLK_l, strprintf(_("Emote Shortcut %d"), 37), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut38", SDLK_SEMICOLON, + strprintf(_("Emote Shortcut %d"), 38), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut39", SDLK_QUOTE, strprintf(_("Emote Shortcut %d"), 39), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut40", SDLK_z, strprintf(_("Emote Shortcut %d"), 40), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut41", SDLK_x, strprintf(_("Emote Shortcut %d"), 41), + KeyboardConfig::GRP_EMOTION}, + {"keyEmoteShortcut42", SDLK_c, strprintf(_("Emote Shortcut %d"), 42), + KeyboardConfig::GRP_EMOTION}, + {"keyWearOutfit", SDLK_RCTRL, _("Wear Outfit"), + KeyboardConfig::GRP_DEFAULT}, + {"keyCopyOutfit", SDLK_RALT, _("Copy Outfit"), + KeyboardConfig::GRP_DEFAULT}, + {"keyCopyEquipedOutfit", SDLK_RIGHTBRACKET, _("Copy Equiped to Outfit"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChat", SDLK_RETURN, _("Toggle Chat"), + KeyboardConfig::GRP_DEFAULT | KeyboardConfig::GRP_CHAT}, + {"keyChatScrollUp", SDLK_PAGEUP, _("Scroll Chat Up"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChatScrollDown", SDLK_PAGEDOWN, _("Scroll Chat Down"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChatPrevTab", SDLK_KP7, _("Previous Chat Tab"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChatNextTab", SDLK_KP9, _("Next Chat Tab"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChatPrevHistory", SDLK_KP7, _("Previous chat tab line"), + KeyboardConfig::GRP_CHAT}, + {"keyChatNextHistory", SDLK_KP9, _("Next chat tab line"), + KeyboardConfig::GRP_CHAT}, + {"keyAutoCompleteChat", SDLK_TAB, _("Chat Auto Complete"), + KeyboardConfig::GRP_CHAT}, + {"keyDeActivateChat", SDLK_ESCAPE, _("Deactivate Chat Input"), + KeyboardConfig::GRP_CHAT}, + {"keyOK", SDLK_SPACE, _("Select OK"), KeyboardConfig::GRP_DEFAULT}, + {"keyQuit", SDLK_ESCAPE, _("Quit"), KeyboardConfig::GRP_DEFAULT}, + {"keyIgnoreInput1", SDLK_LSUPER, _("Ignore input 1"), + KeyboardConfig::GRP_DEFAULT}, + {"keyIgnoreInput2", SDLK_RSUPER, _("Ignore input 2"), + KeyboardConfig::GRP_DEFAULT}, + {"keyDirectUp", SDLK_l, _("Direct Up"), KeyboardConfig::GRP_DEFAULT}, + {"keyDirectDown", SDLK_SEMICOLON, _("Direct Down"), + KeyboardConfig::GRP_DEFAULT}, + {"keyDirectLeft", SDLK_k, _("Direct Left"), KeyboardConfig::GRP_DEFAULT}, + {"keyDirectRight", SDLK_QUOTE, _("Direct Right"), + KeyboardConfig::GRP_DEFAULT}, + {"keyCrazyMoves", SDLK_SLASH, _("Crazy moves"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChangeCrazyMoveType", SDLK_BACKSLASH, _("Change Crazy Move mode"), + KeyboardConfig::GRP_DEFAULT}, + {"keyQuickDrop", SDLK_y, _("Quick Drop N Items from 0 slot"), + KeyboardConfig::GRP_DEFAULT}, + {"keyQuickDropN", SDLK_u, _("Quick Drop N Items"), + KeyboardConfig::GRP_DEFAULT}, + {"keySwitchQuickDrop", SDLK_i, _("Switch Quick Drop Counter"), + KeyboardConfig::GRP_DEFAULT}, + {"keyMagicInma1", SDLK_c, _("Quick heal target or self"), + KeyboardConfig::GRP_DEFAULT}, + {"keyMagicItenplz", SDLK_m, _("Use #itenplz spell"), + KeyboardConfig::GRP_DEFAULT}, + {"keyMagicAttack", SDLK_b, _("Use magic attack"), + KeyboardConfig::GRP_DEFAULT}, + {"keySwitchMagicAttack", SDLK_COMMA, _("Switch magic attack"), + KeyboardConfig::GRP_DEFAULT}, + {"keyInvertDirection", SDLK_KP0, _("Change move type"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChangeAttackWeaponType", SDLK_g, _("Change Attack Weapon Type"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChangeAttackType", SDLK_END, _("Change Attack Type"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChangeFollowMode", SDLK_KP1, _("Change Follow mode"), + KeyboardConfig::GRP_DEFAULT}, + {"keyChangeImitationMode", SDLK_KP4, _("Change Imitation mode"), + KeyboardConfig::GRP_DEFAULT}, + {"keyDisableGameModifiers", SDLK_KP8, + _("Disbale / Enable Game modifier keys"), KeyboardConfig::GRP_DEFAULT}, + {"keyChangeAudio", SDLK_KP3, _("On / Off audio"), + KeyboardConfig::GRP_DEFAULT}, + {"keyAway", SDLK_KP2, _("Enable / Disable away mode"), + KeyboardConfig::GRP_DEFAULT}, + {"keyRightClick", SDLK_TAB, _("Emulate right click from keyboard"), + KeyboardConfig::GRP_DEFAULT}, + {"keyCameraMode", SDLK_KP_PLUS, _("Toggle camera mode"), + KeyboardConfig::GRP_DEFAULT} +}; + +void KeyboardConfig::init() +{ + for (int i = 0; i < KEY_TOTAL; i++) + { + mKey[i].configField = keyData[i].configField; + mKey[i].defaultValue = keyData[i].defaultValue; + mKey[i].caption = keyData[i].caption; + mKey[i].value = KEY_NO_VALUE; + mKey[i].grp = keyData[i].grp; + } + mNewKeyIndex = KEY_NO_VALUE; + mEnabled = true; + + retrieve(); +} + +void KeyboardConfig::retrieve() +{ + for (int i = 0; i < KEY_TOTAL; i++) + { + mKey[i].value = (int) config.getValue( + mKey[i].configField, mKey[i].defaultValue); + } +} + +void KeyboardConfig::store() +{ + for (int i = 0; i < KEY_TOTAL; i++) + config.setValue(mKey[i].configField, mKey[i].value); +} + +void KeyboardConfig::makeDefault() +{ + for (int i = 0; i < KEY_TOTAL; i++) + mKey[i].value = mKey[i].defaultValue; +} + +bool KeyboardConfig::hasConflicts() +{ + int i, j; + /** + * No need to parse the square matrix: only check one triangle + * that's enough to detect conflicts + */ + for (i = 0; i < KEY_TOTAL; i++) + { + if (mKey[i].value == KEY_NO_VALUE) + continue; + + for (j = i, j++; j < KEY_TOTAL; j++) + { + // Allow for item shortcut and emote keys to overlap + // as well as emote and ignore keys, but no other keys + if (mKey[j].value != KEY_NO_VALUE && + mKey[i].value == mKey[j].value && + ((mKey[i].grp & mKey[j].grp) != 0) + ) + { + mBindError = strprintf(_("Conflict \"%s\" and \"%s\" keys. " + "Resolve them, or gameplay may result" + " in strange behaviour."), + mKey[i].caption.c_str(), + mKey[j].caption.c_str()); + return true; + } + } + } + mBindError = ""; + return false; +} + +void KeyboardConfig::callbackNewKey() +{ + mSetupKey->newKeyCallback(mNewKeyIndex); +} + +int KeyboardConfig::getKeyIndex(int keyValue, int grp) const +{ + for (int i = 0; i < KEY_TOTAL; i++) + { + if (keyValue == mKey[i].value && + (grp & mKey[i].grp) != 0) + { + return i; + } + } + return KEY_NO_VALUE; +} + + +int KeyboardConfig::getKeyEmoteOffset(int keyValue) const +{ + for (int i = KEY_EMOTE_1; i <= KEY_EMOTE_42; i++) + { + if (keyValue == mKey[i].value) + return 1 + i - KEY_EMOTE_1; + } + return 0; +} + +bool KeyboardConfig::isKeyActive(int index) const +{ + if (!mActiveKeys) + return false; + return mActiveKeys[mKey[index].value]; +} + +void KeyboardConfig::refreshActiveKeys() +{ + mActiveKeys = SDL_GetKeyState(NULL); +} + +std::string KeyboardConfig::getKeyValueString(int index) const +{ + std::string key = SDL_GetKeyName( + (SDLKey) getKeyValue(index)); + + return getKeyShortString(key); +} + +std::string KeyboardConfig::getKeyShortString(const std::string &key) const +{ + if (key == "backspace") + return "bksp"; + else if (key == "unknown key") + return "u key"; + return key; +} \ No newline at end of file diff --git a/src/keyboardconfig.h b/src/keyboardconfig.h new file mode 100644 index 000000000..dbc90d2c4 --- /dev/null +++ b/src/keyboardconfig.h @@ -0,0 +1,333 @@ +/* + * Custom keyboard shortcuts configuration + * Copyright (C) 2007 Joshua Langley + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEYBOARDCONFIG_H +#define KEYBOARDCONFIG_H + +#include + +#include + +/** + * Each key represents a key function. Such as 'Move up', 'Attack' etc. + */ +struct KeyFunction +{ + const char* configField; /** Field index that is in the config file. */ + int defaultValue; /** The default key value used. */ + std::string caption; /** The caption value for the key function. */ + int value; /** The actual value that is used. */ + int grp; /** The key group */ +}; + +class Setup_Keyboard; + +class KeyboardConfig +{ + public: + /** + * Initializes the keyboard config explicitly. + */ + void init(); + + /** + * Retrieve the key values from config file. + */ + void retrieve(); + + /** + * Store the key values to config file. + */ + void store(); + + /** + * Make the keys their default values. + */ + void makeDefault(); + + /** + * Determines if any key assignments are the same as each other. + */ + bool hasConflicts(); + + /** + * Calls a function back so the key re-assignment(s) can be seen. + */ + void callbackNewKey(); + + /** + * Obtain the value stored in memory. + */ + int getKeyValue(int index) const + { return mKey[index].value; } + + /** + * Get the index of the new key to be assigned. + */ + int getNewKeyIndex() const + { return mNewKeyIndex; } + + /** + * Get the enable flag, which will stop the user from doing actions. + */ + bool isEnabled() const + { return mEnabled; } + + /** + * Get the key caption, providing more meaning to the user. + */ + const std::string &getKeyCaption(int index) const + { return mKey[index].caption; } + + /** + * Get the key function index by providing the keys value. + */ + int getKeyIndex(int keyValue, int grp = 1) const; + + /** + * Get the key function index for an emote by providing the offset value. + */ + int getKeyEmoteOffset(int keyValue) const; + + /** + * Set the enable flag, which will stop the user from doing actions. + */ + void setEnabled(bool flag) + { mEnabled = flag; } + + /** + * Set the index of the new key to be assigned. + */ + void setNewKeyIndex(int value) + { mNewKeyIndex = value; } + + /** + * Set the value of the new key. + */ + void setNewKey(int value) + { mKey[mNewKeyIndex].value = value; } + + /** + * Set a reference to the key setup window. + */ + void setSetupKeyboard(Setup_Keyboard *setupKey) + { mSetupKey = setupKey; } + + /** + * Checks if the key is active, by providing the key function index. + */ + bool isKeyActive(int index) const; + + /** + * Takes a snapshot of all the active keys. + */ + void refreshActiveKeys(); + + std::string getKeyValueString(int index) const; + + std::string getKeyShortString(const std::string &key) const; + + const std::string &getBindError() const + { return mBindError; } + + /** + * All the key functions. + * KEY_NO_VALUE is used in initialization, and should be unchanged. + * KEY_TOTAL should always be last (used as a conditional in loops). + * The key assignment view gets arranged according to the order of + * these values. + */ + enum KeyAction + { + KEY_NO_VALUE = -1, + KEY_MOVE_UP, + KEY_MOVE_DOWN, + KEY_MOVE_LEFT, + KEY_MOVE_RIGHT, + KEY_ATTACK, + KEY_TARGET_ATTACK, + KEY_MOVE_TO_TARGET, + KEY_CHANGE_MOVE_TO_TARGET, + KEY_MOVE_TO_HOME, + KEY_SET_HOME, + KEY_MOVE_TO_POINT, + KEY_EMOTE, + KEY_TALK, + KEY_TARGET, + KEY_TARGET_CLOSEST, + KEY_TARGET_NPC, + KEY_TARGET_PLAYER, + KEY_PICKUP, + KEY_CHANGE_PICKUP_TYPE, + KEY_HIDE_WINDOWS, + KEY_SIT, + KEY_SCREENSHOT, + KEY_TRADE, + KEY_PATHFIND, + KEY_SHORTCUTS_KEY, + KEY_SHORTCUT_1, + KEY_SHORTCUT_2, + KEY_SHORTCUT_3, + KEY_SHORTCUT_4, + KEY_SHORTCUT_5, + KEY_SHORTCUT_6, + KEY_SHORTCUT_7, + KEY_SHORTCUT_8, + KEY_SHORTCUT_9, + KEY_SHORTCUT_10, + KEY_SHORTCUT_11, + KEY_SHORTCUT_12, + KEY_SHORTCUT_13, + KEY_SHORTCUT_14, + KEY_SHORTCUT_15, + KEY_SHORTCUT_16, + KEY_SHORTCUT_17, + KEY_SHORTCUT_18, + KEY_SHORTCUT_19, + KEY_SHORTCUT_20, + KEY_WINDOW_HELP, + KEY_WINDOW_STATUS, + KEY_WINDOW_INVENTORY, + KEY_WINDOW_EQUIPMENT, + KEY_WINDOW_SKILL, + KEY_WINDOW_MINIMAP, + KEY_WINDOW_CHAT, + KEY_WINDOW_SHORTCUT, + KEY_WINDOW_SETUP, + KEY_WINDOW_DEBUG, + KEY_WINDOW_SOCIAL, + KEY_WINDOW_EMOTE_SHORTCUT, + KEY_WINDOW_OUTFIT, + KEY_WINDOW_SHOP, + KEY_WINDOW_DROP, + KEY_WINDOW_KILLS, + KEY_WINDOW_SPELLS, + KEY_WINDOW_BOT_CHECKER, + KEY_WINDOW_ONLINE, + KEY_PREV_SOCIAL_TAB, + KEY_NEXT_SOCIAL_TAB, + KEY_EMOTE_1, + KEY_EMOTE_2, + KEY_EMOTE_3, + KEY_EMOTE_4, + KEY_EMOTE_5, + KEY_EMOTE_6, + KEY_EMOTE_7, + KEY_EMOTE_8, + KEY_EMOTE_9, + KEY_EMOTE_10, + KEY_EMOTE_11, + KEY_EMOTE_12, + KEY_EMOTE_13, + KEY_EMOTE_14, + KEY_EMOTE_15, + KEY_EMOTE_16, + KEY_EMOTE_17, + KEY_EMOTE_18, + KEY_EMOTE_19, + KEY_EMOTE_20, + KEY_EMOTE_21, + KEY_EMOTE_22, + KEY_EMOTE_23, + KEY_EMOTE_24, + KEY_EMOTE_25, + KEY_EMOTE_26, + KEY_EMOTE_27, + KEY_EMOTE_28, + KEY_EMOTE_29, + KEY_EMOTE_30, + KEY_EMOTE_31, + KEY_EMOTE_32, + KEY_EMOTE_33, + KEY_EMOTE_34, + KEY_EMOTE_35, + KEY_EMOTE_36, + KEY_EMOTE_37, + KEY_EMOTE_38, + KEY_EMOTE_39, + KEY_EMOTE_40, + KEY_EMOTE_41, + KEY_EMOTE_42, + KEY_WEAR_OUTFIT, + KEY_COPY_OUTFIT, + KEY_COPY_EQUIPED_OUTFIT, + KEY_TOGGLE_CHAT, + KEY_SCROLL_CHAT_UP, + KEY_SCROLL_CHAT_DOWN, + KEY_PREV_CHAT_TAB, + KEY_NEXT_CHAT_TAB, + KEY_CHAT_PREV_HISTORY, + KEY_CHAT_NEXT_HISTORY, + KEY_AUTOCOMPLETE_CHAT, + KEY_DEACTIVATE_CHAT, + KEY_OK, + KEY_QUIT, + KEY_IGNORE_INPUT_1, + KEY_IGNORE_INPUT_2, + KEY_DIRECT_UP, + KEY_DIRECT_DOWN, + KEY_DIRECT_LEFT, + KEY_DIRECT_RIGHT, + KEY_CRAZY_MOVES, + KEY_CHANGE_CRAZY_MOVES_TYPE, + KEY_QUICK_DROP, + KEY_QUICK_DROPN, + KEY_SWITCH_QUICK_DROP, + KEY_MAGIC_INMA1, + KEY_MAGIC_ITENPLZ, + KEY_MAGIC_ATTACK, + KEY_SWITCH_MAGIC_ATTACK, + KEY_INVERT_DIRECTION, + KEY_CHANGE_ATTACK_WEAPON_TYPE, + KEY_CHANGE_ATTACK_TYPE, + KEY_CHANGE_FOLLOW_MODE, + KEY_CHANGE_IMITATION_MODE, + KEY_DISABLE_GAME_MODIFIERS, + KEY_CHANGE_AUDIO, + KEY_AWAY, + KEY_RIGHT_CLICK, + KEY_CAMERA, + KEY_TOTAL + }; + + enum KeyGroup + { + GRP_DEFAULT = 1, // default game key + GRP_CHAT = 2, // chat key + GRP_EMOTION = 4, // emotions key + GRP_OUTFIT = 8 // outfit key + }; + + private: + int mNewKeyIndex; /**< Index of new key to be assigned */ + bool mEnabled; /**< Flag to respond to key input */ + + Setup_Keyboard *mSetupKey; /**< Reference to setup window */ + + KeyFunction mKey[KEY_TOTAL]; /**< Pointer to all the key data */ + + Uint8 *mActiveKeys; /**< Stores a list of all the keys */ + + std::string mBindError; +}; + +extern KeyboardConfig keyboard; + +#endif diff --git a/src/listener.cpp b/src/listener.cpp new file mode 100644 index 000000000..f9acac95c --- /dev/null +++ b/src/listener.cpp @@ -0,0 +1,43 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "listener.h" + +#include "event.h" + +namespace Mana +{ + +Listener::~Listener() +{ + Event::remove(this); +} + +void Listener::listen(Channels channel) +{ + Event::bind(this, channel); +} + +void Listener::ignore(Channels channel) +{ + Event::unbind(this, channel); +} + +} // namespace Mana diff --git a/src/listener.h b/src/listener.h new file mode 100644 index 000000000..ff5a78f86 --- /dev/null +++ b/src/listener.h @@ -0,0 +1,45 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LISTENER_H +#define LISTENER_H + +#include "event.h" + +#include + +namespace Mana +{ + +class Listener +{ + public: + ~Listener(); + + void listen(Channels channel); + + void ignore(Channels channel); + + virtual void event(Channels channel, const Event &event) = 0; +}; + +} // namespace Mana + +#endif diff --git a/src/localplayer.cpp b/src/localplayer.cpp new file mode 100644 index 000000000..edff2ce67 --- /dev/null +++ b/src/localplayer.cpp @@ -0,0 +1,3633 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "localplayer.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "effectmanager.h" +#include "flooritem.h" +#include "graphics.h" +#include "guild.h" +#include "item.h" +#include "keyboardconfig.h" +#include "log.h" +#include "map.h" +#include "party.h" +#include "particle.h" +#include "playerinfo.h" +#include "simpleanimation.h" +#include "sound.h" +#include "statuseffect.h" +#include "text.h" +#include "dropshortcut.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/inventorywindow.h" +#include "gui/killstats.h" +#include "gui/ministatus.h" +#include "gui/okdialog.h" +#include "gui/outfitwindow.h" +#include "gui/palette.h" +#include "gui/skilldialog.h" +#include "gui/socialwindow.h" +#include "gui/statuswindow.h" +#include "gui/theme.h" +#include "gui/userpalette.h" +#include "gui/viewport.h" + +#include "gui/widgets/chattab.h" + +#include "net/beinghandler.h" +#include "net/chathandler.h" +#include "net/guildhandler.h" +#include "net/inventoryhandler.h" +#include "net/net.h" +#include "net/partyhandler.h" +#include "net/playerhandler.h" +#include "net/specialhandler.h" +#include "net/tradehandler.h" + +#include "resources/animation.h" +#include "resources/imageset.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#include "mumblemanager.h" + +// This is the minimal delay between to permitted +// setDestination() calls using the keyboard. +// TODO: This can fine tuned later on when running is added... +const short walkingKeyboardDelay = 1000; +const short awayLimitTimer = 60; + +LocalPlayer *player_node = NULL; + +extern std::list beingInfoCache; + +LocalPlayer::LocalPlayer(int id, int subtype): + Being(id, PLAYER, subtype, 0), + mAttackRange(0), + mTargetTime(-1), + mLastTarget(-1), + mTarget(NULL), + mPlayerFollowed(""), + mPlayerImitated(""), + mPickUpTarget(NULL), + mGoingToTarget(false), mKeepAttacking(false), + mLastAction(-1), + mWalkingDir(0), + mPathSetByMouse(false), + mLocalWalkTime(-1), + mMessageTime(0), + mAwayDialog(0), + mAfkTime(0), + mAwayMode(false), + mShowNavigePath(false), + mDrawPath(false), + mActivityTime(0), + mNavigateX(0), mNavigateY(0), + mNavigateId(0), + mCrossX(0), mCrossY(0), + mOldX(0), mOldY(0), + mOldTileX(0), mOldTileY(0), + mLastHitFrom(""), + mWaitFor("") +{ + logger->log1("LocalPlayer::LocalPlayer"); + + listen(CHANNEL_ATTRIBUTES); + mLevel = 1; + + mAwayListener = new AwayListener(); + + mUpdateName = true; + + mTextColor = &Theme::getThemeColor(Theme::PLAYER); + mNameColor = &userPalette->getColor(UserPalette::SELF); + + mLastTargetX = 0; + mLastTargetY = 0; + + mInvertDirection = config.getIntValue("invertMoveDirection"); + mCrazyMoveType = config.getIntValue("crazyMoveType"); + mCrazyMoveState = 0; + mAttackWeaponType = config.getIntValue("attackWeaponType"); + mQuickDropCounter = config.getIntValue("quickDropCounter"); + mMoveState = 0; + mDisableCrazyMove = false; + mPickUpType = config.getIntValue("pickUpType"); + mMagicAttackType = config.getIntValue("magicAttackType"); + mMoveToTargetType = config.getIntValue("moveToTargetType"); + mDisableGameModifiers = config.getBoolValue("disableGameModifiers"); + mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers"); + mAttackType = config.getIntValue("attackType"); + mFollowMode = config.getIntValue("followMode"); + mImitationMode = config.getIntValue("imitationMode"); + mIsServerBuggy = serverConfig.getValueBool("enableBuggyServers", true); + mSyncPlayerMove = config.getBoolValue("syncPlayerMove"); + mDrawPath = config.getBoolValue("drawPath"); + mServerAttack = config.getBoolValue("serverAttack"); + mAttackMoving = config.getBoolValue("attackMoving"); + + mPingSendTick = 0; + mWaitPing = false; + mPingTime = 0; + + PlayerInfo::setStatBase(WALK_SPEED, getWalkSpeed().x); + PlayerInfo::setStatMod(WALK_SPEED, 0); + + loadHomes(); +// initTargetCursor(); + + config.addListener("showownname", this); + config.addListener("targetDeadPlayers", this); + serverConfig.addListener("enableBuggyServers", this); + config.addListener("syncPlayerMove", this); + config.addListener("drawPath", this); + config.addListener("serverAttack", this); + config.addListener("attackMoving", this); + setShowName(config.getBoolValue("showownname")); + beingInfoCache.clear(); +} + +LocalPlayer::~LocalPlayer() +{ + logger->log1("LocalPlayer::~LocalPlayer"); + + config.removeListener("showownname", this); + config.removeListener("targetDeadPlayers", this); + serverConfig.removeListener("enableBuggyServers", this); + config.removeListener("syncPlayerMove", this); + config.removeListener("drawPath", this); + config.removeListener("serverAttack", this); + config.removeListener("attackMoving", this); + + delete mAwayDialog; + mAwayDialog = 0; + delete mAwayListener; + mAwayListener = 0; + beingInfoCache.clear(); +} + +void LocalPlayer::logic() +{ + if (mumbleManager) + mumbleManager->setPos(getTileX(), getTileY(), getDirection()); + + // Actions are allowed once per second + if (get_elapsed_time(mLastAction) >= 1000) + mLastAction = -1; + + if (mActivityTime == 0 || mLastAction != -1) + mActivityTime = cur_time; + + if (mAction != MOVE && !mNavigatePath.empty()) + { + int dist = 5; + if (!mSyncPlayerMove) + dist = 20; + + if ((mNavigateX || mNavigateY) && + ((mCrossX + dist >= getTileX() && mCrossX <= getTileX() + dist + && mCrossY + dist >= getTileY() && mCrossY <= getTileY() + dist) + || (!mCrossX && !mCrossY))) + { + for (Path::const_iterator i = mNavigatePath.begin(), + i_end = mNavigatePath.end(); i != i_end; ++i) + { + if ((*i).x == getTileX() && (*i).y == getTileY()) + { + mNavigatePath.pop_front(); + break; + } + moveTo((*i).x, (*i).y); + break; + } + } + } + + // Show XP messages + if (!mMessages.empty()) + { + if (mMessageTime == 0) + { + //const Vector &pos = getPosition(); + + MessagePair info = mMessages.front(); + + if (particleEngine) + { + particleEngine->addTextRiseFadeOutEffect( + info.first, + /*(int) pos.x, + (int) pos.y - 48,*/ + getPixelX(), + getPixelY() - 48, + &userPalette->getColor(info.second), + gui->getInfoParticleFont(), true); + } + + mMessages.pop_front(); + mMessageTime = 30; + } + mMessageTime--; + } + + PlayerInfo::logic(); + + // Targeting allowed 4 times a second + if (get_elapsed_time(mLastTarget) >= 250) + mLastTarget = -1; + +// // Remove target if its been on a being for more than a minute + if (get_elapsed_time(mTargetTime) >= 60000) + { + mTargetTime = tick_time; +// setTarget(NULL); + mLastTarget = -1; + } + + if (mTarget) + { + if (mTarget->getType() == ActorSprite::NPC) + { + // NPCs are always in range + mTarget->setTargetType(TCT_IN_RANGE); + } + else + { + // Find whether target is in range + // TODO: Make this nicer, probably using getPosition() only + const int rangeX = + (Net::getNetworkType() == ServerInfo::MANASERV) ? + static_cast(abs(mTarget->getPosition().x + - getPosition().x)) : + static_cast(abs(mTarget->getTileX() - getTileX())); + const int rangeY = + (Net::getNetworkType() == ServerInfo::MANASERV) ? + static_cast(abs(mTarget->getPosition().y + - getPosition().y)) : + static_cast(abs(mTarget->getTileY() - getTileY())); + + const int attackRange = getAttackRange(); + const TargetCursorType targetType = rangeX > attackRange || + rangeY > attackRange ? + TCT_NORMAL : TCT_IN_RANGE; + mTarget->setTargetType(targetType); + + if (!mTarget->isAlive() && (!mTargetDeadPlayers + || mTarget->getType() != Being::PLAYER)) + { + stopAttack(); + } + + if (mKeepAttacking && mTarget) + attack(mTarget, true); + } + } + + Being::logic(); +} + +void LocalPlayer::setAction(Action action, int attackType) +{ + if (action == DEAD) + { + mLastTarget = -1; + if (!mLastHitFrom.empty()) + { + debugMsg(_("You were killed by ") + mLastHitFrom); + mLastHitFrom = ""; + } + setTarget(NULL); + } + + Being::setAction(action, attackType); + if (mumbleManager) + mumbleManager->setAction(static_cast(action)); +} + +void LocalPlayer::setGMLevel(int level) +{ + mGMLevel = level; + + if (level > 0) + setGM(true); +} + + +Position LocalPlayer::getNextWalkPosition(unsigned char dir) +{ + // check for mMap? + + // Compute where the next tile will be set. + int dx = 0, dy = 0; + if (dir & Being::UP) + dy--; + if (dir & Being::DOWN) + dy++; + if (dir & Being::LEFT) + dx--; + if (dir & Being::RIGHT) + dx++; + + Vector pos = getPosition(); + + // If no map or no direction is given, give back the current player position + if (!mMap || (!dx && !dy)) + return Position(static_cast(pos.x), static_cast(pos.y)); + + // Get the current tile pos and its offset + int tileX = static_cast(pos.x) / mMap->getTileWidth(); + int tileY = static_cast(pos.y) / mMap->getTileHeight(); + int offsetX = static_cast(pos.x) % mMap->getTileWidth(); + int offsetY = static_cast(pos.y) % mMap->getTileHeight(); + + // Get the walkability of every surrounding tiles. + bool wTopLeft = mMap->getWalk(tileX - 1, tileY - 1, getWalkMask()); + bool wTop = mMap->getWalk(tileX, tileY - 1, getWalkMask()); + bool wTopRight = mMap->getWalk(tileX + 1, tileY - 1, getWalkMask()); + bool wLeft = mMap->getWalk(tileX - 1, tileY, getWalkMask()); + bool wRight = mMap->getWalk(tileX + 1, tileY, getWalkMask()); + bool wBottomLeft = mMap->getWalk(tileX - 1, tileY + 1, getWalkMask()); + bool wBottom = mMap->getWalk(tileX, tileY + 1, getWalkMask()); + bool wBottomRight = mMap->getWalk(tileX + 1, tileY + 1, getWalkMask()); + + // Make diagonals unwalkable when both straight directions are blocking + if (!wTop) + { + if (!wRight) + wTopRight = false; + if (!wLeft) + wTopLeft = false; + } + if (!wBottom) + { + if (!wRight) + wBottomRight = false; + if (!wLeft) + wBottomLeft = false; + } + + // We'll make tests for each desired direction + + // Handle diagonal cases by setting the way back to a straight direction + // when necessary. + if (dx && dy) + { + // Going top-right + if (dx > 0 && dy < 0) + { + if (!wTopRight) + { + // Choose a straight direction when diagonal target is blocked + if (!wTop && wRight) + { + dy = 0; + } + else if (wTop && !wRight) + { + dx = 0; + } + else if (!wTop && !wRight) + { + return Position(tileX * 32 + 32 - getCollisionRadius(), + tileY * 32 + getCollisionRadius()); + } + else // Both straight direction are walkable + { + // Go right when below the corner + if (offsetY >= (offsetX / mMap->getTileHeight() + - (offsetX / mMap->getTileWidth() + * mMap->getTileHeight()) )) + { + dy = 0; + } + else // Go up otherwise + { + dx = 0; + } + } + } + else // The diagonal is walkable + { + return mMap->checkNodeOffsets(getCollisionRadius(), + getWalkMask(), Position(static_cast(pos.x) + 32, + static_cast(pos.y) - 32)); + } + } + + // Going top-left + if (dx < 0 && dy < 0) + { + if (!wTopLeft) + { + // Choose a straight direction when diagonal target is blocked + if (!wTop && wLeft) + { + dy = 0; + } + else if (wTop && !wLeft) + { + dx = 0; + } + else if (!wTop && !wLeft) + { + return Position(tileX * 32 + getCollisionRadius(), + tileY * 32 + getCollisionRadius()); + } + else // Both straight direction are walkable + { + // Go left when below the corner + if (offsetY >= (offsetX / mMap->getTileWidth() + * mMap->getTileHeight())) + { + dy = 0; + } + else // Go up otherwise + { + dx = 0; + } + } + } + else // The diagonal is walkable + { + return mMap->checkNodeOffsets(getCollisionRadius(), + getWalkMask(), Position(static_cast(pos.x) - 32, + static_cast(pos.y) - 32)); + } + } + + // Going bottom-left + if (dx < 0 && dy > 0) + { + if (!wBottomLeft) + { + // Choose a straight direction when diagonal target is blocked + if (!wBottom && wLeft) + { + dy = 0; + } + else if (wBottom && !wLeft) + { + dx = 0; + } + else if (!wBottom && !wLeft) + { + return Position(tileX * 32 + getCollisionRadius(), + tileY * 32 + 32 - getCollisionRadius()); + } + else // Both straight direction are walkable + { + // Go down when below the corner + if (offsetY >= (offsetX / mMap->getTileHeight() + - (offsetX / mMap->getTileWidth() + * mMap->getTileHeight()))) + { + dx = 0; + } + else // Go left otherwise + { + dy = 0; + } + } + } + else // The diagonal is walkable + { + return mMap->checkNodeOffsets(getCollisionRadius(), + getWalkMask(), Position(static_cast(pos.x) - 32, + static_cast(pos.y) + 32)); + } + } + + // Going bottom-right + if (dx > 0 && dy > 0) + { + if (!wBottomRight) + { + // Choose a straight direction when diagonal target is blocked + if (!wBottom && wRight) + { + dy = 0; + } + else if (wBottom && !wRight) + { + dx = 0; + } + else if (!wBottom && !wRight) + { + return Position(tileX * 32 + 32 - getCollisionRadius(), + tileY * 32 + 32 - getCollisionRadius()); + } + else // Both straight direction are walkable + { + // Go down when below the corner + if (offsetY >= (offsetX / mMap->getTileWidth() + * mMap->getTileHeight())) + { + dx = 0; + } + else // Go right otherwise + { + dy = 0; + } + } + } + else // The diagonal is walkable + { + return mMap->checkNodeOffsets(getCollisionRadius(), + getWalkMask(), Position(static_cast(pos.x) + 32, + static_cast(pos.y) + 32)); + } + } + + } // End of diagonal cases + + // Straight directions + // Right direction + if (dx > 0 && !dy) + { + // If the straight destination is blocked, + // Make the player go the closest possible. + if (!wRight) + { + return Position(tileX * 32 + 32 - getCollisionRadius(), + static_cast(pos.y)); + } + else + { + if (!wTopRight) + { + // If we're going to collide with the top-right corner + if (offsetY - getCollisionRadius() < 0) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + 32 - getCollisionRadius(), + tileY * 32 + getCollisionRadius()); + + } + } + + if (!wBottomRight) + { + // If we're going to collide with the bottom-right corner + if (offsetY + getCollisionRadius() > 32) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + 32 - getCollisionRadius(), + tileY * 32 + 32 - getCollisionRadius()); + + } + } + // If the way is clear, step up one checked tile ahead. + return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(), + Position(static_cast(pos.x) + 32, + static_cast(pos.y))); + } + } + + // Left direction + if (dx < 0 && !dy) + { + // If the straight destination is blocked, + // Make the player go the closest possible. + if (!wLeft) + { + return Position(tileX * 32 + getCollisionRadius(), + static_cast(pos.y)); + } + else + { + if (!wTopLeft) + { + // If we're going to collide with the top-left corner + if (offsetY - getCollisionRadius() < 0) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + getCollisionRadius(), + tileY * 32 + getCollisionRadius()); + + } + } + + if (!wBottomLeft) + { + // If we're going to collide with the bottom-left corner + if (offsetY + getCollisionRadius() > 32) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + getCollisionRadius(), + tileY * 32 + 32 - getCollisionRadius()); + + } + } + // If the way is clear, step up one checked tile ahead. + return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(), + Position(static_cast(pos.x) - 32, + static_cast(pos.y))); + } + } + + // Up direction + if (!dx && dy < 0) + { + // If the straight destination is blocked, + // Make the player go the closest possible. + if (!wTop) + { + return Position(static_cast(pos.x), + tileY * 32 + getCollisionRadius()); + } + else + { + if (!wTopLeft) + { + // If we're going to collide with the top-left corner + if (offsetX - getCollisionRadius() < 0) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + getCollisionRadius(), + tileY * 32 + getCollisionRadius()); + + } + } + + if (!wTopRight) + { + // If we're going to collide with the top-right corner + if (offsetX + getCollisionRadius() > 32) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + 32 - getCollisionRadius(), + tileY * 32 + getCollisionRadius()); + + } + } + // If the way is clear, step up one checked tile ahead. + return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(), + Position(static_cast(pos.x), + static_cast(pos.y) - 32)); + } + } + + // Down direction + if (!dx && dy > 0) + { + // If the straight destination is blocked, + // Make the player go the closest possible. + if (!wBottom) + { + return Position(static_cast(pos.x), + tileY * 32 + 32 - getCollisionRadius()); + } + else + { + if (!wBottomLeft) + { + // If we're going to collide with the bottom-left corner + if (offsetX - getCollisionRadius() < 0) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + getCollisionRadius(), + tileY * 32 + 32 - getCollisionRadius()); + + } + } + + if (!wBottomRight) + { + // If we're going to collide with the bottom-right corner + if (offsetX + getCollisionRadius() > 32) + { + // We make the player corrects its offset + // before going further + return Position(tileX * 32 + 32 - getCollisionRadius(), + tileY * 32 + 32 - getCollisionRadius()); + + } + } + // If the way is clear, step up one checked tile ahead. + return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(), + Position(static_cast(pos.x), + static_cast(pos.y) + 32)); + } + } + + // Return the current position if everything else has failed. + return Position(static_cast(pos.x), static_cast(pos.y)); +} + +void LocalPlayer::nextTile(unsigned char dir = 0) +{ + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { +// updatePos(); + + if (Party::getParty(1)) + { + PartyMember *pm = Party::getParty(1)->getMember(getName()); + if (pm) + { + pm->setX(getTileX()); + pm->setY(getTileY()); + } + } + + // TODO: Fix picking up when reaching target (this method is obsolete) + // TODO: Fix holding walking button to keep walking smoothly + if (mPath.empty()) + { + if (mPickUpTarget) + pickUp(mPickUpTarget); + + if (mWalkingDir) + startWalking(mWalkingDir); + } + + // TODO: Fix automatically walking within range of target, when wanted + if (mGoingToTarget && mTarget && withinAttackRange(mTarget)) + { + mAction = Being::STAND; + attack(mTarget, true); + mGoingToTarget = false; + mPath.clear(); + return; + } + else if (mGoingToTarget && !mTarget) + { + mGoingToTarget = false; + mPath.clear(); + } + + + Being::nextTile(); + } + else + { + if (!mMap || !dir) + return; + + const Vector &pos = getPosition(); + Position destination = getNextWalkPosition(dir); + + if (static_cast(pos.x) != destination.x + || static_cast(pos.y) != destination.y) + { + setDestination(destination.x, destination.y); + } + else if (dir != mDirection) + { + // If the being can't move, just change direction + + if (!Client::limitPackets(PACKET_DIRECTION)) + return; + + Net::getPlayerHandler()->setDirection(dir); + setDirection(dir); + } + } +} + +bool LocalPlayer::checkInviteRights(const std::string &guildName) +{ + Guild *guild = getGuild(guildName); + if (guild) + return guild->getInviteRights(); + + return false; +} + +void LocalPlayer::inviteToGuild(Being *being) +{ + if (!being || being->getType() != PLAYER) + return; + + // TODO: Allow user to choose which guild to invite being to + // For now, just invite to the first guild you have permissions to invite with + std::map::iterator itr = mGuilds.begin(); + std::map::iterator itr_end = mGuilds.end(); + for (; itr != itr_end; ++itr) + { + if (checkInviteRights(itr->second->getName())) + { + Net::getGuildHandler()->invite(itr->second->getId(), being); + return; + } + } +} + +bool LocalPlayer::pickUp(FloorItem *item) +{ + if (!item) + return false; + + if (!Client::limitPackets(PACKET_PICKUP)) + return false; + + int dx = item->getTileX() - getTileX(); + int dy = item->getTileY() - getTileY(); + int dist = 6; + + if (mPickUpType >= 4 && mPickUpType <= 6) + dist = 4; + + if (dx * dx + dy * dy < dist) + { + Net::getPlayerHandler()->pickUp(item); + mPickUpTarget = NULL; + } + else if (mPickUpType >= 4 && mPickUpType <= 6) + { + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + setDestination(item->getPixelX() + 16, item->getPixelY() + 16); + mPickUpTarget = item; + mPickUpTarget->addActorSpriteListener(this); + } + else + { + const Vector &playerPos = getPosition(); + Path debugPath = mMap->findPath( + static_cast(playerPos.x - 16) / 32, + static_cast(playerPos.y - 32) / 32, + item->getTileX(), item->getTileY(), 0x00, 0); + if (!debugPath.empty()) + navigateTo(item->getTileX(), item->getTileY()); + else + setDestination(item->getTileX(), item->getTileY()); + + mPickUpTarget = item; + mPickUpTarget->addActorSpriteListener(this); +// stopAttack(); + } + } + return true; +} + +void LocalPlayer::actorSpriteDestroyed(const ActorSprite &actorSprite) +{ + if (mPickUpTarget == &actorSprite) + mPickUpTarget = 0; +} + +Being *LocalPlayer::getTarget() const +{ + return mTarget; +} + +void LocalPlayer::setTarget(Being *target) +{ + if ((mLastTarget != -1 || target == this) && target) + return; + + if (target) + mLastTarget = tick_time; + + if (target == mTarget) + return; + + if (target || mAction == ATTACK) + { + mTargetTime = tick_time; + } + else + { + mKeepAttacking = false; + mTargetTime = -1; + } + + Being *oldTarget = 0; + if (mTarget) + { + mTarget->untarget(); + oldTarget = mTarget; + } + + if (mTarget && mTarget->getType() == ActorSprite::MONSTER) + mTarget->setShowName(false); + + mTarget = target; + + if (oldTarget) + oldTarget->updateName(); + + if (mTarget) + { + mLastTargetX = mTarget->getTileX(); + mLastTargetY = mTarget->getTileY(); + mTarget->updateName(); + } + + if (target && target->getType() == ActorSprite::MONSTER) + target->setShowName(true); +} + +void LocalPlayer::setDestination(int x, int y) +{ + mActivityTime = cur_time; + + mPickUpTarget = NULL; + if (getAttackType() == 0 || !mAttackMoving) + mKeepAttacking = false; + + // Only send a new message to the server when destination changes + if (x != mDest.x || y != mDest.y) + { + if (mInvertDirection != 1) + { + Net::getPlayerHandler()->setDestination(x, y, mDirection); + Being::setDestination(x, y); + } + else if (mInvertDirection == 1) + { + Uint8 newDir = 0; + if (mDirection&UP) + newDir |= DOWN; + if (mDirection&LEFT) + newDir |= RIGHT; + if (mDirection&DOWN) + newDir |= UP; + if (mDirection&RIGHT) + newDir |= LEFT; + + Net::getPlayerHandler()->setDestination(x, y, newDir); + + if (Client::limitPackets(PACKET_DIRECTION)) + { + setDirection(newDir); + Net::getPlayerHandler()->setDirection(newDir); + } + + Being::setDestination(x, y); + } + + // Manaserv: + // If the destination given to being class is accepted, + // we inform the Server. + if ((x == mDest.x && y == mDest.y) + || Net::getNetworkType() == ServerInfo::TMWATHENA) + { + Net::getPlayerHandler()->setDestination(x, y, mDirection); + } + + return; + } +} + +void LocalPlayer::setWalkingDir(unsigned char dir) +{ + // This function is called by Game::handleInput() + + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + // First if player is pressing key for the direction he is already + // going, do nothing more... + + // Else if he is pressing a key, and its different from what he has + // been pressing, stop (do not send this stop to the server) and + // start in the new direction + if (dir && (dir != getWalkingDir())) + stopWalking(false); + + // Else, he is not pressing a key, + // and the current path hasn't been sent by mouse, + // then, stop (sending to server). + else if (!dir) + { + if (!mPathSetByMouse) + stopWalking(true); + return; + } + + // If the delay to send another walk message to the server hasn't expired, + // don't do anything or we could get disconnected for spamming the server + if (get_elapsed_time(mLocalWalkTime) < walkingKeyboardDelay) + return; + } + + mWalkingDir = dir; + + // If we're not already walking, start walking. + if (mAction != MOVE && dir) + { + startWalking(dir); + } + else if (mAction == MOVE + && (Net::getNetworkType() == ServerInfo::MANASERV)) + { + nextTile(dir); + } +} + +void LocalPlayer::startWalking(unsigned char dir) +{ + // This function is called by setWalkingDir(), + // but also by nextTile() for TMW-Athena... + if (!mMap || !dir) + return; + + if (mAction == MOVE && !mPath.empty()) + { + // Just finish the current action, otherwise we get out of sync + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + const Vector &pos = getPosition(); + Being::setDestination(static_cast(pos.x), + static_cast(pos.y)); + } + else + { + Being::setDestination(getTileX(), getTileY()); + } + return; + } + + int dx = 0, dy = 0; + if (dir & UP) + dy--; + if (dir & DOWN) + dy++; + if (dir & LEFT) + dx--; + if (dir & RIGHT) + dx++; + + if (Net::getNetworkType() == ServerInfo::TMWATHENA) + { + // Prevent skipping corners over colliding tiles + if (dx && !mMap->getWalk(getTileX() + dx, getTileY(), getWalkMask())) + dx = 0; + if (dy && !mMap->getWalk(getTileX(), getTileY() + dy, getWalkMask())) + dy = 0; + + // Choose a straight direction when diagonal target is blocked + if (dx && dy && !mMap->getWalk(getTileX() + dx, getTileY() + dy, + getWalkMask())) + { + dx = 0; + } + + // Walk to where the player can actually go + if ((dx || dy) && mMap->getWalk(getTileX() + dx, getTileY() + dy, + getWalkMask())) + { + setDestination(getTileX() + dx, getTileY() + dy); + } + else if (dir != mDirection) + { + // If the being can't move, just change direction + + if (Client::limitPackets(PACKET_DIRECTION)) + { + Net::getPlayerHandler()->setDirection(dir); + setDirection(dir); + } + } + } + else + { + nextTile(dir); + } +} + +void LocalPlayer::stopWalking(bool sendToServer) +{ + if (mAction == MOVE && mWalkingDir) + { + mWalkingDir = 0; + mLocalWalkTime = 0; + + setDestination(static_cast(getPosition().x), + static_cast(getPosition().y)); + if (sendToServer) + { + Net::getPlayerHandler()->setDestination( + static_cast(getPosition().x), + static_cast(getPosition().y)); + } + setAction(STAND); + } + + // No path set anymore, so we reset the path by mouse flag + mPathSetByMouse = false; + + clearPath(); +} + +bool LocalPlayer::toggleSit() +{ + if (!Client::limitPackets(PACKET_SIT)) + return false; + + Being::Action newAction; + switch (mAction) + { + case STAND: newAction = SIT; break; + case SIT: newAction = STAND; break; + default: return true; + } + + Net::getPlayerHandler()->changeAction(newAction); + return true; +} + +bool LocalPlayer::updateSit() +{ + if (!Client::limitPackets(PACKET_SIT)) + return false; + + Net::getPlayerHandler()->changeAction(mAction); + return true; +} + +bool LocalPlayer::emote(Uint8 emotion) +{ + if (!Client::limitPackets(PACKET_EMOTE)) + return false; + + Net::getPlayerHandler()->emote(emotion); + return true; +} + +void LocalPlayer::attack(Being *target, bool keep, bool dontChangeEquipment) +{ + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + if (mLastAction != -1) + return; + + // Can only attack when standing still + if (mAction != STAND && mAction != ATTACK) + return; + } + + mKeepAttacking = keep; + + if (!target || target->getType() == ActorSprite::NPC) + return; + + if (mTarget != target || !mTarget) + { + mLastTarget = -1; + setTarget(target); + } + + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + Vector plaPos = this->getPosition(); + Vector tarPos = mTarget->getPosition(); + int dist_x = static_cast(plaPos.x - tarPos.x); + int dist_y = static_cast(plaPos.y - tarPos.y); + + if (abs(dist_y) >= abs(dist_x)) + { + if (dist_y < 0) + setDirection(DOWN); + else + setDirection(UP); + } + else + { + if (dist_x < 0) + setDirection(RIGHT); + else + setDirection(LEFT); + } + + mLastAction = tick_time; + } + else + { + int dist_x = target->getTileX() - getTileX(); + int dist_y = target->getTileY() - getTileY(); + + // Must be standing or sitting to attack + if (mAction != STAND && mAction != SIT) + return; + + if (abs(dist_y) >= abs(dist_x)) + { + if (dist_y > 0) + setDirection(DOWN); + else + setDirection(UP); + } + else + { + if (dist_x > 0) + setDirection(RIGHT); + else + setDirection(LEFT); + } + + mActionTime = tick_time; + mTargetTime = tick_time; + } + + setAction(ATTACK); + + if (mEquippedWeapon) + { + std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE); + if (!soundFile.empty()) + sound.playSfx(soundFile); + } + else + { + sound.playSfx(paths.getValue("attackSfxFile", "fist-swish.ogg")); + } + + if (!Client::limitPackets(PACKET_ATTACK)) + return; + + if (!dontChangeEquipment && target) + changeEquipmentBeforeAttack(target); + + Net::getPlayerHandler()->attack(target->getId(), mServerAttack); + if ((Net::getNetworkType() == ServerInfo::TMWATHENA) && !keep) + stopAttack(); +} + +void LocalPlayer::stopAttack() +{ + if (mServerAttack && mAction == ATTACK) + Net::getPlayerHandler()->stopAttack(); + + if (mAction == ATTACK) + setAction(STAND); + + if (mTarget) + setTarget(NULL); + + mKeepAttacking = false; + mLastTarget = -1; +} + +void LocalPlayer::pickedUp(const ItemInfo &itemInfo, int amount) +{ + if (!amount) + { + if (config.getBoolValue("showpickupchat")) + { + localChatTab->chatLog(_("Unable to pick up item."), + BY_SERVER); + } + } + else + { + if (config.getBoolValue("showpickupchat") && localChatTab) + { + // TRANSLATORS: This sentence may be translated differently + // for different grammatical numbers (singular, plural, ...) + localChatTab->chatLog(strprintf(ngettext("You picked up %d " + "[@@%d|%s@@].", "You picked up %d [@@%d|%s@@].", amount), + amount, itemInfo.getId(), itemInfo.getName().c_str()), + BY_SERVER); + } + + if (mMap && config.getBoolValue("showpickupparticle")) + { + // Show pickup notification + addMessageToQueue(itemInfo.getName(), UserPalette::PICKUP_INFO); + } + } +} + +int LocalPlayer::getAttackRange() +{ + if (mAttackRange > -1) + { + return mAttackRange; + } + else + { + // TODO: Fix this to be more generic + Item *weapon = PlayerInfo::getEquipment(EQUIP_FIGHT1_SLOT); + if (weapon) + { + const ItemInfo info = weapon->getInfo(); + return info.getAttackRange(); + } + return 48; // unarmed range + } +} + +bool LocalPlayer::withinAttackRange(Being *target, bool fixDistance, + int addRange) +{ + if (!target) + return false; + + int range = getAttackRange() + addRange; + int dx; + int dy; + + if (fixDistance && range == 1) + range = 2; + + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + const Vector &targetPos = target->getPosition(); + const Vector &pos = getPosition(); + dx = static_cast(abs(targetPos.x - pos.x)); + dy = static_cast(abs(targetPos.y - pos.y)); + + } + else + { + dx = static_cast(abs(target->getTileX() - getTileX())); + dy = static_cast(abs(target->getTileY() - getTileY())); + } + return !(dx > range || dy > range); +} + +void LocalPlayer::setGotoTarget(Being *target) +{ + mLastTarget = -1; + + if (!target) + return; + + if (Net::getNetworkType() == ServerInfo::MANASERV) + { + mTarget = target; + mGoingToTarget = true; + const Vector &targetPos = target->getPosition(); + setDestination(targetPos.x, targetPos.y); + } + else + { + setTarget(target); + mGoingToTarget = true; + setDestination(target->getTileX(), target->getTileY()); + } +} + +extern MiniStatusWindow *miniStatusWindow; +extern SkillDialog *skillDialog; + +void LocalPlayer::handleStatusEffect(StatusEffect *effect, int effectId) +{ + Being::handleStatusEffect(effect, effectId); + + if (effect) + { + effect->deliverMessage(); + effect->playSFX(); + + AnimatedSprite *sprite = effect->getIcon(); + + if (!sprite) + { + // delete sprite, if necessary + for (unsigned int i = 0; i < mStatusEffectIcons.size(); ) + { + if (mStatusEffectIcons[i] == effectId) + { + mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i); + if (miniStatusWindow) + miniStatusWindow->eraseIcon(i); + } + else + { + i++; + } + } + } + else + { + // replace sprite or append + bool found = false; + + for (unsigned int i = 0; i < mStatusEffectIcons.size(); i++) + { + if (mStatusEffectIcons[i] == effectId) + { + if (miniStatusWindow) + miniStatusWindow->setIcon(i, sprite); + found = true; + break; + } + } + + if (!found) + { // add new + int offset = static_cast(mStatusEffectIcons.size()); + if (miniStatusWindow) + miniStatusWindow->setIcon(offset, sprite); + mStatusEffectIcons.push_back(effectId); + } + } + } +} + +void LocalPlayer::addMessageToQueue(const std::string &message, int color) +{ + if (mMessages.size() < 20) + mMessages.push_back(MessagePair(message, color)); +} + +void LocalPlayer::optionChanged(const std::string &value) +{ + if (value == "showownname") + setShowName(config.getBoolValue("showownname")); + else if (value == "targetDeadPlayers") + mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers"); + else if (value == "enableBuggyServers") + mIsServerBuggy = serverConfig.getBoolValue("enableBuggyServers"); + else if (value == "syncPlayerMove") + mSyncPlayerMove = config.getBoolValue("syncPlayerMove"); + else if (value == "drawPath") + mDrawPath = config.getBoolValue("drawPath"); + else if (value == "serverAttack") + mServerAttack = config.getBoolValue("serverAttack"); + else if (value == "attackMoving") + mAttackMoving = config.getBoolValue("attackMoving"); +} + +void LocalPlayer::event(Channels channel, const Mana::Event &event) +{ + if (channel == CHANNEL_ATTRIBUTES) + { + if (event.getName() == EVENT_UPDATEATTRIBUTE) + { + switch (event.getInt("id")) + { + case EXP: + { + if (event.getInt("oldValue") > event.getInt("newValue")) + break; + + int change = event.getInt("newValue") + - event.getInt("oldValue"); + + if (change != 0) + addMessageToQueue(toString(change) + " xp"); + break; + } + case LEVEL: + mLevel = event.getInt("newValue"); + break; + default: + break; + }; + } + } +} + +void LocalPlayer::moveTo(int x, int y) +{ + setDestination(x, y); +} + +void LocalPlayer::move(int dX, int dY) +{ + moveTo(getTileX() + dX, getTileY() + dY); +} + +void LocalPlayer::moveToTarget(unsigned int dist) +{ + bool gotPos(false); + Path debugPath; + + Vector targetPos(-1, -1); + const Vector &playerPos = getPosition(); + unsigned int limit(0); + + if (static_cast(dist) == -1) + { + dist = mMoveToTargetType; + if (mMoveToTargetType == 0) + { + dist = 0; + } + else + { + switch (mMoveToTargetType) + { + case 1: + dist = 1; + break; + case 2: + dist = 2; + break; + case 3: + dist = 3; + break; + case 4: + dist = 5; + break; + case 5: + dist = 7; + break; + case 6: + dist = mAttackRange; + if (dist == 1) + dist = 2; + default: + break; + } + } + } + + if (mTarget) + { + debugPath = mMap->findPath(static_cast(playerPos.x - 16) / 32, + static_cast(playerPos.y - 32) / 32, + mTarget->getTileX(), mTarget->getTileY(), 0x00, 0); + + if (debugPath.size() < dist) + return; + limit = static_cast(debugPath.size()) - dist; + gotPos = true; + } + else if (mNavigateX || mNavigateY) + { + debugPath = mNavigatePath; + limit = dist; + gotPos = true; + } + + if (gotPos) + { + if (dist == 0) + { + if (mTarget) + navigateTo(mTarget); + } + else + { + Position pos(0, 0); + unsigned int f = 0; + + for (Path::const_iterator i = debugPath.begin(), + i_end = debugPath.end(); + i != i_end && f < limit; ++i, f++) + { + pos = (*i); + } + navigateTo(pos.x, pos.y); + } + } + else if (mLastTargetX || mLastTargetY) + { + navigateTo(mLastTargetX, mLastTargetY); + } +} + +void LocalPlayer::moveToHome() +{ + if ((getTileX() != mCrossX || getTileY() != mCrossY) && mCrossX && mCrossY) + { + moveTo(mCrossX, mCrossY); + } + else + { + std::map::iterator iter = + mHomes.find(mMap->getProperty("_filename")); + + if (iter != mHomes.end()) + { + Vector pos = mHomes[(*iter).first]; + if (getTileX() == pos.x && getTileY() == pos.y) + { + Net::getPlayerHandler()->setDestination( + static_cast(pos.x), + static_cast(pos.y), + static_cast(mDirection)); + } + else + { + navigateTo(pos.x, pos.y); + } + } + } +} + +void LocalPlayer::changeAttackWeaponType() +{ + mAttackWeaponType++; + if (mAttackWeaponType > 3) + mAttackWeaponType = 1; + + config.setValue("attackWeaponType", mAttackWeaponType); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::changeAttackType() +{ + mAttackType++; + if (mAttackType > 3) + mAttackType = 0; + + config.setValue("attackType", mAttackType); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::invertDirection() +{ + mMoveState = 0; + mInvertDirection ++; + if (mInvertDirection > 4) + mInvertDirection = 0; + config.setValue("invertMoveDirection", mInvertDirection); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::changeCrazyMoveType() +{ + mCrazyMoveState = 0; + mCrazyMoveType++; + if (mCrazyMoveType > 10) + mCrazyMoveType = 1; + + config.setValue("crazyMoveType", mCrazyMoveType); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::changePickUpType() +{ + mPickUpType++; + if (mPickUpType > 6) + mPickUpType = 0; + + config.setValue("pickUpType", mPickUpType); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::changeFollowMode() +{ + mFollowMode++; + if (mFollowMode > 3) + mFollowMode = 0; + + config.setValue("followMode", mFollowMode); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::changeImitationMode() +{ + mImitationMode++; + if (mImitationMode > 1) + mImitationMode = 0; + + config.setValue("imitationMode", mImitationMode); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::changeEquipmentBeforeAttack(Being* target) +{ + if (mAttackWeaponType == 1 || !target || !PlayerInfo::getInventory()) + return; + + bool allowSword = false; + int dx = target->getTileX() - getTileX(); + int dy = target->getTileY() - getTileY(); + Item *item = NULL; + + if (dx * dx + dy * dy > 80) + return; + + if (dx * dx + dy * dy < 8) + allowSword = true; + + //if attack distance for sword + if (allowSword) + { + //finding sword + item = PlayerInfo::getInventory()->findItem(571); + + if (!item) + item = PlayerInfo::getInventory()->findItem(570); + + if (!item) + item = PlayerInfo::getInventory()->findItem(536); + + //no swords + if (!item) + return; + + //if sword not equiped + if (!item->isEquipped()) + { + Net::getInventoryHandler()->equipItem(item); + } + + //if need equip shield too + if (mAttackWeaponType == 3) + { + //finding shield + item = PlayerInfo::getInventory()->findItem(601); + if (!item) + item = PlayerInfo::getInventory()->findItem(602); + if (item && !item->isEquipped()) + { + Net::getInventoryHandler()->equipItem(item); + } + } + + } + //big distance. allowed only bow + else + { + //finding bow + item = PlayerInfo::getInventory()->findItem(545); + + if (!item) + item = PlayerInfo::getInventory()->findItem(530); + + //no bow + if (!item) + return; + + if (!item->isEquipped()) + { + Net::getInventoryHandler()->equipItem(item); + } + } + +} + + +void LocalPlayer::crazyMove() +{ +// if (!allowAction()) +// return; + + bool oldDisableCrazyMove = mDisableCrazyMove; + mDisableCrazyMove = true; + switch(mCrazyMoveType) + { + case 1: + crazyMove1(); + break; + case 2: + crazyMove2(); + break; + case 3: + crazyMove3(); + break; + case 4: + crazyMove4(); + break; + case 5: + crazyMove5(); + break; + case 6: + crazyMove6(); + break; + case 7: + crazyMove7(); + break; + case 8: + crazyMove8(); + break; + case 9: + crazyMove9(); + break; + case 10: + crazyMoveA(); + break; + default: + break; + } + mDisableCrazyMove = oldDisableCrazyMove; +} + +void LocalPlayer::crazyMove1() +{ + if (mAction == MOVE) + return; + + if (!Client::limitPackets(PACKET_DIRECTION)) + return; + + if (getDirection() == Being::UP) + { + setWalkingDir(Being::UP); + setDirection(Being::LEFT); + Net::getPlayerHandler()->setDirection(Being::LEFT); + } + else if (getDirection() == Being::LEFT) + { + setWalkingDir(Being::LEFT); + setDirection(Being::DOWN); + Net::getPlayerHandler()->setDirection(Being::DOWN); + } + else if (getDirection() == Being::DOWN) + { + setWalkingDir(Being::DOWN); + setDirection(Being::RIGHT); + Net::getPlayerHandler()->setDirection(Being::RIGHT); + } + else if (getDirection() == Being::RIGHT) + { + setWalkingDir(Being::RIGHT); + setDirection(Being::UP); + Net::getPlayerHandler()->setDirection(Being::UP); + } +} + +void LocalPlayer::crazyMove2() +{ + if (mAction == MOVE) + return; + + if (!Client::limitPackets(PACKET_DIRECTION)) + return; + + if (getDirection() == Being::UP) + { + setWalkingDir(Being::UP | Being::LEFT); + setDirection(Being::RIGHT); + Net::getPlayerHandler()->setDirection(Being::DOWN | Being::RIGHT); + } + else if (getDirection() == Being::RIGHT) + { + setWalkingDir(Being::UP | Being::RIGHT); + setDirection(Being::DOWN); + Net::getPlayerHandler()->setDirection(Being::DOWN | Being::LEFT); + } + else if (getDirection() == Being::DOWN) + { + setWalkingDir(Being::DOWN | Being::RIGHT); + setDirection(Being::LEFT); + Net::getPlayerHandler()->setDirection(Being::UP | Being::LEFT); + } + else if (getDirection() == Being::LEFT) + { + setWalkingDir(Being::DOWN | Being::LEFT); + setDirection(Being::UP); + Net::getPlayerHandler()->setDirection(Being::UP | Being::RIGHT); + } +} + +void LocalPlayer::crazyMove3() +{ + if (mAction == MOVE) + return; + + switch(mCrazyMoveState) + { + case 0: + move(1, 1); + mCrazyMoveState = 1; + break; + case 1: + move(1, -1); + mCrazyMoveState = 2; + break; + case 2: + move(-1, -1); + mCrazyMoveState = 3; + break; + case 3: + move(-1, 1); + mCrazyMoveState = 0; + break; + default: + break; + } + + if (!Client::limitPackets(PACKET_DIRECTION)) + return; + + setDirection(Being::DOWN); + Net::getPlayerHandler()->setDirection(Being::DOWN); +} + +void LocalPlayer::crazyMove4() +{ + if (mAction == MOVE) + return; + + switch(mCrazyMoveState) + { + case 0: + move(7, 0); + mCrazyMoveState = 1; + break; + case 1: + move(-7, 0); + mCrazyMoveState = 0; + break; + default: + break; + } +} + +void LocalPlayer::crazyMove5() +{ + if (mAction == MOVE) + return; + + switch(mCrazyMoveState) + { + case 0: + move(0, 7); + mCrazyMoveState = 1; + break; + case 1: + move(0, -7); + mCrazyMoveState = 0; + break; + default: + break; + } +} + +void LocalPlayer::crazyMove6() +{ + if (mAction == MOVE) + return; + + switch(mCrazyMoveState) + { + case 0: + move(3, 0); + mCrazyMoveState = 1; + break; + case 1: + move(2, -2); + mCrazyMoveState = 2; + break; + case 2: + move(0, -3); + mCrazyMoveState = 3; + break; + case 3: + move(-2, -2); + mCrazyMoveState = 4; + break; + case 4: + move(-3, 0); + mCrazyMoveState = 5; + break; + case 5: + move(-2, 2); + mCrazyMoveState = 6; + break; + case 6: + move(0, 3); + mCrazyMoveState = 7; + break; + case 7: + move(2, 2); + mCrazyMoveState = 0; + break; + default: + break; + } +} + +void LocalPlayer::crazyMove7() +{ + if (mAction == MOVE) + return; + + switch(mCrazyMoveState) + { + case 0: + move(1, 1); + mCrazyMoveState = 1; + break; + case 1: + move(-1, 1); + mCrazyMoveState = 2; + break; + case 2: + move(-1, -1); + mCrazyMoveState = 3; + break; + case 3: + move(1, -1); + mCrazyMoveState = 0; + break; + default: + break; + } +} + +void LocalPlayer::crazyMove8() +{ + if (mAction == MOVE) + return; + int idx = 0; + int dist = 1; + +// look +// up, ri,do,le + static const int movesX[][4] = + { + {-1, 0, 1, 0}, //move left + { 0, 1, 0, -1}, //move up + { 1, 0, -1, 0}, //move right + { 0, -1, 0, 1} //move down + }; + +// look +// up, ri,do,le + static const int movesY[][4] = + { + { 0, -1, 0, 1}, //move left + {-1, 0, 1, 0}, //move up + { 0, 1, 0, -1}, //move right + { 1, 0, -1, 0} //move down + }; + + if (getDirection() == Being::UP) + idx = 0; + else if (getDirection() == Being::RIGHT) + idx = 1; + else if (getDirection() == Being::DOWN) + idx = 2; + else if (getDirection() == Being::LEFT) + idx = 3; + + + int mult = 1; + if (mMap->getWalk(getTileX() + movesX[idx][0], + getTileY() + movesY[idx][0], getWalkMask())) + { + while (mMap->getWalk(getTileX() + movesX[idx][0] * mult, + getTileY() + movesY[idx][0] * mult, + getWalkMask()) && mult <= dist) + { + mult ++; + } + move(movesX[idx][0] * (mult - 1), movesY[idx][0] * (mult - 1)); + } + else if (mMap->getWalk(getTileX() + movesX[idx][1], + getTileY() + movesY[idx][1], getWalkMask())) + { + while (mMap->getWalk(getTileX() + movesX[idx][1] * mult, + getTileY() + movesY[idx][1] * mult, + getWalkMask()) && mult <= dist) + { + mult ++; + } + move(movesX[idx][1] * (mult - 1), movesY[idx][1] * (mult - 1)); + } + else if (mMap->getWalk(getTileX() + movesX[idx][2], + getTileY() + movesY[idx][2], getWalkMask())) + { + while (mMap->getWalk(getTileX() + movesX[idx][2] * mult, + getTileY() + movesY[idx][2] * mult, + getWalkMask()) && mult <= dist) + { + mult ++; + } + move(movesX[idx][2] * (mult - 1), movesY[idx][2] * (mult - 1)); + } + else if (mMap->getWalk(getTileX() + movesX[idx][3], + getTileY() + movesY[idx][3], getWalkMask())) + { + while (mMap->getWalk(getTileX() + movesX[idx][3] * mult, + getTileY() + movesY[idx][3] * mult, + getWalkMask()) && mult <= dist) + { + mult ++; + } + move(movesX[idx][3] * (mult - 1), movesY[idx][3] * (mult - 1)); + } +} + +void LocalPlayer::crazyMove9() +{ + int dx = 0; + int dy = 0; + + if (mAction == MOVE) + return; + + switch (mCrazyMoveState) + { + case 0: + switch (getDirection()) + { + case UP : dy = -1; break; + case DOWN : dy = 1; break; + case LEFT : dx = -1; break; + case RIGHT: dx = 1; break; + default: break; + } + move(dx, dy); + mCrazyMoveState = 1; + break; + case 1: + mCrazyMoveState = 2; + if (!allowAction()) + return; + Net::getPlayerHandler()->changeAction(SIT); + break; + case 2: + mCrazyMoveState = 3; + break; + case 3: + mCrazyMoveState = 0; + break; + default: + break; + } +} + +void LocalPlayer::crazyMoveA() +{ + std::string mMoveProgram(config.getStringValue("crazyMoveProgram")); + + if (mAction == MOVE) + return; + + if (mMoveProgram.length() == 0) + return; + + if (mCrazyMoveState >= mMoveProgram.length()) + mCrazyMoveState = 0; + + Uint8 dir = 0; + + // move command + if (mMoveProgram[mCrazyMoveState] == 'm') + { + int dx = 0; + int dy = 0; + + mCrazyMoveState ++; + if (mCrazyMoveState < mMoveProgram.length()) + { + char param = mMoveProgram[mCrazyMoveState++]; + if (param == '?') + { + char cmd[] = {'l', 'r', 'u', 'd'}; + srand(tick_time); + param = cmd[rand() % 4]; + } + switch (param) + { + case 'd': + move(0, 1); + break; + case 'u': + move(0, -1); + break; + case 'l': + move(-1, 0); + break; + case 'r': + move(1, 0); + break; + case 'f': + switch (getDirection()) + { + case UP : dy = -1; break; + case DOWN : dy = 1; break; + case LEFT : dx = -1; break; + case RIGHT: dx = 1; break; + default: break; + } + move(dx, dy); + break; + case 'b': + switch (getDirection()) + { + case UP : dy = 1; break; + case DOWN : dy = -1; break; + case LEFT : dx = 1; break; + case RIGHT: dx = -1; break; + default: break; + } + move(dx, dy); + break; + default: + break; + } + } + } + // direction command + else if (mMoveProgram[mCrazyMoveState] == 'd') + { + mCrazyMoveState ++; + + if (mCrazyMoveState < mMoveProgram.length()) + { + char param = mMoveProgram[mCrazyMoveState++]; + if (param == '?') + { + char cmd[] = {'l', 'r', 'u', 'd'}; + srand(tick_time); + param = cmd[rand() % 4]; + } + switch (param) + { + case 'd': + + if (Client::limitPackets(PACKET_DIRECTION)) + { + setDirection(Being::DOWN); + Net::getPlayerHandler()->setDirection(Being::DOWN); + } + break; + case 'u': + if (Client::limitPackets(PACKET_DIRECTION)) + { + setDirection(Being::UP); + Net::getPlayerHandler()->setDirection(Being::UP); + } + break; + case 'l': + if (Client::limitPackets(PACKET_DIRECTION)) + { + setDirection(Being::LEFT); + Net::getPlayerHandler()->setDirection(Being::LEFT); + } + break; + case 'r': + if (Client::limitPackets(PACKET_DIRECTION)) + { + setDirection(Being::RIGHT); + Net::getPlayerHandler()->setDirection(Being::RIGHT); + } + break; + case 'L': + if (Client::limitPackets(PACKET_DIRECTION)) + { + switch (getDirection()) + { + case UP : dir = Being::LEFT; break; + case DOWN : dir = Being::RIGHT; break; + case LEFT : dir = Being::DOWN; break; + case RIGHT : dir = Being::UP; break; + default: break; + } + setDirection(dir); + Net::getPlayerHandler()->setDirection(dir); + } + break; + case 'R': + if (Client::limitPackets(PACKET_DIRECTION)) + { + switch (getDirection()) + { + case UP : dir = Being::RIGHT; break; + case DOWN : dir = Being::LEFT; break; + case LEFT : dir = Being::UP; break; + case RIGHT : dir = Being::DOWN; break; + default: break; + } + setDirection(dir); + Net::getPlayerHandler()->setDirection(dir); + } + break; + case 'b': + if (Client::limitPackets(PACKET_DIRECTION)) + { + switch (getDirection()) + { + case UP : dir = Being::DOWN; break; + case DOWN : dir = Being::UP; break; + case LEFT : dir = Being::RIGHT; break; + case RIGHT : dir = Being::LEFT; break; + default: break; + } + setDirection(dir); + Net::getPlayerHandler()->setDirection(dir); + } + break; + case '0': + dropShortcut->dropFirst(); + break; + case 'a': + dropShortcut->dropItems(); + break; + default: + break; + } + } + } + // sit command + else if (mMoveProgram[mCrazyMoveState] == 's') + { + mCrazyMoveState ++; + if (toggleSit()) + mCrazyMoveState ++; + } + // wear outfits + else if (mMoveProgram[mCrazyMoveState] == 'o') + { + mCrazyMoveState ++; + if (mCrazyMoveState < mMoveProgram.length()) + { + // wear next outfit + if (mMoveProgram[mCrazyMoveState] == 'n') + { + mCrazyMoveState ++; + outfitWindow->wearNextOutfit(); + } + // wear previous outfit + else if (mMoveProgram[mCrazyMoveState] == 'p') + { + mCrazyMoveState ++; + outfitWindow->wearPreviousOutfit(); + } + } + } + // pause + else if (mMoveProgram[mCrazyMoveState] == 'w') + { + mCrazyMoveState ++; + } + // pick up + else if (mMoveProgram[mCrazyMoveState] == 'p') + { + mCrazyMoveState ++; + pickUpItems(); + } + // emote + else if (mMoveProgram[mCrazyMoveState] == 'e') + { + mCrazyMoveState ++; + char emo = mMoveProgram[mCrazyMoveState]; + if (emo == '?') + { + srand(tick_time); + emote(static_cast(1 + (rand() % 13))); + } + else + { + if (emo >= '0' && emo <= '9') + emote(static_cast(emo - '0' + 1)); + else if (emo >= 'a' && emo <= 'd') + emote(static_cast(emo - 'a' + 11)); + } + + mCrazyMoveState ++; + } + else + { + mCrazyMoveState ++; + } + +// mCrazyMoveState ++; + if (mCrazyMoveState >= mMoveProgram.length()) + mCrazyMoveState = 0; + +// debugMsg("mCrazyMoveState: " + toString(mCrazyMoveState)); +} + +bool LocalPlayer::isReachable(int x, int y, int maxCost) +{ + if (!mMap) + return false; + + if (x - 1 <= getTileX() && x + 1 >= getTileX() + && y - 1 <= getTileY() && y + 1 >= getTileY() ) + { + return true; + } + + const Vector &playerPos = getPosition(); + + Path debugPath = mMap->findPath( + (int) (playerPos.x - 16) / 32, + (int) (playerPos.y - 32) / 32, + x, y, 0x00, maxCost); + + return !debugPath.empty(); +} + +bool LocalPlayer::isReachable(Being *being, int maxCost) +{ + if (!being || !mMap) + return false; + + if (being->isReachable() == Being::REACH_NO) + return false; + + if (being->getTileX() - 1 <= getTileX() + && being->getTileX() + 1 >= getTileX() + && being->getTileY() - 1 <= getTileY() + && being->getTileY() + 1 >= getTileY()) + { + being->setDistance(0); + being->setIsReachable(Being::REACH_YES); + return true; + } + + const Vector &playerPos = getPosition(); + + Path debugPath = mMap->findPath( + (int) (playerPos.x - 16) / 32, + (int) (playerPos.y - 32) / 32, + being->getTileX(), being->getTileY(), 0x00, maxCost); + + being->setDistance(static_cast(debugPath.size())); + if (!debugPath.empty()) + { + being->setIsReachable(Being::REACH_YES); + return true; + } + else + { + being->setIsReachable(Being::REACH_NO); + return false; + } +} + +bool LocalPlayer::pickUpItems(int pickUpType) +{ + if (!actorSpriteManager) + return false; + + bool status = false; + int x = getTileX(); + int y = getTileY(); + + // first pick up item on player position + FloorItem *item = + actorSpriteManager->findItem(x, y); + if (item) + { + status = pickUp(item); + //status = true; + } + + if (pickUpType == 0) + pickUpType = mPickUpType; + + if (pickUpType == 0) + return status; + + int x1, y1, x2, y2; + switch(pickUpType) + { + case 1: + switch (getDirection()) + { + case UP : --y; break; + case DOWN : ++y; break; + case LEFT : --x; break; + case RIGHT: ++x; break; + default: break; + } + item = actorSpriteManager->findItem(x, y); + if (item) + { + status = pickUp(item); +// status = true; + } + break; + case 2: + switch (getDirection()) + { + case UP : x1 = x - 1; y1 = y - 1; x2 = x + 1; y2 = y; break; + case DOWN : x1 = x - 1; y1 = y; x2 = x + 1; y2 = y + 1; break; + case LEFT : x1 = x - 1; y1 = y - 1; x2 = x; y2 = y + 1; break; + case RIGHT: x1 = x; y1 = y - 1; x2 = x + 1; y2 = y + 1; break; + default: x1 = x; x2 = x; y1 = y; y2 = y; break; + } + if (actorSpriteManager->pickUpAll(x1, y1, x2, y2)) + status = true; + break; + case 3: + if (actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1)) + status = true; + break; + + case 4: + if (!actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1)) + { + if (actorSpriteManager->pickUpNearest(x, y, 4)) + status = true; + } + else + { + status = true; + } + break; + + case 5: + if (!actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1)) + { + if (actorSpriteManager->pickUpNearest(x, y, 8)) + status = true; + } + else + { + status = true; + } + break; + + case 6: + if (!actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1)) + { + if (actorSpriteManager->pickUpNearest(x, y, 90)) + status = true; + } + else + { + status = true; + } + break; + + default: + break; + } + return status; +} + +void LocalPlayer::changeQuickDropCounter() +{ + mQuickDropCounter++; + if (mQuickDropCounter > 9) + mQuickDropCounter = 1; + + config.setValue("quickDropCounter", mQuickDropCounter); + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::moveByDirection(unsigned char dir) +{ + int dx = 0, dy = 0; +#ifdef MANASERV_SUPPORT + if (dir & UP) + dy -= 32; + if (dir & DOWN) + dy += 32; + if (dir & LEFT) + dx -= 32; + if (dir & RIGHT) + dx += 32; +#else + if (dir & UP) + dy--; + if (dir & DOWN) + dy++; + if (dir & LEFT) + dx--; + if (dir & RIGHT) + dx++; +#endif + + move(dx, dy); +} + +void LocalPlayer::specialMove(unsigned char direction) +{ + if (direction && (mNavigateX || mNavigateY)) + naviageClean(); + + if (direction && (getInvertDirection() >= 2 + && getInvertDirection() <= 4) + && !mIsServerBuggy) + { + int max; + if (getInvertDirection() == 2) + max = 10; + else + max = 30; + + if (mAction == MOVE) + return; + + if (getInvertDirection() == 2) + max = 5; + else if (getInvertDirection() == 4) + max = 1; + else + max = 3; + + if (getMoveState() < max) + { + moveByDirection(direction); + mMoveState ++; + } + else + { + mMoveState = 0; + crazyMove(); + } + } + else + { +// if (direction != 0 && getInvertDirection() == 4) +// crazyMove(); + setWalkingDir(direction); + } + +} + +void LocalPlayer::debugMsg(std::string str) +{ + if (debugChatTab) + debugChatTab->chatLog(str); +} + +void LocalPlayer::switchMagicAttack() +{ + mMagicAttackType++; + if (mMagicAttackType > 4) + mMagicAttackType = 0; + + config.setValue("magicAttackType", mMagicAttackType); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::magicAttack() +{ + if (!chatWindow || !isAlive() + || !Net::getPlayerHandler()->canUseMagic()) + { + return; + } + + if (!Client::limitPackets(PACKET_CHAT)) + return; + + switch(mMagicAttackType) + { + //flar W00 + case 0: + tryMagic("#flar", 1, 0, 10); + break; + //chiza W01 + case 1: + tryMagic("#chiza", 1, 0, 9); + break; + //ingrav W10 + case 2: + tryMagic("#ingrav", 2, 2, 20); + break; + //frillyar W11 + case 3: + tryMagic("#frillyar", 2, 2, 25); + break; + //upmarmu W12 + case 4: + tryMagic("#upmarmu", 2, 2, 20); + break; + default: + break; + } +} + +void LocalPlayer::tryMagic(std::string spell, int baseMagic, + int schoolMagic, int mana) +{ + if (!chatWindow) + return; + + if (PlayerInfo::getStatEffective(340) >= baseMagic + && PlayerInfo::getStatEffective(342) >= schoolMagic) + { + if (PlayerInfo::getAttribute(MP) >= mana) + { + if (!Client::limitPackets(PACKET_CHAT)) + return; + + chatWindow->localChatInput(spell); + } + } +} + +void LocalPlayer::changeMoveToTargetType() +{ + mMoveToTargetType++; + if (mMoveToTargetType > 6) + mMoveToTargetType = 0; + + config.setValue("moveToTargetType", mMoveToTargetType); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::loadHomes() +{ + std::string homeStr = serverConfig.getValue("playerHomes", + "maps/018-1.tmx 71 76 maps/013-3.tmx 71 24"); + std::string buf; + std::stringstream ss(homeStr); + + while (ss >> buf) + { + Vector pos; + ss >> pos.x; + ss >> pos.y; + mHomes[buf] = pos; + } + +} + +void LocalPlayer::setMap(Map *map) +{ + if (map) + { + std::map::iterator iter = + mHomes.find(map->getProperty("_filename")); + + if (iter != mHomes.end()) + { + Vector pos = mHomes[(*iter).first]; + SpecialLayer *specialLayer = map->getSpecialLayer(); + if (specialLayer) + { +// specialLayer->clean(); + specialLayer->setTile(static_cast(pos.x), + static_cast(pos.y), + MapItem::HOME); + } + } + if (socialWindow) + socialWindow->updateActiveList(); + } + naviageClean(); + mCrossX = 0; + mCrossY = 0; + + Being::setMap(map); + updateNavigateList(); +// updateCoords(); +} + +void LocalPlayer::setHome() +{ + if (!mMap || !socialWindow) + return; + + SpecialLayer *specialLayer = mMap->getSpecialLayer(); + + if (!specialLayer) + return; + + std::string key = mMap->getProperty("_filename"); + Vector pos = mHomes[key]; + + if (mAction == SIT) + { + std::map::iterator iter = mHomes.find(key); + + if (iter != mHomes.end()) + socialWindow->removePortal(pos.x, pos.y); + + if (iter != mHomes.end() && getTileX() == pos.x && getTileY() == pos.y) + { + mMap->updatePortalTile("", MapItem::EMPTY, pos.x, pos.y); +// if (specialLayer) +// specialLayer->setTile(pos.x, pos.y, MapItem::EMPTY); + mHomes.erase(key); + socialWindow->removePortal(pos.x, pos.y); + } + else + { + if (specialLayer && iter != mHomes.end()) + specialLayer->setTile(pos.x, pos.y, MapItem::EMPTY); + + pos.x = getTileX(); + pos.y = getTileY(); + mHomes[key] = pos; + mMap->updatePortalTile("home", MapItem::HOME, + getTileX(), getTileY()); +// if (specialLayer) +// specialLayer->setTile(getTileX(), getTileY(), MapItem::HOME); + socialWindow->addPortal(getTileX(), getTileY()); + } + MapItem *mapItem = specialLayer->getTile(getTileX(), getTileY()); + if (mapItem) + { + int idx = socialWindow->getPortalIndex(getTileX(), getTileY()); + mapItem->setName(keyboard.getKeyShortString( + outfitWindow->keyName(idx))); + } + saveHomes(); + } + else + { + MapItem *mapItem = specialLayer->getTile(getTileX(), getTileY()); + int type = 0; +// if (!mapItem) +// return; + + std::map::iterator iter = mHomes.find(key); + if (iter != mHomes.end() && getTileX() == pos.x && getTileY() == pos.y) + { + mHomes.erase(key); + saveHomes(); + } + + if (!mapItem || mapItem->getType() == MapItem::EMPTY) + { +// if (mAction == SIT) +// type = MapItem::HOME; + if (mDirection & UP) + type = MapItem::ARROW_UP; + else if (mDirection & LEFT) + type = MapItem::ARROW_LEFT; + else if (mDirection & DOWN) + type = MapItem::ARROW_DOWN; + else if (mDirection & RIGHT) + type = MapItem::ARROW_RIGHT; + } + else + { + type = MapItem::EMPTY; + } + mMap->updatePortalTile("", type, getTileX(), getTileY()); +// mapItem = specialLayer->getTile(getTileX(), getTileY()); + + if (type != MapItem::EMPTY) + { + socialWindow->addPortal(getTileX(), getTileY()); + mapItem = specialLayer->getTile(getTileX(), getTileY()); + if (mapItem) + { + int idx = socialWindow->getPortalIndex(getTileX(), getTileY()); + mapItem->setName(keyboard.getKeyShortString( + outfitWindow->keyName(idx))); + } + } + else + { + specialLayer->setTile(getTileX(), getTileY(), MapItem::EMPTY); + socialWindow->removePortal(getTileX(), getTileY()); + } + +// specialLayer->setTile(getTileX(), getTileY(), type); + } +} + +void LocalPlayer::saveHomes() +{ + std::string homeStr; + std::string buf; + std::stringstream ss(homeStr); + + for (std::map::iterator iter = mHomes.begin(); + iter != mHomes.end(); ++iter ) + { + Vector pos = (*iter).second; + + if (iter != mHomes.begin()) + ss << " "; + ss << (*iter).first << " " << pos.x << " " << pos.y; + } + + serverConfig.setValue("playerHomes", ss.str()); +} + + +void LocalPlayer::switchGameModifiers() +{ + mDisableGameModifiers = !mDisableGameModifiers; + config.setValue("disableGameModifiers", mDisableGameModifiers); + miniStatusWindow->updateStatus(); +} + +void LocalPlayer::pingRequest() +{ + if (mWaitPing == true && mPingSendTick != 0) + { + if (tick_time >= mPingSendTick + && (tick_time - mPingSendTick) > 1000) + { + return; + } + } + + mPingSendTick = tick_time; + mWaitPing = true; + Net::getBeingHandler()->requestNameById(getId()); +} + +void LocalPlayer::pingResponse() +{ + if (mWaitPing == true && mPingSendTick > 0) + { + mWaitPing = false; + if (tick_time < mPingSendTick) + { + mPingSendTick = 0; + mPingTime = 0; + } + else + { + mPingTime = (tick_time - mPingSendTick) * 10; + } + } +} + +void LocalPlayer::tryPingRequest() +{ + if (mPingSendTick == 0 || tick_time < mPingSendTick + || (tick_time - mPingSendTick) > 200) + { + pingRequest(); + } +} + +void LocalPlayer::changeAwayMode() +{ + mAwayMode = !mAwayMode; + mAfkTime = 0; + if (miniStatusWindow) + miniStatusWindow->updateStatus(); + if (mAwayMode) + { + cancelFollow(); + naviageClean(); + if (outfitWindow) + outfitWindow->wearAwayOutfit(); + mAwayDialog = new OkDialog(_("Away"), + config.getStringValue("afkMessage"), true, false); + mAwayDialog->addActionListener(mAwayListener); + sound.volumeOff(); + } + else + { + mAwayDialog = 0; + sound.volumeRestore(); + } +} + +void LocalPlayer::setAway(const std::string &message) +{ + if (!message.empty()) + config.setValue("afkMessage", message); + changeAwayMode(); +} + +void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick) +{ + if (mAwayMode) + { + if (mAfkTime == 0 + || cur_time < mAfkTime + || cur_time - mAfkTime > awayLimitTimer) + { + std::string msg = "*AFK*: " + + config.getStringValue("afkMessage"); + + Net::getChatHandler()->privateMessage(nick, msg); + if (!tab) + { + if (localChatTab) + { + localChatTab->chatLog(getName() + " : " + msg, + ACT_WHISPER, false); + } + } + else + { + tab->chatLog(getName(), msg); + } + mAfkTime = cur_time; + } + } +} + +void LocalPlayer::navigateTo(int x, int y) +{ + if (!mMap) + return; + + SpecialLayer *tmpLayer = mMap->getTempLayer(); + if (!tmpLayer) + return; + + const Vector &playerPos = getPosition(); + mShowNavigePath = true; + mOldX = playerPos.x; + mOldY = playerPos.y; + mOldTileX = getTileX(); + mOldTileY = getTileY(); + mNavigateX = x; + mNavigateY = y; + mNavigateId = 0; + + mNavigatePath = mMap->findPath((int) (playerPos.x - 16) / 32, + (int) (playerPos.y - 32) / 32, + x, y, 0x00, 0); + + if (mDrawPath) + tmpLayer->addRoad(mNavigatePath); +} + +void LocalPlayer::navigateTo(Being *being) +{ + if (!mMap || !being) + return; + + SpecialLayer *tmpLayer = mMap->getTempLayer(); + if (!tmpLayer) + return; + + const Vector &playerPos = getPosition(); + mShowNavigePath = true; + mOldX = playerPos.x; + mOldY = playerPos.y; + mOldTileX = getTileX(); + mOldTileY = getTileY(); + mNavigateX = being->getTileX(); + mNavigateY = being->getTileY(); + + mNavigatePath = mMap->findPath((int) (playerPos.x - 16) / 32, + (int) (playerPos.y - 32) / 32, + being->getTileX(), being->getTileY(), + 0x00, 0); + + if (mDrawPath) + tmpLayer->addRoad(mNavigatePath); +} + +void LocalPlayer::naviageClean() +{ + if (!mMap) + return; + + mShowNavigePath = false; + mOldX = 0; + mOldY = 0; + mOldTileX = 0; + mOldTileY = 0; + mNavigateX = 0; + mNavigateY = 0; + mNavigateId = 0; + + mNavigatePath.clear(); + + SpecialLayer *tmpLayer = mMap->getTempLayer(); + if (!tmpLayer) + return; + + tmpLayer->clean(); +} + +void LocalPlayer::updateCoords() +{ + Being::updateCoords(); + + const Vector &playerPos = getPosition(); + + if (getTileX() != mOldTileX || getTileY() != mOldTileY) + { + if (socialWindow) + socialWindow->updatePortals(); + if (viewport) + viewport->hideBeingPopup(); + } + + if (mShowNavigePath) + { + if (getTileX() != mOldTileX || getTileY() != mOldTileY) +// if (playerPos.x != mOldX || playerPos.y != mOldY) + { + SpecialLayer *tmpLayer = mMap->getTempLayer(); + if (!tmpLayer) + return; + + int x = (int) (playerPos.x - 16) / 32; + int y = (int) (playerPos.y - 32) / 32; + if (mNavigateId) + { + if (!actorSpriteManager) + { + naviageClean(); + return; + } + + Being* being = actorSpriteManager->findBeing(mNavigateId); + if (!being) + { + naviageClean(); + return; + } + mNavigateX = being->getTileX(); + mNavigateY = being->getTileY(); + } + + if (mNavigateX == x && mNavigateY == y) + { + naviageClean(); + return; + } + else + { + for (Path::const_iterator i = mNavigatePath.begin(), + i_end = mNavigatePath.end(); i != i_end; ++i) + { + if ((*i).x == getTileX() && (*i).y == getTileY()) + { + mNavigatePath.pop_front(); + break; + } + } + + if (mDrawPath) + { + tmpLayer->clean(); + tmpLayer->addRoad(mNavigatePath); + } +// navigateTo(mNavigateX, mNavigateY); + } + } + } + mOldX = playerPos.x; + mOldY = playerPos.y; + mOldTileX = getTileX(); + mOldTileY = getTileY(); +} + +void LocalPlayer::targetMoved() +{ +/* + if (mKeepAttacking) + { + if (mTarget && mServerAttack) + { + logger->log("LocalPlayer::targetMoved0"); + if (!Client::limitPackets(PACKET_ATTACK)) + return; + logger->log("LocalPlayer::targetMoved"); + Net::getPlayerHandler()->attack(mTarget->getId(), mServerAttack); + } + } +*/ +} + +int LocalPlayer::getPathLength(Being* being) +{ + if (!mMap || !being) + return 0; + + const Vector &playerPos = getPosition(); + + Path debugPath = mMap->findPath((int) (playerPos.x - 16) / 32, + (int) (playerPos.y - 32) / 32, + being->getTileX(), being->getTileY(), + 0x00, 0); + return static_cast(debugPath.size()); +} + +void LocalPlayer::attack2(Being *target, bool keep, bool dontChangeEquipment) +{ + if (!dontChangeEquipment && target) + changeEquipmentBeforeAttack(target); + + if ((!target || getAttackType() == 0 || getAttackType() == 3) + || (withinAttackRange(target, true, 1) + && getPathLength(target) <= getAttackRange() + 1)) + { + attack(target, keep); + if (getAttackType() == 2) + { + if (!target) + { + if (pickUpItems()) + return; + } + else + { + pickUpItems(3); + } + } + + } + else if (!mPickUpTarget) + { + if (getAttackType() == 2) + { + if (pickUpItems()) + return; + } + setTarget(target); + if (target && target->getType() != Being::NPC) + { + mKeepAttacking = true; + if (mAttackWeaponType == 1) + moveToTarget(); + else + moveToTarget(mAttackRange); + } + } +} + +void LocalPlayer::setFollow(std::string player) +{ + mPlayerFollowed = player; + if (!mPlayerFollowed.empty()) + debugMsg("Follow: " + player); + else + debugMsg("Follow canceled"); +} + +void LocalPlayer::setImitate(std::string player) +{ + mPlayerImitated = player; + if (!mPlayerImitated.empty()) + debugMsg("Imitation: " + player); + else + debugMsg("Imitation canceled"); +} + +void LocalPlayer::cancelFollow() +{ + if (!mPlayerFollowed.empty()) + debugMsg("Follow canceled"); + if (!mPlayerImitated.empty()) + debugMsg("Imitation canceled"); + mPlayerFollowed = ""; + mPlayerImitated = ""; +} + +void LocalPlayer::imitateEmote(Being* being, unsigned char action) +{ + if (!being) + return; + + std::string player_imitated = getImitate(); + if (!player_imitated.empty() && being->getName() == player_imitated) + emote(action); +} + +void LocalPlayer::imitateAction(Being *being, Being::Action action) +{ + if (!being) + return; + + std::string player_imitated = getImitate(); + if (!player_imitated.empty() && being->getName() == player_imitated) + { + setAction(action); + Net::getPlayerHandler()->changeAction(action); + } +} + +void LocalPlayer::imitateDirection(Being *being, unsigned char dir) +{ + if (!being) + return; + + std::string player_imitated = getImitate(); + if (!player_imitated.empty() && being->getName() == player_imitated) + { + if (!Client::limitPackets(PACKET_DIRECTION)) + return; + + if (mFollowMode == 2) + { + Uint8 dir2 = 0; + if (dir & Being::LEFT) + dir2 |= Being::RIGHT; + else if (dir & Being::RIGHT) + dir2 |= Being::LEFT; + if (dir & Being::UP) + dir2 |= Being::DOWN; + else if (dir & Being::DOWN) + dir2 |= Being::UP; + + setDirection(dir2); + Net::getPlayerHandler()->setDirection(dir2); + } + else + { + setDirection(dir); + Net::getPlayerHandler()->setDirection(dir); + } + } +} + +void LocalPlayer::imitateOutfit(Being *player, int sprite) +{ + if (!player) + return; + + std::string player_imitated = getImitate(); + if (mImitationMode == 1 && !player_imitated.empty() + && player->getName() == player_imitated) + { +// std::string filename = ItemDB::get( +// player->getId()).getSprite(mGender); +// logger->log("LocalPlayer::imitateOutfit sprite: " + toString(sprite)); +// logger->log("LocalPlayer::imitateOutfit sprite: " + toString(player->getNumberOfLayers())); +// logger->log("LocalPlayer::imitateOutfit spritecount: " + toString(player->getSpritesCount())); + if (sprite < 0 || sprite >= player->getNumberOfLayers()) +// if (sprite < 0 || sprite >= 20) + return; + +// logger->log("after check"); + AnimatedSprite *equipmentSprite = dynamic_cast(player + ->getSprite(sprite)); + + if (equipmentSprite) + { +// logger->log("have equipmentSprite"); + Inventory *inv = PlayerInfo::getInventory(); + if (!inv) + return; + + std::string path = equipmentSprite->getIdPath(); + if (path.empty()) + return; + +// logger->log("idPath: " + path); + + Item *item = inv->findItemBySprite(path, player->getGender()); +// if (item) +// { +// logger->log("got item"); +// if (item->isEquipped()) +// logger->log("isEquipped"); +// } + + if (item && !item->isEquipped()) + Net::getInventoryHandler()->equipItem(item); + } + else + { +// logger->log("have unequip"); + + int equipmentSlot = Net::getInventoryHandler() + ->convertFromServerSlot(sprite); +// logger->log("equipmentSlot: " + toString(equipmentSlot)); + if (equipmentSlot == EQUIP_PROJECTILE_SLOT) + return; + + Item *item = PlayerInfo::getEquipment(equipmentSlot); + if (item) + { +// logger->log("unequiping"); + Net::getInventoryHandler()->unequipItem(item); + } + } + } +} + +void LocalPlayer::followMoveTo(Being *being, int x, int y) +{ + if (!mPlayerFollowed.empty() && being->getName() == mPlayerFollowed) + setDestination(x, y); +} + +void LocalPlayer::followMoveTo(Being *being, int x1, int y1, int x2, int y2) +{ + if (!being) + return; + + if (!mPlayerFollowed.empty() && being->getName() == mPlayerFollowed) + { + switch (mFollowMode) + { + case 0: + setDestination(x1, y1); + setNextDest(x2, y2); + break; + case 1: + if (x1 != x2 || y1 != y2) + { + setDestination(getTileX() + x2 - x1, getTileY() + y2 - y1); + setNextDest(getTileX() + x2 - x1, getTileY() + y2 - y1); + } + break; + case 2: + if (x1 != x2 || y1 != y2) + { + setDestination(getTileX() + x1 - x2, getTileY() + y1 - y2); + setNextDest(getTileX() + x1 - x2, getTileY() + y1 - y2); + } + break; + case 3: + if (!mTarget || mTarget->getName() != mPlayerFollowed) + { + if (actorSpriteManager) + { + Being *being = actorSpriteManager->findBeingByName( + mPlayerFollowed, Being::PLAYER); + setTarget(being); + } + } + moveToTarget(); + setNextDest(x2, y2); + break; + default: + break; + } + } +} + +void LocalPlayer::setNextDest(int x, int y) +{ + mNextDestX = x; + mNextDestY = y; +} + +bool LocalPlayer::allowAction() +{ + if (mIsServerBuggy) + { + if (mLastAction != -1) + return false; + mLastAction = tick_time; + } + return true; +} + +bool LocalPlayer::allowMove() +{ + if (mIsServerBuggy) + { + if (mAction == MOVE) + return false; + } + return true; +} + +void LocalPlayer::fixPos(int maxDist) +{ + if (!mCrossX && !mCrossY) + return; + + int dx = abs(getTileX() - mCrossX); + int dy = abs(getTileY() - mCrossY); + int dest = (dx * dx) + (dy * dy); + + if (dest > maxDist && mActivityTime + && (cur_time < mActivityTime || cur_time - mActivityTime > 2)) + { + mActivityTime = cur_time; + moveTo(mCrossX, mCrossY); + } +} + +void LocalPlayer::setRealPos(int x, int y) +{ + if (!mMap) + return; + + SpecialLayer *layer = mMap->getTempLayer(); + if (layer) + { + fixPos(1); + + if ((mCrossX || mCrossY) && layer->getTile(mCrossX, mCrossY) + && layer->getTile(mCrossX, mCrossY)->getType() == MapItem::CROSS) + { + layer->setTile(mCrossX, mCrossY, MapItem::EMPTY); + } + + if (!layer->getTile(x, y) + || layer->getTile(x, y)->getType() == MapItem::EMPTY) + { + if (getTileX() != x && getTileY() != y) + layer->setTile(x, y, MapItem::CROSS); + } + + mCrossX = x; + mCrossY = y; + } +} +void LocalPlayer::fixAttackTarget() +{ + if (!mMap || !mTarget) + return; + + if (!getAttackType() || !config.getBoolValue("autofixPos")) + return; + + const Vector &playerPos = getPosition(); + Path debugPath = mMap->findPath((int) (playerPos.x - 16) / 32, + (int) (playerPos.y - 32) / 32, + mTarget->getTileX(), mTarget->getTileY(), + 0x00, 0); + if (!debugPath.empty()) + { + Path::const_iterator i = debugPath.begin(); + moveTo((*i).x, (*i).y); + } +} + +void LocalPlayer::respawn() +{ + naviageClean(); +} + +int LocalPlayer::getTargetTime() +{ + if (mTargetTime != -1) + return get_elapsed_time(mTargetTime); + else + return -1; +} + +int LocalPlayer::getLevel() const +{ + return PlayerInfo::getAttribute(LEVEL); +} + +void LocalPlayer::updateNavigateList() +{ + if (mMap) + { + std::map::iterator iter = + mHomes.find(mMap->getProperty("_filename")); + + if (iter != mHomes.end()) + { + Vector pos = mHomes[(*iter).first]; + mMap->addPortalTile("home", MapItem::HOME, pos.x, pos.y); + } + } +} + +void LocalPlayer::waitFor(std::string nick) +{ + mWaitFor = nick; +} + +void LocalPlayer::checkNewName(Being *being) +{ + if (!being) + return; + + const std::string nick = being->getName(); + if (being->getType() == ActorSprite::PLAYER) + { + const Guild *guild = getGuild(); + if (guild) + { + const GuildMember *gm = guild->getMember(nick); + if (gm) + { + const int level = gm->getLevel(); + if (level > 1 && being->getLevel() != level) + { + being->setLevel(level); + being->updateName(); + } + } + } + if (chatWindow) + { + ChatTab *tab = chatWindow->getWhisperTab(nick); + if (tab) + tab->setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + } + } + + if (!mWaitFor.empty() && mWaitFor == nick) + { + debugMsg(_("You see ") + mWaitFor); + sound.playGuiSfx("system/newmessage.ogg"); + mWaitFor = ""; + } +} + +void AwayListener::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok" && player_node && player_node->getAwayMode()) + { + player_node->changeAwayMode(); + if (outfitWindow) + outfitWindow->unwearAwayOutfit(); + if (miniStatusWindow) + miniStatusWindow->updateStatus(); + } +} \ No newline at end of file diff --git a/src/localplayer.h b/src/localplayer.h new file mode 100644 index 000000000..189483e97 --- /dev/null +++ b/src/localplayer.h @@ -0,0 +1,577 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LOCALPLAYER_H +#define LOCALPLAYER_H + +#include "actorspritelistener.h" +#include "being.h" +#include "client.h" +#include "game.h" +#include "listener.h" + +#include "gui/userpalette.h" + +#include + +#include +#include + +class ChatTab; +class FloorItem; +class ImageSet; +class Item; +class Map; +class OkDialog; +struct SkillInfo; + +class AwayListener : public gcn::ActionListener +{ + public: + void action(const gcn::ActionEvent &event); +}; + +/** + * The local player character. + */ +class LocalPlayer : public Being, public ActorSpriteListener, + public Mana::Listener +{ + public: + /** + * Constructor. + */ + LocalPlayer(int id = 65535, int subtype = 0); + + /** + * Destructor. + */ + ~LocalPlayer(); + + virtual void logic(); + + virtual void setAction(Action action, int attackType = 0); + + /** + * Compute the next pathnode location when walking using keyboard. + * used by nextTile(). + */ + Position getNextWalkPosition(unsigned char dir); + + /** + * Adds a new tile to the path when walking. + * @note Eathena + * Also, when specified, it picks up an item at the end of a path + * or attack target. + */ + virtual void nextTile() + { nextTile(0); } + + virtual void nextTile(unsigned char dir); + + /** + * Check the player has permission to invite users to specific guild + */ + bool checkInviteRights(const std::string &guildName); + + /** + * Invite a player to join guild + */ + void inviteToGuild(Being *being); + +// void clearInventory(); +// void setInvItem(int index, int id, int amount); + + bool pickUp(FloorItem *item); + + /** + * Called when an ActorSprite has been destroyed. + * @param actorSprite the ActorSprite being destroyed. + */ + void actorSpriteDestroyed(const ActorSprite &actorSprite); + + /** + * Sets the attack range. + */ + void setAttackRange(int range) { mAttackRange = range; } + + /** + * Gets the attack range. + */ + int getAttackRange(); + + void attack(Being *target = NULL, bool keep = false, + bool dontChangeEquipment = false); + + void attack2(Being *target = NULL, bool keep = false, + bool dontChangeEquipment = false); + + void setGMLevel(int level); + + int getGMLevel() + { return mGMLevel; } + + void stopAttack(); + + /** + * Overridden to do nothing. The attacks of the local player are + * displayed as soon as the player attacks, not when the server says + * the player does. + * + * @param victim the victim being + * @param damage the amount of damage dealt (0 means miss) + * @param type the attack type + */ + //virtual void handleAttack(Being *victim, int damage, AttackType type) {} + virtual void handleAttack() + { } + + /** + * Returns the current target of the player. Returns 0 if no being is + * currently targeted. + */ + Being *getTarget() const; + + /** + * Sets the target being of the player. + */ + void setTarget(Being *target); + + /** + * Sets a new destination for this being to walk to. + */ + virtual void setDestination(int x, int y); + + /** + * Sets a new direction to keep walking in. + */ + void setWalkingDir(unsigned char dir); + + /** + * Gets the walking direction + */ + unsigned char getWalkingDir() const + { return mWalkingDir; } + + /** + * Sets going to being to attack + */ + void setGotoTarget(Being *target); + + /** + * Returns whether the target is in range to attack + */ + bool withinAttackRange(Being *target, bool fixDistance = false, + int addRange = 0); + + /** + * Stops the player dead in his tracks + */ + void stopWalking(bool sendToServer = true); + + bool toggleSit(); + bool updateSit(); + bool emote(Uint8 emotion); + + /** + * Shows item pickup notifications. + */ + void pickedUp(const ItemInfo &itemInfo, int amount); + + int getLevel() const; + + int getTargetTime(); + +// int getSkillPoints() const +// { return mSkillPoints; } + +// void setSkillPoints(int points); + + /** Tells that the path has been set by mouse. */ + void pathSetByMouse() + { mPathSetByMouse = true; } + + /** Tells if the path has been set by mouse. */ + bool isPathSetByMouse() const + { return mPathSetByMouse; } + + int getInvertDirection() + { return mInvertDirection; } + + void setInvertDirection(int n) + { mInvertDirection = n; } + + void invertDirection(); + + int getAttackWeaponType() + { return mAttackWeaponType; } + + int getAttackType() + { return mAttackType; } + + int getFollowMode() + { return mFollowMode; } + + int getImitationMode() + { return mImitationMode; } + + void changeAttackWeaponType(); + + void changeAttackType(); + + void changeFollowMode(); + + void changeImitationMode(); + + void changePickUpType(); + + int getCrazyMoveType() + { return mCrazyMoveType ; } + + int getPickUpType() + { return mPickUpType ; } + + int getQuickDropCounter() + { return mQuickDropCounter ; } + + void changeQuickDropCounter(); + + int getMoveState() + { return mMoveState ; } + + void setMoveState(int n) + { mMoveState = n ; } + + void switchMagicAttack(); + + int getMagicAttackType() + { return mMagicAttackType ; } + + int getMoveToTargetType() + { return mMoveToTargetType ; } + + int getDisableGameModifiers() + { return mDisableGameModifiers ; } + + int getPingTime() + { return mPingTime ; } + + void tryPingRequest(); + + void changeMoveToTargetType(); + + void switchGameModifiers(); + + void magicAttack(); + + void specialMove(unsigned char direction); + + void moveByDirection(unsigned char dir); + + bool pickUpItems(int pickUpType = 0); + + void changeCrazyMoveType(); + + void crazyMove(); + + void moveTo(int x, int y); + + void move(int dX, int dY); + + void moveToTarget(unsigned int dist = -1); + + void moveToHome(); + + void debugMsg(std::string str); + +// int getSkillLv(int id); + + bool isReachable(int x, int y, int maxCost = 0); + + bool isReachable(Being *being, int maxCost = 0); + + void setHome(); + + void pingRequest(); + + void pingResponse(); + + void changeAwayMode(); + + bool getAwayMode() + { return mAwayMode; } + + void setAway(const std::string &message); + + void afkRespond(ChatTab *tab, const std::string &nick); + + void navigateTo(int x, int y); + + void navigateTo(Being *being); + + void naviageClean(); + + void updateCoords(); + + void imitateEmote(Being* being, unsigned char emote); + + void imitateAction(Being *being, Being::Action action); + + void imitateDirection(Being *being, unsigned char dir); + + void imitateOutfit(Being *player, int sprite = -1); + + void followMoveTo(Being *being, int x, int y); + + void followMoveTo(Being *being, int x1, int y1, int x2, int y2); + + bool allowAction(); + + bool allowMove(); + + void setRealPos(int x, int y); + + bool isServerBuggy() + { return mIsServerBuggy; } + + void fixPos(int maxDist = 1); + + /** + * Sets the map the being is on + */ + void setMap(Map *map); + + void addMessageToQueue(const std::string &message, + int color = UserPalette::EXP_INFO); + + /** + * Called when a option (set with config.addListener()) is changed + */ + void optionChanged(const std::string &value); + + void event(Channels channel, const Mana::Event &event); + + /** + * set a following player. + */ + void setFollow(std::string player); + + /** + * set an imitation player. + */ + void setImitate(std::string player); + + /** + * setting the next destination of the following, in case of warp + */ + void setNextDest(int x, int y); + + + int getNextDestX() const + { return mNextDestX; } + + int getNextDestY() const + { return mNextDestY; } + + void respawn(); + + FloorItem *getPickUpTarget() + { return mPickUpTarget; } + + void unSetPickUpTarget() + { mPickUpTarget = 0; } + + /** + * Stop following a player. + */ + void cancelFollow(); + + /** + * Get the playername followed by the current player. + */ + std::string getFollow() const + { return mPlayerFollowed; } + + /** + * Get the playername imitated by the current player. + */ + std::string getImitate() const + { return mPlayerImitated; } + + /** + * Tells the engine whether to check + * if the Player Name is to be displayed. + */ + void setCheckNameSetting(bool checked) + { mUpdateName = checked; } + + /** + * Gets if the engine has to check + * if the Player Name is to be displayed. + */ + bool getCheckNameSetting() const + { return mUpdateName; } + + void fixAttackTarget(); + + void updateNavigateList(); + + int getPathLength(Being* being); + + void targetMoved(); + + void setLastHitFrom(std::string n) + { mLastHitFrom = n; } + + void waitFor(std::string nick); + + void checkNewName(Being *being); + + protected: + /** Whether or not the name settings have changed */ + bool mUpdateName; + + virtual void handleStatusEffect(StatusEffect *effect, int effectId); + + void startWalking(unsigned char dir); + + void changeEquipmentBeforeAttack(Being* target); + + void tryMagic(std::string spell, int baseMagic, + int schoolMagic, int mana); + + void crazyMove1(); + void crazyMove2(); + void crazyMove3(); + void crazyMove4(); + void crazyMove5(); + void crazyMove6(); + void crazyMove7(); + void crazyMove8(); + void crazyMove9(); + void crazyMoveA(); + + void loadHomes(); + + void saveHomes(); + + bool mInStorage; /**< Whether storage is currently accessible */ + + int mAttackRange; + + int mTargetTime; /** How long the being has been targeted **/ + int mLastTarget; /** Time stamp of last targeting action, + -1 if none. */ + + int mGMLevel; + + //move type + unsigned int mInvertDirection; + //crazy move type + unsigned int mCrazyMoveType; + //crazy move state + unsigned int mCrazyMoveState; + //attack weapon type + unsigned int mAttackWeaponType; + //quick drop counter + unsigned int mQuickDropCounter; + //move state. used if mInvertDirection == 2 + unsigned int mMoveState; + //temporary disable crazy moves in moves + bool mDisableCrazyMove; + //pick up type 1x1, normal aka 2x1, forward aka 2x3, 3x3, 3x3 + 1 + unsigned int mPickUpType; + //magic attack type + unsigned int mMagicAttackType; + //type how move to target + unsigned int mMoveToTargetType; + unsigned int mAttackType; + unsigned int mFollowMode; + unsigned int mImitationMode; + + bool mDisableGameModifiers; + + int mLastTargetX; + int mLastTargetY; + + std::map mHomes; + + Being *mTarget; + + /** Follow system **/ + std::string mPlayerFollowed; + std::string mPlayerImitated; + int mNextDestX; + int mNextDestY; + + FloorItem *mPickUpTarget; + + bool mGoingToTarget; + bool mKeepAttacking; /** Whether or not to continue to attack */ + int mLastAction; /**< Time stamp of the last action, -1 if none.*/ + unsigned char mWalkingDir; /**< The direction the player is + walking in. */ + bool mPathSetByMouse; /**< Tells if the path was set using mouse */ + + std::vector mStatusEffectIcons; + + int mLocalWalkTime; /**< Timestamp used to control keyboard walk + messages flooding */ + + typedef std::pair MessagePair; + /** Queued messages*/ + std::list mMessages; + int mMessageTime; + AwayListener *mAwayListener; + OkDialog *mAwayDialog; + + int mPingSendTick; + bool mWaitPing; + int mPingTime; + int mAfkTime; + bool mAwayMode; + + bool mShowNavigePath; + bool mIsServerBuggy; + bool mSyncPlayerMove; + bool mDrawPath; + bool mAttackMoving; + int mActivityTime; + int mNavigateX; + int mNavigateY; + int mNavigateId; + int mCrossX; + int mCrossY; + int mOldX; + int mOldY; + int mOldTileX; + int mOldTileY; + Path mNavigatePath; + + bool mTargetDeadPlayers; + bool mServerAttack; + std::string mLastHitFrom; + std::string mWaitFor; +}; + +extern LocalPlayer *player_node; + +#endif diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 000000000..bf8519443 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,213 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "log.h" + +#include "configuration.h" + +#include "gui/widgets/chattab.h" + +#ifdef WIN32 +#include +#elif defined __APPLE__ +#include +#elif __linux__ || __linux +#include +#endif + +#include + +Logger::Logger(): + mLogToStandardOut(true), + mChatWindow(NULL), + mDebugLog(false) +{ +} + +Logger::~Logger() +{ + if (mLogFile.is_open()) + { + mLogFile.close(); + } +} + +void Logger::setLogFile(const std::string &logFilename) +{ + if (mLogFile.is_open()) + mLogFile.close(); + + mLogFile.open(logFilename.c_str(), std::ios_base::trunc); + + if (!mLogFile.is_open()) + { + std::cout << "Warning: error while opening " << logFilename << + " for writing.\n"; + } +} + +void Logger::log(std::string str) +{ + log("%s", str.c_str()); +} + +void Logger::dlog(std::string str) +{ + if (!mDebugLog) + return; + + // Get the current system time + timeval tv; + gettimeofday(&tv, NULL); + + // Print the log entry + std::stringstream timeStr; + timeStr << "[" + << ((((tv.tv_sec / 60) / 60) % 24 < 10) ? "0" : "") + << (int)(((tv.tv_sec / 60) / 60) % 24) + << ":" + << (((tv.tv_sec / 60) % 60 < 10) ? "0" : "") + << (int)((tv.tv_sec / 60) % 60) + << ":" + << ((tv.tv_sec % 60 < 10) ? "0" : "") + << (int)(tv.tv_sec % 60) + << "." + << (((tv.tv_usec / 10000) % 100) < 10 ? "0" : "") + << (int)((tv.tv_usec / 10000) % 100) + << "] "; + + if (mLogFile.is_open()) + mLogFile << timeStr.str() << str << std::endl; + + if (mLogToStandardOut) + std::cout << timeStr.str() << str << std::endl; + + if (mChatWindow && debugChatTab) + debugChatTab->chatLog(str, BY_LOGGER); +} + +void Logger::log1(const char *buf) +{ + // Get the current system time + timeval tv; + gettimeofday(&tv, NULL); + + // Print the log entry + std::stringstream timeStr; + timeStr << "[" + << ((((tv.tv_sec / 60) / 60) % 24 < 10) ? "0" : "") + << (int)(((tv.tv_sec / 60) / 60) % 24) + << ":" + << (((tv.tv_sec / 60) % 60 < 10) ? "0" : "") + << (int)((tv.tv_sec / 60) % 60) + << ":" + << ((tv.tv_sec % 60 < 10) ? "0" : "") + << (int)(tv.tv_sec % 60) + << "." + << (((tv.tv_usec / 10000) % 100) < 10 ? "0" : "") + << (int)((tv.tv_usec / 10000) % 100) + << "] "; + + if (mLogFile.is_open()) + mLogFile << timeStr.str() << buf << std::endl; + + if (mLogToStandardOut) + std::cout << timeStr.str() << buf << std::endl; + + if (mChatWindow && debugChatTab) + debugChatTab->chatLog(buf, BY_LOGGER); +} + +void Logger::log(const char *log_text, ...) +{ + unsigned int size = 1024; + char* buf = 0; + if (strlen(log_text) * 3 > size) + size = (unsigned)strlen(log_text) * 3; + + buf = new char[size]; + va_list ap; + + // Use a temporary buffer to fill in the variables + va_start(ap, log_text); + vsprintf(buf, log_text, ap); + va_end(ap); + + // Get the current system time + timeval tv; + gettimeofday(&tv, NULL); + + // Print the log entry + std::stringstream timeStr; + timeStr << "[" + << ((((tv.tv_sec / 60) / 60) % 24 < 10) ? "0" : "") + << (int)(((tv.tv_sec / 60) / 60) % 24) + << ":" + << (((tv.tv_sec / 60) % 60 < 10) ? "0" : "") + << (int)((tv.tv_sec / 60) % 60) + << ":" + << ((tv.tv_sec % 60 < 10) ? "0" : "") + << (int)(tv.tv_sec % 60) + << "." + << (((tv.tv_usec / 10000) % 100) < 10 ? "0" : "") + << (int)((tv.tv_usec / 10000) % 100) + << "] "; + + if (mLogFile.is_open()) + mLogFile << timeStr.str() << buf << std::endl; + + if (mLogToStandardOut) + std::cout << timeStr.str() << buf << std::endl; + + if (mChatWindow && debugChatTab) + debugChatTab->chatLog(buf, BY_LOGGER); + + // Delete temporary buffer + delete[] buf; +} + +void Logger::error(const std::string &error_text) +{ + log("Error: %s", error_text.c_str()); +#ifdef WIN32 + MessageBox(NULL, error_text.c_str(), "Error", MB_ICONERROR | MB_OK); +#elif defined __APPLE__ + Str255 msg; + CFStringRef error; + error = CFStringCreateWithCString(NULL, + error_text.c_str(), + kCFStringEncodingMacRoman); + CFStringGetPascalString(error, msg, 255, kCFStringEncodingMacRoman); + StandardAlert(kAlertStopAlert, + "\pError", + (ConstStr255Param) msg, NULL, NULL); +#elif defined __linux__ || __linux + std::cerr << "Error: " << error_text << std::endl; + std::string msg = "xmessage \"" + error_text + "\""; + system(msg.c_str()); +#else + std::cerr << "Error: " << error_text << std::endl; +#endif + exit(1); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 000000000..5f0a6b60d --- /dev/null +++ b/src/log.h @@ -0,0 +1,109 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _LOG_H +#define _LOG_H + +#include "main.h" +#include + +class ChatWindow; + +#ifdef ENABLEDEBUGLOG +#define DEBUGLOG(msg) logger->dlog(msg) +#else +#define DEBUGLOG(msg) {} +#endif + +/** + * The Log Class : Useful to write debug or info messages + */ +class Logger +{ + public: + /** + * Constructor. + */ + Logger(); + + /** + * Destructor, closes log file. + */ + ~Logger(); + + /** + * Sets the file to log to and opens it + */ + void setLogFile(const std::string &logFilename); + + /** + * Sets whether the log should be written to standard output. + */ + void setLogToStandardOut(bool value) { mLogToStandardOut = value; } + + /** + * Enables logging to chat window + */ + void setChatWindow(ChatWindow *window) { mChatWindow = window; } + + /** + * Enters a message in the log. The message will be timestamped. + */ + void log(const char *log_text, ...) +#ifdef __GNUC__ + __attribute__((__format__(__printf__, 2, 3))) +#endif + ; + + /** + * Enters a message in the log. The message will be timestamped. + */ + void log1(const char *log_text); + + /** + * Enters a message in the log. The message will be timestamped. + */ + void log(std::string str); + + /** + * Enters debug message in the log. The message will be timestamped. + */ + void dlog(std::string str); + + void setDebugLog(bool n) + { mDebugLog = n; } + + /** + * Log an error and quit. The error will pop-up on Windows and Mac, and + * will be printed to standard error everywhere else. + */ + void error(const std::string &error_text) __attribute__ ((noreturn)); + + private: + std::ofstream mLogFile; + bool mLogToStandardOut; + ChatWindow *mChatWindow; + bool mDebugLog; +}; + +extern Logger *logger; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 000000000..7cddd633b --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,261 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "main.h" + +#include "utils/gettext.h" + +#include "client.h" +#include "log.h" + +#include + +#include +#include +#include + +#ifdef __MINGW32__ +#include +#endif + +static void printHelp() +{ + using std::endl; + + std::cout + << _("manaplus [options] [mana-file]") << endl << endl + << _("[mana-file] : The mana file is an XML file (.mana)") << endl + << _(" used to set custom parameters") << endl + << _(" to the mana client.") + << endl << endl + << _("Options:") << endl + << _(" -l --log-file : Log file to use") << endl + << _(" -L --chat-log-dir : Chat log dir to use") << endl + << _(" -v --version : Display the version") << endl + << _(" -h --help : Display this help") << endl + << _(" -C --config-dir : Configuration directory to use") << endl + << _(" -U --username : Login with this username") << endl + << _(" -P --password : Login with this password") << endl + << _(" -c --character : Login with this character") << endl + << _(" -s --server : Login server name or IP") << endl + << _(" -p --port : Login server port") << endl + << _(" --update-host : Use this update host") << endl + << _(" -D --default : Choose default character server and " + "character") << endl + << _(" -u --skip-update : Skip the update downloads") << endl + << _(" -d --data : Directory to load game " + "data from") << endl + << _(" -L --localdata-dir : Directory to use as local data" + " directory") << endl + << _(" --screenshot-dir : Directory to store screenshots") << endl + << _(" --safemode : Start game in safe mode") << endl +#ifdef USE_OPENGL + << _(" --no-opengl : Disable OpenGL for this session") << endl +#endif + ; +} + +static void printVersion() +{ + std::cout << strprintf("ManaPlus client %s", FULL_VERSION) << std::endl; +} + +static void parseOptions(int argc, char *argv[], Client::Options &options) +{ + const char *optstring = "hvud:U:P:Dc:p:l:L:C:"; + + const struct option long_options[] = + { + { "config-dir", required_argument, 0, 'C' }, + { "data", required_argument, 0, 'd' }, + { "default", no_argument, 0, 'D' }, + { "password", required_argument, 0, 'P' }, + { "character", required_argument, 0, 'c' }, + { "help", no_argument, 0, 'h' }, + { "localdata-dir", required_argument, 0, 'L' }, + { "update-host", required_argument, 0, 'H' }, + { "port", required_argument, 0, 'p' }, + { "server", required_argument, 0, 's' }, + { "skip-update", no_argument, 0, 'u' }, + { "username", required_argument, 0, 'U' }, + { "no-opengl", no_argument, 0, 'O' }, + { "chat-log-dir", required_argument, 0, 'l' }, + { "version", no_argument, 0, 'v' }, + { "log-file", required_argument, 0, 'l' }, + { "chat-log-dir", required_argument, 0, 'L' }, + { "screenshot-dir", required_argument, 0, 'i' }, + { "safemode", no_argument, 0, 'm' }, + { 0, 0, 0, 0 } + }; + + while (optind < argc) + { + int result = getopt_long(argc, argv, optstring, long_options, NULL); + + if (result == -1) + break; + + switch (result) + { + case 'C': + options.configDir = optarg; + break; + case 'd': + options.dataPath = optarg; + break; + case 'D': + options.chooseDefault = true; + break; + case '?': // Unknown option + case ':': // Missing argument + case 'h': + options.printHelp = true; + break; + case 'H': + options.updateHost = optarg; + break; + case 'c': + options.character = optarg; + break; + case 'P': + options.password = optarg; + break; + case 's': + options.serverName = optarg; + break; + case 'p': + options.serverPort = (short) atoi(optarg); + break; + case 'u': + options.skipUpdate = true; + break; + case 'U': + options.username = optarg; + break; + case 'v': + options.printVersion = true; + break; + case 'S': + options.localDataDir = optarg; + break; + case 'O': + options.noOpenGL = true; + break; + case 'l': + options.logFileName = std::string(optarg); + break; + case 'L': + options.chatLogDir = std::string(optarg); + case 'i': + options.screenshotDir = optarg; + break; + case 'm': + options.safeMode = true; + break; + default: + break; + } + } + + // when there are still options left use the last + // one as branding file + if (optind < argc) + { + options.brandingPath = argv[optind]; + } +} + +#ifdef WIN32 +extern "C" char const *_nl_locale_name_default(void); +#endif + +static void initInternationalization() +{ +#if ENABLE_NLS +#ifdef WIN32 + putenv(("LANG=" + std::string(_nl_locale_name_default())).c_str()); + // mingw doesn't like LOCALEDIR to be defined for some reason + bindtextdomain("manaplus", "translations/"); +#else + bindtextdomain("manaplus", LOCALEDIR); +#endif + setlocale(LC_MESSAGES, ""); + bind_textdomain_codeset("manaplus", "UTF-8"); + textdomain("manaplus"); +#endif +} + +static void xmlNullLogger(void *ctx _UNUSED_, const char *msg _UNUSED_, ...) +{ + // Does nothing, that's the whole point of it +} + +// Initialize libxml2 and check for potential ABI mismatches between +// compiled version and the shared library actually used. +static void initXML() +{ + xmlInitParser(); + LIBXML_TEST_VERSION; + + // Suppress libxml2 error messages + xmlSetGenericErrorFunc(NULL, xmlNullLogger); +} + + +int main(int argc, char *argv[]) +{ +#if defined(__MINGW32__) + // load mingw crash handler. Won't fail if dll is not present. + // may load libray from current dir, it may not same as program dir + LoadLibrary("exchndl.dll"); +#endif + + // Parse command line options + Client::Options options; + parseOptions(argc, argv, options); + + if (options.printHelp) + { + printHelp(); + return 0; + } + else if (options.printVersion) + { + printVersion(); + return 0; + } + + initInternationalization(); + + // Initialize PhysicsFS + if (!PHYSFS_init(argv[0])) + { + std::cout << "Error while initializing PhysFS: " + << PHYSFS_getLastError() << std::endl; + return 1; + } + atexit((void(*)()) PHYSFS_deinit); + + initXML(); + + Client client(options); + return client.exec(); +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 000000000..480c9d374 --- /dev/null +++ b/src/main.h @@ -0,0 +1,109 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MAIN_H +#define MAIN_H + +/** + * \mainpage + * + * \section Introduction Introduction + * + * This is the documentation for the Mana client (http://manasource.org). It is + * always a work in progress, with the intent to make it easier for new + * developers to grow familiar with the source code. + * + * \section General General information + * + * During the game, the current Map is displayed by the main Viewport, which + * is the bottom-most widget in the WindowContainer. Aside the viewport, the + * window container keeps track of all the \link Window Windows\endlink + * displayed during the game. It is the top widget for Guichan. + * + * A Map is composed of several layers of \link Image Images\endlink (tiles), + * a layer with collision information and \link Sprite Sprites\endlink. The + * sprites define the visible part of \link Being Beings\endlink and + * \link FloorItem FloorItems\endlink, they are drawn from top to bottom + * by the map, interleaved with the tiles in the fringe layer. + * + * The client supports two servers, \link EAthena eAthena\endlink (the TMW + * version) and the \link ManaServ Mana server\endlink. To achieve this, the + * \link Net network communication layer\endlink is abstracted in many + * different interfaces, which have different implementations for each server. + */ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#elif defined WIN32 +#include "winver.h" +#elif defined __APPLE__ +#define PACKAGE_VERSION "1.0.0" +#endif + +#if defined __APPLE__ +#define PACKAGE_OS "Apple" +#elif defined __FreeBSD__ || defined __DragonFly__ +#define PACKAGE_OS "FreeBSD" +#elif defined __NetBSD__ +#define PACKAGE_OS "NetBSD" +#elif defined __OpenBSD__ +#define PACKAGE_OS "OpenBSD" +#elif defined __linux__ || defined __linux +#define PACKAGE_OS "Linux" +#elif defined __GNU__ +#define PACKAGE_OS "GNU Hurd" +#elif defined WIN32 || defined _WIN32 || defined __WIN32__ || defined __NT__ \ + || defined WIN64 || defined _WIN64 || defined __WIN64__ \ + || defined __MINGW32__ || defined _MSC_VER +#define PACKAGE_OS "Windows" +#else +#define PACKAGE_OS "Other" +#endif + + +#define ENABLEDEBUGLOG 1 +//define DEBUG_LEAKS +//define DEBUG_CONFIG 1 +//define DEBUG_FONT 1 +//define DEBUG_FONT_COUNTERS 1 +//define DEBUG_ALPHA_CACHE 1 + +#define SMALL_VERSION "1.0.12.26" +#define CHECK_VERSION "01.00.12.26" + + +#define PACKAGE_EXTENDED_VERSION "ManaPlus (" PACKAGE_OS \ +"; %s; 4144 v" SMALL_VERSION ")" +#define PACKAGE_VERSION_4144 "ManaPlus 4144-" SMALL_VERSION "" + +#define FULL_VERSION "ManaPlus 4144 " SMALL_VERSION " " PACKAGE_OS + +#ifndef PKG_DATADIR +#define PKG_DATADIR "" +#endif + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +#endif diff --git a/src/mana.rc b/src/mana.rc new file mode 100644 index 000000000..b7720e2cc --- /dev/null +++ b/src/mana.rc @@ -0,0 +1,23 @@ +#include // include for version info constants + +#include "winver.h" + +A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "../data/icons/manaplus.ico" + +1 VERSIONINFO +FILEVERSION VER_MAJOR,VER_MINOR,VER_RELEASE,VER_BUILD +PRODUCTVERSION VER_MAJOR,VER_MINOR,VER_RELEASE,VER_BUILD +//FILETYPE VFT_APP +{ + BLOCK "StringFileInfo" { + BLOCK "040904E4" { + VALUE "CompanyName", "The Mana Development Team" + VALUE "FileVersion", PACKAGE_VERSION "4144" + VALUE "FileDescription", "ManaPlus" + VALUE "LegalCopyright", "2004-2010 (C)" + VALUE "OriginalFilename", "manaplus.exe" + VALUE "ProductName", "ManaPlus MMORPG Client" + VALUE "ProductVersion", PACKAGE_VERSION "4144" + } + } +} diff --git a/src/map.cpp b/src/map.cpp new file mode 100644 index 000000000..5dd933f0b --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,1654 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "map.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "log.h" +#include "particle.h" +#include "simpleanimation.h" +#include "tileset.h" +#include "localplayer.h" + +#include "resources/ambientlayer.h" +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/truetypefont.h" + +#include "gui/widgets/chattab.h" + +#include "utils/dtor.h" +#include "utils/mkdir.h" +#include "utils/stringutils.h" + +#include + +#include + +bool actorCompare(const Actor *a, const Actor *b); + +/** + * A location on a tile map. Used for pathfinding, open list. + */ +struct Location +{ + /** + * Constructor. + */ + Location(int px, int py, MetaTile *ptile): + x(px), y(py), tile(ptile) + {} + + /** + * Comparison operator. + */ + bool operator< (const Location &loc) const + { + return tile->Fcost > loc.tile->Fcost; + } + + int x, y; + MetaTile *tile; +}; + +TileAnimation::TileAnimation(Animation *ani): + mLastImage(NULL) +{ + mAnimation = new SimpleAnimation(ani); +} + +TileAnimation::~TileAnimation() +{ + delete mAnimation; + mAnimation = 0; +} + +void TileAnimation::update(int ticks) +{ + if (!mAnimation) + return; + + // update animation + mAnimation->update(ticks); + + // exchange images + Image *img = mAnimation->getCurrentImage(); + if (img != mLastImage) + { + for (std::list >::iterator i = + mAffected.begin(); i != mAffected.end(); i++) + { + i->first->setTile(i->second, img); + } + mLastImage = img; + } +} + +MapLayer::MapLayer(int x, int y, int width, int height, bool isFringeLayer): + mX(x), mY(y), + mWidth(width), mHeight(height), + mIsFringeLayer(isFringeLayer), + mHighlightAttackRange(config.getBoolValue("highlightAttackRange")) +{ + const int size = mWidth * mHeight; + mTiles = new Image*[size]; + + std::fill_n(mTiles, size, (Image*) 0); + + config.addListener("highlightAttackRange", this); +} + +MapLayer::~MapLayer() +{ + config.removeListener("highlightAttackRange", this); + delete[] mTiles; +} + +void MapLayer::optionChanged(const std::string &value) +{ + if (value == "highlightAttackRange") + { + mHighlightAttackRange = + config.getBoolValue("highlightAttackRange"); + } +} + +void MapLayer::setTile(int x, int y, Image *img) +{ + setTile(x + y * mWidth, img); +} + +Image* MapLayer::getTile(int x, int y) const +{ + return mTiles[x + y * mWidth]; +} + +void MapLayer::draw(Graphics *graphics, int startX, int startY, + int endX, int endY, int scrollX, int scrollY, + const Actors &actors, int debugFlags) const +{ + if (!player_node) + return; + + startX -= mX; + startY -= mY; + endX -= mX; + endY -= mY; + + if (startX < 0) + startX = 0; + if (startY < 0) + startY = 0; + if (endX > mWidth) + endX = mWidth; + if (endY > mHeight) + endY = mHeight; + + Actors::const_iterator ai = actors.begin(); + + const int dx = (mX * 32) - scrollX; + const int dy = (mY * 32) - scrollY + 32; + + int specialWidth = 0; + int specialHeight = 0; + + const bool extraDraw = + mIsFringeLayer && mSpecialLayer && mTempLayer; + + if (mIsFringeLayer) + { + if (mSpecialLayer) + { + specialWidth = mSpecialLayer->mWidth; + specialHeight = mSpecialLayer->mHeight; + } + } + + for (int y = startY; y < endY; y++) + { + const int y32 = y * 32; + const int yWidth = y * mWidth; + + // If drawing the fringe layer, make sure all actors above this row of + // tiles have been drawn + if (mIsFringeLayer) + { + while (ai != actors.end() && (*ai)->getPixelY() <= y32) + { + (*ai)->draw(graphics, -scrollX, -scrollY); + ai++; + } + } + + if (debugFlags == Map::MAP_SPECIAL3 + || debugFlags == Map::MAP_BLACKWHITE) + { + if (extraDraw && y < specialHeight) + { + //x + y * mWidth + int ptr = y * specialWidth; + const int py1 = y32 - scrollY; + int endX1 = endX; + if (endX1 > specialWidth) + endX1 = specialWidth; + if (endX1 < 0) + endX1 = 0; + + for (int x = startX; x < endX1; x++) + { + const int px1 = x * 32 - scrollX; + + MapItem *item = mSpecialLayer->mTiles[ptr + x]; + if (item) + item->draw(graphics, px1, py1, 32, 32); + + item = mTempLayer->mTiles[ptr + x]; + if (item) + item->draw(graphics, px1, py1, 32, 32); + } + } + } + else + { + const int py0 = y32 + dy; + const int py1 = y32 - scrollY; + + for (int x = startX; x < endX; x++) + { + const int px1 = x * 32 - scrollX; + const int tilePtr = x + yWidth; + int c = 0; + Image *img = mTiles[tilePtr]; + if (img) + { + const int px = (x * 32) + dx; + const int py = py0 - img->getHeight(); + if ((debugFlags != Map::MAP_SPECIAL + && debugFlags != Map::MAP_SPECIAL2) + || img->getHeight() <= 32) + { + int width = 0; + c = getTileDrawWidth(tilePtr, endX - x, width); + + if (!c) + { + graphics->drawImage(img, px, py); + } + else + { + graphics->drawImagePattern(img, px, py, + width, img->getHeight()); + } + } + } + + if (extraDraw && y < specialHeight) + { + int c1 = c; + if (c1 + x + 1 > specialWidth) + c1 = specialWidth - x - 1; + if (c1 < 0) + c1 = 0; + + int ptr = y * specialWidth + x; + + for (int x1 = 0; x1 < c1 + 1; x1 ++) + { + MapItem *item1 = mSpecialLayer->mTiles[ptr + x1]; + MapItem *item2 = mTempLayer->mTiles[ptr + x1]; + if (item1 || item2) + { + const int px2 = px1 + (x1 * 32); + if (item1 && item1->mType != MapItem::EMPTY) + item1->draw(graphics, px2, py1, 32, 32); + + if (item2 && item2->mType != MapItem::EMPTY) + item2->draw(graphics, px2, py1, 32, 32); + } + } + } + x += c; + } + } + } + + // Draw any remaining actors + if (mIsFringeLayer && debugFlags != Map::MAP_SPECIAL3) + { + while (ai != actors.end()) + { + (*ai)->draw(graphics, -scrollX, -scrollY); + ai++; + } + if (mHighlightAttackRange && player_node) + { + const int px = player_node->getPixelX() - scrollX - 16; + const int py = player_node->getPixelY() - scrollY - 32; + const int attackRange = player_node->getAttackRange() * 32; + + int x = px - attackRange; + int y = py - attackRange; + int w = 2 * attackRange + 32; + int h = w; + if (attackRange <= 32) + { + x -= 16; + y -= 16; + w += 32; + h += 32; + } + + if (userPalette) + { + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::ATTACK_RANGE)); + + graphics->fillRectangle(gcn::Rectangle( + x, y, + w, h)); + + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::ATTACK_RANGE_BORDER)); + + graphics->drawRectangle(gcn::Rectangle( + x, y, + w, h)); + } + } + } +} + +int MapLayer::getTileDrawWidth(int tilePtr, int endX, int &width) const +{ + Image *img1 = mTiles[tilePtr]; + int c = 0; + if (!img1) + { + width = 0; + return c; + } + width = img1->getWidth(); + for (int x = 1; x < endX; x++) + { + tilePtr ++; + Image *img = mTiles[tilePtr]; + if (img != img1) + break; + c ++; + if (img) + width += img->getWidth(); + } + return c; +} + +Map::Map(int width, int height, int tileWidth, int tileHeight): + mWidth(width), mHeight(height), + mTileWidth(tileWidth), mTileHeight(tileHeight), + mMaxTileHeight(height), + mHasWarps(false), + mDebugFlags(MAP_NORMAL), + mOnClosedList(1), mOnOpenList(2), + mLastScrollX(0.0f), mLastScrollY(0.0f), + mOverlayDetail(config.getIntValue("OverlayDetail")), + mOpacity(config.getFloatValue("guialpha")), + mPvp(0) +// mSpritesUpdated(true) +{ + const int size = mWidth * mHeight; + + mDebugFlags = 0; + mMetaTiles = new MetaTile[size]; + for (int i = 0; i < NB_BLOCKTYPES; i++) + { + mOccupation[i] = new int[size]; + memset(mOccupation[i], 0, size * sizeof(int)); + } + mSpecialLayer = new SpecialLayer(width, height); + mTempLayer = new SpecialLayer(width, height, true); + config.addListener("OverlayDetail", this); + config.addListener("guialpha", this); + +#ifdef USE_OPENGL + mOpenGL = config.getIntValue("opengl"); +#else + mOpenGL = 0; +#endif +} + +Map::~Map() +{ + config.removeListener("OverlayDetail", this); + config.removeListener("guialpha", this); + + // delete metadata, layers, tilesets and overlays + delete[] mMetaTiles; + for (int i = 0; i < NB_BLOCKTYPES; i++) + delete[] mOccupation[i]; + + delete_all(mLayers); + delete_all(mTilesets); + delete_all(mForegrounds); + delete_all(mBackgrounds); + delete_all(mTileAnimations); + delete mSpecialLayer; + mSpecialLayer = 0; + delete mTempLayer; + mTempLayer = 0; + delete_all(mMapPortals); +} + +void Map::optionChanged(const std::string &value) +{ + if (value == "OverlayDetail") + mOverlayDetail = config.getIntValue("OverlayDetail"); + else if (value == "guialpha") + mOpacity = config.getFloatValue("guialpha"); +} + +void Map::initializeAmbientLayers() +{ + ResourceManager *resman = ResourceManager::getInstance(); + + // search for "foreground*" or "overlay*" (old term) in map properties + for (int i = 0; /* terminated by a break */; i++) + { + std::string name; + if (hasProperty("foreground" + toString(i) + "image")) + name = "foreground" + toString(i); + else if (hasProperty("overlay" + toString(i) + "image")) + name = "overlay" + toString(i); + else + break; // the FOR loop + + Image *img = resman->getImage(getProperty(name + "image")); + const float speedX = getFloatProperty(name + "scrollX"); + const float speedY = getFloatProperty(name + "scrollY"); + const float parallax = getFloatProperty(name + "parallax"); + const bool keepRatio = getBoolProperty(name + "keepratio"); + + if (img) + { + mForegrounds.push_back( + new AmbientLayer(img, parallax, speedX, speedY, keepRatio)); + + // The AmbientLayer takes control over the image. + img->decRef(); + } + } + + + // search for "background*" in map properties + for (int i = 0; + hasProperty("background" + toString(i) + "image"); + i++) + { + const std::string name = "background" + toString(i); + + Image *img = resman->getImage(getProperty(name + "image")); + const float speedX = getFloatProperty(name + "scrollX"); + const float speedY = getFloatProperty(name + "scrollY"); + const float parallax = getFloatProperty(name + "parallax"); + const bool keepRatio = getBoolProperty(name + "keepratio"); + + if (img) + { + mBackgrounds.push_back( + new AmbientLayer(img, parallax, speedX, speedY, keepRatio)); + + // The AmbientLayer takes control over the image. + img->decRef(); + } + } +} + +void Map::addLayer(MapLayer *layer) +{ + mLayers.push_back(layer); +} + +void Map::addTileset(Tileset *tileset) +{ + if (!tileset) + return; + + mTilesets.push_back(tileset); + + if (tileset->getHeight() > mMaxTileHeight) + mMaxTileHeight = tileset->getHeight(); +} + +bool actorCompare(const Actor *a, const Actor *b) +{ + if (!a || !b) + return false; + + return a->getPixelY() < b->getPixelY(); +} + +void Map::update(int ticks) +{ + // Update animated tiles + for (std::map::iterator + iAni = mTileAnimations.begin(); + iAni != mTileAnimations.end(); iAni++) + { + iAni->second->update(ticks); + } +} + +void Map::draw(Graphics *graphics, int scrollX, int scrollY) +{ + // Calculate range of tiles which are on-screen + int endPixelY = graphics->getHeight() + scrollY + mTileHeight - 1; + endPixelY += mMaxTileHeight - mTileHeight; + int startX = scrollX / mTileWidth; + int startY = scrollY / mTileHeight; + int endX = (graphics->getWidth() + scrollX + mTileWidth - 1) / mTileWidth; + int endY = endPixelY / mTileHeight; + + // Make sure actors are sorted ascending by Y-coordinate + // so that they overlap correctly +// if (mSpritesUpdated) +// { + mActors.sort(actorCompare); +// mSpritesUpdated = false; +// } + + // update scrolling of all ambient layers + updateAmbientLayers(static_cast(scrollX), + static_cast(scrollY)); + + // Draw backgrounds + drawAmbientLayers(graphics, BACKGROUND_LAYERS, mOverlayDetail); + + if (mDebugFlags == MAP_BLACKWHITE) + { + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::WALKABLE_HIGHLIGHT)); + + graphics->fillRectangle(gcn::Rectangle(0, 0, + graphics->getWidth(), graphics->getHeight())); + } + + // draw the game world + Layers::const_iterator layeri = mLayers.begin(); + + bool overFringe = false; + + if (mDebugFlags == MAP_SPECIAL3 || mDebugFlags == MAP_BLACKWHITE) + { + for (; layeri != mLayers.end(); ++layeri) + { + if ((*layeri)->isFringeLayer()) + { + (*layeri)->setSpecialLayer(mSpecialLayer); + (*layeri)->setTempLayer(mTempLayer); + (*layeri)->draw(graphics, + startX, startY, endX, endY, + scrollX, scrollY, + mActors, mDebugFlags); + break; + } + } + } + else + { + for (; layeri != mLayers.end() && !overFringe; ++layeri) + { + if ((*layeri)->isFringeLayer()) + { + (*layeri)->setSpecialLayer(mSpecialLayer); + (*layeri)->setTempLayer(mTempLayer); + if (mDebugFlags == MAP_SPECIAL2) + overFringe = true; + } + + (*layeri)->draw(graphics, + startX, startY, endX, endY, + scrollX, scrollY, + mActors, mDebugFlags); + } + } + + // Dont draw if gui opacity == 1 + if (mOpacity != 1.0f) + { + // Draws beings with a lower opacity to make them visible + // even when covered by a wall or some other elements... + Actors::const_iterator ai = mActors.begin(); + while (ai != mActors.end()) + { + if (Actor *actor = *ai) + { + if (!mOpenGL && (actor->getTileX() < startX + || actor->getTileX() > endX || actor->getTileY() < startY + || actor->getTileY() > endY)) + { + ai++; + continue; + } + // For now, just draw actors with only one layer. + if (actor->getNumberOfLayers() == 1) + { + actor->setAlpha(0.3f); + actor->draw(graphics, -scrollX, -scrollY); + actor->setAlpha(1.0f); + } + } + ai++; + } + } + + drawAmbientLayers(graphics, FOREGROUND_LAYERS, mOverlayDetail); +} + +void Map::drawCollision(Graphics *graphics, int scrollX, int scrollY, + int debugFlags) +{ + int endPixelY = graphics->getHeight() + scrollY + mTileHeight - 1; + int startX = scrollX / mTileWidth; + int startY = scrollY / mTileHeight; + int endX = (graphics->getWidth() + scrollX + mTileWidth - 1) / mTileWidth; + int endY = endPixelY / mTileHeight; + + if (startX < 0) startX = 0; + if (startY < 0) startY = 0; + if (endX > mWidth) endX = mWidth; + if (endY > mHeight) endY = mHeight; + + + if (debugFlags < MAP_SPECIAL) + { + graphics->setColor(gcn::Color(0, 0, 0, 64)); + graphics->drawNet( + startX * mTileWidth - scrollX, + startY * mTileHeight - scrollY, + endX * mTileWidth - scrollX, + endY * mTileHeight - scrollY, + 32, 32); + } + + for (int y = startY; y < endY; y++) + { + for (int x = startX; x < endX; x++) + { + int width = 0; + int x0 = x; + + if (!getWalk(x, y, BLOCKMASK_WALL)) + { + width = 32; + for (int x2 = x + 1; x < endX; x2 ++) + { + if (getWalk(x2, y, BLOCKMASK_WALL)) + break; + width += 32; + x ++; + } + } + + if (width && userPalette) + { + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::COLLISION_HIGHLIGHT)); + + graphics->fillRectangle(gcn::Rectangle( + x0 * mTileWidth - scrollX, + y * mTileHeight - scrollY, + width, 32)); + } + } + } +} + +void Map::updateAmbientLayers(float scrollX, float scrollY) +{ + static int lastTick = tick_time; // static = only initialized at first call + + if (mLastScrollX == 0.0f && mLastScrollY == 0.0f) + { + // First call - initialisation + mLastScrollX = scrollX; + mLastScrollY = scrollY; + } + + // Update Overlays + float dx = scrollX - mLastScrollX; + float dy = scrollY - mLastScrollY; + int timePassed = get_elapsed_time(lastTick); + + std::list::iterator i; + for (i = mBackgrounds.begin(); i != mBackgrounds.end(); i++) + (*i)->update(timePassed, dx, dy); + + for (i = mForegrounds.begin(); i != mForegrounds.end(); i++) + (*i)->update(timePassed, dx, dy); + + mLastScrollX = scrollX; + mLastScrollY = scrollY; + lastTick = tick_time; +} + +void Map::drawAmbientLayers(Graphics *graphics, LayerType type, + int detail) +{ + // Detail 0 = no ambient effects except background image + if (detail <= 0 && type != BACKGROUND_LAYERS) + return; + + // find out which layer list to draw + std::list *layers; + switch (type) + { + case FOREGROUND_LAYERS: + layers = &mForegrounds; + break; + case BACKGROUND_LAYERS: + layers = &mBackgrounds; + break; + default: + // New type of ambient layers added here without adding it + // to Map::drawAmbientLayers. + assert(false); + break; + } + + // Draw overlays + for (std::list::iterator i = layers->begin(); + i != layers->end(); i++) + { + (*i)->draw(graphics, graphics->getWidth(), graphics->getHeight()); + + // Detail 1: only one overlay, higher: all overlays + if (detail == 1) + break; + } +} + +Tileset *Map::getTilesetWithGid(int gid) const +{ + Tileset *s = NULL; + for (Tilesets::const_iterator it = mTilesets.begin(), + it_end = mTilesets.end(); it < it_end && (*it)->getFirstGid() <= gid; + it++) + { + s = *it; + } + + return s; +} + +void Map::blockTile(int x, int y, BlockType type) +{ + if (type == BLOCKTYPE_NONE || !contains(x, y)) + return; + + const int tileNum = x + y * mWidth; + + if ((++mOccupation[type][tileNum]) > 0) + { + switch (type) + { + case BLOCKTYPE_WALL: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_WALL; + break; + case BLOCKTYPE_CHARACTER: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_CHARACTER; + break; + case BLOCKTYPE_MONSTER: + mMetaTiles[tileNum].blockmask |= BLOCKMASK_MONSTER; + break; + default: + // Do nothing. + break; + } + } +} + +bool Map::getWalk(int x, int y, unsigned char walkmask) const +{ + // You can't walk outside of the map + if (!contains(x, y)) + return false; + + // Check if the tile is walkable + return !(mMetaTiles[x + y * mWidth].blockmask & walkmask); +} + +bool Map::occupied(int x, int y) const +{ + const ActorSprites &actors = actorSpriteManager->getAll(); + ActorSpritesConstIterator it, it_end; + for (it = actors.begin(), it_end = actors.end(); it != it_end; it++) + { + const ActorSprite *actor = *it; + +//+++ if (actor->getTileX() == x && actor->getTileY() == y +// && being->getSubType() != 45) + if (actor->getTileX() == x && actor->getTileY() == y && + actor->getType() != ActorSprite::FLOOR_ITEM) + { + return true; + } + } + + return false; +} + +bool Map::contains(int x, int y) const +{ + return x >= 0 && y >= 0 && x < mWidth && y < mHeight; +} + +MetaTile *Map::getMetaTile(int x, int y) const +{ + return &mMetaTiles[x + y * mWidth]; +} + +Actors::iterator Map::addActor(Actor *actor) +{ + mActors.push_front(actor); +// mSpritesUpdated = true; + return mActors.begin(); +} + +void Map::removeActor(Actors::iterator iterator) +{ + mActors.erase(iterator); +// mSpritesUpdated = true; +} + +const std::string Map::getMusicFile() const +{ + return getProperty("music"); +} + +const std::string Map::getName() const +{ + if (hasProperty("name")) + return getProperty("name"); + + return getProperty("mapname"); +} + +const std::string Map::getFilename() const +{ + std::string fileName = getProperty("_filename"); + int lastSlash = static_cast(fileName.rfind("/")) + 1; + int lastDot = static_cast(fileName.rfind(".")); + + return fileName.substr(lastSlash, lastDot - lastSlash); +} + +Position Map::checkNodeOffsets(int radius, unsigned char walkMask, + const Position &position) const +{ + // Pre-computing character's position in tiles + const int tx = position.x / 32; + const int ty = position.y / 32; + + // Pre-computing character's position offsets. + int fx = position.x % 32; + int fy = position.y % 32; + + // Compute the being radius: + // FIXME: Hande beings with more than 1/2 tile radius by not letting them + // go or spawn in too narrow places. The server will have to be aware + // of being's radius value (in tiles) to handle this gracefully. + if (radius > 32 / 2) + radius = 32 / 2; + // set a default value if no value returned. + if (radius < 1) + radius = 32 / 3; + + // We check diagonal first as they are more restrictive. + // Top-left border check + if (!getWalk(tx - 1, ty - 1, walkMask) + && fy < radius && fx < radius) + { + fx = radius; + fy = radius; + } + // Top-right border check + if (!getWalk(tx + 1, ty - 1, walkMask) + && (fy < radius) && fx > (32 - radius)) + { + fx = 32 - radius; + fy = radius; + } + // Bottom-left border check + if (!getWalk(tx - 1, ty + 1, walkMask) + && fy > (32 - radius) && fx < radius) + { + fx = radius; + fy = 32 - radius; + } + // Bottom-right border check + if (!getWalk(tx + 1, ty + 1, walkMask) + && fy > (32 - radius) && fx > (32 - radius)) + { + fx = 32 - radius; + fy = fx; + } + + // Fix coordinates so that the player does not seem to dig into walls. + if (fx > (32 - radius) && !getWalk(tx + 1, ty, walkMask)) + fx = 32 - radius; + else if (fx < radius && !getWalk(tx - 1, ty, walkMask)) + fx = radius; + else if (fy > (32 - radius) && !getWalk(tx, ty + 1, walkMask)) + fy = 32 - radius; + else if (fy < radius && !getWalk(tx, ty - 1, walkMask)) + fy = radius; + + return Position(tx * 32 + fx, ty * 32 + fy); +} + +Path Map::findPixelPath(int startPixelX, int startPixelY, int endPixelX, + int endPixelY, + int radius, unsigned char walkMask, int maxCost) +{ + Path myPath = findPath(startPixelX / 32, startPixelY / 32, + endPixelX / 32, endPixelY / 32, walkMask, maxCost); + + // Don't compute empty coordinates. + if (myPath.empty()) + return myPath; + + // Find the starting offset + float startOffsetX = static_cast(startPixelX % 32); + float startOffsetY = static_cast(startPixelY % 32); + + // Find the ending offset + float endOffsetX = static_cast(endPixelX % 32); + float endOffsetY = static_cast(endPixelY % 32); + + // Find the distance, and divide it by the number of steps + int changeX = (int)((endOffsetX - startOffsetX) + / static_cast(myPath.size())); + int changeY = (int)((endOffsetY - startOffsetY) + / static_cast(myPath.size())); + + // Convert the map path to pixels over tiles + // And add interpolation between the starting and ending offsets + Path::iterator it = myPath.begin(); + int i = 0; + while (it != myPath.end()) + { + // A position that is valid on the start and end tile is not + // necessarily valid on all the tiles in between, so check the offsets. + *it = checkNodeOffsets(radius, walkMask, + it->x * 32 + startOffsetX + static_cast(changeX * i), + it->y * 32 + startOffsetY + static_cast(changeY * i)); + i++; + it++; + } + + // Remove the last path node, as it's more clever to go to the destination. + // It also permit to avoid zigzag at the end of the path, + // especially with mouse. + Position destination = checkNodeOffsets(radius, walkMask, + endPixelX, endPixelY); + myPath.pop_back(); + myPath.push_back(destination); + + return myPath; +} + +Path Map::findPath(int startX, int startY, int destX, int destY, + unsigned char walkmask, int maxCost) +{ + static int const basicCost = 100; + + // Path to be built up (empty by default) + Path path; + + // Declare open list, a list with open tiles sorted on F cost + std::priority_queue openList; + + // Return when destination not walkable + if (!getWalk(destX, destY, walkmask)) + return path; + + // Reset starting tile's G cost to 0 + MetaTile *startTile = getMetaTile(startX, startY); + startTile->Gcost = 0; + + // Add the start point to the open list + openList.push(Location(startX, startY, startTile)); + + bool foundPath = false; + + // Keep trying new open tiles until no more tiles to try or target found + while (!openList.empty() && !foundPath) + { + // Take the location with the lowest F cost from the open list. + Location curr = openList.top(); + openList.pop(); + + // If the tile is already on the closed list, this means it has already + // been processed with a shorter path to the start point (lower G cost) + if (curr.tile->whichList == mOnClosedList) + continue; + + // Put the current tile on the closed list + curr.tile->whichList = mOnClosedList; + + // Check the adjacent tiles + for (int dy = -1; dy <= 1; dy++) + { + for (int dx = -1; dx <= 1; dx++) + { + // Calculate location of tile to check + const int x = curr.x + dx; + const int y = curr.y + dy; + + // Skip if if we're checking the same tile we're leaving from, + // or if the new location falls outside of the map boundaries + if ((dx == 0 && dy == 0) || !contains(x, y)) + continue; + + MetaTile *newTile = getMetaTile(x, y); + + // Skip if the tile is on the closed list or is not walkable + // unless its the destination tile + if (!newTile || newTile->whichList == mOnClosedList || + ((newTile->blockmask & walkmask) + && !(x == destX && y == destY)) + || (newTile->blockmask & BLOCKMASK_WALL)) + { + continue; + } + + // When taking a diagonal step, verify that we can skip the + // corner. + if (dx != 0 && dy != 0) + { + MetaTile *t1 = getMetaTile(curr.x, curr.y + dy); + MetaTile *t2 = getMetaTile(curr.x + dx, curr.y); + + if (!t1 || !t2 || ((t1->blockmask | t2->blockmask) + & BLOCKMASK_WALL)) + { + continue; + } + } + + // Calculate G cost for this route, ~sqrt(2) for moving diagonal + int Gcost = curr.tile->Gcost + + (dx == 0 || dy == 0 ? basicCost : basicCost * 362 / 256); + + /* Demote an arbitrary direction to speed pathfinding by + adding a defect (TODO: change depending on the desired + visual effect, e.g. a cross-product defect toward + destination). + Important: as long as the total defect along any path is + less than the basicCost, the pathfinder will still find one + of the shortest paths! */ + if (dx == 0 || dy == 0) + { + // Demote horizontal and vertical directions, so that two + // consecutive directions cannot have the same Fcost. + ++Gcost; + } + + // It costs extra to walk through a being (needs to be enough + // to make it more attractive to walk around). +// if (occupied(x, y)) +// { +// Gcost += 3 * basicCost; +// } + + // Skip if Gcost becomes too much + // Warning: probably not entirely accurate + if (maxCost > 0 && Gcost > maxCost * basicCost) + continue; + + if (newTile->whichList != mOnOpenList) + { + // Found a new tile (not on open nor on closed list) + + /* Update Hcost of the new tile. The pathfinder does not + work reliably if the heuristic cost is higher than the + real cost. In particular, using Manhattan distance is + forbidden here. */ + int dx = std::abs(x - destX), dy = std::abs(y - destY); + newTile->Hcost = std::abs(dx - dy) * basicCost + + std::min(dx, dy) * (basicCost * 362 / 256); + + // Set the current tile as the parent of the new tile + newTile->parentX = curr.x; + newTile->parentY = curr.y; + + // Update Gcost and Fcost of new tile + newTile->Gcost = Gcost; + newTile->Fcost = Gcost + newTile->Hcost; + + if (x != destX || y != destY) + { + // Add this tile to the open list + newTile->whichList = mOnOpenList; + openList.push(Location(x, y, newTile)); + } + else + { + // Target location was found + foundPath = true; + } + } + else if (Gcost < newTile->Gcost) + { + // Found a shorter route. + // Update Gcost and Fcost of the new tile + newTile->Gcost = Gcost; + newTile->Fcost = Gcost + newTile->Hcost; + + // Set the current tile as the parent of the new tile + newTile->parentX = curr.x; + newTile->parentY = curr.y; + + // Add this tile to the open list (it's already + // there, but this instance has a lower F score) + openList.push(Location(x, y, newTile)); + } + } + } + } + + // Two new values to indicate whether a tile is on the open or closed list, + // this way we don't have to clear all the values between each pathfinding. + mOnClosedList += 2; + mOnOpenList += 2; + + // If a path has been found, iterate backwards using the parent locations + // to extract it. + if (foundPath) + { + int pathX = destX; + int pathY = destY; + + while (pathX != startX || pathY != startY) + { + // Add the new path node to the start of the path list + path.push_front(Position(pathX, pathY)); + + // Find out the next parent + MetaTile *tile = getMetaTile(pathX, pathY); + pathX = tile->parentX; + pathY = tile->parentY; + } + } + + return path; +} + +void Map::addParticleEffect(const std::string &effectFile, + int x, int y, int w, int h) +{ + ParticleEffectData newEffect; + newEffect.file = effectFile; + newEffect.x = x; + newEffect.y = y; + newEffect.w = w; + newEffect.h = h; + particleEffects.push_back(newEffect); +} + +void Map::initializeParticleEffects(Particle *particleEngine) +{ + if (!particleEngine) + return; + + Particle *p; + + if (config.getBoolValue("particleeffects")) + { + for (std::list::iterator + i = particleEffects.begin(); + i != particleEffects.end(); i++) + { + p = particleEngine->addEffect(i->file, i->x, i->y); + if (p && i->w > 0 && i->h > 0) + p->adjustEmitterSize(i->w, i->h); + } + } +} + +void Map::addExtraLayer() +{ + if (!mSpecialLayer) + { + logger->log1("No special layer"); + return; + } + std::string mapFileName = getUserMapDirectory() + "/extralayer.txt"; + logger->log("try load extra layer: " + mapFileName); + struct stat statbuf; + if (!stat(mapFileName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) + { + std::ifstream mapFile; + mapFile.open(mapFileName.c_str(), std::ios::in); + char line[201]; + std::string buf; + while (mapFile.getline(line, 200)) + { + std::string buf; + std::string str = line; + if (!str.empty()) + { + std::string x; + std::string y; + std::string type1; + std::string comment; + std::stringstream ss(str); + ss >> x; + ss >> y; + ss >> type1; + std::vector tokens; + ss >> comment; + while (ss >> buf) + comment += " " + buf; + + int type = atoi(type1.c_str()); +/* + MapItem *item = new MapItem(atoi(type.c_str()), comment); + int x1 = atoi(x.c_str()); + int y1 = atoi(y.c_str()); + mSpecialLayer->setTile(x1, y1, item); +*/ + if (comment.empty()) + { + if (type < MapItem::ARROW_UP + || type > MapItem::ARROW_RIGHT) + { + comment = "unknown"; + } + } + if (type == MapItem::PORTAL) + { + updatePortalTile(comment, type, atoi(x.c_str()), + atoi(y.c_str()), false); + } + else if (type == MapItem::HOME) + { + updatePortalTile(comment, type, atoi(x.c_str()), + atoi(y.c_str())); + } + else + { + addPortalTile(comment, type, atoi(x.c_str()), + atoi(y.c_str())); + } + } + } + mapFile.close(); + } +} + +void Map::saveExtraLayer() +{ + if (!mSpecialLayer) + { + logger->log1("No special layer"); + return; + } + std::string mapFileName = getUserMapDirectory() + "/extralayer.txt"; + logger->log("try save extra layer: " + mapFileName); + + if (mkdir_r(getUserMapDirectory().c_str())) + { + logger->log(strprintf("%s doesn't exist and can't be created! " + "Exiting.", getUserMapDirectory().c_str())); + return; + } + + std::ofstream mapFile; + mapFile.open(mapFileName.c_str(), std::ios::binary); + if (!mapFile.is_open()) + { + logger->log1("Unable to open extralayer.txt for writing"); + return; + } + + int width = mSpecialLayer->mWidth; + int height = mSpecialLayer->mHeight; + + for (int x = 0; x < width; x ++) + { + for (int y = 0; y < height; y ++) + { + MapItem *item = mSpecialLayer->getTile(x, y); + if (item && item->mType != MapItem::EMPTY + && item->mType != MapItem::HOME) + { + mapFile << x << " " << y << " " << (int)item->mType + << " " << item->mComment << std::endl; + } + } + } + mapFile.close(); +} + +std::string Map::getUserMapDirectory() const +{ + return Client::getServerConfigDirectory() + "/" + getProperty("_filename"); +} + +void Map::addPortal(const std::string &name, int type, + int x, int y, int dx, int dy) +{ + addPortalTile(name, type, (x / 32) + (dx / 64), (y / 32) + (dy / 64)); +} + +void Map::addPortalTile(const std::string &name, int type, int x, int y) +{ + MapItem *item = new MapItem(type, name, x, y); + if (mSpecialLayer) + mSpecialLayer->setTile(x, y, item); + + item = new MapItem(type, name, x, y); + mMapPortals.push_back(item); +} + +void Map::updatePortalTile(const std::string &name, int type, + int x, int y, bool addNew) +{ + MapItem *item = findPortalXY(x, y); + if (item) + { + item->mComment = name; + item->setType(type); + item->mX = x; + item->mY = y; + if (mSpecialLayer) + { + item = new MapItem(type, name, x, y); + mSpecialLayer->setTile(x, y, item); + } + } + else if (addNew) + { + addPortalTile(name, type, x, y); + } +} + +MapItem *Map::findPortalXY(int x, int y) +{ + std::list::iterator it; + std::list::iterator it_end; + + for (it = mMapPortals.begin(), it_end = mMapPortals.end(); + it != it_end; it++) + { + MapItem *item = *it; + if (item->mX == x && item->mY == y) + return item; + } + return 0; +} + +TileAnimation *Map::getAnimationForGid(int gid) const +{ + std::map::const_iterator + i = mTileAnimations.find(gid); + return (i == mTileAnimations.end()) ? NULL : i->second; +} + +void Map::setPvpMode(int mode) +{ + int oldMode = mPvp; + + if (!mode) + mPvp = 0; + else + mPvp |= mode; + + if (mPvp != oldMode && player_node) + { + switch (mPvp) + { + case 0: + player_node->setSpeech("pvp off, gvg off", SPEECH_TIME); + break; + case 1: + player_node->setSpeech("pvp on", SPEECH_TIME); + break; + case 2: + player_node->setSpeech("gvg on", SPEECH_TIME); + break; + case 3: + player_node->setSpeech("pvp on, gvg on", SPEECH_TIME); + break; + default: + player_node->setSpeech("unknown pvp", SPEECH_TIME); + break; + } + } +} + +SpecialLayer::SpecialLayer(int width, int height, bool drawSprites): + mWidth(width), mHeight(height) +{ + const int size = mWidth * mHeight; + mTiles = new MapItem*[size]; + std::fill_n(mTiles, size, (MapItem*) 0); + mDrawSprites = drawSprites; +} + +SpecialLayer::~SpecialLayer() +{ + for (int f = 0; f < mWidth * mHeight; f ++) + { + delete mTiles[f]; + mTiles[f] = 0; + } + delete[] mTiles; +} + +MapItem* SpecialLayer::getTile(int x, int y) const +{ + if (x < 0 || x >= mWidth || + y < 0 || y >= mHeight) + { + return 0; + } + return mTiles[x + y * mWidth]; +} + +void SpecialLayer::setTile(int x, int y, MapItem *item) +{ + if (x < 0 || x >= mWidth || + y < 0 || y >= mHeight) + { + return; + } + + int idx = x + y * mWidth; + delete mTiles[idx]; + if (item) + item->setPos(x, y); + mTiles[idx] = item; +} + +void SpecialLayer::setTile(int x, int y, int type) +{ + if (x < 0 || x >= mWidth || + y < 0 || y >= mHeight) + { + return; + } + + int idx = x + y * mWidth; + if (mTiles[idx]) + { + mTiles[idx]->setType(type); + } + else + { + delete mTiles[idx]; + mTiles[idx] = new MapItem(type); + } + mTiles[idx]->setPos(x, y); +} + +void SpecialLayer::addRoad(Path road) +{ + for (Path::const_iterator i = road.begin(), i_end = road.end(); + i != i_end; ++i) + { + Position pos = (*i); + MapItem *item = getTile(pos.x, pos.y); + if (!item) + { + item = new MapItem(MapItem::ROAD); + setTile(pos.x, pos.y, item); + } + else + { + item->setType(MapItem::ROAD); + } + } +} + +void SpecialLayer::clean() +{ + for (int f = 0; f < mWidth * mHeight; f ++) + { + MapItem *item = mTiles[f]; + if (item) + item->setType(MapItem::EMPTY); + } +} + +void SpecialLayer::draw(Graphics *graphics, int startX, int startY, + int endX, int endY, int scrollX, int scrollY) +{ + if (startX < 0) + startX = 0; + if (startY < 0) + startY = 0; + if (endX > mWidth) + endX = mWidth; + if (endY > mHeight) + endY = mHeight; + +// MapSprites::const_iterator si = sprites.begin(); + + for (int y = startY; y < endY; y++) + { + for (int x = startX; x < endX; x++) + itemDraw(graphics, x, y, scrollX, scrollY); + } +} + +void SpecialLayer::itemDraw(Graphics *graphics, int x, int y, + int scrollX, int scrollY) +{ + MapItem *item = getTile(x, y); + if (item) + { + const int px = x * 32 - scrollX; + const int py = y * 32 - scrollY; + item->draw(graphics, px, py, 32, 32); + } +} + + +MapItem::MapItem(): + mImage(0), mComment(""), mName(""), mX(-1), mY(-1) +{ + setType(EMPTY); +} + +MapItem::MapItem(int type): + mImage(0), mComment(""), mName(""), mX(-1), mY(-1) +{ + setType(type); +} + +MapItem::MapItem(int type, std::string comment): + mImage(0), mComment(comment), mName(""), mX(-1), mY(-1) +{ + setType(type); +} + +MapItem::MapItem(int type, std::string comment, int x, int y): + mImage(0), mComment(comment), mName(""), mX(x), mY(y) +{ + setType(type); +} + +MapItem::~MapItem() +{ + if (mImage) + mImage->decRef(); +} + +void MapItem::setType(int type) +{ + std::string name = ""; + mType = type; + if (mImage) + mImage->decRef(); + + switch (type) + { + case ARROW_UP: + name = "graphics/sprites/arrow_up.gif"; + break; + case ARROW_DOWN: + name = "graphics/sprites/arrow_down.gif"; + break; + case ARROW_LEFT: + name = "graphics/sprites/arrow_left.gif"; + break; + case ARROW_RIGHT: + name = "graphics/sprites/arrow_right.gif"; + break; + default: + break; + } + + if (name != "") + { + ResourceManager *resman = ResourceManager::getInstance(); + mImage = resman->getImage(name); + } + else + { + mImage = 0; + } +} + +void MapItem::setPos(int x, int y) +{ + mX = x; + mY = y; +} + +void MapItem::draw(Graphics *graphics, int x, int y, int dx, int dy) +{ + if (mImage) + graphics->drawImage(mImage, x, y); + + switch(mType) + { + case ROAD: + case CROSS: + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::ROAD_POINT)); + graphics->fillRectangle(gcn::Rectangle(x + dx / 3, y + dy / 3, + dx / 3, dy / 3)); + break; + case HOME: + { + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::HOME_PLACE)); + graphics->fillRectangle(gcn::Rectangle( + x, y, + dx, dy)); + graphics->setColor(userPalette->getColorWithAlpha( + UserPalette::HOME_PLACE_BORDER)); + graphics->drawRectangle(gcn::Rectangle( + x, y, + dx, dy)); + break; + } + default: + break; + } + if (!mName.empty() && mType != PORTAL && mType != EMPTY) + { + gcn::Font *font = gui->getFont(); + if (font) + { + graphics->setColor(userPalette->getColor(UserPalette::BEING)); + font->drawString(graphics, mName, x, y); + } + } +} + diff --git a/src/map.h b/src/map.h new file mode 100644 index 000000000..a51ecab6c --- /dev/null +++ b/src/map.h @@ -0,0 +1,611 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MAP_H +#define MAP_H + +#include "actor.h" +#include "configlistener.h" +#include "position.h" +#include "properties.h" + +#include +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Animation; +class AmbientLayer; +class Graphics; +class MapLayer; +class Particle; +class SimpleAnimation; +class Tileset; +class SpecialLayer; +class MapItem; + +typedef std::vector Tilesets; +typedef std::vector Layers; + +/** + * A meta tile stores additional information about a location on a tile map. + * This is information that doesn't need to be repeated for each tile in each + * layer of the map. + */ +struct MetaTile +{ + /** + * Constructor. + */ + MetaTile() : Fcost(0), Gcost(0), Hcost(0), whichList(0), + parentX(0), parentY(0), blockmask(0) + {} + + // Pathfinding members + int Fcost; /**< Estimation of total path cost */ + int Gcost; /**< Cost from start to this location */ + int Hcost; /**< Estimated cost to goal */ + int whichList; /**< No list, open list or closed list */ + int parentX; /**< X coordinate of parent tile */ + int parentY; /**< Y coordinate of parent tile */ + unsigned char blockmask; /**< Blocking properties of this tile */ +}; + +/* +struct MapBox +{ + MapBox() : name(""), x(0), y(0) + { + } + + MapBox(std::string name1, int x1, int y1): + name(name1), x(x1), y(y1) + { + } + + std::string name; + int x; + int y; +}; +*/ + +/** + * Animation cycle of a tile image which changes the map accordingly. + */ +class TileAnimation +{ + public: + TileAnimation(Animation *ani); + ~TileAnimation(); + void update(int ticks = 1); + void addAffectedTile(MapLayer *layer, int index) + { mAffected.push_back(std::make_pair(layer, index)); } + private: + std::list > mAffected; + SimpleAnimation *mAnimation; + Image *mLastImage; +}; + +/** + * A map layer. Stores a grid of tiles and their offset, and implements layer + * rendering. + */ +class MapLayer: public ConfigListener +{ + public: + /** + * Constructor, taking layer origin, size and whether this layer is the + * fringe layer. The fringe layer is the layer that draws the actors. + * There can be only one fringe layer per map. + */ + MapLayer(int x, int y, int width, int height, bool isFringeLayer); + + /** + * Destructor. + */ + ~MapLayer(); + + /** + * Set tile image, with x and y in layer coordinates. + */ + void setTile(int x, int y, Image *img); + + /** + * Set tile image with x + y * width already known. + */ + void setTile(int index, Image *img) { mTiles[index] = img; } + + /** + * Get tile image, with x and y in layer coordinates. + */ + Image *getTile(int x, int y) const; + + /** + * Draws this layer to the given graphics context. The coordinates are + * expected to be in map range and will be translated to local layer + * coordinates and clipped to the layer's dimensions. + * + * The given actors are only drawn when this layer is the fringe + * layer. + */ + void draw(Graphics *graphics, + int startX, int startY, + int endX, int endY, + int scrollX, int scrollY, + const Actors &actors, + int mDebugFlags) const; + + bool isFringeLayer() + { return mIsFringeLayer; } + + void setSpecialLayer(SpecialLayer *val) + { mSpecialLayer = val; } + + void setTempLayer(SpecialLayer *val) + { mTempLayer = val; } + + int getWidth() + { return mWidth; } + + int getHeight() + { return mHeight; } + +// void setTileInfo(int x, int y, int width, int cnt); + +// void getTileInfo(int x, int y, int &width, int &cnt) const; + + void optionChanged(const std::string &value); + + int getTileDrawWidth(int tilePtr, int endX, int &width) const; + +// void initTileInfo(); + + private: + int mX, mY; + int mWidth, mHeight; + bool mIsFringeLayer; /**< Whether the actors are drawn. */ + bool mHighlightAttackRange; + Image **mTiles; +// int *mTilesWidth; +// int *mTilesCount; + SpecialLayer *mSpecialLayer; + SpecialLayer *mTempLayer; +}; + +/** + * A tile map. + */ +class Map : public Properties, public ConfigListener +{ + public: + enum BlockType + { + BLOCKTYPE_NONE = -1, + BLOCKTYPE_WALL, + BLOCKTYPE_CHARACTER, + BLOCKTYPE_MONSTER, + NB_BLOCKTYPES + }; + + enum BlockMask + { + BLOCKMASK_WALL = 0x80, // = bin 1000 0000 + BLOCKMASK_CHARACTER = 0x01, // = bin 0000 0001 + BLOCKMASK_MONSTER = 0x02 // = bin 0000 0010 + }; + + enum DebugType + { + MAP_NORMAL = 0, + MAP_DEBUG = 1, + MAP_SPECIAL = 2, + MAP_SPECIAL2 = 3, + MAP_SPECIAL3 = 4, + MAP_BLACKWHITE = 5 + }; + + /** + * Constructor, taking map and tile size as parameters. + */ + Map(int width, int height, int tileWidth, int tileHeight); + + /** + * Destructor. + */ + ~Map(); + + /** + * Initialize ambient layers. Has to be called after all the properties + * are set. + */ + void initializeAmbientLayers(); + + /** + * Updates animations. Called as needed. + */ + void update(int ticks = 1); + + /** + * Draws the map to the given graphics output. This method draws all + * layers, actors and overlay effects. + * + * TODO: For efficiency reasons, this method could take into account + * the clipping rectangle set on the Graphics object. However, + * currently the map is always drawn full-screen. + */ + void draw(Graphics *graphics, int scrollX, int scrollY); + + /** + * Visualizes collision layer for debugging + */ + void drawCollision(Graphics *graphics, int scrollX, int scrollY, + int debugFlags); + + /** + * Adds a layer to this map. The map takes ownership of the layer. + */ + void addLayer(MapLayer *layer); + + /** + * Adds a tileset to this map. The map takes ownership of the tileset. + */ + void addTileset(Tileset *tileset); + + /** + * Finds the tile set that a tile with the given global id is part of. + */ + Tileset *getTilesetWithGid(int gid) const; + + /** + * Get tile reference. + */ + MetaTile *getMetaTile(int x, int y) const; + + /** + * Marks a tile as occupied. + */ + void blockTile(int x, int y, BlockType type); + + /** + * Gets walkability for a tile with a blocking bitmask. When called + * without walkmask, only blocks against colliding tiles. + */ + bool getWalk(int x, int y, + unsigned char walkmask = BLOCKMASK_WALL) const; + + /** + * Tells whether a tile is occupied by a being. + */ + bool occupied(int x, int y) const; + + /** + * Returns the width of this map in tiles. + */ + int getWidth() const { return mWidth; } + + /** + * Returns the height of this map in tiles. + */ + int getHeight() const { return mHeight; } + + /** + * Returns the tile width of this map. + */ + int getTileWidth() const + { return mTileWidth; } + + /** + * Returns the tile height used by this map. + */ + int getTileHeight() const + { return mTileHeight; } + + const std::string getMusicFile() const; + const std::string getName() const; + + /** + * Gives the map id based on filepath (ex: 009-1) + */ + const std::string getFilename() const; + + /** + * Check the current position against surrounding blocking tiles, and + * correct the position offset within tile when needed. + */ + Position checkNodeOffsets(int radius, unsigned char walkMask, + const Position &position) const; + + Position checkNodeOffsets(int radius, unsigned char walkMask, + int x, int y) const + { return checkNodeOffsets(radius, walkMask, Position(x, y)); } + + /** + * Find a pixel path from one location to the next. + */ + Path findPixelPath(int startPixelX, int startPixelY, + int destPixelX, int destPixelY, + int radius, unsigned char walkmask, + int maxCost = 20); + + /** + * Find a path from one location to the next. + */ + Path findPath(int startX, int startY, int destX, int destY, + unsigned char walkmask, int maxCost = 20); + + /** + * Adds a particle effect + */ + void addParticleEffect(const std::string &effectFile, + int x, int y, int w = 0, int h = 0); + + /** + * Initializes all added particle effects + */ + void initializeParticleEffects(Particle* particleEngine); + + /** + * Adds a tile animation to the map + */ + void addAnimation(int gid, TileAnimation *animation) + { mTileAnimations[gid] = animation; } + + void setDebugFlags(int n) + { mDebugFlags = n; } + + int getDebugFlags() const + { return mDebugFlags; } + + void addExtraLayer(); + + void saveExtraLayer(); + + SpecialLayer *getTempLayer() + { return mTempLayer; } + + SpecialLayer *getSpecialLayer() + { return mSpecialLayer; } + + void setHasWarps(bool n) + { mHasWarps = n; } + + bool getHasWarps() + { return mHasWarps; } + + std::string getUserMapDirectory() const; + + void addPortal(const std::string &name, int type, + int x, int y, int dx, int dy); + + void addPortalTile(const std::string &name, int type, int x, int y); + + void updatePortalTile(const std::string &name, int type, + int x, int y, bool addNew = true); + + std::list &getPortals() + { return mMapPortals; } + + /** + * Gets the tile animation for a specific gid + */ + TileAnimation *getAnimationForGid(int gid) const; + + void optionChanged(const std::string &value); + + MapItem *findPortalXY(int x, int y); + + int getActorsCount() const + { return mActors.size(); } + + void setPvpMode(int mode); + + protected: + friend class Actor; + + /** + * Adds an actor to the map. + */ + Actors::iterator addActor(Actor *actor); + + /** + * Removes an actor from the map. + */ + void removeActor(Actors::iterator iterator); + + private: + + enum LayerType + { + FOREGROUND_LAYERS = 0, + BACKGROUND_LAYERS + }; + + /** + * Updates scrolling of ambient layers. Has to be called each game tick. + */ + void updateAmbientLayers(float scrollX, float scrollY); + + /** + * Draws the foreground or background layers to the given graphics output. + */ + void drawAmbientLayers(Graphics *graphics, LayerType type, + int detail); + + /** + * Tells whether the given coordinates fall within the map boundaries. + */ + bool contains(int x, int y) const; + + /** + * Blockmasks for different entities + */ + int *mOccupation[NB_BLOCKTYPES]; + + int mWidth, mHeight; + int mTileWidth, mTileHeight; + int mMaxTileHeight; + MetaTile *mMetaTiles; + Layers mLayers; + Tilesets mTilesets; + Actors mActors; + bool mHasWarps; + + // debug flags + int mDebugFlags; + + // Pathfinding members + int mOnClosedList, mOnOpenList; + + // Overlay data + std::list mBackgrounds; + std::list mForegrounds; + float mLastScrollX; + float mLastScrollY; +// bool mSpritesUpdated; + + // Particle effect data + struct ParticleEffectData + { + std::string file; + int x; + int y; + int w; + int h; + }; + std::list particleEffects; + + std::list mMapPortals; + + std::map mTileAnimations; + + int mOverlayDetail; + float mOpacity; + int mOpenGL; + int mPvp; + + SpecialLayer *mSpecialLayer; + SpecialLayer *mTempLayer; +}; + + +class SpecialLayer +{ + public: + friend class Map; + friend class MapLayer; + + SpecialLayer(int width, int height, bool drawSprites = false); + + ~SpecialLayer(); + + void draw(Graphics *graphics, int startX, int startY, + int endX, int endY, int scrollX, int scrollY); + + MapItem* getTile(int x, int y) const; + + void setTile(int x, int y, MapItem* item); + + void setTile(int x, int y, int type); + + void addRoad(Path road); + + void clean(); + + void itemDraw(Graphics *graphics, int x, int y, + int scrollX, int scrollY); + + private: + int mWidth, mHeight; + bool mDrawSprites; + MapItem **mTiles; +}; + +class MapItem +{ + public: + friend class Map; + friend class MapLayer; + + enum ItemType + { + EMPTY = 0, + HOME = 1, + ROAD = 2, + CROSS = 3, + ARROW_UP = 4, + ARROW_DOWN = 5, + ARROW_LEFT = 6, + ARROW_RIGHT = 7, + PORTAL = 8 + }; + + MapItem(); + + MapItem(int type); + + MapItem(int type, std::string comment); + + MapItem(int type, std::string comment, int x, int y); + + ~MapItem(); + + int getType() const + { return mType; } + + void setType(int type); + + void setPos(int x, int y); + + int getX() const + { return mX; } + + int getY() const + { return mY; } + + std::string &getComment() + { return mComment; } + + void setComment(std::string comment) + { mComment = comment; } + + std::string &getName() + { return mName; } + + void setName(std::string name) + { mName = name; } + + void draw(Graphics *graphics, int x, int y, int dx, int dy); + + private: + int mType; + Image *mImage; + std::string mComment; + std::string mName; + int mX; + int mY; +}; + +#endif diff --git a/src/mumblemanager.cpp b/src/mumblemanager.cpp new file mode 100644 index 000000000..6586f9546 --- /dev/null +++ b/src/mumblemanager.cpp @@ -0,0 +1,273 @@ +/* Code taken from: http://mumble.sourceforge.net/Link */ + +#include "mumblemanager.h" + +#include "configuration.h" +#include "log.h" + +#include "utils/mathutils.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifndef WIN32 +#include +#endif + +MumbleManager::MumbleManager() : + mLinkedMem(0) +{ + mMapBase[0] = mMapBase[1] = mMapBase[2] = 0.; + init(); +} + +MumbleManager::~MumbleManager() +{ + if (mLinkedMem) + { +#ifdef WIN32 + UnmapViewOfFile(mLinkedMem); +#elif defined __FreeBSD__ || defined __DragonFly__ +#else + munmap(mLinkedMem, sizeof(struct LinkedMem)); +#endif + mLinkedMem = 0; + } +} + +uint16_t MumbleManager::getMapId(std::string mapName) +{ + uint16_t res = 0; + if (mapName.size() != 5 || mapName[3] != '-') + { + res = getCrc16(mapName); + } + else + { + mapName = mapName.substr(0, 3) + mapName[4]; + res = atoi(mapName.c_str()); + } + return res; +} + +void MumbleManager::setMapBase(uint16_t mapid) +{ + mMapBase[0] = 10000. * (mapid & 0x1f); + mapid >>= 5; + mMapBase[1] = 1000. * (mapid & 0x3f); + mapid >>= 6; + mMapBase[2] = 10000. * (mapid & 0x1f); +} + +void MumbleManager::init() +{ +#if defined __FreeBSD__ || defined __DragonFly__ + return; +#endif + + if (mLinkedMem || !config.getBoolValue("enableMumble")) + return; + + logger->log1("MumbleManager::init"); +#ifdef WIN32 + HANDLE hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, + FALSE, L"MumbleLink"); + if (hMapObject == NULL) + { + logger->log1("MumbleManager::init cant open MumbleLink"); + return; + } + + mLinkedMem = (LinkedMem *) MapViewOfFile(hMapObject, + FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem)); + + if (mLinkedMem == NULL) + { + CloseHandle(hMapObject); + hMapObject = NULL; + logger->log1("MumbleManager::init cant map MumbleLink"); + return; + } +#elif defined __FreeBSD__ || defined __DragonFly__ +#else + char memName[256]; + snprintf(memName, 256, "/MumbleLink.%d", getuid()); + + int shmfd = shm_open(memName, O_RDWR, S_IRUSR | S_IWUSR); + + if (shmfd < 0) + { + logger->log1("MumbleManager::init cant open shared memory MumbleLink"); + return; + } + + mLinkedMem = (LinkedMem *)(mmap(NULL, sizeof(struct LinkedMem), + PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0)); + + if (mLinkedMem == (void *)(-1)) + { + mLinkedMem = NULL; + logger->log1("MumbleManager::init cant map MumbleLink"); + return; + } + +#endif + wcsncpy(mLinkedMemCache.name, L"Mana", 256); + wcsncpy(mLinkedMemCache.description, L"TheManaWorld Plugin", 2048); + mLinkedMemCache.uiVersion = 2; + + // Left handed coordinate system. + // X positive towards "left". + // Y positive towards "up". + // Z positive towards "into screen". + // + // 1 unit = 1 meter + + // Unit vector pointing out of the avatars eyes (here Front looks into scene). + /* no way to look "up", 2d */ + mLinkedMemCache.fAvatarFront[1] = 0.0f; + + // Unit vector pointing out of the top of the avatars head + // (here Top looks straight up). + /* no way to change this in tmw */ + mLinkedMemCache.fAvatarTop[0] = 0.0f; + mLinkedMemCache.fAvatarTop[1] = 1.0f; + mLinkedMemCache.fAvatarTop[2] = 0.0f; + + mLinkedMemCache.fCameraFront[0] = 0.0f; + mLinkedMemCache.fCameraFront[1] = 0.0f; + mLinkedMemCache.fCameraFront[2] = 1.0f; + + mLinkedMemCache.fCameraTop[0] = 0.0f; + mLinkedMemCache.fCameraTop[1] = 1.0f; + mLinkedMemCache.fCameraTop[2] = 0.0f; + + mLinkedMemCache.uiTick++; +} + +void MumbleManager::setPlayer(const std::string &userName) +{ + if (!mLinkedMem) + return; + + // Identifier which uniquely identifies a certain player in a context + // (e.g. the ingame Name). + mbstowcs(mLinkedMemCache.identity, userName.c_str(), 256); + mLinkedMemCache.uiTick ++; + memcpy(mLinkedMem, &mLinkedMemCache, sizeof(mLinkedMemCache)); +} + +void MumbleManager::setAction(int action) +{ + if (!mLinkedMem) + return; + + switch(action) + { + case 0: /* STAND */ + case 1: /* WALK */ + case 2: /* ATTACK */ + case 5: /* HURT */ + mLinkedMemCache.fAvatarPosition[1] = 1.5f; + break; + case 3: /* SIT */ + mLinkedMemCache.fAvatarPosition[1] = 1.0f; + break; + case 4: /* DEAD */ + default: + mLinkedMemCache.fAvatarPosition[1] = 0.0f; + break; + } + mLinkedMemCache.fAvatarPosition[1] += mMapBase[1]; + mLinkedMemCache.fCameraPosition[1] = mLinkedMemCache.fAvatarPosition[1]; + + mLinkedMemCache.uiTick++; + memcpy(mLinkedMem, &mLinkedMemCache, sizeof(mLinkedMemCache)); +} + +void MumbleManager::setPos(int tileX, int tileY, int direction) +{ + if (!mLinkedMem) + return; + + // Position of the avatar (here standing slightly off the origin) + // lm->fAvatarPosition + + /* tmw coordinates work exactly the other way round */ + mLinkedMemCache.fAvatarPosition[0] = tileX + mMapBase[0]; + mLinkedMemCache.fAvatarPosition[2] = tileY + mMapBase[2]; + + // Same as avatar but for the camera. + // lm->fCameraPosition, fCameraFront, fCameraTop + + // Same as avatar but for the camera. + mLinkedMemCache.fCameraPosition[0] = mLinkedMemCache.fAvatarPosition[0]; + mLinkedMemCache.fCameraPosition[2] = mLinkedMemCache.fAvatarPosition[2]; + + // Unit vector pointing out of the avatars eyes + // (here Front looks into scene). + switch(direction) + { + case 4: /* UP */ + mLinkedMemCache.fAvatarFront[0] = 0.0f; + mLinkedMemCache.fAvatarFront[2] = 1.0f; + break; + case 1: /* DOWN */ + mLinkedMemCache.fAvatarFront[0] = 0.0f; + mLinkedMemCache.fAvatarFront[2] = -1.0f; + break; + case 2: /* LEFT */ + mLinkedMemCache.fAvatarFront[0] = 1.0f; + mLinkedMemCache.fAvatarFront[2] = 0.0f; + break; + case 8: /* RIGHT */ + mLinkedMemCache.fAvatarFront[0] = -1.0f; + mLinkedMemCache.fAvatarFront[2] = 0.0f; + break; + default: + break; + } + + mLinkedMemCache.uiTick ++; + memcpy(mLinkedMem, &mLinkedMemCache, sizeof(mLinkedMemCache)); +} + +void MumbleManager::setMap(const std::string &mapName) +{ + if (!mLinkedMem) + return; + + // Context should be equal for players which should be able to hear each + // other positional and differ for those who shouldn't + // (e.g. it could contain the server+port and team) + + setMapBase(getMapId(mapName)); + setAction(0); /* update y coordinate */ +} + +void MumbleManager::setServer(const std::string &serverName) +{ + if (!mLinkedMem) + return; + + unsigned size = serverName.size(); + if (size > sizeof(mLinkedMemCache.context) - 1) + size = sizeof(mLinkedMemCache.context) - 1; + + memset(mLinkedMemCache.context, 0, sizeof(mLinkedMemCache.context)); + memcpy(mLinkedMemCache.context, serverName.c_str(), size); + mLinkedMemCache.context[size] = '\0'; + mLinkedMemCache.context_len = size; + mLinkedMemCache.uiTick ++; + memcpy(mLinkedMem, &mLinkedMemCache, sizeof(mLinkedMemCache)); +} diff --git a/src/mumblemanager.h b/src/mumblemanager.h new file mode 100644 index 000000000..a3eddee02 --- /dev/null +++ b/src/mumblemanager.h @@ -0,0 +1,57 @@ +/* Code taken from: http://mumble.sourceforge.net/Link */ + +#ifndef MUMBLEMANAGER_H +#define MUMBLEMANAGER_H + +#include +#include + +struct LinkedMem +{ + uint32_t uiVersion; + uint32_t uiTick; + float fAvatarPosition[3]; + float fAvatarFront[3]; + float fAvatarTop[3]; + wchar_t name[256]; + float fCameraPosition[3]; + float fCameraFront[3]; + float fCameraTop[3]; + wchar_t identity[256]; + uint32_t context_len; + char context[256]; + wchar_t description[2048]; +}; + +class MumbleManager +{ + public: + MumbleManager(); + + ~MumbleManager(); + + void init(); + + void setPlayer(const std::string &userName); + + void setAction(int action); + + void setPos(int tileX, int tileY, int direction); + + void setMap(const std::string &mapName); + + void setServer(const std::string &serverName); + + private: + uint16_t getMapId(std::string mapName); + + void setMapBase(uint16_t mapid); + + LinkedMem *mLinkedMem; + LinkedMem mLinkedMemCache; + float mMapBase[3]; +}; + +extern MumbleManager *mumbleManager; + +#endif diff --git a/src/net/adminhandler.h b/src/net/adminhandler.h new file mode 100644 index 000000000..7c24554c9 --- /dev/null +++ b/src/net/adminhandler.h @@ -0,0 +1,60 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ADMINHANDLER_H +#define ADMINHANDLER_H + +#include + +namespace Net +{ + +class AdminHandler +{ + public: + virtual ~AdminHandler() {} + + virtual void announce(const std::string &text) = 0; + + virtual void localAnnounce(const std::string &text) = 0; + + virtual void hide(bool hide) = 0; + + virtual void kick(int playerId) = 0; + + virtual void kick(const std::string &name) = 0; + + virtual void ban(int playerId) = 0; + + virtual void ban(const std::string &name) = 0; + + virtual void unban(int playerId) = 0; + + virtual void unban(const std::string &name) = 0; + + virtual void mute(int playerId, int type, int limit) = 0; + + // TODO +}; + +} // namespace Net + +#endif // ADMINHANDLER_H diff --git a/src/net/beinghandler.h b/src/net/beinghandler.h new file mode 100644 index 000000000..89f972e6d --- /dev/null +++ b/src/net/beinghandler.h @@ -0,0 +1,43 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef BEINGHANDLER_H +#define BEINGHANDLER_H + +#include "being.h" +#include "net/messagein.h" + +namespace Net +{ + +class BeingHandler +{ + public: + virtual void handleMessage(Net::MessageIn &msg) = 0; + + virtual void requestNameById(int id) = 0; + + virtual void undress(Being *being) = 0; +}; + +} // namespace Net + +#endif // BEINGHANDLER_H diff --git a/src/net/buysellhandler.h b/src/net/buysellhandler.h new file mode 100644 index 000000000..88851e94e --- /dev/null +++ b/src/net/buysellhandler.h @@ -0,0 +1,47 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BUYSELLHANDLER_H +#define BUYSELLHANDLER_H + +#include "net/messagein.h" + +#include "being.h" +#include "shopitem.h" + +namespace Net +{ + +class BuySellHandler +{ + public: + virtual void handleMessage(Net::MessageIn &msg) = 0; + virtual void requestSellList(std::string nick) = 0; + virtual void requestBuyList(std::string nick) = 0; + virtual void sendBuyRequest(std::string nick, ShopItem* item, + int amount) = 0; + virtual void sendSellRequest(std::string nick, ShopItem* item, + int amount) = 0; +}; + +} // namespace Net + +#endif // BUYSELLHANDLER_H diff --git a/src/net/charhandler.cpp b/src/net/charhandler.cpp new file mode 100644 index 000000000..699d930a2 --- /dev/null +++ b/src/net/charhandler.cpp @@ -0,0 +1,37 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/charhandler.h" + +#include "gui/charselectdialog.h" + +using namespace Net; + +void CharHandler::updateCharSelectDialog() +{ + if (mCharSelectDialog) + mCharSelectDialog->setCharacters(mCharacters); +} + +void CharHandler::unlockCharSelectDialog() +{ + if (mCharSelectDialog) + mCharSelectDialog->unlock(); +} diff --git a/src/net/charhandler.h b/src/net/charhandler.h new file mode 100644 index 000000000..005d995c8 --- /dev/null +++ b/src/net/charhandler.h @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHARHANDLER_H +#define CHARHANDLER_H + +#include "localplayer.h" +#include "logindata.h" +#include "playerinfo.h" + +#include +#include + +class CharCreateDialog; +class CharSelectDialog; + +namespace Net +{ + +/** + * A structure to hold information about a character. + */ +struct Character +{ + Character() : + slot(0), + dummy(0) + { + } + + ~Character() + { + delete dummy; + dummy = 0; + } + + int slot; /**< The index in the list of characters */ + LocalPlayer *dummy; /**< A dummy representing this character */ + PlayerInfoBackend data; +}; + +typedef std::list Characters; + +class CharHandler +{ + public: + virtual ~CharHandler() + { } + + virtual void setCharSelectDialog(CharSelectDialog *window) = 0; + + virtual void setCharCreateDialog(CharCreateDialog *window) = 0; + + virtual void requestCharacters() = 0; + + virtual void chooseCharacter(Net::Character *character) = 0; + + virtual void newCharacter(const std::string &name, int slot, + bool gender, int hairstyle, int hairColor, + const std::vector &stats) = 0; + + virtual void deleteCharacter(Net::Character *character) = 0; + + virtual void switchCharacter() = 0; + + virtual unsigned int baseSprite() const = 0; + + virtual unsigned int hairSprite() const = 0; + + virtual unsigned int maxSprite() const = 0; + + protected: + CharHandler(): + mSelectedCharacter(0), + mCharSelectDialog(0), + mCharCreateDialog(0) + {} + + void updateCharSelectDialog(); + void unlockCharSelectDialog(); + + /** The list of available characters. */ + Net::Characters mCharacters; + + /** The selected character. */ + Net::Character *mSelectedCharacter; + + CharSelectDialog *mCharSelectDialog; + CharCreateDialog *mCharCreateDialog; +}; + +} // namespace Net + +#endif // CHARHANDLER_H diff --git a/src/net/chathandler.h b/src/net/chathandler.h new file mode 100644 index 000000000..20938c78b --- /dev/null +++ b/src/net/chathandler.h @@ -0,0 +1,71 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CHATHANDLER_H +#define CHATHANDLER_H + +#include + +namespace Net +{ +class ChatHandler +{ + public: + virtual ~ChatHandler() + { } + + virtual void talk(const std::string &text) = 0; + + virtual void talkRaw(const std::string &text) = 0; + + virtual void me(const std::string &text) = 0; + + virtual void privateMessage(const std::string &recipient, + const std::string &text) = 0; + + virtual void channelList() = 0; + + virtual void enterChannel(const std::string &channel, + const std::string &password) = 0; + + virtual void quitChannel(int channelId) = 0; + + virtual void sendToChannel(int channelId, const std::string &text) = 0; + + virtual void userList(const std::string &channel) = 0; + + virtual void setChannelTopic(int channelId, + const std::string &text) = 0; + + virtual void setUserMode(int channelId, const std::string &name, + int mode) = 0; + + virtual void kickUser(int channelId, const std::string &name) = 0; + + virtual void who() = 0; + + virtual void sendRaw(const std::string &args) = 0; + +// virtual ~ChatHandler() {} +}; +} + +#endif // CHATHANDLER_H diff --git a/src/net/download.cpp b/src/net/download.cpp new file mode 100644 index 000000000..2d391b783 --- /dev/null +++ b/src/net/download.cpp @@ -0,0 +1,355 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/download.h" + +#include "configuration.h" +#include "log.h" +#include "main.h" + +#include "utils/stringutils.h" + +#include + +#include +#include + +#include + +const char *DOWNLOAD_ERROR_MESSAGE_THREAD + = "Could not create download thread!"; + +/** + * Calculates the Alder-32 checksum for the given file. + */ +static unsigned long fadler32(FILE *file) +{ + // Obtain file size + fseek(file, 0, SEEK_END); + long fileSize = ftell(file); + rewind(file); + + // Calculate Adler-32 checksum + char *buffer = static_cast(malloc(fileSize)); + const size_t read = fread(buffer, 1, fileSize, file); + unsigned long adler = adler32(0L, Z_NULL, 0); + adler = adler32(static_cast(adler), (Bytef*)buffer, read); + free(buffer); + + return adler; +} + +enum +{ + OPTIONS_NONE = 0, + OPTIONS_MEMORY = 1 +}; + +namespace Net +{ + +Download::Download(void *ptr, const std::string &url, + DownloadUpdate updateFunction, bool ignoreError): + mPtr(ptr), + mUrl(url), + mFileName(""), + mWriteFunction(NULL), + mUpdateFunction(updateFunction), + mThread(NULL), + mCurl(NULL), + mHeaders(NULL), + mIgnoreError(ignoreError) +{ + mError = static_cast(malloc(CURL_ERROR_SIZE + 1)); + mError[0] = 0; + + mOptions.cancel = false; +} + +Download::~Download() +{ + if (mHeaders) + curl_slist_free_all(mHeaders); + + int status; + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, &status); + mThread = 0; + free(mError); +} + +void Download::addHeader(const std::string &header) +{ + mHeaders = curl_slist_append(mHeaders, header.c_str()); +} + +void Download::noCache() +{ + addHeader("pragma: no-cache"); + addHeader("Cache-Control: no-cache"); +} + +void Download::setFile(const std::string &filename, Sint64 adler32) +{ + mOptions.memoryWrite = false; + mFileName = filename; + + if (adler32 > -1) + { + mAdler = static_cast(adler32); + mOptions.checkAdler = true; + } + else + { + mOptions.checkAdler = false; + } +} + +void Download::setWriteFunction(WriteFunction write) +{ + mOptions.memoryWrite = true; + mWriteFunction = write; +} + +bool Download::start() +{ + logger->log("Starting download: %s", mUrl.c_str()); + + mThread = SDL_CreateThread(downloadThread, this); + + if (!mThread) + { + logger->log1(DOWNLOAD_ERROR_MESSAGE_THREAD); + strcpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD); + mUpdateFunction(mPtr, DOWNLOAD_STATUS_THREAD_ERROR, 0, 0); + if (!mIgnoreError) + return false; + } + + return true; +} + +void Download::cancel() +{ + logger->log("Canceling download: %s", mUrl.c_str()); + + mOptions.cancel = true; + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, NULL); + + mThread = NULL; +} + +char *Download::getError() +{ + return mError; +} + +int Download::downloadProgress(void *clientp, double dltotal, double dlnow, + double ultotal _UNUSED_, double ulnow _UNUSED_) +{ + Download *d = reinterpret_cast(clientp); + if (!d) + return -5; + + if (d->mOptions.cancel) + { + return d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_CANCELLED, + static_cast(dltotal), + static_cast(dlnow)); + return -5; + } + + return d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_IDLE, + static_cast(dltotal), + static_cast(dlnow)); +} + +int Download::downloadThread(void *ptr) +{ + int attempts = 0; + bool complete = false; + Download *d = reinterpret_cast(ptr); + CURLcode res; + std::string outFilename; + + if (!d) + return 0; + + if (!d->mOptions.memoryWrite) + outFilename = d->mFileName + ".part"; + + while (attempts < 3 && !complete && !d->mOptions.cancel) + { + FILE *file = NULL; + + d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_STARTING, 0, 0); + + if (d->mOptions.cancel) + { + //need terminate thread? + d->mThread = NULL; + return 0; + } + + d->mCurl = curl_easy_init(); + + if (d->mCurl && !d->mOptions.cancel) + { + logger->log("Downloading: %s", d->mUrl.c_str()); + + curl_easy_setopt(d->mCurl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(d->mCurl, CURLOPT_HTTPHEADER, d->mHeaders); +// curl_easy_setopt(d->mCurl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + + if (d->mOptions.memoryWrite) + { + curl_easy_setopt(d->mCurl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(d->mCurl, CURLOPT_WRITEFUNCTION, + d->mWriteFunction); + curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, d->mPtr); + } + else + { + file = fopen(outFilename.c_str(), "w+b"); + curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, file); + } + + curl_easy_setopt(d->mCurl, CURLOPT_USERAGENT, + strprintf(PACKAGE_EXTENDED_VERSION, + branding.getStringValue("appShort").c_str()).c_str()); + curl_easy_setopt(d->mCurl, CURLOPT_ERRORBUFFER, d->mError); + curl_easy_setopt(d->mCurl, CURLOPT_URL, d->mUrl.c_str()); + curl_easy_setopt(d->mCurl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(d->mCurl, CURLOPT_PROGRESSFUNCTION, + downloadProgress); + curl_easy_setopt(d->mCurl, CURLOPT_PROGRESSDATA, ptr); + curl_easy_setopt(d->mCurl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(d->mCurl, CURLOPT_CONNECTTIMEOUT, 30); + curl_easy_setopt(d->mCurl, CURLOPT_TIMEOUT, 1800); + + if ((res = curl_easy_perform(d->mCurl)) != 0 + && !d->mOptions.cancel) + { + switch (res) + { + case CURLE_ABORTED_BY_CALLBACK: + d->mOptions.cancel = true; + break; + case CURLE_COULDNT_CONNECT: + default: + logger->log("curl error %d: %s host: %s", + res, d->mError, d->mUrl.c_str()); + break; + } + + if (d->mOptions.cancel) + break; + + d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_ERROR, 0, 0); + + if (!d->mOptions.memoryWrite) + { + fclose(file); + ::remove(outFilename.c_str()); + } + attempts++; + continue; + } + + curl_easy_cleanup(d->mCurl); + d->mCurl = 0; + + if (!d->mOptions.memoryWrite) + { + // Don't check resources.xml checksum + if (d->mOptions.checkAdler) + { + unsigned long adler = fadler32(file); + + if (d->mAdler != adler) + { + fclose(file); + + // Remove the corrupted file + ::remove(d->mFileName.c_str()); + logger->log("Checksum for file %s failed: (%lx/%lx)", + d->mFileName.c_str(), + adler, d->mAdler); + attempts++; + continue; // Bail out here to avoid the renaming + } + } + fclose(file); + + // Any existing file with this name is deleted first, otherwise + // the rename will fail on Windows. + ::remove(d->mFileName.c_str()); + ::rename(outFilename.c_str(), d->mFileName.c_str()); + + // Check if we can open it and no errors were encountered + // during renaming + file = fopen(d->mFileName.c_str(), "rb"); + if (file) + { + fclose(file); + complete = true; + } + } + else + { + // It's stored in memory, we're done + complete = true; + } + } + + if (d->mCurl) + { + curl_easy_cleanup(d->mCurl); + d->mCurl = 0; + } + + if (d->mOptions.cancel) + { + //need ternibate thread? + d->mThread = NULL; + return 0; + } + attempts++; + } + + if (d->mOptions.cancel) + { + // Nothing to do... + } + else if (!complete || attempts >= 3) + { + d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_ERROR, 0, 0); + } + else + { + d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_COMPLETE, 0, 0); + } + + d->mThread = NULL; + return 0; +} + +} // namespace Net diff --git a/src/net/download.h b/src/net/download.h new file mode 100644 index 000000000..11bca3238 --- /dev/null +++ b/src/net/download.h @@ -0,0 +1,123 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef NET_DOWNLOAD_H +#define NET_DOWNLOAD_H + +#include + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +enum DownloadStatus +{ + DOWNLOAD_STATUS_CANCELLED = -3, + DOWNLOAD_STATUS_THREAD_ERROR = -2, + DOWNLOAD_STATUS_ERROR = -1, + DOWNLOAD_STATUS_STARTING = 0, + DOWNLOAD_STATUS_IDLE, + DOWNLOAD_STATUS_COMPLETE +}; + +typedef int (*DownloadUpdate)(void *ptr, DownloadStatus status, + size_t total, size_t remaining); + +// Matches what CURL expects +typedef size_t (*WriteFunction)( void *ptr, size_t size, size_t nmemb, + void *stream); + +struct SDL_Thread; +typedef void CURL; +struct curl_slist; + +namespace Net +{ +class Download +{ + public: + Download(void *ptr, const std::string &url, + DownloadUpdate updateFunction, bool ignoreError = false); + + ~Download(); + + void addHeader(const std::string &header); + + /** + * Convience method for adding no-cache headers. + */ + void noCache(); + + void setFile(const std::string &filename, Sint64 adler32 = -1); + + void setWriteFunction(WriteFunction write); + + /** + * Starts the download thread. + * @returns true if thread was created + * false if the thread could not be made or download wasn't + * properly setup + */ + bool start(); + + /** + * Cancels the download. Returns immediately, the cancelled status will + * be noted in the next avialable update call. + */ + void cancel(); + + char *getError(); + + void setIgnoreError(bool n) + { mIgnoreError = n; } + + private: + static int downloadThread(void *ptr); + static int downloadProgress(void *clientp, double dltotal, + double dlnow, double ultotal, + double ulnow); + void *mPtr; + std::string mUrl; + struct + { + unsigned cancel : 1; + unsigned memoryWrite: 1; + unsigned checkAdler: 1; + } mOptions; + std::string mFileName; + WriteFunction mWriteFunction; + unsigned long mAdler; + DownloadUpdate mUpdateFunction; + SDL_Thread *mThread; + CURL *mCurl; + curl_slist *mHeaders; + char *mError; + bool mIgnoreError; +}; + +} // namespace Net + +#endif // NET_DOWNLOAD_H diff --git a/src/net/gamehandler.h b/src/net/gamehandler.h new file mode 100644 index 000000000..a0208f6c7 --- /dev/null +++ b/src/net/gamehandler.h @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MAPHANDLER_H +#define MAPHANDLER_H + +#include "logindata.h" + +#include + +namespace Net +{ + +class GameHandler +{ + public: + virtual ~GameHandler() + {} + + virtual void connect() = 0; + + virtual bool isConnected() = 0; + + virtual void disconnect() = 0; + + virtual void who() = 0; + + virtual void quit() = 0; + + virtual void ping(int tick) = 0; + + virtual bool removeDeadBeings() const = 0; + + virtual void disconnect2() = 0; + + /** + * Tells whether the protocol is using the MP status bar + */ + virtual bool canUseMagicBar() const = 0; +}; + +} // namespace Net + +#endif // MAPHANDLER_H diff --git a/src/net/generalhandler.h b/src/net/generalhandler.h new file mode 100644 index 000000000..9454bc7e1 --- /dev/null +++ b/src/net/generalhandler.h @@ -0,0 +1,50 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "client.h" +#include "main.h" + +#ifndef GENERALHANDLER_H +#define GENERALHANDLER_H + +namespace Net +{ + +class GeneralHandler +{ + public: + virtual ~GeneralHandler() + { } + + virtual void load() = 0; + + virtual void reload() = 0; + + virtual void unload() = 0; + + virtual void flushNetwork() = 0; + + virtual void clearHandlers() = 0; +}; + +} // namespace Net + +#endif // GENERALHANDLER_H diff --git a/src/net/guildhandler.h b/src/net/guildhandler.h new file mode 100644 index 000000000..6a269ff74 --- /dev/null +++ b/src/net/guildhandler.h @@ -0,0 +1,76 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GUILDHANDLER_H +#define GUILDHANDLER_H + +#include "guild.h" + +#include + +class Being; + +namespace Net +{ + +class GuildHandler +{ + public: + virtual ~GuildHandler() + { } + + virtual bool isSupported() + { return false; } + + virtual void create(const std::string &name) = 0; + + virtual void invite(int guildId, const std::string &name) = 0; + + virtual void invite(int guildId, Being *being) = 0; + + virtual void inviteResponse(int guildId, bool response) = 0; + + virtual void leave(int guildId) = 0; + + virtual void kick(GuildMember *member, std::string reason = "") = 0; + + virtual void chat(int guildId, const std::string &text) = 0; + + virtual void memberList(int guildId) = 0; + + virtual void info(int guildId) = 0; + + virtual void changeMemberPostion(GuildMember *member, int level) = 0; + + virtual void requestAlliance(int guildId, int otherGuildId) = 0; + + virtual void requestAllianceResponse(int guildId, int otherGuildId, + bool response) = 0; + + virtual void endAlliance(int guildId, int otherGuildId) = 0; + + virtual void changeNotice(int guildId, std::string msg1, + std::string msg2) = 0; +}; + +} + +#endif // GUILDHANDLER_H diff --git a/src/net/inventoryhandler.h b/src/net/inventoryhandler.h new file mode 100644 index 000000000..18ec6968b --- /dev/null +++ b/src/net/inventoryhandler.h @@ -0,0 +1,72 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INVENTORYHANDLER_H +#define INVENTORYHANDLER_H + +#include "inventory.h" +#include "item.h" + +#include + +namespace Net +{ + +class InventoryHandler +{ + public: + virtual ~InventoryHandler() + { } + + virtual void equipItem(const Item *item) = 0; + + virtual void unequipItem(const Item *item) = 0; + + virtual void useItem(const Item *item) = 0; + + virtual void dropItem(const Item *item, int amount) = 0; + + virtual bool canSplit(const Item *item) = 0; + + virtual void splitItem(const Item *item, int amount) = 0; + + virtual void moveItem(int oldIndex, int newIndex) = 0; + + virtual void openStorage(int type) = 0; + + virtual void closeStorage(int type) = 0; + + //void changeCart() = 0; + + virtual void moveItem(int source, int slot, int amount, + int destination) = 0; + + // TODO: fix/remove me + virtual size_t getSize(int type) const = 0; + + virtual int convertFromServerSlot(int eAthenaSlot) = 0; + +// virtual ~InventoryHandler() {} +}; + +} // namespace Net + +#endif // INVENTORYHANDLER_H diff --git a/src/net/logindata.h b/src/net/logindata.h new file mode 100644 index 000000000..5617cea32 --- /dev/null +++ b/src/net/logindata.h @@ -0,0 +1,95 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LOGINDATA_H +#define LOGINDATA_H + +#include "being.h" + +#include "net/serverinfo.h" + +#include + +class LoginData +{ + public: + LoginData(): + username(""), + password(""), + newPassword(""), + updateHost(""), + updateType(0), + email(""), + captchaResponse(""), + gender(GENDER_UNSPECIFIED), + remember(false), + registerLogin(false) + { + resetCharacterSlots(); + } + + enum UpdateType + { + Upd_Normal = 0, + Upd_Close = 1, + Upd_Skip = 2, + Upd_Custom = 4 + }; + + std::string username; + std::string password; + std::string newPassword; + std::string updateHost; + int updateType; + + std::string email; + std::string captchaResponse; + + Gender gender; + + bool remember; /**< Whether to store the username. */ + bool registerLogin; /**< Whether an account + is being registered. */ + + unsigned short characterSlots; /**< The number of character slots */ + + void clear() + { + username.clear(); + password.clear(); + newPassword.clear(); + updateHost.clear(); + updateType = Upd_Normal; + email.clear(); + captchaResponse.clear(); + gender = GENDER_UNSPECIFIED; + resetCharacterSlots(); + } + /** + * Initialize character slots to 3 for TmwAthena compatibility + */ + void resetCharacterSlots() + { + characterSlots = 3; // Default value, used for TmwAthena. + } +}; + +#endif // LOGINDATA_H diff --git a/src/net/loginhandler.h b/src/net/loginhandler.h new file mode 100644 index 000000000..5511e46a4 --- /dev/null +++ b/src/net/loginhandler.h @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LOGINHANDLER_H +#define LOGINHANDLER_H + +#include "net/logindata.h" +#include "net/serverinfo.h" +#include "net/worldinfo.h" + +#include +#include + +namespace Net +{ + +class LoginHandler +{ + public: + /** + * This enum describes options specific to either eAthena or Manaserv. + * By querying for these flags, the GUI can adapt to the current + * server type dynamically. + */ + enum OptionalAction + { + Unregister = 0x1, + ChangeEmail = 0x2, + SetEmailOnRegister = 0x4, + SetGenderOnRegister = 0x8 + }; + + void setServer(const ServerInfo &server) + { mServer = server; } + + ServerInfo getServer() const + { return mServer; } + + virtual void connect() = 0; + + virtual bool isConnected() = 0; + + virtual void disconnect() = 0; + + /** + * @see OptionalAction + */ + virtual int supportedOptionalActions() const = 0; + + virtual bool isRegistrationEnabled() = 0; + + virtual void getRegistrationDetails() = 0; + + virtual unsigned int getMinUserNameLength() const + { return 4; } + + virtual unsigned int getMaxUserNameLength() const + { return 25; } + + virtual unsigned int getMinPasswordLength() const + { return 4; } + + virtual unsigned int getMaxPasswordLength() const + { return 255; } + + virtual void loginAccount(LoginData *loginData) = 0; + + virtual void logout() = 0; + + virtual void changeEmail(const std::string &email) = 0; + + virtual void changePassword(const std::string &username, + const std::string &oldPassword, + const std::string &newPassword) = 0; + + virtual void chooseServer(unsigned int server) = 0; + + virtual void registerAccount(LoginData *loginData) = 0; + + virtual void unregisterAccount(const std::string &username, + const std::string &password) = 0; + + virtual Worlds getWorlds() const = 0; + + virtual ~LoginHandler () + { } + + protected: + ServerInfo mServer; +}; + +} // namespace Net + +#endif // LOGINHANDLER_H diff --git a/src/net/manaserv/adminhandler.cpp b/src/net/manaserv/adminhandler.cpp new file mode 100644 index 000000000..cd71f00a2 --- /dev/null +++ b/src/net/manaserv/adminhandler.cpp @@ -0,0 +1,93 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/adminhandler.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +extern Net::AdminHandler *adminHandler; + +namespace ManaServ +{ + +extern Connection *chatServerConnection; + +AdminHandler::AdminHandler() +{ + adminHandler = this; +} + +void AdminHandler::announce(const std::string &text) +{ + MessageOut msg(PCMSG_ANNOUNCE); + msg.writeString(text); + chatServerConnection->send(msg); +} + +void AdminHandler::localAnnounce(const std::string &text _UNUSED_) +{ + // TODO +} + +void AdminHandler::hide(bool hide _UNUSED_) +{ + // TODO +} + +void AdminHandler::kick(int playerId _UNUSED_) +{ + // TODO +} + +void AdminHandler::kick(const std::string &name _UNUSED_) +{ + // TODO +} + +void AdminHandler::ban(int playerId _UNUSED_) +{ + // TODO +} + +void AdminHandler::ban(const std::string &name _UNUSED_) +{ + // TODO +} + +void AdminHandler::unban(int playerId _UNUSED_) +{ + // TODO +} + +void AdminHandler::unban(const std::string &name _UNUSED_) +{ + // TODO +} + +void AdminHandler::mute(int playerId _UNUSED_, int type _UNUSED_, + int limit _UNUSED_) +{ + // TODO +} + +} // namespace ManaServ diff --git a/src/net/manaserv/adminhandler.h b/src/net/manaserv/adminhandler.h new file mode 100644 index 000000000..596ccbccd --- /dev/null +++ b/src/net/manaserv/adminhandler.h @@ -0,0 +1,64 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_ADMINHANDLER_H +#define NET_MANASERV_ADMINHANDLER_H + +#include "net/adminhandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class AdminHandler : public Net::AdminHandler +{ + public: + AdminHandler(); + + void announce(const std::string &text); + + void localAnnounce(const std::string &text); + + void hide(bool hide); + + void kick(int playerId); + + void kick(const std::string &name); + + void ban(int playerId); + + void ban(const std::string &name); + + void unban(int playerId); + + void unban(const std::string &name); + + void mute(int playerId, int type, int limit); +}; + +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/attributes.cpp b/src/net/manaserv/attributes.cpp new file mode 100644 index 000000000..7cc0e053d --- /dev/null +++ b/src/net/manaserv/attributes.cpp @@ -0,0 +1,411 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/attributes.h" + +#include "log.h" +#include "playerinfo.h" + +#include "gui/statuswindow.h" + +#include "resources/itemdb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include +#include + +#define DEFAULT_ATTRIBUTESDB_FILE "attributes.xml" +#define DEFAULT_POINTS 60 +#define DEFAULT_MIN_PTS 1 +#define DEFAULT_MAX_PTS 20 + +namespace ManaServ +{ +namespace Attributes +{ + typedef struct + { + unsigned int id; + std::string name; + std::string description; + /** Whether the attribute value can be modified by the player */ + bool modifiable; + /**< Attribute scope. */ + std::string scope; + /** The playerInfo core Id the attribute is linked with or -1 if not */ + int playerInfoId; + } Attribute; + + /** Map for attributes. */ + typedef std::map AttributeMap; + static AttributeMap attributes; + + /** tags = effects on attributes. */ + typedef std::map< std::string, std::string > TagMap; + static TagMap tags; + + /** List of modifiable attribute names used at character's creation. */ + static std::vector attributeLabels; + + /** Characters creation points. */ + static unsigned int creationPoints = 0; + static unsigned int attributeMinimum = 0; + static unsigned int attributeMaximum = 0; + + unsigned int getCreationPoints() + { return creationPoints; } + + unsigned int getAttributeMinimum() + { return attributeMinimum; } + + unsigned int getAttributeMaximum() + { return attributeMaximum; } + + std::vector& getLabels() + { return attributeLabels; } + + /** + * Fills the list of base attribute labels. + */ + static void fillLabels() + { + // Fill up the modifiable attribute label list. + attributeLabels.clear(); + AttributeMap::const_iterator it, it_end; + for (it = attributes.begin(), it_end = attributes.end(); + it != it_end; it++) + { + if (it->second.modifiable && (it->second.scope == "character" + || it->second.scope == "being")) + { + attributeLabels.push_back(it->second.name + ":"); + } + } + } + + /** + * Fills the list of base attribute labels. + */ + static int getPlayerInfoIdFromAttrType(std::string attrType) + { + toLower(attrType); + if (attrType == "level") + return ::LEVEL; + else if (attrType == "hp") + return ::HP; + else if (attrType == "max-hp") + return ::MAX_HP; + else if (attrType == "mp") + return ::MP; + else if (attrType == "max-mp") + return ::MAX_MP; + else if (attrType == "exp") + return ::EXP; + else if (attrType == "exp-needed") + return ::EXP_NEEDED; + else if (attrType == "money") + return ::MONEY; + else if (attrType == "total-weight") + return ::TOTAL_WEIGHT; + else if (attrType == "max-weight") + return ::MAX_WEIGHT; + else if (attrType == "skill-points") + return ::SKILL_POINTS; + else if (attrType == "char-points") + return ::CHAR_POINTS; + else if (attrType == "corr-points") + return ::CORR_POINTS; + else if (attrType == "none") + return -2; // Used to hide the attribute display. + + return -1; // Not linked to a playerinfo stat. + } + + int getPlayerInfoIdFromAttrId(int attrId) + { + AttributeMap::const_iterator it = attributes.find(attrId); + + if (it != attributes.end()) + return it->second.playerInfoId; + + return -1; + } + + static void loadBuiltins() + { + { + Attribute a; + a.id = 16; + a.name = _("Strength"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("str", _("Strength %+.1f"))); + } + + { + Attribute a; + a.id = 17; + a.name = _("Agility"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("agi", _("Agility %+.1f"))); + } + + { + Attribute a; + a.id = 18; + a.name = _("Dexterity"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("dex", _("Dexterity %+.1f"))); + } + + { + Attribute a; + a.id = 19; + a.name = _("Vitality"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("vit", _("Vitality %+.1f"))); + } + + { + Attribute a; + a.id = 20; + a.name = _("Intelligence"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("int", _("Intelligence %+.1f"))); + } + + { + Attribute a; + a.id = 21; + a.name = _("Willpower"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("wil", _("Willpower %+.1f"))); + } + } + + void load() + { + logger->log("Initializing attributes database..."); + + XML::Document doc(DEFAULT_ATTRIBUTESDB_FILE); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "attributes")) + { + logger->log("Attributes: Error while loading " + DEFAULT_ATTRIBUTESDB_FILE ". Using Built-ins."); + loadBuiltins(); + fillLabels(); + return; + } + + for_each_xml_child_node(node, rootNode) + { + if (xmlStrEqual(node->name, BAD_CAST "attribute")) + { + int id = XML::getProperty(node, "id", 0); + + if (!id) + { + logger->log("Attributes: Invalid or missing stat ID in " + DEFAULT_ATTRIBUTESDB_FILE "!"); + continue; + } + else if (attributes.find(id) != attributes.end()) + { + logger->log("Attributes: Redefinition of stat ID %d", id); + } + + std::string name = XML::getProperty(node, "name", ""); + + if (name.empty()) + { + logger->log("Attributes: Invalid or missing stat name in " + DEFAULT_ATTRIBUTESDB_FILE "!"); + continue; + } + + // Create the attribute. + Attribute a; + a.id = id; + a.name = name; + a.description = XML::getProperty(node, "desc", ""); + a.modifiable = XML::getBoolProperty(node, "modifiable", false); + a.scope = XML::getProperty(node, "scope", "none"); + a.playerInfoId = getPlayerInfoIdFromAttrType( + XML::getProperty(node, "player-info", "")); + + attributes[id] = a; + + unsigned int count = 0; + for_each_xml_child_node(effectNode, node) + { + if (!xmlStrEqual(effectNode->name, BAD_CAST "modifier")) + continue; + ++count; + std::string tag = XML::getProperty(effectNode, "tag", ""); + if (tag.empty()) + { + if (name.empty()) + { + logger->log("Attribute modifier in attribute" + " %u:%s: Empty name definition " + "on empty tag definition, skipping.", + a.id, a.name.c_str()); + --count; + continue; + } + tag = name.substr(0, name.size() > 3 + ? 3 : name.size()); + tag = toLower(tag) + toString(count); + } + + std::string effect = XML::getProperty( + effectNode, "effect", ""); + + if (effect.empty()) + { + if (name.empty()) + { + logger->log("Attribute modifier in attribute" + " %u:%s: Empty name definition " + "on empty effect definition, skipping.", + a.id, a.name.c_str()); + --count; + continue; + } + else + { + effect = name + " %+f"; + } + } + tags.insert(std::make_pair(tag, effect)); + } + logger->log("Found %d tags for attribute %d.", count, id); + + }// End attribute + else if (xmlStrEqual(node->name, BAD_CAST "points")) + { + creationPoints = XML::getProperty( + node, "start", DEFAULT_POINTS); + attributeMinimum = XML::getProperty( + node, "minimum", DEFAULT_MIN_PTS); + attributeMaximum = XML::getProperty( + node, "maximum", DEFAULT_MAX_PTS); + logger->log("Loaded points: start: %i, min: %i, max: %i.", + creationPoints, attributeMinimum, attributeMaximum); + } + else + { + continue; + } + } + logger->log("Found %d tags for %d attributes.", int(tags.size()), + int(attributes.size())); + + fillLabels(); + + // Sanity checks on starting points + float modifiableAttributeCount = (float) attributeLabels.size(); + float averageValue = 1; + if (modifiableAttributeCount) + averageValue = ((float) creationPoints) / modifiableAttributeCount; + + if (averageValue > attributeMaximum || averageValue < attributeMinimum + || creationPoints < 1) + { + logger->log("Attributes: Character's point values make " + "the character's creation impossible. " + "Switch back to defaults."); + creationPoints = DEFAULT_POINTS; + attributeMinimum = DEFAULT_MIN_PTS; + attributeMaximum = DEFAULT_MAX_PTS; + } + } + + void unload() + { + attributes.clear(); + } + + void informItemDB() + { + std::list dbStats; + + TagMap::const_iterator it, it_end; + for (it = tags.begin(), it_end = tags.end(); it != it_end; ++it) + dbStats.push_back(ItemDB::Stat(it->first, it->second)); + + ItemDB::setStatsList(dbStats); + } + + void informStatusWindow() + { + if (!statusWindow) + return; + + AttributeMap::const_iterator it, it_end; + for (it = attributes.begin(), it_end = attributes.end(); + it != it_end; it++) + { + if (it->second.playerInfoId == -1 + && (it->second.scope == "character" + || it->second.scope == "being")) + { + statusWindow->addAttribute(it->second.id, it->second.name, + it->second.modifiable, it->second.description); + } + } + } + +} // namespace Attributes +} // namespace ManaServ diff --git a/src/net/manaserv/attributes.h b/src/net/manaserv/attributes.h new file mode 100644 index 000000000..216bae411 --- /dev/null +++ b/src/net/manaserv/attributes.h @@ -0,0 +1,72 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_ATTRIBUTES_H +#define NET_MANASERV_ATTRIBUTES_H + +#include +#include + +namespace ManaServ +{ + +namespace Attributes +{ + void load(); + + void unload(); + + void informItemDB(); + + void informStatusWindow(); + + /** + * Returns the list of base attribute labels. + */ + std::vector& getLabels(); + + /** + * Give back the corresponding playerinfo Id from the attribute id + * defined in the xml file. + */ + int getPlayerInfoIdFromAttrId(int attrId); + + /** + * Give the attribute points given to a character + * at its creation. + */ + unsigned int getCreationPoints(); + + /** + * Give the minimum attribute point possible + * at character's creation. + */ + unsigned int getAttributeMinimum(); + + /** + * Give the maximum attribute point possible + * at character's creation. + */ + unsigned int getAttributeMaximum(); + +} // namespace Attributes +} // namespace ManaServ + +#endif // NET_MANASERV_ATTRIBUTES_H diff --git a/src/net/manaserv/beinghandler.cpp b/src/net/manaserv/beinghandler.cpp new file mode 100644 index 000000000..ae6a417e5 --- /dev/null +++ b/src/net/manaserv/beinghandler.cpp @@ -0,0 +1,385 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/beinghandler.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "client.h" +#include "game.h" +#include "localplayer.h" +#include "log.h" +#include "particle.h" + +#include "gui/okdialog.h" + +#include "net/messagein.h" +#include "net/net.h" + +#include "net/manaserv/playerhandler.h" +#include "net/manaserv/protocol.h" + +#include "resources/colordb.h" + +#include "utils/gettext.h" + +extern Net::BeingHandler *beingHandler; + +namespace ManaServ +{ + +BeingHandler::BeingHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_BEING_ATTACK, + GPMSG_BEING_ENTER, + GPMSG_BEING_LEAVE, + GPMSG_BEINGS_MOVE, + GPMSG_BEINGS_DAMAGE, + GPMSG_BEING_ACTION_CHANGE, + GPMSG_BEING_LOOKS_CHANGE, + GPMSG_BEING_DIR_CHANGE, + 0 + }; + handledMessages = _messages; + beingHandler = this; +} + +void BeingHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_BEING_ENTER: + handleBeingEnterMessage(msg); + break; + case GPMSG_BEING_LEAVE: + handleBeingLeaveMessage(msg); + break; + case GPMSG_BEINGS_MOVE: + handleBeingsMoveMessage(msg); + break; + case GPMSG_BEING_ATTACK: + handleBeingAttackMessage(msg); + break; + case GPMSG_BEINGS_DAMAGE: + handleBeingsDamageMessage(msg); + break; + case GPMSG_BEING_ACTION_CHANGE: + handleBeingActionChangeMessage(msg); + break; + case GPMSG_BEING_LOOKS_CHANGE: + handleBeingLooksChangeMessage(msg); + break; + case GPMSG_BEING_DIR_CHANGE: + handleBeingDirChangeMessage(msg); + break; + default: + break; + } +} + +Vector BeingHandler::giveSpeedInPixelsPerTicks(float speedInTilesPerSeconds) +{ + Vector speedInTicks; + Game *game = Game::instance(); + Map *map = 0; + if (game) + { + map = game->getCurrentMap(); + if (map) + { + speedInTicks.x = speedInTilesPerSeconds + * (float)map->getTileWidth() + / 1000 * (float) MILLISECONDS_IN_A_TICK; + speedInTicks.y = speedInTilesPerSeconds + * (float)map->getTileHeight() + / 1000 * (float) MILLISECONDS_IN_A_TICK; + } + } + + if (!game || !map) + { + speedInTicks.x = speedInTicks.y = 0; + logger->log1("Manaserv::BeingHandler: Speed wasn't given back" + " because game/Map not initialized."); + } + // We don't use z for now. + speedInTicks.z = 0; + + return speedInTicks; +} + +static void handleLooks(Being *being, Net::MessageIn &msg) +{ + // Order of sent slots. Has to be in sync with the server code. + static int const nb_slots = 4; + static int const slots[nb_slots] = + { + SPRITE_WEAPON, + SPRITE_HAT, + SPRITE_TOPCLOTHES, + SPRITE_BOTTOMCLOTHES + }; + + int mask = msg.readInt8(); + + if (mask & (1 << 7)) + { + // The equipment has to be cleared first. + for (int i = 0; i < nb_slots; ++i) + being->setSprite(slots[i], 0); + } + + // Fill slots enumerated by the bitmask. + for (int i = 0; i < nb_slots; ++i) + { + if (!(mask & (1 << i))) continue; + int id = msg.readInt16(); + being->setSprite(slots[i], id, "", (slots[i] == SPRITE_WEAPON)); + } +} + +void BeingHandler::handleBeingEnterMessage(Net::MessageIn &msg) +{ + int type = msg.readInt8(); + int id = msg.readInt16(); + Being::Action action = (Being::Action)msg.readInt8(); + int px = msg.readInt16(); + int py = msg.readInt16(); + Being *being; + + switch (type) + { + case OBJECT_CHARACTER: + { + std::string name = msg.readString(); + if (player_node->getName() == name) + { + being = player_node; + being->setId(id); + } + else + { + being = actorSpriteManager->createBeing(id, + ActorSprite::PLAYER, 0); + being->setName(name); + } + int hs = msg.readInt8(), hc = msg.readInt8(); + being->setSprite(SPRITE_HAIR, hs * -1, ColorDB::get(hc)); + being->setGender(msg.readInt8() == GENDER_MALE ? + GENDER_MALE : GENDER_FEMALE); + handleLooks(being, msg); + } break; + + case OBJECT_MONSTER: + case OBJECT_NPC: + { + int subtype = msg.readInt16(); + being = actorSpriteManager->createBeing(id, type == OBJECT_MONSTER + ? ActorSprite::MONSTER : ActorSprite::NPC, subtype); + std::string name = msg.readString(); + if (name.length() > 0) being->setName(name); + } break; + + default: + return; + } + + being->setPosition(px, py); + being->setDestination(px, py); + being->setAction(action); +} + +void BeingHandler::handleBeingLeaveMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being) + return; + + actorSpriteManager->destroy(being); +} + +void BeingHandler::handleBeingsMoveMessage(Net::MessageIn &msg) +{ + while (msg.getUnreadLength()) + { + int id = msg.readInt16(); + int flags = msg.readInt8(); + Being *being = actorSpriteManager->findBeing(id); + int sx = 0; + int sy = 0; + int speed = 0; + + if (flags & MOVING_POSITION) + { + sx = msg.readInt16(); + sy = msg.readInt16(); + speed = msg.readInt8(); + } + if (!being || !(flags & (MOVING_POSITION | MOVING_DESTINATION))) + { + continue; + } + if (speed) + { + /* + * The being's speed is transfered in tiles per second * 10 + * to keep it transferable in a Byte. + * We set it back to tiles per second and in a float. + * Then, we translate it in pixels per ticks, to correspond + * with the Being::logic() function calls + * @see MILLISECONDS_IN_A_TICK + */ + being->setWalkSpeed( + giveSpeedInPixelsPerTicks((float) speed / 10)); + } + + // Ignore messages from the server for the local player + if (being == player_node) + continue; + + if (flags & MOVING_POSITION) + { + being->setDestination(sx, sy); + } + } +} + +void BeingHandler::handleBeingAttackMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + const int direction = msg.readInt8(); + const int attackType = msg.readInt8(); + + if (!being) + return; + + switch (direction) + { + case DIRECTION_UP: being->setDirection(Being::UP); break; + case DIRECTION_DOWN: being->setDirection(Being::DOWN); break; + case DIRECTION_LEFT: being->setDirection(Being::LEFT); break; + case DIRECTION_RIGHT: being->setDirection(Being::RIGHT); break; + default: break; + } + + being->setAction(Being::ATTACK, attackType); +} + +void BeingHandler::handleBeingsDamageMessage(Net::MessageIn &msg) +{ + while (msg.getUnreadLength()) + { + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + int damage = msg.readInt16(); + if (being) + { + being->takeDamage(0, damage, Being::HIT); + } + } +} + +void BeingHandler::handleBeingActionChangeMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + Being::Action action = (Being::Action) msg.readInt8(); + if (!being) + return; + + being->setAction(action); + + if (action == Being::DEAD && being == player_node) + { + static char const *const deadMsg[] = + { + _("You are dead."), + _("We regret to inform you that your character was killed in " + "battle."), + _("You are not that alive anymore."), + _("The cold hands of the grim reaper are grabbing for your soul."), + _("Game Over!"), + _("No, kids. Your character did not really die. It... err... " + "went to a better place."), + _("Your plan of breaking your enemies weapon by bashing it with " + "your throat failed."), + _("I guess this did not run too well."), + _("Do you want your possessions identified?"), // Nethack reference + _("Sadly, no trace of you was ever found..."), // Secret of Mana + // reference + _("Annihilated."), // Final Fantasy VI reference + _("Looks like you got your head handed to you."), // Earthbound + // reference + _("You screwed up again, dump your body down the tubes and get " + "you another one.") // Leisure Suit Larry 1 Reference + + }; + std::string message(deadMsg[rand() % 13]); + message.append(std::string(" ") + _("Press OK to respawn.")); + OkDialog *dlg = new OkDialog(_("You Died"), message, false); + dlg->addActionListener(&(ManaServ::respawnListener)); + } +} + +void BeingHandler::handleBeingLooksChangeMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being || being->getType() != ActorSprite::PLAYER) + return; + handleLooks(being, msg); + if (msg.getUnreadLength()) + { + int style = msg.readInt16(); + int color = msg.readInt16(); + being->setSprite(SPRITE_HAIR, style * -1, ColorDB::get(color)); + } +} + +void BeingHandler::handleBeingDirChangeMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being) + return; + int data = msg.readInt8(); + + // The direction for the player's character is handled on client side. + if (being != player_node) + { + switch (data) + { + case DIRECTION_UP: being->setDirection(Being::UP); break; + case DIRECTION_DOWN: being->setDirection(Being::DOWN); break; + case DIRECTION_LEFT: being->setDirection(Being::LEFT); break; + case DIRECTION_RIGHT: being->setDirection(Being::RIGHT); break; + default: break; + } + } +} + +void BeingHandler::requestNameById(int id _UNUSED_) +{ +} + +void BeingHandler::undress(Being *being _UNUSED_) +{ +} + +} // namespace ManaServ diff --git a/src/net/manaserv/beinghandler.h b/src/net/manaserv/beinghandler.h new file mode 100644 index 000000000..7ed657520 --- /dev/null +++ b/src/net/manaserv/beinghandler.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_BEINGHANDLER_H +#define NET_MANASERV_BEINGHANDLER_H + +#include "net/manaserv/messagehandler.h" + +#include "net/beinghandler.h" + +#include "vector.h" +#include "map.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class BeingHandler : public MessageHandler, public Net::BeingHandler +{ + public: + BeingHandler(); + + void handleMessage(Net::MessageIn &msg); + + /** + * Translate a given speed in tiles per seconds + * into pixels per ticks. + * Used to optimize Being::logic() calls. + * @see MILLISECONDS_IN_A_TICKS + */ + static Vector giveSpeedInPixelsPerTicks(float speedInTilesPerSeconds); + + void requestNameById(int id); + + void undress(Being *being); + + private: + void handleBeingAttackMessage(Net::MessageIn &msg); + void handleBeingEnterMessage(Net::MessageIn &msg); + void handleBeingLeaveMessage(Net::MessageIn &msg); + void handleBeingsMoveMessage(Net::MessageIn &msg); + void handleBeingsDamageMessage(Net::MessageIn &msg); + void handleBeingActionChangeMessage(Net::MessageIn &msg); + void handleBeingLooksChangeMessage(Net::MessageIn &msg); + void handleBeingDirChangeMessage(Net::MessageIn &msg); +}; + +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/buysellhandler.cpp b/src/net/manaserv/buysellhandler.cpp new file mode 100644 index 000000000..dbfdfd46d --- /dev/null +++ b/src/net/manaserv/buysellhandler.cpp @@ -0,0 +1,132 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/buysellhandler.h" + +#include "actorspritemanager.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "shopitem.h" + +#include "gui/buy.h" +#include "gui/chat.h" +#include "gui/sell.h" + +#include "net/messagein.h" +#include "net/net.h" + +#include "net/manaserv/protocol.h" + + +extern Net::BuySellHandler *buySellHandler; + +namespace ManaServ +{ + +BuySellHandler::BuySellHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_NPC_BUY, + GPMSG_NPC_SELL, + 0 + }; + handledMessages = _messages; + buySellHandler = this; +} + +void BuySellHandler::handleMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being || being->getType() != ActorSprite::NPC) + { + return; + } + + int npcId = being->getId(); + + switch (msg.getId()) + { + case GPMSG_NPC_BUY: + { + BuyDialog* dialog = new BuyDialog(npcId); + + dialog->reset(); + dialog->setMoney(PlayerInfo::getAttribute(MONEY)); + + while (msg.getUnreadLength()) + { + int itemId = msg.readInt16(); + int amount = msg.readInt16(); + int value = msg.readInt16(); + dialog->addItem(itemId, amount, value); + } + break; + } + + case GPMSG_NPC_SELL: + { + SellDialog* dialog = new SellDialog(npcId); + + dialog->reset(); + dialog->setMoney(PlayerInfo::getAttribute(MONEY)); + + while (msg.getUnreadLength()) + { + int itemId = msg.readInt16(); + int amount = msg.readInt16(); + int value = msg.readInt16(); + dialog->addItem(new Item(itemId, amount, false), value); + } + break; + } + + default: + break; + } +} + +void BuySellHandler::requestSellList(std::string nick _UNUSED_) +{ + // TODO +} + +void BuySellHandler::requestBuyList(std::string nick _UNUSED_) +{ + // TODO +} + +void BuySellHandler::sendBuyRequest(std::string nick _UNUSED_, + ShopItem* item _UNUSED_, + int amount _UNUSED_) +{ + // TODO +} + +void BuySellHandler::sendSellRequest(std::string nick _UNUSED_, + ShopItem* item _UNUSED_, + int amount _UNUSED_) +{ + // TODO +} + +} // namespace ManaServ diff --git a/src/net/manaserv/buysellhandler.h b/src/net/manaserv/buysellhandler.h new file mode 100644 index 000000000..0ceebe61f --- /dev/null +++ b/src/net/manaserv/buysellhandler.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_BUYSELLHANDLER_H +#define NET_MANASERV_BUYSELLHANDLER_H + +#include "net/manaserv/messagehandler.h" + +#include "net/buysellhandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class BuySellHandler : public MessageHandler, public Net::BuySellHandler +{ + public: + BuySellHandler(); + + void handleMessage(Net::MessageIn &msg); + + void requestSellList(std::string nick); + + void requestBuyList(std::string nick); + + void sendBuyRequest(std::string nick, ShopItem* item, int amount); + + void sendSellRequest(std::string nick, ShopItem* item, int amount); + +}; + +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/charhandler.cpp b/src/net/manaserv/charhandler.cpp new file mode 100644 index 000000000..dd5430c03 --- /dev/null +++ b/src/net/manaserv/charhandler.cpp @@ -0,0 +1,406 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/charhandler.h" + +#include "client.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/charcreatedialog.h" +#include "gui/okdialog.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/gamehandler.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" +#include "net/manaserv/attributes.h" + +#include "resources/colordb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +extern Net::CharHandler *charHandler; +extern ManaServ::GameHandler *gameHandler; + +namespace ManaServ +{ + +extern Connection *accountServerConnection; +extern Connection *gameServerConnection; +extern Connection *chatServerConnection; +extern std::string netToken; +extern ServerInfo gameServer; +extern ServerInfo chatServer; + +CharHandler::CharHandler() +{ + static const Uint16 _messages[] = + { + APMSG_CHAR_CREATE_RESPONSE, + APMSG_CHAR_DELETE_RESPONSE, + APMSG_CHAR_INFO, + APMSG_CHAR_SELECT_RESPONSE, + 0 + }; + handledMessages = _messages; + charHandler = this; +} + +CharHandler::~CharHandler() +{ + clear(); +} + +void CharHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case APMSG_CHAR_CREATE_RESPONSE: + handleCharacterCreateResponse(msg); + break; + + case APMSG_CHAR_DELETE_RESPONSE: + handleCharacterDeleteResponse(msg); + break; + + case APMSG_CHAR_INFO: + handleCharacterInfo(msg); + break; + + case APMSG_CHAR_SELECT_RESPONSE: + handleCharacterSelectResponse(msg); + break; + + default: + break; + } +} + +void CharHandler::handleCharacterInfo(Net::MessageIn &msg) +{ + CachedCharacterInfo info; + info.slot = msg.readInt8(); + info.name = msg.readString(); + info.gender = msg.readInt8() == GENDER_MALE ? GENDER_MALE : + GENDER_FEMALE; + info.hairStyle = msg.readInt8(); + info.hairColor = msg.readInt8(); + info.level = msg.readInt16(); + info.characterPoints = msg.readInt16(); + info.correctionPoints = msg.readInt16(); + + + while (msg.getUnreadLength() > 0) + { + int id = msg.readInt32(); + CachedAttrbiute attr; + attr.base = msg.readInt32() / 256.0; + attr.mod = msg.readInt32() / 256.0; + + info.attribute[id] = attr; + } + + mCachedCharacterInfos.push_back(info); + + updateCharacters(); +} + +void CharHandler::handleCharacterCreateResponse(Net::MessageIn &msg) +{ + const int errMsg = msg.readInt8(); + + if (errMsg != ERRMSG_OK) + { + // Character creation failed + std::string errorMessage = ""; + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = _("Not logged in."); + break; + case CREATE_TOO_MUCH_CHARACTERS: + errorMessage = _("No empty slot."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Invalid name."); + break; + case CREATE_EXISTS_NAME: + errorMessage = _("Character's name already exists."); + break; + case CREATE_INVALID_HAIRSTYLE: + errorMessage = _("Invalid hairstyle."); + break; + case CREATE_INVALID_HAIRCOLOR: + errorMessage = _("Invalid hair color."); + break; + case CREATE_INVALID_GENDER: + errorMessage = _("Invalid gender."); + break; + case CREATE_ATTRIBUTES_TOO_HIGH: + errorMessage = _("Character's stats are too high."); + break; + case CREATE_ATTRIBUTES_TOO_LOW: + errorMessage = _("Character's stats are too low."); + break; + case CREATE_ATTRIBUTES_OUT_OF_RANGE: + errorMessage = strprintf( _("At least one stat" + "is out of the permitted range: (%u - %u)."), + Attributes::getAttributeMinimum(), + Attributes::getAttributeMaximum()); + break; + case CREATE_INVALID_SLOT: + errorMessage = _("Invalid slot number."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + new OkDialog(_("Error"), errorMessage); + + if (mCharCreateDialog) + mCharCreateDialog->unlock(); + } + else + { + // Close the character create dialog + if (mCharCreateDialog) + { + mCharCreateDialog->scheduleDelete(); + mCharCreateDialog = 0; + } + } +} + +void CharHandler::handleCharacterDeleteResponse(Net::MessageIn &msg) +{ + int errMsg = msg.readInt8(); + if (errMsg == ERRMSG_OK) + { + // Character deletion successful + delete mSelectedCharacter; + mCharacters.remove(mSelectedCharacter); +// mSelectedCharacter = 0; + updateCharSelectDialog(); + new OkDialog(_("Info"), _("Player deleted.")); + } + else + { + // Character deletion failed + std::string errorMessage = ""; + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = _("Not logged in."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Selection out of range."); + break; + default: + errorMessage = strprintf(_("Unknown error (%d)."), errMsg); + } + new OkDialog(_("Error"), errorMessage); + } + mSelectedCharacter = 0; + unlockCharSelectDialog(); +} + +void CharHandler::handleCharacterSelectResponse(Net::MessageIn &msg) +{ + int errMsg = msg.readInt8(); + + if (errMsg == ERRMSG_OK) + { + netToken = msg.readString(32); + + gameServer.hostname.assign(msg.readString()); + gameServer.port = msg.readInt16(); + + chatServer.hostname.assign(msg.readString()); + chatServer.port = msg.readInt16(); + + logger->log("Game server: %s:%d", gameServer.hostname.c_str(), + gameServer.port); + logger->log("Chat server: %s:%d", chatServer.hostname.c_str(), + chatServer.port); + + // Prevent the selected local player from being deleted + player_node = mSelectedCharacter->dummy; + PlayerInfo::setBackend(mSelectedCharacter->data); + mSelectedCharacter->dummy = 0; + + Client::setState(STATE_CONNECT_GAME); + } + else if (errMsg == ERRMSG_FAILURE) + { + errorMessage = _("No gameservers are available."); + delete_all(mCharacters); + mCharacters.clear(); + Client::setState(STATE_ERROR); + } +} + +void CharHandler::setCharSelectDialog(CharSelectDialog *window) +{ + mCharSelectDialog = window; + updateCharacters(); +} + +void CharHandler::setCharCreateDialog(CharCreateDialog *window) +{ + mCharCreateDialog = window; + + if (!mCharCreateDialog) + return; + + mCharCreateDialog->setAttributes(Attributes::getLabels(), + Attributes::getCreationPoints(), + Attributes::getAttributeMinimum(), + Attributes::getAttributeMaximum()); +} + +void CharHandler::requestCharacters() +{ + if (!accountServerConnection->isConnected()) + { + Net::getLoginHandler()->connect(); + } + else + { + // The characters are already there, continue to character selection + Client::setState(STATE_CHAR_SELECT); + } +} + +void CharHandler::chooseCharacter(Net::Character *character) +{ + mSelectedCharacter = character; + + MessageOut msg(PAMSG_CHAR_SELECT); + msg.writeInt8(mSelectedCharacter->slot); + accountServerConnection->send(msg); +} + +void CharHandler::newCharacter(const std::string &name, + int slot, + bool gender, + int hairstyle, + int hairColor, + const std::vector &stats) +{ + MessageOut msg(PAMSG_CHAR_CREATE); + + msg.writeString(name); + msg.writeInt8(hairstyle); + msg.writeInt8(hairColor); + msg.writeInt8(gender); + msg.writeInt8(slot); + + std::vector::const_iterator it, it_end; + for (it = stats.begin(), it_end = stats.end(); it != it_end; it++) + msg.writeInt16((*it)); + + accountServerConnection->send(msg); +} + +void CharHandler::deleteCharacter(Net::Character *character) +{ + mSelectedCharacter = character; + + MessageOut msg(PAMSG_CHAR_DELETE); + msg.writeInt8(mSelectedCharacter->slot); + accountServerConnection->send(msg); +} + +void CharHandler::switchCharacter() +{ + gameHandler->quit(true); +} + +unsigned int CharHandler::baseSprite() const +{ + return SPRITE_BASE; +} + +unsigned int CharHandler::hairSprite() const +{ + return SPRITE_HAIR; +} + +unsigned int CharHandler::maxSprite() const +{ + return SPRITE_VECTOREND; +} + +void CharHandler::updateCharacters() +{ + // Delete previous characters + delete_all(mCharacters); + mCharacters.clear(); + + if (!mCharSelectDialog) + return; + + // Create new characters and initialize them from the cached infos + for (unsigned i = 0; i < mCachedCharacterInfos.size(); ++i) + { + const CachedCharacterInfo &info = mCachedCharacterInfos.at(i); + + Net::Character *character = new Net::Character; + character->slot = info.slot; + LocalPlayer *player = character->dummy = new LocalPlayer; + player->setName(info.name); + player->setGender(info.gender); + player->setSprite(SPRITE_HAIR, info.hairStyle * -1, + ColorDB::get(info.hairColor)); + character->data.mAttributes[LEVEL] = info.level; + character->data.mAttributes[CHAR_POINTS] = info.characterPoints; + character->data.mAttributes[CORR_POINTS] = info.correctionPoints; + + for (CachedAttributes::const_iterator it = info.attribute.begin(), + it_end = info.attribute.end(); it != it_end; it++) + { + character->data.mStats[i].base = it->second.base; + character->data.mStats[i].mod = it->second.mod; + } + + mCharacters.push_back(character); + } + + updateCharSelectDialog(); +} + +void CharHandler::clear() +{ + setCharCreateDialog(0); + setCharSelectDialog(0); + + mCachedCharacterInfos.clear(); + updateCharacters(); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/charhandler.h b/src/net/manaserv/charhandler.h new file mode 100644 index 000000000..512cca451 --- /dev/null +++ b/src/net/manaserv/charhandler.h @@ -0,0 +1,119 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_CHARSERVERHANDLER_H +#define NET_MANASERV_CHARSERVERHANDLER_H + +#include "gui/charselectdialog.h" + +#include "net/charhandler.h" + +#include "net/manaserv/messagehandler.h" + +#include + +class LoginData; + +namespace ManaServ +{ + +/** + * Deals with incoming messages related to character selection. + */ +class CharHandler : public MessageHandler, public Net::CharHandler +{ + public: + CharHandler(); + + ~CharHandler(); + + void handleMessage(Net::MessageIn &msg); + + void setCharSelectDialog(CharSelectDialog *window); + + /** + * Sets the character create dialog. The handler will clean up this + * dialog when a new character is succesfully created, and will unlock + * the dialog when a new character failed to be created. + */ + void setCharCreateDialog(CharCreateDialog *window); + + void requestCharacters(); + + void chooseCharacter(Net::Character *character); + + void newCharacter(const std::string &name, int slot, + bool gender, int hairstyle, int hairColor, + const std::vector &stats); + + void deleteCharacter(Net::Character *character); + + void switchCharacter(); + + unsigned int baseSprite() const; + + unsigned int hairSprite() const; + + unsigned int maxSprite() const; + + void clear(); + + private: + /** + * Character information needs to be cached since we receive it before + * we have loaded the dynamic data, so we can't resolve load any + * sprites yet. + */ + struct CachedAttrbiute + { + double base; + double mod; + }; + + typedef std::map CachedAttributes; + + struct CachedCharacterInfo + { + int slot; + std::string name; + Gender gender; + int hairStyle; + int hairColor; + int level; + int characterPoints; + int correctionPoints; + CachedAttributes attribute; + }; + + void handleCharacterInfo(Net::MessageIn &msg); + void handleCharacterCreateResponse(Net::MessageIn &msg); + void handleCharacterDeleteResponse(Net::MessageIn &msg); + void handleCharacterSelectResponse(Net::MessageIn &msg); + + void updateCharacters(); + + /** Cached character information */ + std::vector mCachedCharacterInfos; +}; + +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/chathandler.cpp b/src/net/manaserv/chathandler.cpp new file mode 100644 index 000000000..5e588c583 --- /dev/null +++ b/src/net/manaserv/chathandler.cpp @@ -0,0 +1,472 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/chathandler.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "client.h" +#include "channel.h" +#include "channelmanager.h" + +#include "gui/chat.h" + +#include "gui/widgets/channeltab.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +extern Being *player_node; + +extern Net::ChatHandler *chatHandler; + +namespace ManaServ +{ + +extern Connection *chatServerConnection; +extern Connection *gameServerConnection; +extern std::string netToken; +extern ServerInfo chatServer; + +ChatHandler::ChatHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_SAY, + CPMSG_ENTER_CHANNEL_RESPONSE, + CPMSG_LIST_CHANNELS_RESPONSE, + CPMSG_PUBMSG, + CPMSG_ANNOUNCEMENT, + CPMSG_PRIVMSG, + CPMSG_QUIT_CHANNEL_RESPONSE, + CPMSG_LIST_CHANNELUSERS_RESPONSE, + CPMSG_CHANNEL_EVENT, + CPMSG_WHO_RESPONSE, + CPMSG_DISCONNECT_RESPONSE, + 0 + }; + handledMessages = _messages; + chatHandler = this; +} + +void ChatHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_SAY: + handleGameChatMessage(msg); + break; + + case CPMSG_ENTER_CHANNEL_RESPONSE: + handleEnterChannelResponse(msg); + break; + + case CPMSG_LIST_CHANNELS_RESPONSE: + handleListChannelsResponse(msg); + break; + + case CPMSG_PRIVMSG: + handlePrivateMessage(msg); + break; + + case CPMSG_ANNOUNCEMENT: + handleAnnouncement(msg); + break; + + case CPMSG_PUBMSG: + handleChatMessage(msg); + break; + + case CPMSG_QUIT_CHANNEL_RESPONSE: + handleQuitChannelResponse(msg); + break; + + case CPMSG_LIST_CHANNELUSERS_RESPONSE: + handleListChannelUsersResponse(msg); + break; + + case CPMSG_CHANNEL_EVENT: + handleChannelEvent(msg); + break; + + case CPMSG_WHO_RESPONSE: + handleWhoResponse(msg); + break; + case CPMSG_DISCONNECT_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful logout + if (errMsg == ERRMSG_OK) + { + // TODO: Handle logout + } + else + { + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = "Chatserver: Not logged in"; + break; + default: + errorMessage = "Chatserver: Unknown error"; + break; + } + Client::setState(STATE_ERROR); + } + } + break; + default: + break; + } +} + +void ChatHandler::handleGameChatMessage(Net::MessageIn &msg) +{ + short id = msg.readInt16(); + std::string chatMsg = msg.readString(); + + if (id == 0) + { + localChatTab->chatLog(chatMsg, BY_SERVER); + return; + } + + Being *being = actorSpriteManager->findBeing(id); + + std::string mes; + if (being) + { + mes = being->getName() + " : " + chatMsg; + being->setSpeech(chatMsg, SPEECH_TIME); + } + else + mes = "Unknown : " + chatMsg; + + localChatTab->chatLog(mes, being == player_node + ? BY_PLAYER : BY_OTHER); +} + +void ChatHandler::handleEnterChannelResponse(Net::MessageIn &msg) +{ + if (msg.readInt8() == ERRMSG_OK) + { + short channelId = msg.readInt16(); + std::string channelName = msg.readString(); + std::string announcement = msg.readString(); + Channel *channel = new Channel(channelId, channelName, announcement); + channelManager->addChannel(channel); + ChatTab *tab = channel->getTab(); + tab->chatLog(strprintf(_("Topic: %s"), announcement.c_str()), + BY_CHANNEL); + + std::string user; + std::string userModes; + tab->chatLog(_("Players in this channel:"), BY_CHANNEL); + while (msg.getUnreadLength()) + { + user = msg.readString(); + if (user == "") + return; + userModes = msg.readString(); + if (userModes.find('o') != std::string::npos) + { + user = "@" + user; + } + tab->chatLog(user, BY_CHANNEL); + } + + } + else + { + localChatTab->chatLog(_("Error joining channel."), BY_SERVER); + } +} + +void ChatHandler::handleListChannelsResponse(Net::MessageIn &msg) +{ + localChatTab->chatLog(_("Listing channels."), BY_SERVER); + while (msg.getUnreadLength()) + { + std::string channelName = msg.readString(); + if (channelName == "") + return; + std::ostringstream numUsers; + numUsers << msg.readInt16(); + channelName += " - "; + channelName += numUsers.str(); + localChatTab->chatLog(channelName, BY_SERVER); + } + localChatTab->chatLog(_("End of channel list."), BY_SERVER); +} + +void ChatHandler::handlePrivateMessage(Net::MessageIn &msg) +{ + std::string userNick = msg.readString(); + std::string chatMsg = msg.readString(); + + chatWindow->whisper(userNick, chatMsg); +} + +void ChatHandler::handleAnnouncement(Net::MessageIn &msg) +{ + std::string chatMsg = msg.readString(); + localChatTab->chatLog(chatMsg, BY_GM); +} + +void ChatHandler::handleChatMessage(Net::MessageIn &msg) +{ + short channelId = msg.readInt16(); + std::string userNick = msg.readString(); + std::string chatMsg = msg.readString(); + + Channel *channel = channelManager->findById(channelId); + channel->getTab()->chatLog(userNick, chatMsg); +} + +void ChatHandler::handleQuitChannelResponse(Net::MessageIn &msg) +{ + if (msg.readInt8() == ERRMSG_OK) + { + short channelId = msg.readInt16(); + Channel *channel = channelManager->findById(channelId); + channelManager->removeChannel(channel); + } +} + +void ChatHandler::handleListChannelUsersResponse(Net::MessageIn &msg) +{ + std::string channelName = msg.readString(); + std::string userNick; + std::string userModes; + Channel *channel = channelManager->findByName(channelName); + channel->getTab()->chatLog(_("Players in this channel:"), + BY_CHANNEL); + while (msg.getUnreadLength()) + { + userNick = msg.readString(); + if (userNick == "") + { + break; + } + userModes = msg.readString(); + if (userModes.find('o') != std::string::npos) + { + userNick = "@" + userNick; + } + localChatTab->chatLog(userNick, BY_CHANNEL, channel); + } +} + +void ChatHandler::handleChannelEvent(Net::MessageIn &msg) +{ + short channelId = msg.readInt16(); + char eventId = msg.readInt8(); + std::string line = msg.readString(); + Channel *channel = channelManager->findById(channelId); + + if (channel) + { + switch(eventId) + { + case CHAT_EVENT_NEW_PLAYER: + channel->getTab()->chatLog(strprintf(_("%s entered the " + "channel."), line.c_str()), BY_CHANNEL); + break; + + case CHAT_EVENT_LEAVING_PLAYER: + channel->getTab()->chatLog(strprintf(_("%s left the channel."), + line.c_str()), BY_CHANNEL); + break; + + case CHAT_EVENT_TOPIC_CHANGE: + channel->getTab()->chatLog(strprintf(_("Topic: %s"), + line.c_str()), BY_CHANNEL); + break; + + case CHAT_EVENT_MODE_CHANGE: + { + int first = line.find(":"); + int second = line.find(":", first + 1); + std::string user1 = line.substr(0, first); + std::string user2 = line.substr(first + 1, second); + std::string mode = line.substr(second + 1, line.length()); + channel->getTab()->chatLog(strprintf(_("%s has set mode %s " + "on user %s."), user1.c_str(), mode.c_str(), + user2.c_str()), BY_CHANNEL); + } + break; + + case CHAT_EVENT_KICKED_PLAYER: + { + int first = line.find(":"); + std::string user1 = line.substr(0, first); + std::string user2 = line.substr(first + 1, line.length()); + channel->getTab()->chatLog(strprintf(_("%s has kicked %s."), + user1.c_str(), user2.c_str()), BY_CHANNEL); + } + break; + + default: + channel->getTab()->chatLog(_("Unknown channel event."), + BY_CHANNEL); + } + } +} + +void ChatHandler::handleWhoResponse(Net::MessageIn &msg) +{ + std::string userNick; + + while (msg.getUnreadLength()) + { + userNick = msg.readString(); + if (userNick == "") + break; + localChatTab->chatLog(userNick, BY_SERVER); + } +} + +void ChatHandler::connect() +{ + MessageOut msg(PCMSG_CONNECT); + msg.writeString(netToken, 32); + chatServerConnection->send(msg); +} + +bool ChatHandler::isConnected() +{ + return chatServerConnection->isConnected(); +} + +void ChatHandler::disconnect() +{ + chatServerConnection->disconnect(); +} + +void ChatHandler::talk(const std::string &text) +{ + MessageOut msg(PGMSG_SAY); + msg.writeString(text); + gameServerConnection->send(msg); +} + +void ChatHandler::talkRaw(const std::string &text) +{ + MessageOut msg(PGMSG_SAY); + msg.writeString(text); + gameServerConnection->send(msg); +} + +void ChatHandler::me(const std::string &text _UNUSED_) +{ + // TODO +} + +void ChatHandler::privateMessage(const std::string &recipient, + const std::string &text) +{ + MessageOut msg(PCMSG_PRIVMSG); + msg.writeString(recipient); + msg.writeString(text); + chatServerConnection->send(msg); +} + +void ChatHandler::channelList() +{ + MessageOut msg(PCMSG_LIST_CHANNELS); + chatServerConnection->send(msg); +} + +void ChatHandler::enterChannel(const std::string &channel, + const std::string &password) +{ + MessageOut msg(PCMSG_ENTER_CHANNEL); + msg.writeString(channel); + msg.writeString(password); + chatServerConnection->send(msg); +} + +void ChatHandler::quitChannel(int channelId) +{ + MessageOut msg(PCMSG_QUIT_CHANNEL); + msg.writeInt16(channelId); + chatServerConnection->send(msg); +} + +void ChatHandler::sendToChannel(int channelId, const std::string &text) +{ + MessageOut msg(PCMSG_CHAT); + msg.writeString(text); + msg.writeInt16(channelId); + chatServerConnection->send(msg); +} + +void ChatHandler::userList(const std::string &channel) +{ + MessageOut msg(PCMSG_LIST_CHANNELUSERS); + msg.writeString(channel); + chatServerConnection->send(msg); +} + +void ChatHandler::setChannelTopic(int channelId, const std::string &text) +{ + MessageOut msg(PCMSG_TOPIC_CHANGE); + msg.writeInt16(channelId); + msg.writeString(text); + chatServerConnection->send(msg); +} + +void ChatHandler::setUserMode(int channelId, const std::string &name, int mode) +{ + MessageOut msg(PCMSG_USER_MODE); + msg.writeInt16(channelId); + msg.writeString(name); + msg.writeInt8(mode); + chatServerConnection->send(msg); +} + +void ChatHandler::kickUser(int channelId, const std::string &name) +{ + MessageOut msg(PCMSG_KICK_USER); + msg.writeInt16(channelId); + msg.writeString(name); + chatServerConnection->send(msg); +} + +void ChatHandler::who() +{ + MessageOut msg(PCMSG_WHO); + chatServerConnection->send(msg); +} + +void ChatHandler::sendRaw(const std::string &args _UNUSED_) +{ + +} +} // namespace ManaServ diff --git a/src/net/manaserv/chathandler.h b/src/net/manaserv/chathandler.h new file mode 100644 index 000000000..8ffa6d28d --- /dev/null +++ b/src/net/manaserv/chathandler.h @@ -0,0 +1,139 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_CHATHANDLER_H +#define NET_MANASERV_CHATHANDLER_H + +#include "net/chathandler.h" +#include "net/serverinfo.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class ChatHandler : public MessageHandler, public Net::ChatHandler +{ + public: + ChatHandler(); + + /** + * Handle the given message appropriately. + */ + void handleMessage(Net::MessageIn &msg); + + void connect(); + + bool isConnected(); + + void disconnect(); + + void talk(const std::string &text); + + void talkRaw(const std::string &text); + + void me(const std::string &text); + + void privateMessage(const std::string &recipient, + const std::string &text); + + void channelList(); + + void enterChannel(const std::string &channel, + const std::string &password); + + void quitChannel(int channelId); + + void sendToChannel(int channelId, const std::string &text); + + void userList(const std::string &channel); + + void setChannelTopic(int channelId, const std::string &text); + + void setUserMode(int channelId, const std::string &name, int mode); + + void kickUser(int channelId, const std::string &name); + + void who(); + + void sendRaw(const std::string &args); + + private: + /** + * Handle chat messages sent from the game server. + */ + void handleGameChatMessage(Net::MessageIn &msg); + + /** + * Handle channel entry responses. + */ + void handleEnterChannelResponse(Net::MessageIn &msg); + + /** + * Handle list channels responses. + */ + void handleListChannelsResponse(Net::MessageIn &msg); + + /** + * Handle private messages. + */ + void handlePrivateMessage(Net::MessageIn &msg); + + /** + * Handle announcements. + */ + void handleAnnouncement(Net::MessageIn &msg); + + /** + * Handle chat messages. + */ + void handleChatMessage(Net::MessageIn &msg); + + /** + * Handle quit channel responses. + */ + void handleQuitChannelResponse(Net::MessageIn &msg); + + /** + * Handle list channel users responses. + */ + void handleListChannelUsersResponse(Net::MessageIn &msg); + + /** + * Handle channel events. + */ + void handleChannelEvent(Net::MessageIn &msg); + + /** + * Handle who responses. + */ + void handleWhoResponse(Net::MessageIn &msg); +}; + +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/connection.cpp b/src/net/manaserv/connection.cpp new file mode 100644 index 000000000..944d8a94c --- /dev/null +++ b/src/net/manaserv/connection.cpp @@ -0,0 +1,113 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/connection.h" + +#include "log.h" + +#include "net/manaserv/internal.h" +#include "net/manaserv/messageout.h" + +#include + +namespace ManaServ +{ + +Connection::Connection(ENetHost *client): + mConnection(0), mClient(client) +{ + mPort = 0; + connections++; +} + +Connection::~Connection() +{ + connections--; +} + +bool Connection::connect(const std::string &address, short port) +{ + logger->log("Net::Connection::connect(%s, %i)", address.c_str(), port); + + if (address.empty()) + { + logger->log1("Net::Connection::connect() got empty address!"); + mState = NET_ERROR; + return false; + } + + ENetAddress enetAddress; + + enet_address_set_host(&enetAddress, address.c_str()); + enetAddress.port = port; + + // Initiate the connection, allocating channel 0. +#if defined(ENET_VERSION) && ENET_VERSION >= ENET_CUTOFF + mConnection = enet_host_connect(mClient, &enetAddress, 1, 0); +#else + mConnection = enet_host_connect(mClient, &enetAddress, 1); +#endif + + if (!mConnection) + { + logger->log1("Unable to initiate connection to the server."); + mState = NET_ERROR; + return false; + } + + mPort = port; + + return true; +} + +void Connection::disconnect() +{ + if (!mConnection) + return; + + enet_peer_disconnect(mConnection, 0); + enet_host_flush(mClient); + enet_peer_reset(mConnection); + + mConnection = 0; +} + +bool Connection::isConnected() +{ + return (mConnection) ? + (mConnection->state == ENET_PEER_STATE_CONNECTED) : false; +} + +void Connection::send(const ManaServ::MessageOut &msg) +{ + if (!isConnected()) + { + logger->log1("Warning: cannot send message to not connected server!"); + return; + } + + ENetPacket *packet = enet_packet_create(msg.getData(), + msg.getDataSize(), + ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(mConnection, 0, packet); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/connection.h b/src/net/manaserv/connection.h new file mode 100644 index 000000000..260a177ee --- /dev/null +++ b/src/net/manaserv/connection.h @@ -0,0 +1,89 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_CONNECTION_H +#define NET_MANASERV_CONNECTION_H + +#include "enet/enet.h" + +#include + +#ifdef ENET_VERSION_CREATE +#define ENET_CUTOFF ENET_VERSION_CREATE(1, 3, 0) +#else +#define ENET_CUTOFF 0xFFFFFFFF +#endif + +namespace ManaServ +{ + class MessageOut; + + /** + * \ingroup Network + */ + class Connection + { + public: + enum State + { + OK = 0, + NET_ERROR + }; + + ~Connection(); + + /** + * Connects to the given server with the specified address and port. + * This method is non-blocking, use isConnected to check whether the + * server is connected. + */ + bool connect(const std::string &address, short port); + + /** + * Disconnects from the given server. + */ + void disconnect(); + + State getState() + { return mState; } + + /** + * Returns whether the server is connected. + */ + bool isConnected(); + + /** + * Sends a message. + */ + void send(const ManaServ::MessageOut &msg); + + private: + friend Connection *ManaServ::getConnection(); + Connection(ENetHost *client); + + short mPort; + ENetPeer *mConnection; + ENetHost *mClient; + State mState; + }; +} + +#endif // NET_MANASERV_CONNECTION_H diff --git a/src/net/manaserv/defines.h b/src/net/manaserv/defines.h new file mode 100644 index 000000000..f09175f65 --- /dev/null +++ b/src/net/manaserv/defines.h @@ -0,0 +1,77 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MANASERV_DEFINES_H +#define MANASERV_DEFINES_H + +/** + * Attributes used during combat. Available to all the beings. + */ +enum +{ + BASE_ATTR_BEGIN = 0, + BASE_ATTR_PHY_ATK_MIN = BASE_ATTR_BEGIN, + BASE_ATTR_PHY_ATK_DELTA, + /**< Physical attack power. */ + BASE_ATTR_MAG_ATK, /**< Magical attack power. */ + BASE_ATTR_PHY_RES, /**< Resistance to physical damage. */ + BASE_ATTR_MAG_RES, /**< Resistance to magical damage. */ + BASE_ATTR_EVADE, /**< Ability to avoid hits. */ + BASE_ATTR_HIT, /**< Ability to hit stuff. */ + BASE_ATTR_HP, /**< Hit Points (Base value: maximum, + Modded value: current) */ + BASE_ATTR_HP_REGEN, /**< number of HP regenerated every 10 game ticks */ + BASE_ATTR_END, + BASE_ATTR_NB = BASE_ATTR_END - BASE_ATTR_BEGIN, + + BASE_ELEM_BEGIN = BASE_ATTR_END, + BASE_ELEM_NEUTRAL = BASE_ELEM_BEGIN, + BASE_ELEM_FIRE, + BASE_ELEM_WATER, + BASE_ELEM_EARTH, + BASE_ELEM_AIR, + BASE_ELEM_SACRED, + BASE_ELEM_DEATH, + BASE_ELEM_END, + BASE_ELEM_NB = BASE_ELEM_END - BASE_ELEM_BEGIN, + + NB_BEING_ATTRIBUTES = BASE_ELEM_END +}; + +/** + * Attributes of characters. Used to derive being attributes. + */ +enum +{ + CHAR_ATTR_BEGIN = NB_BEING_ATTRIBUTES, + CHAR_ATTR_STRENGTH = CHAR_ATTR_BEGIN, + CHAR_ATTR_AGILITY, + CHAR_ATTR_DEXTERITY, + CHAR_ATTR_VITALITY, + CHAR_ATTR_INTELLIGENCE, + CHAR_ATTR_WILLPOWER, + CHAR_ATTR_END, + CHAR_ATTR_NB = CHAR_ATTR_END - CHAR_ATTR_BEGIN, + + NB_CHARACTER_ATTRIBUTES = CHAR_ATTR_END +}; + +#endif // MANASERV_DEFINES_H diff --git a/src/net/manaserv/effecthandler.cpp b/src/net/manaserv/effecthandler.cpp new file mode 100644 index 000000000..5e0083744 --- /dev/null +++ b/src/net/manaserv/effecthandler.cpp @@ -0,0 +1,80 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/effecthandler.h" + +#include "actorspritemanager.h" +#include "effectmanager.h" +#include "log.h" + +#include "net/messagein.h" + +#include "net/manaserv/protocol.h" + +namespace ManaServ +{ + +EffectHandler::EffectHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_CREATE_EFFECT_POS, + GPMSG_CREATE_EFFECT_BEING, + 0 + }; + handledMessages = _messages; +} + +void EffectHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_CREATE_EFFECT_POS: + handleCreateEffectPos(msg); + break; + case GPMSG_CREATE_EFFECT_BEING: + handleCreateEffectBeing(msg); + break; + default: + break; + } +} + +void EffectHandler::handleCreateEffectPos(Net::MessageIn &msg) +{ + int id = msg.readInt16(); + Uint16 x = msg.readInt16(); + Uint16 y = msg.readInt16(); + effectManager->trigger(id, x, y); +} + +void EffectHandler::handleCreateEffectBeing(Net::MessageIn &msg) +{ + int eid = msg.readInt16(); + int bid = msg.readInt16(); + Being* b = actorSpriteManager->findBeing(bid); + if (b) + effectManager->trigger(eid, b); + else + logger->log("Warning: CreateEffect called for unknown being #%d", bid); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/effecthandler.h b/src/net/manaserv/effecthandler.h new file mode 100644 index 000000000..5f6c07580 --- /dev/null +++ b/src/net/manaserv/effecthandler.h @@ -0,0 +1,44 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_EFFECTSHANDLER_H +#define NET_MANASERV_EFFECTSHANDLER_H + +#include "net/manaserv/messagehandler.h" + +namespace ManaServ +{ + +class EffectHandler : public MessageHandler +{ + public: + EffectHandler(); + + void handleMessage(Net::MessageIn &msg); + + private: + void handleCreateEffectPos(Net::MessageIn &msg); + void handleCreateEffectBeing(Net::MessageIn &msg); +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_EFFECTSHANDLER_H diff --git a/src/net/manaserv/gamehandler.cpp b/src/net/manaserv/gamehandler.cpp new file mode 100644 index 000000000..9ca6c0d6b --- /dev/null +++ b/src/net/manaserv/gamehandler.cpp @@ -0,0 +1,154 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/gamehandler.h" + +#include "client.h" +#include "localplayer.h" + +#include "net/manaserv/chathandler.h" +#include "net/manaserv/connection.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +extern Net::GameHandler *gameHandler; + +extern ManaServ::ChatHandler *chatHandler; + +namespace ManaServ +{ + +extern Connection *chatServerConnection; +extern Connection *gameServerConnection; +extern std::string netToken; +extern ServerInfo gameServer; +extern ServerInfo chatServer; + +GameHandler::GameHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_DISCONNECT_RESPONSE, + 0 + }; + handledMessages = _messages; + gameHandler = this; +} + +void GameHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_DISCONNECT_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful logout + if (errMsg == ERRMSG_OK) + { + netToken = msg.readString(32); + + if (!netToken.empty()) + { + Client::setState(STATE_SWITCH_CHARACTER); + } + else + { + // TODO: Handle logout + } + } + // Logout failed + else + { + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = "Gameserver: Not logged in"; + break; + default: + errorMessage = "Gameserver: Unknown error"; + break; + } + Client::setState(STATE_ERROR); + } + } + break; + default: + break; + } +} + +void GameHandler::connect() +{ + gameServerConnection->connect(gameServer.hostname, gameServer.port); + + // Will already be connected if we just changed gameservers + if (!chatServerConnection->isConnected()) + chatServerConnection->connect(chatServer.hostname, chatServer.port); +} + +bool GameHandler::isConnected() +{ + return gameServerConnection->isConnected() && + chatHandler->isConnected(); +} + +void GameHandler::disconnect() +{ + gameServerConnection->disconnect(); + // No need if we're just changing gameservers + if (Client::getState() != STATE_CHANGE_MAP) + chatHandler->disconnect(); +} + +void GameHandler::who() +{ + // TODO +} + +void GameHandler::quit(bool reconnectAccount) +{ + MessageOut msg(PGMSG_DISCONNECT); + msg.writeInt8((unsigned char) reconnectAccount); + gameServerConnection->send(msg); +} + +void GameHandler::ping(int tick _UNUSED_) +{ + // TODO +} + +void GameHandler::gameLoading() +{ + MessageOut msg(PGMSG_CONNECT); + msg.writeString(netToken, 32); + gameServerConnection->send(msg); + + chatHandler->connect(); + + // Attack range from item DB + player_node->setAttackRange(-1); +} + +void GameHandler::disconnect2() +{ +} + +} // namespace ManaServ diff --git a/src/net/manaserv/gamehandler.h b/src/net/manaserv/gamehandler.h new file mode 100644 index 000000000..246a3c736 --- /dev/null +++ b/src/net/manaserv/gamehandler.h @@ -0,0 +1,74 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_MAPHANDLER_H +#define NET_MANASERV_MAPHANDLER_H + +#include "net/gamehandler.h" +#include "net/serverinfo.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class GameHandler : public MessageHandler, public Net::GameHandler +{ + public: + GameHandler(); + + void handleMessage(Net::MessageIn &msg); + + void connect(); + + bool isConnected(); + + void disconnect(); + + void who(); + + void quit(bool reconnectAccount); + + void quit() { quit(false); } + + void ping(int tick); + + bool removeDeadBeings() const { return false; } + + void clear(); + + void gameLoading(); + + /** The ManaServ protocol doesn't use the MP status bar. */ + bool canUseMagicBar() const { return false; } + + void disconnect2(); +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_MAPHANDLER_H diff --git a/src/net/manaserv/generalhandler.cpp b/src/net/manaserv/generalhandler.cpp new file mode 100644 index 000000000..acf731dec --- /dev/null +++ b/src/net/manaserv/generalhandler.cpp @@ -0,0 +1,211 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/generalhandler.h" + +#include "client.h" + +#include "gui/changeemaildialog.h" +#include "gui/charselectdialog.h" +#include "gui/inventorywindow.h" +#include "gui/register.h" +#include "gui/skilldialog.h" +#include "gui/specialswindow.h" + +#include "net/manaserv/beinghandler.h" +#include "net/manaserv/buysellhandler.h" +#include "net/manaserv/charhandler.h" +#include "net/manaserv/chathandler.h" +#include "net/manaserv/connection.h" +#include "net/manaserv/effecthandler.h" +#include "net/manaserv/gamehandler.h" +#include "net/manaserv/guildhandler.h" +#include "net/manaserv/inventoryhandler.h" +#include "net/manaserv/itemhandler.h" +#include "net/manaserv/loginhandler.h" +#include "net/manaserv/network.h" +#include "net/manaserv/npchandler.h" +#include "net/manaserv/partyhandler.h" +#include "net/manaserv/playerhandler.h" +#include "net/manaserv/specialhandler.h" +#include "net/manaserv/attributes.h" +#include "net/manaserv/tradehandler.h" + +#include "utils/gettext.h" + +#include + +extern Net::GeneralHandler *generalHandler; + +extern ManaServ::LoginHandler *loginHandler; + +namespace ManaServ +{ + +Connection *accountServerConnection = 0; +Connection *chatServerConnection = 0; +Connection *gameServerConnection = 0; +std::string netToken = ""; +ServerInfo gameServer; +ServerInfo chatServer; + +GeneralHandler::GeneralHandler(): + mBeingHandler(new BeingHandler), + mBuySellHandler(new BuySellHandler), + mCharHandler(new CharHandler), + mChatHandler(new ChatHandler), + mEffectHandler(new EffectHandler), + mGameHandler(new GameHandler), + mGuildHandler(new GuildHandler), + mInventoryHandler(new InventoryHandler), + mItemHandler(new ItemHandler), + mLoginHandler(new LoginHandler), + mNpcHandler(new NpcHandler), + mPartyHandler(new PartyHandler), + mPlayerHandler(new PlayerHandler), + mTradeHandler(new TradeHandler), + mSpecialHandler(new SpecialHandler) +{ + initialize(); + + accountServerConnection = getConnection(); + gameServerConnection = getConnection(); + chatServerConnection = getConnection(); + + generalHandler = this; + + listen(CHANNEL_CLIENT); + listen(CHANNEL_GAME); +} + +void GeneralHandler::load() +{ + registerHandler(mBeingHandler.get()); + registerHandler(mBuySellHandler.get()); + registerHandler(mCharHandler.get()); + registerHandler(mChatHandler.get()); + registerHandler(mEffectHandler.get()); + registerHandler(mGameHandler.get()); + registerHandler(mGuildHandler.get()); + registerHandler(mInventoryHandler.get()); + registerHandler(mItemHandler.get()); + registerHandler(mLoginHandler.get()); + registerHandler(mNpcHandler.get()); + registerHandler(mPartyHandler.get()); + registerHandler(mPlayerHandler.get()); + registerHandler(mTradeHandler.get()); +} + +void GeneralHandler::reload() +{ + static_cast(Net::getCharHandler())->clear(); + + if (accountServerConnection) + accountServerConnection->disconnect(); + + if (gameServerConnection) + gameServerConnection->disconnect(); + + if (chatServerConnection) + chatServerConnection->disconnect(); + + netToken.clear(); + gameServer.clear(); + chatServer.clear(); + + Attributes::unload(); + Attributes::load(); + Attributes::informItemDB(); +} + +void GeneralHandler::unload() +{ + clearHandlers(); + + if (accountServerConnection) + accountServerConnection->disconnect(); + if (gameServerConnection) + gameServerConnection->disconnect(); + if (chatServerConnection) + chatServerConnection->disconnect(); + + delete accountServerConnection; + accountServerConnection = 0; + delete gameServerConnection; + gameServerConnection = 0; + delete chatServerConnection; + chatServerConnection = 0; + + Attributes::unload(); + finalize(); +} + +void GeneralHandler::flushNetwork() +{ + flush(); + + if (Client::getState() == STATE_SWITCH_CHARACTER && + Net::getLoginHandler()->isConnected()) + { + loginHandler->reconnect(); + Client::setState(STATE_GET_CHARACTERS); + } +} + +void GeneralHandler::clearHandlers() +{ + clearNetworkHandlers(); +} + +void GeneralHandler::event(Channels channel, + const Mana::Event &event) +{ + if (channel == CHANNEL_CLIENT) + { + int newState = event.getInt("newState"); + + if (newState == STATE_GAME) + { + GameHandler *game = static_cast( + Net::getGameHandler()); + game->gameLoading(); + } + else if (newState == STATE_LOAD_DATA) + { + Attributes::load(); + Attributes::informItemDB(); + } + } + else if (channel == CHANNEL_GAME) + { + if (event.getName() == EVENT_GUIWINDOWSLOADED) + { + inventoryWindow->setSplitAllowed(true); + skillDialog->loadSkills("mana-skills.xml"); + + PlayerInfo::setAttribute(EXP_NEEDED, 100); + + Attributes::informStatusWindow(); + } + } +} + +} // namespace ManaServ diff --git a/src/net/manaserv/generalhandler.h b/src/net/manaserv/generalhandler.h new file mode 100644 index 000000000..944a3d305 --- /dev/null +++ b/src/net/manaserv/generalhandler.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_GENERALHANDLER_H +#define NET_MANASERV_GENERALHANDLER_H + +#include "listener.h" + +#include "net/generalhandler.h" +#include "net/net.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class GeneralHandler : public Net::GeneralHandler, public Mana::Listener +{ + public: + GeneralHandler(); + + void load(); + + void reload(); + + void unload(); + + void flushNetwork(); + + void clearHandlers(); + + void event(Channels channel, const Mana::Event &event); + + protected: + MessageHandlerPtr mBeingHandler; + MessageHandlerPtr mBuySellHandler; + MessageHandlerPtr mCharHandler; + MessageHandlerPtr mChatHandler; + MessageHandlerPtr mEffectHandler; + MessageHandlerPtr mGameHandler; + MessageHandlerPtr mGuildHandler; + MessageHandlerPtr mInventoryHandler; + MessageHandlerPtr mItemHandler; + MessageHandlerPtr mLoginHandler; + MessageHandlerPtr mNpcHandler; + MessageHandlerPtr mPartyHandler; + MessageHandlerPtr mPlayerHandler; + MessageHandlerPtr mTradeHandler; + MessageHandlerPtr mSpecialHandler; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_GENERALHANDLER_H diff --git a/src/net/manaserv/guildhandler.cpp b/src/net/manaserv/guildhandler.cpp new file mode 100644 index 000000000..26af3eff1 --- /dev/null +++ b/src/net/manaserv/guildhandler.cpp @@ -0,0 +1,360 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/guildhandler.h" + +#include "event.h" +#include "guild.h" +#include "log.h" +#include "localplayer.h" +#include "channel.h" +#include "channelmanager.h" + +#include "gui/widgets/channeltab.h" +#include "gui/chat.h" +#include "gui/socialwindow.h" + +#include "net/messagein.h" +#include "net/net.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +extern Net::GuildHandler *guildHandler; + +namespace ManaServ +{ + +extern Connection *chatServerConnection; + +GuildHandler::GuildHandler() +{ + static const Uint16 _messages[] = + { + CPMSG_GUILD_CREATE_RESPONSE, + CPMSG_GUILD_INVITE_RESPONSE, + CPMSG_GUILD_ACCEPT_RESPONSE, + CPMSG_GUILD_GET_MEMBERS_RESPONSE, + CPMSG_GUILD_UPDATE_LIST, + CPMSG_GUILD_INVITED, + CPMSG_GUILD_REJOIN, + CPMSG_GUILD_QUIT_RESPONSE, + 0 + }; + handledMessages = _messages; + + guildHandler = this; +} + +void GuildHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case CPMSG_GUILD_CREATE_RESPONSE: + { + logger->log1("Received CPMSG_GUILD_CREATE_RESPONSE"); + if (msg.readInt8() == ERRMSG_OK) + { + // TODO - Acknowledge guild was created + SERVER_NOTICE(_("Guild created.")) + joinedGuild(msg); + } + else + { + SERVER_NOTICE(_("Error creating guild.")) + } + } break; + + case CPMSG_GUILD_INVITE_RESPONSE: + { + logger->log1("Received CPMSG_GUILD_INVITE_RESPONSE"); + if (msg.readInt8() == ERRMSG_OK) + { + // TODO - Acknowledge invite was sent + SERVER_NOTICE(_("Invite sent.")) + } + } break; + + case CPMSG_GUILD_ACCEPT_RESPONSE: + { + logger->log1("Received CPMSG_GUILD_ACCEPT_RESPONSE"); + if (msg.readInt8() == ERRMSG_OK) + { + // TODO - Acknowledge accepted into guild + joinedGuild(msg); + } + } break; + + case CPMSG_GUILD_GET_MEMBERS_RESPONSE: + { + logger->log1("Received CPMSG_GUILD_GET_MEMBERS_RESPONSE"); + if (msg.readInt8() == ERRMSG_OK) + { + std::string name; + bool online; + Guild *guild; + GuildMember *member; + + short guildId = msg.readInt16(); + guild = player_node->getGuild(guildId); + + if (!guild) + return; + + guild->clearMembers(); + + while (msg.getUnreadLength()) + { + name = msg.readString(); + online = msg.readInt8(); + if (name != "") + { + member = guild->addMember(name); + member->setOnline(online); + } + } + } + } break; + + case CPMSG_GUILD_UPDATE_LIST: + { + logger->log1("Received CPMSG_GUILD_UPDATE_LIST"); + short guildId = msg.readInt16(); + std::string name = msg.readString(); + char eventId = msg.readInt8(); + GuildMember *member; + + Guild *guild = player_node->getGuild(guildId); + if (guild) + { + switch(eventId) + { + case GUILD_EVENT_NEW_PLAYER: + member = guild->addMember(name); + member->setOnline(true); + break; + + case GUILD_EVENT_LEAVING_PLAYER: + guild->removeMember(name); + break; + + case GUILD_EVENT_ONLINE_PLAYER: + member = guild->getMember(name); + if (member) + { + member->setOnline(true); + } + break; + + case GUILD_EVENT_OFFLINE_PLAYER: + member = guild->getMember(name); + if (member) + { + member->setOnline(false); + } + break; + + default: + logger->log1("Invalid guild event"); + } + } + } break; + + case CPMSG_GUILD_INVITED: + { + logger->log1("Received CPMSG_GUILD_INVITED"); + std::string inviterName = msg.readString(); + std::string guildName = msg.readString(); + int guildId = msg.readInt16(); + + // Open a dialog asking if the player accepts joining the guild. + socialWindow->showGuildInvite(guildName, guildId, inviterName); + } break; + + case CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE: + { + logger->log1("Received CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE"); + + if (msg.readInt8() == ERRMSG_OK) + { + // promotion succeeded + SERVER_NOTICE(_("Member was promoted successfully.")) + } + else + { + // promotion failed + SERVER_NOTICE(_("Failed to promote member.")) + } + } + + case CPMSG_GUILD_REJOIN: + { + logger->log1("Received CPMSG_GUILD_REJOIN"); + + joinedGuild(msg); + } break; + + case CPMSG_GUILD_QUIT_RESPONSE: + { + logger->log1("Received CPMSG_GUILD_QUIT_RESPONSE"); + + if (msg.readInt8() == ERRMSG_OK) + { + // Must remove tab first, as it wont find the guild + // name after its removed from the player + int guildId = msg.readInt16(); + Guild *guild = player_node->getGuild(guildId); + if (guild) + { + Channel *channel = channelManager->findByName( + guild->getName()); + channelManager->removeChannel(channel); + player_node->removeGuild(guildId); + } + } + } break; + default: break; + } +} + +void GuildHandler::joinedGuild(Net::MessageIn &msg) +{ + std::string guildName = msg.readString(); + short guildId = msg.readInt16(); + short permissions = msg.readInt16(); + short channelId = msg.readInt16(); + std::string announcement = msg.readString(); + + // Add guild to player + Guild *guild = Guild::getGuild(guildId); + guild->setName(guildName); + guild->setRights(permissions); + player_node->addGuild(guild); + Net::getGuildHandler()->memberList(guildId); + + // Automatically create the guild channel + // COMMENT: Should this go here?? + Channel *channel = new Channel(channelId, guildName, announcement); + channelManager->addChannel(channel); + channel->getTab()->chatLog(strprintf(_("Topic: %s"), announcement.c_str()), + BY_CHANNEL); +} + +void GuildHandler::create(const std::string &name) +{ + MessageOut msg(PCMSG_GUILD_CREATE); + msg.writeString(name); + chatServerConnection->send(msg); +} + +void GuildHandler::invite(int guildId, const std::string &name) +{ + MessageOut msg(PCMSG_GUILD_INVITE); + msg.writeInt16(guildId); + msg.writeString(name); + chatServerConnection->send(msg); +} + +void GuildHandler::invite(int guildId, Being *being) +{ + invite(guildId, being->getName()); +} + +void GuildHandler::inviteResponse(int guildId _UNUSED_, bool response _UNUSED_) +{ + /*MessageOut msg(PCMSG_GUILD_ACCEPT); + msg.writeString(name); + chatServerConnection->send(msg);*/ +} + +void GuildHandler::leave(int guildId) +{ + MessageOut msg(PCMSG_GUILD_QUIT); + msg.writeInt16(guildId); + chatServerConnection->send(msg); +} + +void GuildHandler::kick(GuildMember *member _UNUSED_, + std::string reason _UNUSED_) +{ + // TODO +} + +void GuildHandler::chat(int guildId _UNUSED_, const std::string &text _UNUSED_) +{ + // TODO +} + +void GuildHandler::memberList(int guildId) +{ + MessageOut msg(PCMSG_GUILD_GET_MEMBERS); + msg.writeInt16(guildId); + chatServerConnection->send(msg); +} + +void GuildHandler::info(int guildId _UNUSED_) +{ + // TODO +} + +void GuildHandler::changeMemberPostion(GuildMember *member _UNUSED_, + int level _UNUSED_) +{ + /*MessageOut msg(PCMSG_GUILD_PROMOTE_MEMBER); + msg.writeInt16(guildId); + msg.writeString(name); + msg.writeInt8(level); + chatServerConnection->send(msg);*/ +} + +void GuildHandler::requestAlliance(int guildId _UNUSED_, + int otherGuildId _UNUSED_) +{ + // TODO +} + +void GuildHandler::requestAllianceResponse(int guildId _UNUSED_, + int otherGuildId _UNUSED_, + bool response _UNUSED_) +{ + // TODO +} + +void GuildHandler::endAlliance(int guildId _UNUSED_, int otherGuildId _UNUSED_) +{ + // TODO +} + +void GuildHandler::changeNotice(int guildId _UNUSED_, + std::string msg1 _UNUSED_, + std::string msg2 _UNUSED_) +{ + // TODO +} + +} // namespace ManaServ diff --git a/src/net/manaserv/guildhandler.h b/src/net/manaserv/guildhandler.h new file mode 100644 index 000000000..18862398b --- /dev/null +++ b/src/net/manaserv/guildhandler.h @@ -0,0 +1,84 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_GUILDHANDLER_H +#define NET_MANASERV_GUILDHANDLER_H + +#include "net/guildhandler.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class GuildHandler : public Net::GuildHandler, public MessageHandler +{ +public: + GuildHandler(); + + bool isSupported() + { return true; } + + void handleMessage(Net::MessageIn &msg); + + void create(const std::string &name); + + void invite(int guildId, const std::string &name); + + void invite(int guidId, Being *being); + + void inviteResponse(int guidId, bool response); + + void leave(int guildId); + + void kick(GuildMember *member, std::string reason = ""); + + void chat(int guildId, const std::string &text); + + void memberList(int guildId); + + void info(int guildId _UNUSED_); + + void changeMemberPostion(GuildMember *member, int level); + + void requestAlliance(int guildId, int otherGuildId); + + void requestAllianceResponse(int guildId, int otherGuildId, + bool response); + + void endAlliance(int guildId, int otherGuildId); + + void changeNotice(int guildId, std::string msg1, + std::string msg2); + +protected: + void joinedGuild(Net::MessageIn &msg); +}; + +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/internal.cpp b/src/net/manaserv/internal.cpp new file mode 100644 index 000000000..fcba3fb40 --- /dev/null +++ b/src/net/manaserv/internal.cpp @@ -0,0 +1,27 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/internal.h" + +namespace ManaServ +{ + int connections = 0; +} diff --git a/src/net/manaserv/internal.h b/src/net/manaserv/internal.h new file mode 100644 index 000000000..f600c207e --- /dev/null +++ b/src/net/manaserv/internal.h @@ -0,0 +1,30 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_INTERNAL_H +#define NET_MANASERV_INTERNAL_H + +namespace ManaServ +{ + extern int connections; +} + +#endif diff --git a/src/net/manaserv/inventoryhandler.cpp b/src/net/manaserv/inventoryhandler.cpp new file mode 100644 index 000000000..1d7736f1a --- /dev/null +++ b/src/net/manaserv/inventoryhandler.cpp @@ -0,0 +1,219 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/inventoryhandler.h" + +#include "equipment.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "gui/chat.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "resources/iteminfo.h" + +#include "log.h" // <<< REMOVE ME! + +extern Net::InventoryHandler *inventoryHandler; + +namespace ManaServ +{ + +extern Connection *gameServerConnection; + +InventoryHandler::InventoryHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_INVENTORY_FULL, + GPMSG_INVENTORY, + GPMSG_EQUIP, + 0 + }; + handledMessages = _messages; + inventoryHandler = this; +} + +void InventoryHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_INVENTORY_FULL: + { + PlayerInfo::clearInventory(); + PlayerInfo::getEquipment()->setBackend(&mEquips); + int count = msg.readInt16(); + while (count--) + { + unsigned int slot = msg.readInt16(); + int id = msg.readInt16(); + unsigned int amount = msg.readInt16(); + PlayerInfo::setInventoryItem(slot, id, amount, 0); + } + while (msg.getUnreadLength()) + { + unsigned int slot = msg.readInt8(); + unsigned int ref = msg.readInt16(); + + mEquips.addEquipment(slot, ref); + } + } + break; + + case GPMSG_INVENTORY: + while (msg.getUnreadLength()) + { + unsigned int slot = msg.readInt16(); + int id = msg.readInt16(); + unsigned int amount = id ? msg.readInt16() : 0; + PlayerInfo::setInventoryItem(slot, id, amount, 0); + } + break; + + case GPMSG_EQUIP: + while (msg.getUnreadLength()) + { + unsigned int ref = msg.readInt16(); + int count = msg.readInt8(); + while (count--) + { + unsigned int slot = msg.readInt8(); + unsigned int used = msg.readInt8(); + + mEquips.setEquipment(slot, used, ref); + } + } + break; + default: + break; + } +} + +void InventoryHandler::equipItem(const Item *item) +{ + MessageOut msg(PGMSG_EQUIP); + msg.writeInt8(item->getInvIndex()); + gameServerConnection->send(msg); +} + +void InventoryHandler::unequipItem(const Item *item) +{ + MessageOut msg(PGMSG_UNEQUIP); + msg.writeInt8(item->getInvIndex()); + gameServerConnection->send(msg); + +/* + // Tidy equipment directly to avoid weapon still shown bug, for instance + int equipSlot = item->getInvIndex(); + logger->log("Unequipping %d", equipSlot); + mEquips.setEquipment(equipSlot, 0); +*/ +} + +void InventoryHandler::useItem(const Item *item) +{ + MessageOut msg(PGMSG_USE_ITEM); + msg.writeInt8(item->getInvIndex()); + gameServerConnection->send(msg); +} + +void InventoryHandler::dropItem(const Item *item, int amount) +{ + MessageOut msg(PGMSG_DROP); + msg.writeInt8(item->getInvIndex()); + msg.writeInt8(amount); + gameServerConnection->send(msg); +} + +bool InventoryHandler::canSplit(const Item *item) +{ + return item && !item->isEquipment() && item->getQuantity() > 1; +} + +void InventoryHandler::splitItem(const Item *item, int amount) +{ + int newIndex = PlayerInfo::getInventory()->getFreeSlot(); + if (newIndex > Inventory::NO_SLOT_INDEX) + { + MessageOut msg(PGMSG_MOVE_ITEM); + msg.writeInt8(item->getInvIndex()); + msg.writeInt8(newIndex); + msg.writeInt8(amount); + gameServerConnection->send(msg); + } +} + +void InventoryHandler::moveItem(int oldIndex, int newIndex) +{ + if (oldIndex == newIndex) + return; + + MessageOut msg(PGMSG_MOVE_ITEM); + msg.writeInt8(oldIndex); + msg.writeInt8(newIndex); + msg.writeInt8(PlayerInfo::getInventory()->getItem(oldIndex) + ->getQuantity()); + gameServerConnection->send(msg); +} + +void InventoryHandler::openStorage(int type _UNUSED_) +{ + // TODO +} + +void InventoryHandler::closeStorage(int type _UNUSED_) +{ + // TODO +} + +void InventoryHandler::moveItem(int source _UNUSED_, int slot _UNUSED_, + int amount _UNUSED_, int destination _UNUSED_) +{ + // TODO +} + +size_t InventoryHandler::getSize(int type) const +{ + switch (type) + { + case Inventory::INVENTORY: + case Inventory::TRADE: + return 50; + case Inventory::STORAGE: + return 300; + default: + return 0; + } +} + +int InventoryHandler::convertFromServerSlot(int eAthenaSlot) +{ + return eAthenaSlot; +} + +} // namespace ManaServ diff --git a/src/net/manaserv/inventoryhandler.h b/src/net/manaserv/inventoryhandler.h new file mode 100644 index 000000000..07d168ce7 --- /dev/null +++ b/src/net/manaserv/inventoryhandler.h @@ -0,0 +1,109 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_INVENTORYHANDLER_H +#define NET_MANASERV_INVENTORYHANDLER_H + +#include "equipment.h" + +#include "net/inventoryhandler.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class EquipBackend : public Equipment::Backend +{ + public: + EquipBackend() + { memset(mEquipment, 0, sizeof(mEquipment)); } + + Item *getEquipment(int index) const + { return mEquipment[index]; } + + void clear() + { + for (int i = 0; i < EQUIPMENT_SIZE; ++i) + delete mEquipment[i]; + + std::fill_n(mEquipment, EQUIPMENT_SIZE, (Item*) 0); + } + + void setEquipment(unsigned int slot, unsigned int used, int reference) + { + printf("Equip: %d at %dx%d\n", reference, slot, used); + } + + void addEquipment(unsigned int slot, int reference) + { + printf("Equip: %d at %d\n", reference, slot); + } + + private: + Item *mEquipment[EQUIPMENT_SIZE]; +}; + +class InventoryHandler : public MessageHandler, Net::InventoryHandler +{ + public: + InventoryHandler(); + + void handleMessage(Net::MessageIn &msg); + + void equipItem(const Item *item); + + void unequipItem(const Item *item); + + void useItem(const Item *item); + + void dropItem(const Item *item, int amount); + + bool canSplit(const Item *item); + + void splitItem(const Item *item, int amount); + + void moveItem(int oldIndex, int newIndex); + + void openStorage(int type); + + void closeStorage(int type); + + void moveItem(int source, int slot, int amount, + int destination); + + size_t getSize(int type) const; + + int convertFromServerSlot(int eAthenaSlot); + + private: + EquipBackend mEquips; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_INVENTORYHANDLER_H diff --git a/src/net/manaserv/itemhandler.cpp b/src/net/manaserv/itemhandler.cpp new file mode 100644 index 000000000..73320ecfb --- /dev/null +++ b/src/net/manaserv/itemhandler.cpp @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/itemhandler.h" + +#include "actorspritemanager.h" + +#include "net/manaserv/protocol.h" +#include "net/manaserv/messagein.h" + +#include "game.h" +#include "map.h" +#include "log.h" + +namespace ManaServ +{ + +ItemHandler::ItemHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_ITEMS, + GPMSG_ITEM_APPEAR, + 0 + }; + handledMessages = _messages; +} + +void ItemHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_ITEM_APPEAR: + case GPMSG_ITEMS: + { + while (msg.getUnreadLength()) + { + int itemId = msg.readInt16(); + int x = msg.readInt16(); + int y = msg.readInt16(); + int id = (x << 16) | y; // dummy id + + if (itemId) + { + if (Game *game = Game::instance()) + { + if (Map *map = game->getCurrentMap()) + { + actorSpriteManager->createItem(id, itemId, + x / map->getTileWidth(), + y / map->getTileHeight(), + 0); + } + else + { + logger->log( + "ItemHandler: An item wasn't created " + "because of Game/Map not initialized..."); + } + } + } + else if (FloorItem *item = actorSpriteManager->findItem(id)) + { + actorSpriteManager->destroy(item); + } + } + } break; + default: break; + } +} + +} // namespace ManaServ diff --git a/src/net/manaserv/itemhandler.h b/src/net/manaserv/itemhandler.h new file mode 100644 index 000000000..58f3695fd --- /dev/null +++ b/src/net/manaserv/itemhandler.h @@ -0,0 +1,40 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_ITEMHANDLER_H +#define NET_MANASERV_ITEMHANDLER_H + +#include "net/manaserv/messagehandler.h" + +namespace ManaServ +{ + +class ItemHandler : public MessageHandler +{ + public: + ItemHandler(); + + void handleMessage(Net::MessageIn &msg); +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_ITEMHANDLER_H diff --git a/src/net/manaserv/loginhandler.cpp b/src/net/manaserv/loginhandler.cpp new file mode 100644 index 000000000..1588d762d --- /dev/null +++ b/src/net/manaserv/loginhandler.cpp @@ -0,0 +1,479 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/loginhandler.h" + +#include "client.h" +#include "log.h" + +#include "net/logindata.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "utils/gettext.h" +#include "utils/sha256.h" + +extern Net::LoginHandler *loginHandler; + +namespace ManaServ +{ + +extern Connection *accountServerConnection; +extern std::string netToken; + +LoginHandler::LoginHandler() +{ + static const Uint16 _messages[] = + { + APMSG_LOGIN_RESPONSE, + APMSG_REGISTER_RESPONSE, + APMSG_RECONNECT_RESPONSE, + APMSG_PASSWORD_CHANGE_RESPONSE, + APMSG_EMAIL_CHANGE_RESPONSE, + APMSG_LOGOUT_RESPONSE, + APMSG_UNREGISTER_RESPONSE, + APMSG_REGISTER_INFO_RESPONSE, + 0 + }; + handledMessages = _messages; + loginHandler = this; +} + +void LoginHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case APMSG_LOGIN_RESPONSE: + handleLoginResponse(msg); + break; + + case APMSG_REGISTER_RESPONSE: + handleRegisterResponse(msg); + break; + + case APMSG_RECONNECT_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful login + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_CHAR_SELECT); + } + // Login failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Wrong magic_token."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Already logged in."); + break; + case LOGIN_BANNED: + errorMessage = _("Account banned."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ERROR); + } + } + break; + + case APMSG_PASSWORD_CHANGE_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful pass change + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_CHANGEPASSWORD_SUCCESS); + } + // pass change failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("New password incorrect."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Old password incorrect."); + break; + case ERRMSG_NO_LOGIN: + errorMessage = + _("Account not connected. Please login first."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + + case APMSG_EMAIL_CHANGE_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful pass change + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_CHANGEEMAIL_SUCCESS); + } + // pass change failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("New email address incorrect."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Old email address incorrect."); + break; + case ERRMSG_NO_LOGIN: + errorMessage = + _("Account not connected. Please login first."); + break; + case ERRMSG_EMAIL_ALREADY_EXISTS: + errorMessage = + _("The new email address already exists."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + case APMSG_LOGOUT_RESPONSE: + { + int errMsg = msg.readInt8(); + + // Successful logout + if (errMsg == ERRMSG_OK) + { + // TODO: handle logout + } + // Logout failed + else + { + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = "Accountserver: Not logged in"; + break; + default: + errorMessage = "Accountserver: Unknown error"; + break; + } + Client::setState(STATE_ERROR); + } + } + break; + case APMSG_UNREGISTER_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful unregistration + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_UNREGISTER); + } + // Unregistration failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = + "Accountserver: Wrong username or password"; + break; + default: + errorMessage = "Accountserver: Unknown error"; + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + + case APMSG_REGISTER_INFO_RESPONSE: + { + int allowed = msg.readInt8(); + + if (allowed) + { + mMinUserNameLength = msg.readInt8(); + mMaxUserNameLength = msg.readInt8(); + std::string captchaURL = msg.readString(); + std::string captchaInstructions = msg.readString(); + + printf("%s: %s\n", captchaURL.c_str(), captchaInstructions.c_str()); + + Client::setState(STATE_REGISTER); + } + else + { + errorMessage = msg.readString(); + + if (errorMessage.empty()) + errorMessage = _("Client registration is not allowed. " + "Please contact server administration."); + Client::setState(STATE_LOGIN_ERROR); + } + } + break; + default: + break; + } +} + +void LoginHandler::handleLoginResponse(Net::MessageIn &msg) +{ + const int errMsg = msg.readInt8(); + + if (errMsg == ERRMSG_OK) + { + readServerInfo(msg); + // No worlds atm, but future use :-D + Client::setState(STATE_WORLD_SELECT); + } + else + { + switch (errMsg) + { + case LOGIN_INVALID_VERSION: + errorMessage = _("Client version is too old."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Wrong username or password."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Already logged in."); + break; + case LOGIN_BANNED: + errorMessage = _("Account banned"); + break; + case LOGIN_INVALID_TIME: + errorMessage = _("Login attempt too soon after previous " + "attempt."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_LOGIN_ERROR); + } +} + +void LoginHandler::handleRegisterResponse(Net::MessageIn &msg) +{ + const int errMsg = msg.readInt8(); + + if (errMsg == ERRMSG_OK) + { + readServerInfo(msg); + Client::setState(STATE_WORLD_SELECT); + } + else + { + switch (errMsg) + { + case REGISTER_INVALID_VERSION: + errorMessage = _("Client version is too old."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Wrong username, password or email address."); + break; + case REGISTER_EXISTS_USERNAME: + errorMessage = _("Username already exists."); + break; + case REGISTER_EXISTS_EMAIL: + errorMessage = _("Email address already exists."); + break; + case REGISTER_CAPTCHA_WRONG: + errorMessage = _("You took too long with the captcha or your " + "response was incorrect."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_LOGIN_ERROR); + } +} + +void LoginHandler::readServerInfo(Net::MessageIn &msg) +{ + // Safety check for outdated manaserv versions (remove me later) + if (msg.getUnreadLength() == 0) + return; + + // Set the update host when included in the message + const std::string updateHost = msg.readString(); + if (!updateHost.empty()) + mLoginData->updateHost = updateHost; + else + logger->log1("Warning: server does not have an update host set!"); + + // Read the client data folder for dynamic data loading. + // This is only used by the QT client. + msg.readString(); + + // Read the number of character slots + mLoginData->characterSlots = msg.readInt8(); +} + +void LoginHandler::connect() +{ + accountServerConnection->connect(mServer.hostname, mServer.port); +} + +bool LoginHandler::isConnected() +{ + return accountServerConnection->isConnected(); +} + +void LoginHandler::disconnect() +{ + accountServerConnection->disconnect(); + + if (Client::getState() == STATE_CONNECT_GAME) + { + Client::setState(STATE_GAME); + } +} + +bool LoginHandler::isRegistrationEnabled() +{ + return true; +} + +void LoginHandler::getRegistrationDetails() +{ + MessageOut msg(PAMSG_REQUEST_REGISTER_INFO); + accountServerConnection->send(msg); +} + +unsigned int LoginHandler::getMinUserNameLength() const +{ + return mMinUserNameLength; +} + +unsigned int LoginHandler::getMaxUserNameLength() const +{ + return mMaxUserNameLength; +} + +void LoginHandler::loginAccount(LoginData *loginData) +{ + mLoginData = loginData; + + MessageOut msg(PAMSG_LOGIN); + + msg.writeInt32(0); // client version + msg.writeString(loginData->username); + msg.writeString(sha256(loginData->username + loginData->password)); + + accountServerConnection->send(msg); +} + +void LoginHandler::logout() +{ + MessageOut msg(PAMSG_LOGOUT); + accountServerConnection->send(msg); +} + +void LoginHandler::changeEmail(const std::string &email) +{ + MessageOut msg(PAMSG_EMAIL_CHANGE); + + // Email is sent clearly so the server can validate the data. + // Encryption is assumed server-side. + msg.writeString(email); + + accountServerConnection->send(msg); +} + +void LoginHandler::changePassword(const std::string &username, + const std::string &oldPassword, + const std::string &newPassword) +{ + MessageOut msg(PAMSG_PASSWORD_CHANGE); + + // Change password using SHA2 encryption + msg.writeString(sha256(username + oldPassword)); + msg.writeString(sha256(username + newPassword)); + + accountServerConnection->send(msg); +} + +void LoginHandler::chooseServer(unsigned int server _UNUSED_) +{ + // TODO +} + +void LoginHandler::registerAccount(LoginData *loginData) +{ + mLoginData = loginData; + + MessageOut msg(PAMSG_REGISTER); + + msg.writeInt32(0); // client version + msg.writeString(loginData->username); + // Use a hashed password for privacy reasons + msg.writeString(sha256(loginData->username + loginData->password)); + msg.writeString(loginData->email); + msg.writeString(loginData->captchaResponse); + + accountServerConnection->send(msg); +} + +void LoginHandler::unregisterAccount(const std::string &username, + const std::string &password) +{ + MessageOut msg(PAMSG_UNREGISTER); + + msg.writeString(username); + msg.writeString(sha256(username + password)); + + accountServerConnection->send(msg); +} + +Worlds LoginHandler::getWorlds() const +{ + return Worlds(); +} + +void LoginHandler::reconnect() +{ + MessageOut msg(PAMSG_RECONNECT); + msg.writeString(netToken, 32); + accountServerConnection->send(msg); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/loginhandler.h b/src/net/manaserv/loginhandler.h new file mode 100644 index 000000000..1c47184a7 --- /dev/null +++ b/src/net/manaserv/loginhandler.h @@ -0,0 +1,99 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_LOGINHANDLER_H +#define NET_MANASERV_LOGINHANDLER_H + +#include "net/loginhandler.h" +#include "net/serverinfo.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class LoginData; + +namespace ManaServ +{ + +class LoginHandler : public MessageHandler, public Net::LoginHandler +{ + public: + LoginHandler(); + + void handleMessage(Net::MessageIn &msg); + + void connect(); + + bool isConnected(); + + void disconnect(); + + int supportedOptionalActions() const + { return Unregister | ChangeEmail | SetEmailOnRegister; } + + bool isRegistrationEnabled(); + + void getRegistrationDetails(); + + unsigned int getMinUserNameLength() const; + + unsigned int getMaxUserNameLength() const; + + void loginAccount(LoginData *loginData); + + void logout(); + + void changeEmail(const std::string &email); + + void changePassword(const std::string &username, + const std::string &oldPassword, + const std::string &newPassword); + + void chooseServer(unsigned int server); + + void registerAccount(LoginData *loginData); + + void unregisterAccount(const std::string &username, + const std::string &password); + + Worlds getWorlds() const; + + void reconnect(); + + private: + void handleLoginResponse(Net::MessageIn &msg); + void handleRegisterResponse(Net::MessageIn &msg); + + void readServerInfo(Net::MessageIn &msg); + + LoginData *mLoginData; + unsigned int mMinUserNameLength; + unsigned int mMaxUserNameLength; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_LOGINHANDLER_H diff --git a/src/net/manaserv/messagehandler.cpp b/src/net/manaserv/messagehandler.cpp new file mode 100644 index 000000000..2f4d53dc6 --- /dev/null +++ b/src/net/manaserv/messagehandler.cpp @@ -0,0 +1,36 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/messagehandler.h" + +#include "net/manaserv/network.h" + +#include + +namespace ManaServ +{ + +MessageHandler::~MessageHandler() +{ + unregisterHandler(this); +} + +} diff --git a/src/net/manaserv/messagehandler.h b/src/net/manaserv/messagehandler.h new file mode 100644 index 000000000..2181438f3 --- /dev/null +++ b/src/net/manaserv/messagehandler.h @@ -0,0 +1,44 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_MESSAGEHANDLER_H +#define NET_MANASERV_MESSAGEHANDLER_H + +#include "net/messagehandler.h" + +namespace ManaServ +{ + +/** + * \ingroup Network + */ +class MessageHandler : public Net::MessageHandler +{ + public: + ~MessageHandler(); + +}; + +typedef const std::auto_ptr MessageHandlerPtr; + +} + +#endif // NET_MANASERV_MESSAGEHANDLER_H diff --git a/src/net/manaserv/messagein.cpp b/src/net/manaserv/messagein.cpp new file mode 100644 index 000000000..458aae740 --- /dev/null +++ b/src/net/manaserv/messagein.cpp @@ -0,0 +1,62 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/messagein.h" + +#include "enet/enet.h" + +namespace ManaServ +{ + +MessageIn::MessageIn(const char *data, unsigned int length): + Net::MessageIn(data, length) +{ + // Read the message ID + mId = readInt16(); +} + +Sint16 MessageIn::readInt16() +{ + Sint16 value = -1; + if (mPos + 2 <= mLength) + { + uint16_t t; + memcpy(&t, mData + mPos, 2); + value = (unsigned short) ENET_NET_TO_HOST_16(t); + } + mPos += 2; + return value; +} + +int MessageIn::readInt32() +{ + int value = -1; + if (mPos + 4 <= mLength) + { + uint32_t t; + memcpy(&t, mData + mPos, 4); + value = ENET_NET_TO_HOST_32(t); + } + mPos += 4; + return value; +} + +} diff --git a/src/net/manaserv/messagein.h b/src/net/manaserv/messagein.h new file mode 100644 index 000000000..e3528e5bf --- /dev/null +++ b/src/net/manaserv/messagein.h @@ -0,0 +1,49 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_MESSAGEIN_H +#define NET_MANASERV_MESSAGEIN_H + +#include "net/messagein.h" + +namespace ManaServ +{ + +/** + * Used for parsing an incoming message. + * + * \ingroup Network + */ +class MessageIn : public Net::MessageIn +{ + public: + /** + * Constructor. + */ + MessageIn(const char *data, unsigned int length); + + Sint16 readInt16(); /**< Reads a short. */ + int readInt32(); /**< Reads a long. */ +}; + +} + +#endif // NET_MANASERV_MESSAGEIN_H diff --git a/src/net/manaserv/messageout.cpp b/src/net/manaserv/messageout.cpp new file mode 100644 index 000000000..f4856e640 --- /dev/null +++ b/src/net/manaserv/messageout.cpp @@ -0,0 +1,65 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/messageout.h" + +#include "enet/enet.h" + +#include +#include + +namespace ManaServ +{ + +MessageOut::MessageOut(short id): + Net::MessageOut(id) +{ + writeInt16(id); +} + +MessageOut::~MessageOut() +{ + free(mData); +} + +void MessageOut::expand(size_t bytes) +{ + mData = (char*)realloc(mData, mPos + bytes); + mDataSize = mPos + bytes; +} + +void MessageOut::writeInt16(Sint16 value) +{ + expand(2); + uint16_t t = ENET_HOST_TO_NET_16(value); + memcpy(mData + mPos, &t, 2); + mPos += 2; +} + +void MessageOut::writeInt32(Sint32 value) +{ + expand(4); + uint32_t t = ENET_HOST_TO_NET_32(value); + memcpy(mData + mPos, &t, 4); + mPos += 4; +} + +} // namespace ManaServ diff --git a/src/net/manaserv/messageout.h b/src/net/manaserv/messageout.h new file mode 100644 index 000000000..00ae069de --- /dev/null +++ b/src/net/manaserv/messageout.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_MESSAGEOUT_H +#define NET_MANASERV_MESSAGEOUT_H + +#include "net/messageout.h" + +namespace ManaServ +{ + +class MessageOut : public Net::MessageOut +{ + public: + /** + * Constructor. + */ + MessageOut(short id); + + /** + * Destructor. + */ + ~MessageOut(); + + void writeInt16(Sint16 value); /**< Writes a short. */ + void writeInt32(Sint32 value); /**< Writes a long. */ + + protected: + /** + * Expand the packet data to be able to hold more data. + * + * NOTE: For performance enhancements this method could allocate extra + * memory in advance instead of expanding size every time more data is + * added. + */ + void expand(size_t size); +}; + +} + +#endif // NET_MANASERV_MESSAGEOUT_H diff --git a/src/net/manaserv/network.cpp b/src/net/manaserv/network.cpp new file mode 100644 index 000000000..c8389bb82 --- /dev/null +++ b/src/net/manaserv/network.cpp @@ -0,0 +1,178 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/network.h" + +#include "log.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/internal.h" +#include "net/manaserv/messagehandler.h" +#include "net/manaserv/messagein.h" + +#include "enet/enet.h" + +#include + +/** + * The local host which is shared for all outgoing connections. + */ +namespace +{ + ENetHost *client; +} + +namespace ManaServ +{ + +typedef std::map MessageHandlers; +typedef MessageHandlers::iterator MessageHandlerIterator; +static MessageHandlers mMessageHandlers; + +void initialize() +{ + if (enet_initialize()) + { + logger->error("Failed to initialize ENet."); + } + +#if defined(ENET_VERSION) && ENET_VERSION >= ENET_CUTOFF + client = enet_host_create(NULL, 3, 0, 0, 0); +#else + client = enet_host_create(NULL, 3, 0, 0); +#endif + + if (!client) + { + logger->error("Failed to create the local host."); + } +} + +void finalize() +{ + if (!client) + return; // Wasn't initialized at all + + if (connections) + { + logger->error("Tried to shutdown the network subsystem while there " + "are network connections left!"); + } + + clearNetworkHandlers(); + enet_deinitialize(); +} + +Connection *getConnection() +{ + if (!client) + { + logger->error("Tried to instantiate a network object before " + "initializing the network subsystem!"); + } + + return new Connection(client); +} + +void registerHandler(MessageHandler *handler) +{ + for (const Uint16 *i = handler->handledMessages; *i; i++) + { + mMessageHandlers[*i] = handler; + } +} + +void unregisterHandler(MessageHandler *handler) +{ + for (const Uint16 *i = handler->handledMessages; *i; i++) + { + mMessageHandlers.erase(*i); + } +} + +void clearNetworkHandlers() +{ + mMessageHandlers.clear(); +} + + +/** + * Dispatches a message to the appropriate message handler and + * destroys it afterwards. + */ +namespace +{ + void dispatchMessage(ENetPacket *packet) + { + MessageIn msg((const char *)packet->data, packet->dataLength); + + MessageHandlerIterator iter = mMessageHandlers.find(msg.getId()); + + if (iter != mMessageHandlers.end()) + { + //logger->log("Received packet %x (%i B)", + // msg.getId(), msg.getLength()); + iter->second->handleMessage(msg); + } + else + { + logger->log("Unhandled packet %x (%i B)", + msg.getId(), msg.getLength()); + } + + // Clean up the packet now that we're done using it. + enet_packet_destroy(packet); + } +} + +void flush() +{ + ENetEvent event; + + // Check if there are any new events + while (enet_host_service(client, &event, 0) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + logger->log("Connected to port %d.", event.peer->address.port); + // Store any relevant server information here. + event.peer->data = 0; + break; + + case ENET_EVENT_TYPE_RECEIVE: + dispatchMessage(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + logger->log1("Disconnected."); + // Reset the server information. + event.peer->data = 0; + break; + + case ENET_EVENT_TYPE_NONE: + default: + break; + } + } +} + +} diff --git a/src/net/manaserv/network.h b/src/net/manaserv/network.h new file mode 100644 index 000000000..149f484e5 --- /dev/null +++ b/src/net/manaserv/network.h @@ -0,0 +1,75 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_NETWORK_H +#define NET_MANASERV_NETWORK_H + +#include + +/** + * \ingroup Network + */ +namespace ManaServ +{ + class MessageHandler; + class MessageOut; + + class Connection; + + /** + * Initializes the network subsystem. + */ + void initialize(); + + /** + * Finalizes the network subsystem. + */ + void finalize(); + + /** + * Returns a new Connection object. Should be deleted by the caller. + */ + Connection *getConnection(); + + /** + * Registers a message handler. A message handler handles a certain + * subset of incoming messages. + */ + void registerHandler(MessageHandler *handler); + + /** + * Unregisters a message handler. + */ + void unregisterHandler(MessageHandler *handler); + + /** + * Clears all registered message handlers. + */ + void clearNetworkHandlers(); + + /* + * Handles all events and dispatches incoming messages to the + * registered handlers + */ + void flush(); +} // namespace ManaServ + +#endif diff --git a/src/net/manaserv/npchandler.cpp b/src/net/manaserv/npchandler.cpp new file mode 100644 index 000000000..6d4010e83 --- /dev/null +++ b/src/net/manaserv/npchandler.cpp @@ -0,0 +1,237 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/npchandler.h" + +#include "actorspritemanager.h" + +#include "gui/npcdialog.h" +#include "gui/npcpostdialog.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +extern Net::NpcHandler *npcHandler; + +namespace ManaServ +{ + +extern Connection *gameServerConnection; + +NpcHandler::NpcHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_NPC_CHOICE, + GPMSG_NPC_POST, + GPMSG_NPC_MESSAGE, + GPMSG_NPC_ERROR, + GPMSG_NPC_CLOSE, + GPMSG_NPC_NUMBER, + GPMSG_NPC_STRING, + 0 + }; + handledMessages = _messages; + npcHandler = this; +} + +void NpcHandler::handleMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being || being->getType() != ActorSprite::NPC) + { + return; + } + + int npcId = being->getId(); + NpcDialogs::iterator diag = mNpcDialogs.find(npcId); + NpcDialog *dialog; + + if (diag == mNpcDialogs.end()) + { + if (msg.getId() == GPMSG_NPC_ERROR || msg.getId() == GPMSG_NPC_CLOSE) + return; // Dialog is pointless in these cases + + dialog = new NpcDialog(npcId); + Wrapper wrap; + wrap.dialog = dialog; + mNpcDialogs[npcId] = wrap; + } + else + { + dialog = diag->second.dialog; + } + + switch (msg.getId()) + { + case GPMSG_NPC_CHOICE: + dialog->choiceRequest(); + while (msg.getUnreadLength()) + { + dialog->addChoice(msg.readString()); + } + break; + + case GPMSG_NPC_NUMBER: + { + int min_num = msg.readInt32(); + int max_num = msg.readInt32(); + dialog->integerRequest(msg.readInt32(), min_num, max_num); + break; + } + + case GPMSG_NPC_STRING: + dialog->textRequest(""); + break; + + case GPMSG_NPC_POST: + { + new NpcPostDialog(npcId); + break; + } + + case GPMSG_NPC_ERROR: + dialog->close(); + if (diag != mNpcDialogs.end()) + { + mNpcDialogs.erase(diag); + } + break; + + case GPMSG_NPC_MESSAGE: + dialog->addText(msg.readString(msg.getUnreadLength())); + dialog->showNextButton(); + break; + + case GPMSG_NPC_CLOSE: + dialog->showCloseButton(); + break; + + default: + break; + } +} + +void NpcHandler::talk(int npcId) +{ + MessageOut msg(PGMSG_NPC_TALK); + msg.writeInt16(npcId); + gameServerConnection->send(msg); +} + +void NpcHandler::nextDialog(int npcId) +{ + MessageOut msg(PGMSG_NPC_TALK_NEXT); + msg.writeInt16(npcId); + gameServerConnection->send(msg); +} + +void NpcHandler::closeDialog(int npcId) +{ + MessageOut msg(PGMSG_NPC_TALK_NEXT); + msg.writeInt16(npcId); + gameServerConnection->send(msg); + + NpcDialogs::iterator it = mNpcDialogs.find(npcId); + if (it != mNpcDialogs.end()) + { + (*it).second.dialog->close(); + mNpcDialogs.erase(it); + } +} + +void NpcHandler::listInput(int npcId, unsigned char value) +{ + MessageOut msg(PGMSG_NPC_SELECT); + msg.writeInt16(npcId); + msg.writeInt8(value); + gameServerConnection->send(msg); +} + +void NpcHandler::integerInput(int npcId, int value) +{ + MessageOut msg(PGMSG_NPC_NUMBER); + msg.writeInt16(npcId); + msg.writeInt32(value); + gameServerConnection->send(msg); +} + +void NpcHandler::stringInput(int npcId, const std::string &value) +{ + MessageOut msg(PGMSG_NPC_STRING); + msg.writeInt16(npcId); + msg.writeString(value); + gameServerConnection->send(msg); +} + +void NpcHandler::sendLetter(int npcId _UNUSED_, const std::string &recipient, + const std::string &text) +{ + MessageOut msg(PGMSG_NPC_POST_SEND); + msg.writeString(recipient); + msg.writeString(text); + gameServerConnection->send(msg); +} + +void NpcHandler::startShopping(int beingId _UNUSED_) +{ + // TODO +} + +void NpcHandler::buy(int beingId _UNUSED_) +{ + // TODO +} + +void NpcHandler::sell(int beingId _UNUSED_) +{ + // TODO +} + +void NpcHandler::buyItem(int beingId _UNUSED_, int itemId, int amount) +{ + MessageOut msg(PGMSG_NPC_BUYSELL); + msg.writeInt16(itemId); + msg.writeInt16(amount); + gameServerConnection->send(msg); +} + +void NpcHandler::sellItem(int beingId _UNUSED_, int itemId, int amount) +{ + MessageOut msg(PGMSG_NPC_BUYSELL); + msg.writeInt16(itemId); + msg.writeInt16(amount); + gameServerConnection->send(msg); +} + +void NpcHandler::endShopping(int beingId _UNUSED_) +{ + // TODO +} + +void NpcHandler::clearDialogs() +{ + mNpcDialogs.clear(); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/npchandler.h b/src/net/manaserv/npchandler.h new file mode 100644 index 000000000..5f47ee388 --- /dev/null +++ b/src/net/manaserv/npchandler.h @@ -0,0 +1,89 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_NPCHANDLER_H +#define NET_MANASERV_NPCHANDLER_H + +#include "net/npchandler.h" + +#include "net/manaserv/messagehandler.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class NpcDialog; + +namespace ManaServ +{ + +class NpcHandler : public MessageHandler, public Net::NpcHandler +{ + public: + NpcHandler(); + + void handleMessage(Net::MessageIn &msg); + + void talk(int npcId); + + void nextDialog(int npcId); + + void closeDialog(int npcId); + + void listInput(int npcId, unsigned char value); + + void integerInput(int npcId, int value); + + void stringInput(int npcId, const std::string &value); + + void sendLetter(int npcId, const std::string &recipient, + const std::string &text); + + void startShopping(int beingId); + + void buy(int beingId); + + void sell(int beingId); + + void buyItem(int beingId, int itemId, int amount); + + void sellItem(int beingId, int itemId, int amount); + + void endShopping(int beingId); + + void clearDialogs(); + + private: + typedef struct + { + NpcDialog* dialog; + } Wrapper; + typedef std::map NpcDialogs; + NpcDialogs mNpcDialogs; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_NPCHANDLER_H diff --git a/src/net/manaserv/partyhandler.cpp b/src/net/manaserv/partyhandler.cpp new file mode 100644 index 000000000..3aec20b6b --- /dev/null +++ b/src/net/manaserv/partyhandler.cpp @@ -0,0 +1,197 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/partyhandler.h" + +#include "event.h" +#include "log.h" +#include "localplayer.h" + +#include "gui/socialwindow.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +#define PARTY_ID 1 + +extern Net::PartyHandler *partyHandler; + +namespace ManaServ +{ + +extern Connection *chatServerConnection; + +PartyHandler::PartyHandler(): + mParty(Party::getParty(PARTY_ID)) +{ + static const Uint16 _messages[] = + { + CPMSG_PARTY_INVITE_RESPONSE, + CPMSG_PARTY_INVITED, + CPMSG_PARTY_ACCEPT_INVITE_RESPONSE, + CPMSG_PARTY_QUIT_RESPONSE, + CPMSG_PARTY_NEW_MEMBER, + CPMSG_PARTY_MEMBER_LEFT, + CPMSG_PARTY_REJECTED, + 0 + }; + handledMessages = _messages; + partyHandler = this; +} + +void PartyHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case CPMSG_PARTY_INVITE_RESPONSE: + { + if (msg.readInt8() == ERRMSG_OK) + { + + } + } break; + + case CPMSG_PARTY_INVITED: + { + socialWindow->showPartyInvite(msg.readString()); + } break; + + case CPMSG_PARTY_ACCEPT_INVITE_RESPONSE: + { + if (msg.readInt8() == ERRMSG_OK) + { + // + SERVER_NOTICE(_("Joined party.")); + } + } + + case CPMSG_PARTY_QUIT_RESPONSE: + { + if (msg.readInt8() == ERRMSG_OK) + { + mParty->clearMembers(); + player_node->setParty(NULL); + } + } break; + + case CPMSG_PARTY_NEW_MEMBER: + { + int id = msg.readInt16(); // being id + std::string name = msg.readString(); + + SERVER_NOTICE(strprintf(_("%s joined the party."), + name.c_str())); + + if (id == player_node->getId()) + player_node->setParty(mParty); + + mParty->addMember(id, name); + } break; + + case CPMSG_PARTY_MEMBER_LEFT: + { + mParty->removeMember(msg.readString()); + } break; + + case CPMSG_PARTY_REJECTED: + { + std::string name = msg.readString(); + SERVER_NOTICE(strprintf( + _("%s rejected your invite."), name.c_str())); + } break; + default: + break; + } +} + +void PartyHandler::create(const std::string &name _UNUSED_) +{ + // TODO +} + +void PartyHandler::join(int partyId _UNUSED_) +{ + // TODO +} + +void PartyHandler::invite(Being *being) +{ + invite(being->getName()); +} + +void PartyHandler::invite(const std::string &name) +{ + MessageOut msg(PCMSG_PARTY_INVITE); + + msg.writeString(name); + + chatServerConnection->send(msg); +} + +void PartyHandler::inviteResponse(const std::string &inviter, bool accept) +{ + MessageOut msg = MessageOut(accept ? PCMSG_PARTY_ACCEPT_INVITE : + PCMSG_PARTY_REJECT_INVITE); + + msg.writeString(inviter); + + chatServerConnection->send(msg); +} + +void PartyHandler::leave() +{ + MessageOut msg(PCMSG_PARTY_QUIT); + + chatServerConnection->send(msg); +} + +void PartyHandler::kick(Being *being _UNUSED_) +{ + // TODO +} + +void PartyHandler::kick(const std::string &name _UNUSED_) +{ + // TODO +} + +void PartyHandler::chat(const std::string &text _UNUSED_) +{ + // TODO +} + +void PartyHandler::requestPartyMembers() +{ + //MessageOut msg(PCMSG_GUILD_GET_MEMBERS); + + //msg.writeInt16(guildId); + + //chatServerConnection->send(msg); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/partyhandler.h b/src/net/manaserv/partyhandler.h new file mode 100644 index 000000000..b2c8ce49e --- /dev/null +++ b/src/net/manaserv/partyhandler.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_PARTYHANDLER_H +#define NET_MANASERV_PARTYHANDLER_H + +#include "net/partyhandler.h" + +#include "net/manaserv/messagehandler.h" + +#include "party.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class PartyHandler : public MessageHandler, public Net::PartyHandler +{ +public: + PartyHandler(); + + void handleMessage(Net::MessageIn &msg); + + void create(const std::string &name = ""); + + void join(int partyId); + + void invite(Being *being); + + void invite(const std::string &name); + + void inviteResponse(const std::string &inviter, bool accept); + + void leave(); + + void kick(Being *being); + + void kick(const std::string &name); + + void chat(const std::string &text); + + void requestPartyMembers(); + + PartyShare getShareExperience() { return PARTY_SHARE_NO; } + + void setShareExperience(PartyShare share _UNUSED_) {} + + PartyShare getShareItems() { return PARTY_SHARE_NO; } + + void setShareItems(PartyShare share _UNUSED_) {} +private: + Party *mParty; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_PARTYHANDLER_H diff --git a/src/net/manaserv/playerhandler.cpp b/src/net/manaserv/playerhandler.cpp new file mode 100644 index 000000000..3af82486c --- /dev/null +++ b/src/net/manaserv/playerhandler.cpp @@ -0,0 +1,440 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/playerhandler.h" +#include "net/manaserv/beinghandler.h" + +#include "client.h" +#include "effectmanager.h" +#include "game.h" +#include "localplayer.h" +#include "log.h" +#include "particle.h" +#include "playerinfo.h" +#include "configuration.h" + +#include "gui/chat.h" +#include "gui/gui.h" +#include "gui/okdialog.h" +#include "gui/viewport.h" + +#include "net/net.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/defines.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/npchandler.h" +#include "net/manaserv/protocol.h" +#include "net/manaserv/attributes.h" + +/** + * Max. distance we are willing to scroll after a teleport; + * everything beyond will reset the port hard. + * 32 is the nominal tile width/height. + * @todo: Make this parameter read from config. + */ +static const int MAP_TELEPORT_SCROLL_DISTANCE = 8 * 32; + +extern Net::PlayerHandler *playerHandler; + +namespace ManaServ +{ + +void RespawnRequestListener::action(const gcn::ActionEvent &event _UNUSED_) +{ + Net::getPlayerHandler()->respawn(); + + ManaServ::NpcHandler *handler = + static_cast(Net::getNpcHandler()); + handler->clearDialogs(); +} + +extern Connection *gameServerConnection; + +PlayerHandler::PlayerHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_PLAYER_MAP_CHANGE, + GPMSG_PLAYER_SERVER_CHANGE, + GPMSG_PLAYER_ATTRIBUTE_CHANGE, + GPMSG_PLAYER_EXP_CHANGE, + GPMSG_LEVELUP, + GPMSG_LEVEL_PROGRESS, + GPMSG_RAISE_ATTRIBUTE_RESPONSE, + GPMSG_LOWER_ATTRIBUTE_RESPONSE, + GPMSG_SPECIAL_STATUS, + 0 + }; + handledMessages = _messages; + playerHandler = this; +} + +void PlayerHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_PLAYER_MAP_CHANGE: + handleMapChangeMessage(msg); + break; + + case GPMSG_PLAYER_SERVER_CHANGE: + { // TODO: Implement reconnecting to another game server + std::string token = msg.readString(32); + std::string address = msg.readString(); + int port = msg.readInt16(); + logger->log("Changing server to %s:%d", address.c_str(), port); + } break; + + case GPMSG_PLAYER_ATTRIBUTE_CHANGE: + { + logger->log1("ATTRIBUTE UPDATE:"); + while (msg.getUnreadLength()) + { + int attrId = msg.readInt16(); + double base = msg.readInt32() / 256.0; + double value = msg.readInt32() / 256.0; + + // Set the core player attribute the stat + // depending on attribute link. + int playerInfoId = + Attributes::getPlayerInfoIdFromAttrId(attrId); + if (playerInfoId > -1) + { + PlayerInfo::setAttribute(playerInfoId, value); + } + else + { + PlayerInfo::setStatBase(attrId, base); + PlayerInfo::setStatMod(attrId, value - base); + } + } + } break; + + case GPMSG_PLAYER_EXP_CHANGE: + { + logger->log1("EXP Update"); + while (msg.getUnreadLength()) + { + int skill = msg.readInt16(); + int current = msg.readInt32(); + int next = msg.readInt32(); + + PlayerInfo::setStatExperience(skill, current, next); + } + } break; + + case GPMSG_LEVELUP: + { + PlayerInfo::setAttribute(LEVEL, msg.readInt16()); + PlayerInfo::setAttribute(CHAR_POINTS, msg.readInt16()); + PlayerInfo::setAttribute(CORR_POINTS, msg.readInt16()); + Particle* effect = particleEngine->addEffect( + paths.getStringValue("particles") + + paths.getStringValue("levelUpEffectFile") + , 0, 0); + player_node->controlParticle(effect); + } break; + + + case GPMSG_LEVEL_PROGRESS: + { + PlayerInfo::setAttribute(EXP, msg.readInt8()); + } break; + + + case GPMSG_RAISE_ATTRIBUTE_RESPONSE: + { + int errCode = msg.readInt8(); + int attrNum = msg.readInt16(); + switch (errCode) + { + case ATTRIBMOD_OK: + { + // feel(acknowledgment); + } break; + case ATTRIBMOD_INVALID_ATTRIBUTE: + { + logger->log("Warning: Server denied increase of attribute" + " %d (unknown attribute) ", attrNum); + } break; + case ATTRIBMOD_NO_POINTS_LEFT: + { + // when the server says "you got no points" it + // has to be correct. The server is always right! + // undo attribute change and set points to 0 + logger->log("Warning: Server denied increase of attribute" + " %d (no points left) ", attrNum); + int attrValue = PlayerInfo::getStatBase(attrNum) - 1; + PlayerInfo::setAttribute(CHAR_POINTS, 0); + PlayerInfo::setStatBase(attrNum, attrValue); + } break; + case ATTRIBMOD_DENIED: + { + // undo attribute change + logger->log("Warning: Server denied increase of attribute" + " %d (reason unknown) ", attrNum); + int points = PlayerInfo::getAttribute(CHAR_POINTS) - 1; + PlayerInfo::setAttribute(CHAR_POINTS, points); + + int attrValue = PlayerInfo::getStatBase(attrNum) - 1; + PlayerInfo::setStatBase(attrNum, attrValue); + } break; + default: + break; + } + } break; + + case GPMSG_LOWER_ATTRIBUTE_RESPONSE: + { + int errCode = msg.readInt8(); + int attrNum = msg.readInt16(); + switch (errCode) + { + case ATTRIBMOD_OK: + { + // feel(acknowledgment); + } break; + case ATTRIBMOD_INVALID_ATTRIBUTE: + { + logger->log("Warning: Server denied reduction of attribute" + " %d (unknown attribute) ", attrNum); + } break; + case ATTRIBMOD_NO_POINTS_LEFT: + { + // when the server says "you got no points" it + // has to be correct. The server is always right! + // undo attribute change and set points to 0 + logger->log("Warning: Server denied reduction of attribute" + " %d (no points left) ", attrNum); + int attrValue = PlayerInfo::getStatBase(attrNum) + 1; + PlayerInfo::setAttribute(CHAR_POINTS, 0); + PlayerInfo::setAttribute(CORR_POINTS, 0); + PlayerInfo::setStatBase(attrNum, attrValue); + break; + } break; + case ATTRIBMOD_DENIED: + { + // undo attribute change + logger->log("Warning: Server denied reduction of attribute" + " %d (reason unknown) ", attrNum); + int charaPoints = PlayerInfo::getAttribute( + CHAR_POINTS) - 1; + + PlayerInfo::setAttribute(CHAR_POINTS, charaPoints); + + int correctPoints = PlayerInfo::getAttribute(CORR_POINTS) + + 1; + + PlayerInfo::setAttribute(CORR_POINTS, correctPoints); + + int attrValue = PlayerInfo::getStatBase(attrNum) + 1; + PlayerInfo::setStatBase(attrNum, attrValue); + } break; + default: + break; + } + + } break; + + case GPMSG_SPECIAL_STATUS : + { + while (msg.getUnreadLength()) + { + // { B specialID, L current, L max, L recharge } + int id = msg.readInt8(); + int current = msg.readInt32(); + int max = msg.readInt32(); + int recharge = msg.readInt32(); + PlayerInfo::setSpecialStatus(id, current, max, recharge); + } + } break; + /* + case SMSG_PLAYER_ARROW_MESSAGE: + { + Sint16 type = msg.readInt16(); + + switch (type) + { + case 0: + localChatTab->chatLog(_("Equip arrows first."), + BY_SERVER); + break; + default: + logger->log("0x013b: Unhandled message %i", type); + break; + } + } + break; + */ + default: + break; + } +} + +void PlayerHandler::handleMapChangeMessage(Net::MessageIn &msg) +{ + const std::string mapName = msg.readString(); + const unsigned short x = msg.readInt16(); + const unsigned short y = msg.readInt16(); + + Game *game = Game::instance(); + const bool sameMap = (game->getCurrentMapName() == mapName); + + logger->log("Changing map to %s (%d, %d)", mapName.c_str(), x, y); + + // Switch the actual map, deleting the previous one + game->changeMap(mapName); + + const Vector &playerPos = player_node->getPosition(); + float scrollOffsetX = 0.0f; + float scrollOffsetY = 0.0f; + + /* Scroll if neccessary */ + if (!sameMap + || (abs(x - (int) playerPos.x) > MAP_TELEPORT_SCROLL_DISTANCE) + || (abs(y - (int) playerPos.y) > MAP_TELEPORT_SCROLL_DISTANCE)) + { + scrollOffsetX = x - (int) playerPos.x; + scrollOffsetY = y - (int) playerPos.y; + } + + player_node->setAction(Being::STAND); + player_node->setPosition(x, y); + player_node->setDestination(x, y); + + logger->log("Adjust scrolling by %d,%d", (int) scrollOffsetX, + (int) scrollOffsetY); + viewport->scrollBy(scrollOffsetX, scrollOffsetY); +} + +void PlayerHandler::attack(int id, bool keep _UNUSED_) +{ + MessageOut msg(PGMSG_ATTACK); + msg.writeInt16(id); + gameServerConnection->send(msg); +} + +void PlayerHandler::stopAttack() +{ + +} + +void PlayerHandler::emote(int emoteId _UNUSED_) +{ + // TODO +} + +void PlayerHandler::increaseAttribute(int attr) +{ + MessageOut msg(PGMSG_RAISE_ATTRIBUTE); + msg.writeInt16(attr); + gameServerConnection->send(msg); +} + +void PlayerHandler::decreaseAttribute(int attr) +{ + MessageOut msg(PGMSG_LOWER_ATTRIBUTE); + msg.writeInt16(attr); + gameServerConnection->send(msg); +} + +void PlayerHandler::increaseSkill(int skillId _UNUSED_) +{ + // Not used atm +} + +void PlayerHandler::pickUp(FloorItem *floorItem) +{ + if (floorItem) + { + int id = floorItem->getId(); + MessageOut msg(PGMSG_PICKUP); + msg.writeInt16(id >> 16); + msg.writeInt16(id & 0xFFFF); + gameServerConnection->send(msg); + } +} + +void PlayerHandler::setDirection(char direction) +{ + MessageOut msg(PGMSG_DIRECTION_CHANGE); + msg.writeInt8(direction); + gameServerConnection->send(msg); +} + +void PlayerHandler::setDestination(int x, int y, int /* direction */) +{ + MessageOut msg(PGMSG_WALK); + msg.writeInt16(x); + msg.writeInt16(y); + gameServerConnection->send(msg); +} + +void PlayerHandler::changeAction(Being::Action action) +{ + player_node->setAction(action); + + MessageOut msg(PGMSG_ACTION_CHANGE); + msg.writeInt8(action); + gameServerConnection->send(msg); +} + +void PlayerHandler::respawn() +{ + MessageOut msg(PGMSG_RESPAWN); + gameServerConnection->send(msg); +} + +void PlayerHandler::ignorePlayer(const std::string &player _UNUSED_, + bool ignore _UNUSED_) +{ + // TODO +} + +void PlayerHandler::ignoreAll(bool ignore _UNUSED_) +{ + // TODO +} + +bool PlayerHandler::canUseMagic() +{ + return true; +} + +bool PlayerHandler::canCorrectAttributes() +{ + return true; +} + +int PlayerHandler::getJobLocation() +{ + return -1; +} + +Vector PlayerHandler::getDefaultWalkSpeed() +{ + // Return translation in pixels per ticks. + return ManaServ::BeingHandler::giveSpeedInPixelsPerTicks(6.0f); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/playerhandler.h b/src/net/manaserv/playerhandler.h new file mode 100644 index 000000000..ddd510714 --- /dev/null +++ b/src/net/manaserv/playerhandler.h @@ -0,0 +1,85 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_PLAYERHANDLER_H +#define NET_MANASERV_PLAYERHANDLER_H + +#include "net/playerhandler.h" + +#include "net/manaserv/messagehandler.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +struct RespawnRequestListener : public gcn::ActionListener +{ + void action(const gcn::ActionEvent &event); +}; + +static RespawnRequestListener respawnListener; + +class PlayerHandler : public MessageHandler, public Net::PlayerHandler +{ + public: + PlayerHandler(); + + void handleMessage(Net::MessageIn &msg); + + void attack(int id, bool keep = false); + void stopAttack(); + void emote(int emoteId); + + void increaseAttribute(int attr); + void decreaseAttribute(int attr); + void increaseSkill(int skillId); + + void pickUp(FloorItem *floorItem); + void setDirection(char direction); + void setDestination(int x, int y, int direction = -1); + void changeAction(Being::Action action); + + void respawn(); + + void ignorePlayer(const std::string &player, bool ignore); + void ignoreAll(bool ignore); + + bool canUseMagic(); + bool canCorrectAttributes(); + + int getJobLocation(); + + Vector getDefaultWalkSpeed(); + + private: + void handleMapChangeMessage(Net::MessageIn &msg); +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_PLAYERHANDLER_H diff --git a/src/net/manaserv/protocol.h b/src/net/manaserv/protocol.h new file mode 100644 index 000000000..2b5efd69c --- /dev/null +++ b/src/net/manaserv/protocol.h @@ -0,0 +1,392 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MANASERV_PROTOCOL_H +#define MANASERV_PROTOCOL_H + +/** + * Enumerated type for communicated messages: + * + * - PAMSG_*: from client to account server + * - APMSG_*: from account server to client + * - PCMSG_*: from client to chat server + * - CPMSG_*: from chat server to client + * - PGMSG_*: from client to game server + * - GPMSG_*: from game server to client + * - GAMSG_*: from game server to account server + * + * Components: B byte, W word, D double word, S variable-size string + * C tile-based coordinates (B*3) + * + * Hosts: P (player's client), A (account server), C (char server), + * G (game server) + * + * TODO - Document specific error codes for each packet + */ +enum { + // Login/Register + PAMSG_REGISTER = 0x0000, // D version, S username, S password, S email, S captcha response + APMSG_REGISTER_RESPONSE = 0x0002, // B error, S updatehost, S Client data URL, B Character slots + PAMSG_UNREGISTER = 0x0003, // S username, S password + APMSG_UNREGISTER_RESPONSE = 0x0004, // B error + PAMSG_REQUEST_REGISTER_INFO = 0x0005, // + APMSG_REGISTER_INFO_RESPONSE = 0x0006, // B byte registration Allowed, byte minNameLength, byte maxNameLength, string captchaURL, string captchaInstructions + PAMSG_LOGIN = 0x0010, // D version, S username, S password + APMSG_LOGIN_RESPONSE = 0x0012, // B error, S updatehost, S Client data URL, B Character slots + PAMSG_LOGOUT = 0x0013, // - + APMSG_LOGOUT_RESPONSE = 0x0014, // B error + PAMSG_CHAR_CREATE = 0x0020, // S name, B hair style, B hair color, B gender, B slot, W*6 stats + APMSG_CHAR_CREATE_RESPONSE = 0x0021, // B error + PAMSG_CHAR_DELETE = 0x0022, // B slot + APMSG_CHAR_DELETE_RESPONSE = 0x0023, // B error + // B slot, S name, B gender, B hair style, B hair color, W level, + // W character points, W correction points, + // {D attr id, D base value (in 1/256ths) D mod value (in 256ths) }* + APMSG_CHAR_INFO = 0x0024, // ^ + PAMSG_CHAR_SELECT = 0x0026, // B slot + APMSG_CHAR_SELECT_RESPONSE = 0x0027, // B error, B*32 token, S game address, W game port, S chat address, W chat port + PAMSG_EMAIL_CHANGE = 0x0030, // S email + APMSG_EMAIL_CHANGE_RESPONSE = 0x0031, // B error + PAMSG_PASSWORD_CHANGE = 0x0034, // S old password, S new password + APMSG_PASSWORD_CHANGE_RESPONSE = 0x0035, // B error + + PGMSG_CONNECT = 0x0050, // B*32 token + GPMSG_CONNECT_RESPONSE = 0x0051, // B error + PCMSG_CONNECT = 0x0053, // B*32 token + CPMSG_CONNECT_RESPONSE = 0x0054, // B error + + PGMSG_DISCONNECT = 0x0060, // B reconnect account + GPMSG_DISCONNECT_RESPONSE = 0x0061, // B error, B*32 token + PCMSG_DISCONNECT = 0x0063, // - + CPMSG_DISCONNECT_RESPONSE = 0x0064, // B error + + PAMSG_RECONNECT = 0x0065, // B*32 token + APMSG_RECONNECT_RESPONSE = 0x0066, // B error + + // Game + GPMSG_PLAYER_MAP_CHANGE = 0x0100, // S filename, W x, W y + GPMSG_PLAYER_SERVER_CHANGE = 0x0101, // B*32 token, S game address, W game port + PGMSG_PICKUP = 0x0110, // W*2 position + PGMSG_DROP = 0x0111, // B slot, B amount + PGMSG_EQUIP = 0x0112, // B slot + PGMSG_UNEQUIP = 0x0113, // B slot + PGMSG_MOVE_ITEM = 0x0114, // B slot1, B slot2, B amount + GPMSG_INVENTORY = 0x0120, // { W slot, W item id [, W amount] (if item id is nonzero) }* + GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount } { B equip slot, W invy slot}* + GPMSG_EQUIP = 0x0122, // { W Invy slot, B equip slot type count { B equip slot, B number used} }* + GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, D base value (in 1/256ths), D modified value (in 1/256ths)}* + GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { W skill, D exp got, D exp needed }* + GPMSG_LEVELUP = 0x0150, // W new level, W character points, W correction points + GPMSG_LEVEL_PROGRESS = 0x0151, // B percent completed to next levelup + PGMSG_RAISE_ATTRIBUTE = 0x0160, // W attribute + GPMSG_RAISE_ATTRIBUTE_RESPONSE = 0x0161, // B error, W attribute + PGMSG_LOWER_ATTRIBUTE = 0x0170, // W attribute + GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, W attribute + PGMSG_RESPAWN = 0x0180, // - + GPMSG_BEING_ENTER = 0x0200, // B type, W being id, B action, W*2 position + // character: S name, B hair style, B hair color, B gender, B item bitmask, { W item id }* + // monster: W type id + // npc: W type id + GPMSG_BEING_LEAVE = 0x0201, // W being id + GPMSG_ITEM_APPEAR = 0x0202, // W item id, W*2 position + GPMSG_BEING_LOOKS_CHANGE = 0x0210, // W weapon, W hat, W top clothes, W bottom clothes + PGMSG_WALK = 0x0260, // W*2 destination + PGMSG_ACTION_CHANGE = 0x0270, // B Action + GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action + PGMSG_DIRECTION_CHANGE = 0x0272, // B Direction + GPMSG_BEING_DIR_CHANGE = 0x0273, // W being id, B direction + GPMSG_BEING_HEALTH_CHANGE = 0x0274, // W being id, W hp, W max hp + GPMSG_BEINGS_MOVE = 0x0280, // { W being id, B flags [, W*2 position, B speed] }* + GPMSG_ITEMS = 0x0281, // { W item id, W*2 position }* + PGMSG_ATTACK = 0x0290, // W being id + GPMSG_BEING_ATTACK = 0x0291, // W being id, B direction, B attacktype + PGMSG_USE_SPECIAL = 0x0292, // B specialID + GPMSG_SPECIAL_STATUS = 0x0293, // { B specialID, D current, D max, D recharge } + PGMSG_SAY = 0x02A0, // S text + GPMSG_SAY = 0x02A1, // W being id, S text + GPMSG_NPC_CHOICE = 0x02B0, // W being id, { S text }* + GPMSG_NPC_MESSAGE = 0x02B1, // W being id, B* text + PGMSG_NPC_TALK = 0x02B2, // W being id + PGMSG_NPC_TALK_NEXT = 0x02B3, // W being id + PGMSG_NPC_SELECT = 0x02B4, // W being id, B choice + GPMSG_NPC_BUY = 0x02B5, // W being id, { W item id, W amount, W cost }* + GPMSG_NPC_SELL = 0x02B6, // W being id, { W item id, W amount, W cost }* + PGMSG_NPC_BUYSELL = 0x02B7, // W item id, W amount + GPMSG_NPC_ERROR = 0x02B8, // B error + GPMSG_NPC_CLOSE = 0x02B9, // W being id + GPMSG_NPC_POST = 0x02D0, // W being id + PGMSG_NPC_POST_SEND = 0x02D1, // W being id, { S name, S text, W item id } + GPMSG_NPC_POST_GET = 0x02D2, // W being id, { S name, S text, W item id } + PGMSG_NPC_NUMBER = 0x02D3, // W being id, D number + PGMSG_NPC_STRING = 0x02D4, // W being id, S string + GPMSG_NPC_NUMBER = 0x02D5, // W being id, D max, D min, D default + GPMSG_NPC_STRING = 0x02D6, // W being id + PGMSG_TRADE_REQUEST = 0x02C0, // W being id + GPMSG_TRADE_REQUEST = 0x02C1, // W being id + GPMSG_TRADE_START = 0x02C2, // - + GPMSG_TRADE_COMPLETE = 0x02C3, // - + PGMSG_TRADE_CANCEL = 0x02C4, // - + GPMSG_TRADE_CANCEL = 0x02C5, // - + PGMSG_TRADE_AGREED = 0x02C6, // - + GPMSG_TRADE_AGREED = 0x02C7, // - + PGMSG_TRADE_CONFIRM = 0x02C8, // - + GPMSG_TRADE_CONFIRM = 0x02C9, // - + PGMSG_TRADE_ADD_ITEM = 0x02CA, // B slot, B amount + GPMSG_TRADE_ADD_ITEM = 0x02CB, // W item id, B amount + PGMSG_TRADE_SET_MONEY = 0x02CC, // D amount + GPMSG_TRADE_SET_MONEY = 0x02CD, // D amount + GPMSG_TRADE_BOTH_CONFIRM = 0x02CE, // - + PGMSG_USE_ITEM = 0x0300, // B slot + GPMSG_USE_RESPONSE = 0x0301, // B error + GPMSG_BEINGS_DAMAGE = 0x0310, // { W being id, W amount }* + GPMSG_CREATE_EFFECT_POS = 0x0320, // W effect id, W*2 position + GPMSG_CREATE_EFFECT_BEING = 0x0321, // W effect id, W BeingID + + // Guild + PCMSG_GUILD_CREATE = 0x0350, // S name + CPMSG_GUILD_CREATE_RESPONSE = 0x0351, // B error, W guild, B rights, W channel + PCMSG_GUILD_INVITE = 0x0352, // W id, S name + CPMSG_GUILD_INVITE_RESPONSE = 0x0353, // B error + PCMSG_GUILD_ACCEPT = 0x0354, // W id + CPMSG_GUILD_ACCEPT_RESPONSE = 0x0355, // B error, W guild, B rights, W channel + PCMSG_GUILD_GET_MEMBERS = 0x0356, // W id + CPMSG_GUILD_GET_MEMBERS_RESPONSE = 0x0357, // S names, B online + CPMSG_GUILD_UPDATE_LIST = 0x0358, // W id, S name, B event + PCMSG_GUILD_QUIT = 0x0360, // W id + CPMSG_GUILD_QUIT_RESPONSE = 0x0361, // B error + PCMSG_GUILD_PROMOTE_MEMBER = 0x0365, // W guild, S name, B rights + CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE = 0x0366, // B error + PCMSG_GUILD_KICK_MEMBER = 0x0370, // W guild, S name + CPMSG_GUILD_KICK_MEMBER_RESPONSE = 0x0371, // B error + + CPMSG_GUILD_INVITED = 0x0388, // S char name, S guild name, W id + CPMSG_GUILD_REJOIN = 0x0389, // S name, W guild, W rights, W channel, S announce + + // Party + PCMSG_PARTY_INVITE = 0x03A0, // S name + CPMSG_PARTY_INVITE_RESPONSE = 0x03A1, // B error, S name + CPMSG_PARTY_INVITED = 0x03A2, // S name + PCMSG_PARTY_ACCEPT_INVITE = 0x03A5, // S name + CPMSG_PARTY_ACCEPT_INVITE_RESPONSE = 0x03A6, // B error, { S name } + PCMSG_PARTY_REJECT_INVITE = 0x03A7, // S name + CPMSG_PARTY_REJECTED = 0x03A8, // S name + PCMSG_PARTY_QUIT = 0x03AA, // - + CPMSG_PARTY_QUIT_RESPONSE = 0x03AB, // B error + CPMSG_PARTY_NEW_MEMBER = 0x03B0, // W being id, S name + CPMSG_PARTY_MEMBER_LEFT = 0x03B1, // W being id + + // Chat + CPMSG_ERROR = 0x0401, // B error + CPMSG_ANNOUNCEMENT = 0x0402, // S text + CPMSG_PRIVMSG = 0x0403, // S user, S text + CPMSG_PUBMSG = 0x0404, // W channel, S user, S text + PCMSG_CHAT = 0x0410, // S text, W channel + PCMSG_ANNOUNCE = 0x0411, // S text + PCMSG_PRIVMSG = 0x0412, // S user, S text + PCMSG_WHO = 0x0415, // - + CPMSG_WHO_RESPONSE = 0x0416, // { S user } + + // -- Channeling + CPMSG_CHANNEL_EVENT = 0x0430, // W channel, B event, S info + PCMSG_ENTER_CHANNEL = 0x0440, // S channel, S password + CPMSG_ENTER_CHANNEL_RESPONSE = 0x0441, // B error, W id, S name, S topic, S userlist + PCMSG_QUIT_CHANNEL = 0x0443, // W channel id + CPMSG_QUIT_CHANNEL_RESPONSE = 0x0444, // B error, W channel id + PCMSG_LIST_CHANNELS = 0x0445, // - + CPMSG_LIST_CHANNELS_RESPONSE = 0x0446, // S names, W number of users + PCMSG_LIST_CHANNELUSERS = 0x0460, // S channel + CPMSG_LIST_CHANNELUSERS_RESPONSE = 0x0461, // S channel, { S user, B mode } + PCMSG_TOPIC_CHANGE = 0x0462, // W channel id, S topic + // -- User modes + PCMSG_USER_MODE = 0x0465, // W channel id, S name, B mode + PCMSG_KICK_USER = 0x0466, // W channel id, S name + + // Inter-server + GAMSG_REGISTER = 0x0500, // S address, W port, S password, D items db revision, { W map id }* + AGMSG_REGISTER_RESPONSE = 0x0501, // C item version, C password response + AGMSG_ACTIVE_MAP = 0x0502, // W map id + AGMSG_PLAYER_ENTER = 0x0510, // B*32 token, D id, S name, serialised character data + GAMSG_PLAYER_DATA = 0x0520, // D id, serialised character data + GAMSG_REDIRECT = 0x0530, // D id + AGMSG_REDIRECT_RESPONSE = 0x0531, // D id, B*32 token, S game address, W game port + GAMSG_PLAYER_RECONNECT = 0x0532, // D id, B*32 token + GAMSG_PLAYER_SYNC = 0x0533, // serialised sync data + GAMSG_SET_QUEST = 0x0540, // D id, S name, S value + GAMSG_GET_QUEST = 0x0541, // D id, S name + AGMSG_GET_QUEST_RESPONSE = 0x0542, // D id, S name, S value + GAMSG_BAN_PLAYER = 0x0550, // D id, W duration + GAMSG_CHANGE_PLAYER_LEVEL = 0x0555, // D id, W level + GAMSG_CHANGE_ACCOUNT_LEVEL = 0x0556, // D id, W level + GAMSG_STATISTICS = 0x0560, // { W map id, W thing nb, W monster nb, W player nb, { D character id }* }* + CGMSG_CHANGED_PARTY = 0x0590, // D character id, D party id + GCMSG_REQUEST_POST = 0x05A0, // D character id + CGMSG_POST_RESPONSE = 0x05A1, // D receiver id, { S sender name, S letter, W num attachments { W attachment item id, W quantity } } + GCMSG_STORE_POST = 0x05A5, // D sender id, S receiver name, S letter, { W attachment item id, W quantity } + CGMSG_STORE_POST_RESPONSE = 0x05A6, // D id, B error + GAMSG_TRANSACTION = 0x0600, // D character id, D action, S message + + XXMSG_INVALID = 0x7FFF +}; + +// Generic return values + +enum { + ERRMSG_OK = 0, // everything is fine + ERRMSG_FAILURE, // the action failed + ERRMSG_NO_LOGIN, // the user is not yet logged + ERRMSG_NO_CHARACTER_SELECTED, // the user needs a character + ERRMSG_INSUFFICIENT_RIGHTS, // the user is not privileged + ERRMSG_INVALID_ARGUMENT, // part of the received message was invalid + ERRMSG_EMAIL_ALREADY_EXISTS, // The Email Address already exists + ERRMSG_ALREADY_TAKEN, // name used was already taken + ERRMSG_SERVER_FULL, // the server is overloaded + ERRMSG_TIME_OUT, // data failed to arrive in due time + ERRMSG_LIMIT_REACHED // limit reached +}; + +// used in AGMSG_REGISTER_RESPONSE to show state of item db +enum { + DATA_VERSION_OK = 0x00, + DATA_VERSION_OUTDATED = 0x01 +}; + +// used in AGMSG_REGISTER_RESPNSE to show if password was accepted +enum { + PASSWORD_OK = 0x00, + PASSWORD_BAD = 0x01 +}; + +// used to identify part of sync message +enum { + SYNC_CHARACTER_POINTS = 0x01, // D charId, D charPoints, D corrPoints + SYNC_CHARACTER_ATTRIBUTE = 0x02, // D charId, D attrId, DF base, DF mod + SYNC_CHARACTER_SKILL = 0x03, // D charId, B skillId, D skill value + SYNC_ONLINE_STATUS = 0x04, // D charId, B 0x00 = offline, 0x01 = online + SYNC_END_OF_BUFFER = 0xFF // shows, that the buffer ends here. +}; + +// Login specific return values +enum { + LOGIN_INVALID_VERSION = 0x40, // the user is using an incompatible protocol + LOGIN_INVALID_TIME = 0x50, // the user tried logging in too fast + LOGIN_BANNED // the user is currently banned +}; + +// Account register specific return values +enum { + REGISTER_INVALID_VERSION = 0x40, // the user is using an incompatible protocol + REGISTER_EXISTS_USERNAME, // there already is an account with this username + REGISTER_EXISTS_EMAIL, // there already is an account with this email address + REGISTER_CAPTCHA_WRONG // user didn't solve the captcha correctly +}; + +// Character creation specific return values +enum { + CREATE_INVALID_HAIRSTYLE = 0x40, + CREATE_INVALID_HAIRCOLOR, + CREATE_INVALID_GENDER, + CREATE_ATTRIBUTES_TOO_HIGH, + CREATE_ATTRIBUTES_TOO_LOW, + CREATE_ATTRIBUTES_OUT_OF_RANGE, + CREATE_EXISTS_NAME, + CREATE_TOO_MUCH_CHARACTERS, + CREATE_INVALID_SLOT +}; + +// Character attribute modification specific return value +enum AttribmodResponseCode { + ATTRIBMOD_OK = ERRMSG_OK, + ATTRIBMOD_INVALID_ATTRIBUTE = 0x40, + ATTRIBMOD_NO_POINTS_LEFT, + ATTRIBMOD_DENIED +}; + +// Object type enumeration +enum ThingType +{ + // A simple item. + OBJECT_ITEM = 0, + // An item that toggle map/quest actions (doors, switchs, ...) + // and can speak (map panels). + OBJECT_ACTOR, + // Non-Playable-Character is an actor capable of movement and maybe actions. + OBJECT_NPC, + // A monster (moving actor with AI. Should be able to toggle map/quest + // actions, too). + OBJECT_MONSTER, + // A normal being. + OBJECT_CHARACTER, + // A effect to be shown. + OBJECT_EFFECT, + // Server-only object. + OBJECT_OTHER +}; + +// Moving object flags +enum { + // Payload contains the current position. + MOVING_POSITION = 1, + // Payload contains the destination. + MOVING_DESTINATION = 2 +}; + +// Email change specific return values +enum { + EMAILCHG_EXISTS_EMAIL = 0x40 +}; + +// Chat errors return values +enum { + CHAT_USING_BAD_WORDS = 0x40, + CHAT_UNHANDLED_COMMAND +}; + +// Chat channels event values +enum { + CHAT_EVENT_NEW_PLAYER = 0, + CHAT_EVENT_LEAVING_PLAYER, + CHAT_EVENT_TOPIC_CHANGE, + CHAT_EVENT_MODE_CHANGE, + CHAT_EVENT_KICKED_PLAYER +}; + +// Guild member event values +enum { + GUILD_EVENT_NEW_PLAYER = 0, + GUILD_EVENT_LEAVING_PLAYER, + GUILD_EVENT_ONLINE_PLAYER, + GUILD_EVENT_OFFLINE_PLAYER +}; + + +enum +{ + SPRITE_BASE = 0, + SPRITE_SHOE, + SPRITE_BOTTOMCLOTHES, + SPRITE_TOPCLOTHES, + SPRITE_HAIR, + SPRITE_HAT, + SPRITE_WEAPON, + SPRITE_VECTOREND +}; + +#endif // MANASERV_PROTOCOL_H diff --git a/src/net/manaserv/specialhandler.cpp b/src/net/manaserv/specialhandler.cpp new file mode 100644 index 000000000..8508c9b56 --- /dev/null +++ b/src/net/manaserv/specialhandler.cpp @@ -0,0 +1,70 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/specialhandler.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +extern Net::SpecialHandler *specialHandler; + +namespace ManaServ +{ + +extern Connection *gameServerConnection; + +SpecialHandler::SpecialHandler() +{ + specialHandler = this; +} + +void SpecialHandler::handleMessage(Net::MessageIn &msg _UNUSED_) +{ + // TODO +} + +void SpecialHandler::use(int id) +{ + MessageOut msg(PGMSG_USE_SPECIAL); + msg.writeInt8(id); + gameServerConnection->send(msg); +} + +void SpecialHandler::use(int id _UNUSED_, int level _UNUSED_, + int beingId _UNUSED_) +{ + // TODO +} + +void SpecialHandler::use(int id _UNUSED_, int level _UNUSED_, int x _UNUSED_, + int y _UNUSED_) +{ + // TODO +} + +void SpecialHandler::use(int id _UNUSED_, const std::string &map _UNUSED_) +{ + // TODO +} + +} // namespace ManaServ diff --git a/src/net/manaserv/specialhandler.h b/src/net/manaserv/specialhandler.h new file mode 100644 index 000000000..1f48688bf --- /dev/null +++ b/src/net/manaserv/specialhandler.h @@ -0,0 +1,56 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_SKILLHANDLER_H +#define NET_MANASERV_SKILLHANDLER_H + +#include "net/specialhandler.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class SpecialHandler : public MessageHandler, public Net::SpecialHandler +{ + public: + SpecialHandler(); + + void handleMessage(Net::MessageIn &msg); + + void use(int id); + + void use(int id, int level, int beingId); + + void use(int id, int level, int x, int y); + + void use(int id, const std::string &map); +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_SKILLHANDLER_H diff --git a/src/net/manaserv/tradehandler.cpp b/src/net/manaserv/tradehandler.cpp new file mode 100644 index 000000000..5a9fdfabd --- /dev/null +++ b/src/net/manaserv/tradehandler.cpp @@ -0,0 +1,237 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/manaserv/tradehandler.h" + +#include "actorspritemanager.h" +#include "event.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "playerrelations.h" + +#include "gui/confirmdialog.h" +#include "gui/trade.h" + +#include "net/net.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern std::string tradePartnerName; +int tradePartnerID; + +extern Net::TradeHandler *tradeHandler; + +namespace ManaServ +{ + +extern Connection *gameServerConnection; + +/** + * Listener for request trade dialogs + */ +namespace +{ + struct RequestTradeListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event) + { + if (event.getId() == "yes") + { + ManaServ::MessageOut msg(PGMSG_TRADE_REQUEST); + msg.writeInt16(tradePartnerID); + gameServerConnection->send(msg); + } + else if (event.getId() == "ignore") + { + player_relations.ignoreTrade(tradePartnerName); + Net::getTradeHandler()->cancel(); + } + else + { + Net::getTradeHandler()->cancel(); + } + } + } listener; +} + +TradeHandler::TradeHandler(): + mAcceptTradeRequests(true) +{ + static const Uint16 _messages[] = + { + GPMSG_TRADE_REQUEST, + GPMSG_TRADE_CANCEL, + GPMSG_TRADE_START, + GPMSG_TRADE_COMPLETE, + GPMSG_TRADE_AGREED, + GPMSG_TRADE_BOTH_CONFIRM, + GPMSG_TRADE_CONFIRM, + GPMSG_TRADE_ADD_ITEM, + GPMSG_TRADE_SET_MONEY, + 0 + }; + handledMessages = _messages; + tradeHandler = this; +} + +void TradeHandler::setAcceptTradeRequests(bool acceptTradeRequests) +{ + mAcceptTradeRequests = acceptTradeRequests; + if (mAcceptTradeRequests) + SERVER_NOTICE(_("Accepting incoming trade requests.")) + else + SERVER_NOTICE(_("Ignoring incoming trade requests.")) +} + +void TradeHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_TRADE_REQUEST: + { + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being || !mAcceptTradeRequests) + { + respond(false); + break; + } + PlayerInfo::setTrading(true); + tradePartnerName = being->getName(); + tradePartnerID = being->getId(); + ConfirmDialog *dlg = new ConfirmDialog(_("Request for Trade"), + strprintf(_("%s wants to trade with you, do you accept?"), + tradePartnerName.c_str()), true); + dlg->addActionListener(&listener); + } break; + + case GPMSG_TRADE_ADD_ITEM: + { + int type = msg.readInt16(); + int amount = msg.readInt8(); + tradeWindow->addItem(type, false, amount, 0); + } break; + + case GPMSG_TRADE_SET_MONEY: + tradeWindow->setMoney(msg.readInt32()); + break; + + case GPMSG_TRADE_START: + tradeWindow->reset(); + tradeWindow->setCaption(strprintf(_("Trading with %s"), + tradePartnerName.c_str())); + tradeWindow->setVisible(true); + break; + + case GPMSG_TRADE_BOTH_CONFIRM: + tradeWindow->receivedOk(false); + break; + + case GPMSG_TRADE_AGREED: + tradeWindow->receivedOk(false); + break; + + case GPMSG_TRADE_CANCEL: + SERVER_NOTICE(_("Trade canceled.")) + tradeWindow->setVisible(false); + tradeWindow->reset(); + PlayerInfo::setTrading(false); + break; + + case GPMSG_TRADE_COMPLETE: + SERVER_NOTICE(_("Trade completed.")) + tradeWindow->setVisible(false); + tradeWindow->reset(); + PlayerInfo::setTrading(false); + break; + + default: + break; + } +} + +void TradeHandler::request(Being *being) +{ + tradePartnerName = being->getName(); + tradePartnerID = being->getId(); + + MessageOut msg(PGMSG_TRADE_REQUEST); + msg.writeInt16(tradePartnerID); + gameServerConnection->send(msg); +} + +void TradeHandler::respond(bool accept) +{ + MessageOut msg(accept ? PGMSG_TRADE_REQUEST : PGMSG_TRADE_CANCEL); + gameServerConnection->send(msg); + + if (!accept) + PlayerInfo::setTrading(false); +} + +void TradeHandler::addItem(Item *item, int amount) +{ + MessageOut msg(PGMSG_TRADE_ADD_ITEM); + msg.writeInt8(item->getInvIndex()); + msg.writeInt8(amount); + gameServerConnection->send(msg); + + tradeWindow->addItem(item->getId(), true, amount, 0); + item->increaseQuantity(-amount); +} + +void TradeHandler::removeItem(int slotNum _UNUSED_, int amount _UNUSED_) +{ + // TODO +} + +void TradeHandler::setMoney(int amount) +{ + MessageOut msg(PGMSG_TRADE_SET_MONEY); + msg.writeInt32(amount); + gameServerConnection->send(msg); +} + +void TradeHandler::confirm() +{ + MessageOut msg(PGMSG_TRADE_CONFIRM); + gameServerConnection->send(msg); +} + +void TradeHandler::finish() +{ + MessageOut msg(PGMSG_TRADE_AGREED); + gameServerConnection->send(msg); +} + +void TradeHandler::cancel() +{ + MessageOut msg(PGMSG_TRADE_CANCEL); + gameServerConnection->send(msg); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/tradehandler.h b/src/net/manaserv/tradehandler.h new file mode 100644 index 000000000..3bbf15470 --- /dev/null +++ b/src/net/manaserv/tradehandler.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MANASERV_TRADEHANDLER_H +#define NET_MANASERV_TRADEHANDLER_H + +#include "net/tradehandler.h" + +#include "net/manaserv/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace ManaServ +{ + +class TradeHandler : public MessageHandler, public Net::TradeHandler +{ + public: + TradeHandler(); + + void handleMessage(Net::MessageIn &msg); + + /** + * Returns whether trade requests are accepted. + * + * @see setAcceptTradeRequests + */ + bool acceptTradeRequests() const + { return mAcceptTradeRequests; } + + /** + * Sets whether trade requests are accepted. When set to false, trade + * requests are automatically denied. When true, a popup will ask the + * player whether he wants to trade. + */ + void setAcceptTradeRequests(bool acceptTradeRequests); + + void request(Being *being); + + void respond(bool accept); + + void addItem(Item *item, int amount); + + void removeItem(int slotNum, int amount); + + void setMoney(int amount); + + void confirm(); + + void finish(); + + void cancel(); + + private: + bool mAcceptTradeRequests; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_TRADEHANDLER_H diff --git a/src/net/messagehandler.h b/src/net/messagehandler.h new file mode 100644 index 000000000..3a454b1f9 --- /dev/null +++ b/src/net/messagehandler.h @@ -0,0 +1,50 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MESSAGEHANDLER_H +#define NET_MESSAGEHANDLER_H + +#include "net/messagein.h" + +#include + +#include + +namespace Net +{ + +/** + * \ingroup Network + */ +class MessageHandler +{ + public: + const Uint16 *handledMessages; + + virtual void handleMessage(MessageIn &msg) = 0; + + virtual ~MessageHandler() + { } +}; + +} + +#endif // NET_MESSAGEHANDLER_H diff --git a/src/net/messagein.cpp b/src/net/messagein.cpp new file mode 100644 index 000000000..0ac391ee2 --- /dev/null +++ b/src/net/messagein.cpp @@ -0,0 +1,227 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/messagein.h" + +#include "net/packetcounters.h" + +#include "log.h" + +#include "utils/stringutils.h" + +#define MAKEWORD(low, high) \ + ((unsigned short)(((unsigned char)(low)) | \ + ((unsigned short)((unsigned char)(high))) << 8)) + +namespace Net +{ + +MessageIn::MessageIn(const char *data, unsigned int length): + mData(data), + mLength(length), + mPos(0) +{ + PacketCounters::incInPackets(); + DEBUGLOG("MessageIn"); +} + +unsigned char MessageIn::readInt8() +{ + unsigned char value = -1; + if (mPos < mLength) + value = static_cast(mData[mPos]); + + mPos += 1; + PacketCounters::incInBytes(1); + DEBUGLOG("readInt8: " + toString(static_cast(value))); + return value; +} + +void MessageIn::readCoordinates(Uint16 &x, Uint16 &y) +{ + if (mPos + 3 <= mLength) + { + unsigned char const *p + = reinterpret_cast< unsigned char const * >(mData + mPos); + x = static_cast(p[0] | ((p[1] & 0x07) << 8)); + y = static_cast((p[1] >> 3) | ((p[2] & 0x3F) << 5)); + } + mPos += 3; + PacketCounters::incInBytes(3); + DEBUGLOG("readCoordinates: " + toString(static_cast(x)) + "," + + toString(static_cast(y))); +} + +void MessageIn::readCoordinates(Uint16 &x, Uint16 &y, Uint8 &direction) +{ + if (mPos + 3 <= mLength) + { + const char *data = mData + mPos; + Sint16 temp; + + temp = MAKEWORD(data[1] & 0x00c0, data[0] & 0x00ff); + x = static_cast(temp >> 6); + temp = MAKEWORD(data[2] & 0x00f0, data[1] & 0x003f); + y = static_cast(temp >> 4); + + direction = data[2] & 0x000f; + + // Translate from eAthena format + switch (direction) + { + case 0: + direction = 1; + break; + case 1: + direction = 3; + break; + case 2: + direction = 2; + break; + case 3: + direction = 6; + break; + case 4: + direction = 4; + break; + case 5: + direction = 12; + break; + case 6: + direction = 8; + break; + case 7: + direction = 9; + break; + default: + // OOPSIE! Impossible or unknown + direction = 0; + } + } + mPos += 3; + PacketCounters::incInBytes(3); + DEBUGLOG("readCoordinates: " + toString((int)x) + "," + toString((int)y)); +} + +void MessageIn::readCoordinatePair(Uint16 &srcX, Uint16 &srcY, + Uint16 &dstX, Uint16 &dstY) +{ + if (mPos + 5 <= mLength) + { + const char *data = mData + mPos; + Sint16 temp; + + temp = MAKEWORD(data[3], data[2] & 0x000f); + dstX = static_cast(temp >> 2); + + dstY = MAKEWORD(data[4], data[3] & 0x0003); + + temp = MAKEWORD(data[1], data[0]); + srcX = static_cast(temp >> 6); + + temp = MAKEWORD(data[2], data[1] & 0x003f); + srcY = static_cast(temp >> 4); + } + mPos += 5; + DEBUGLOG("readCoordinatePair: " + toString((int)srcX) + "," + + toString((int)srcY) + " " + toString((int)dstX) + "," + + toString((int)dstY)); + PacketCounters::incInBytes(5); +} + +void MessageIn::skip(unsigned int length) +{ + mPos += length; + PacketCounters::incInBytes(length); + DEBUGLOG("skip: " + toString((int)length)); +} + +std::string MessageIn::readString(int length) +{ + // Get string length + if (length < 0) + length = readInt16(); + + // Make sure the string isn't erroneous + if (length < 0 || mPos + length > mLength) + { + mPos = mLength + 1; + DEBUGLOG("readString error"); + return ""; + } + + // Read the string + char const *stringBeg = mData + mPos; + char const *stringEnd + = static_cast(memchr(stringBeg, '\0', length)); + + std::string readString(stringBeg, + stringEnd ? stringEnd - stringBeg : length); + mPos += length; + PacketCounters::incInBytes(length); + DEBUGLOG("readString: " + readString); + return readString; +} + +std::string MessageIn::readRawString(int length) +{ + // Get string length + if (length < 0) + length = readInt16(); + + // Make sure the string isn't erroneous + if (length < 0 || mPos + length > mLength) + { + mPos = mLength + 1; + return ""; + } + + // Read the string + char const *stringBeg = mData + mPos; + char const *stringEnd + = static_cast(memchr(stringBeg, '\0', length)); + std::string readString(stringBeg, + stringEnd ? stringEnd - stringBeg : length); + + mPos += length; + PacketCounters::incInBytes(length); + DEBUGLOG("readString: " + readString); + + if (stringEnd) + { + long len2 = length - (stringEnd - stringBeg) - 1; + char const *stringBeg2 = stringEnd + 1; + char const *stringEnd2 + = static_cast(memchr(stringBeg2, '\0', len2)); + std::string hiddenPart = std::string(stringBeg2, + stringEnd2 ? stringEnd2 - stringBeg2 : len2); + if (hiddenPart.length() > 0) + { + DEBUGLOG("readString2: " + hiddenPart); + + return readString + "|" + hiddenPart; + } + } + + return readString; +} + +} diff --git a/src/net/messagein.h b/src/net/messagein.h new file mode 100644 index 000000000..8781b3050 --- /dev/null +++ b/src/net/messagein.h @@ -0,0 +1,118 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MESSAGEIN_H +#define NET_MESSAGEIN_H + +#include + +#include + +namespace Net +{ + +/** + * Used for parsing an incoming message. + * + * \ingroup Network + */ +class MessageIn +{ + public: + /** + * Returns the message ID. + */ + int getId() const + { return mId; } + + /** + * Returns the message length. + */ + unsigned int getLength() const + { return mLength; } + + /** + * Returns the length of unread data. + */ + unsigned int getUnreadLength() const + { return mLength - mPos; } + + virtual unsigned char readInt8(); /**< Reads a byte. */ + virtual Sint16 readInt16() = 0; /**< Reads a short. */ + virtual int readInt32() = 0; /**< Reads a long. */ + + /** + * Reads a 3-byte block containing tile-based coordinates. Used by + * manaserv. + */ + virtual void readCoordinates(Uint16 &x, Uint16 &y); + + /** + * Reads a special 3 byte block used by eAthena, containing x and y + * coordinates and direction. + */ + virtual void readCoordinates(Uint16 &x, Uint16 &y, Uint8 &direction); + + /** + * Reads a special 5 byte block used by eAthena, containing a source + * and destination coordinate pair. + */ + virtual void readCoordinatePair(Uint16 &srcX, Uint16 &srcY, + Uint16 &dstX, Uint16 &dstY); + + /** + * Skips a given number of bytes. + */ + virtual void skip(unsigned int length); + + /** + * Reads a string. If a length is not given (-1), it is assumed + * that the length of the string is stored in a short at the + * start of the string. + */ + virtual std::string readString(int length = -1); + + virtual std::string readRawString(int length); + + virtual ~MessageIn() + { } + + protected: + /** + * Constructor. + */ + MessageIn(const char *data, unsigned int length); + + const char *mData; /**< The message data. */ + unsigned int mLength; /**< The length of the data. */ + unsigned short mId; /**< The message ID. */ + + /** + * Actual position in the packet. From 0 to packet->length. + * A value bigger than packet->length means EOP was reached when + * reading it. + */ + unsigned int mPos; +}; + +} + +#endif // NET_MESSAGEIN_H diff --git a/src/net/messageout.cpp b/src/net/messageout.cpp new file mode 100644 index 000000000..5152b73e2 --- /dev/null +++ b/src/net/messageout.cpp @@ -0,0 +1,92 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/messageout.h" + +#include "net/packetcounters.h" + +#include "log.h" + +#include "utils/stringutils.h" + +#include +#include + +namespace Net +{ + +MessageOut::MessageOut(short id _UNUSED_): + mData(0), + mDataSize(0), + mPos(0) +{ + PacketCounters::incOutPackets(); + DEBUGLOG("MessageOut"); +} + +void MessageOut::writeInt8(Sint8 value) +{ + DEBUGLOG("writeInt8: " + toString((int)value)); + expand(1); + mData[mPos] = value; + mPos += 1; + PacketCounters::incOutBytes(1); +} + +void MessageOut::writeString(const std::string &string, int length) +{ + DEBUGLOG("writeString: " + string); + int stringLength = static_cast(string.length()); + if (length < 0) + { + // Write the length at the start if not fixed + writeInt16(static_cast(stringLength)); + length = stringLength; + } + else if (length < stringLength) + { + // Make sure the length of the string is no longer than specified + stringLength = length; + } + expand(length); + + // Write the actual string + memcpy(mData + mPos, string.c_str(), stringLength); + + // Pad remaining space with zeros + if (length > stringLength) + memset(mData + mPos + stringLength, '\0', length - stringLength); + + mPos += length; + PacketCounters::incOutBytes(length); +} + +char *MessageOut::getData() const +{ + return mData; +} + +unsigned int MessageOut::getDataSize() const +{ + return mDataSize; +} + +} diff --git a/src/net/messageout.h b/src/net/messageout.h new file mode 100644 index 000000000..11a9ff552 --- /dev/null +++ b/src/net/messageout.h @@ -0,0 +1,91 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_MESSAGEOUT_H +#define NET_MESSAGEOUT_H + +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace Net +{ + +/** + * Used for building an outgoing message. + * + * \ingroup Network + */ +class MessageOut +{ + public: + virtual void writeInt8(Sint8 value); /**< Writes a byte. */ + virtual void writeInt16(Sint16 value) = 0; /**< Writes a short. */ + virtual void writeInt32(Sint32 value) = 0; /**< Writes a long. */ + + /** + * Writes a string. If a fixed length is not given (-1), it is stored + * as a short at the start of the string. + */ + virtual void writeString(const std::string &string, int length = -1); + + /** + * Returns the content of the message. + */ + virtual char *getData() const; + + /** + * Returns the length of the data. + */ + virtual unsigned int getDataSize() const; + + virtual ~MessageOut() + { } + + protected: + /** + * Constructor. + */ + MessageOut(short id); + + /** + * Expand the packet data to be able to hold more data. + * + * NOTE: For performance enhancements this method could allocate extra + * memory in advance instead of expanding size every time more data is + * added. + */ + virtual void expand(size_t size) = 0; + + char *mData; /**< Data building up. */ + unsigned int mDataSize; /**< Size of data. */ + unsigned int mPos; /**< Position in the data. */ +}; + +} + +#endif // NET_MESSAGEOUT_H diff --git a/src/net/net.cpp b/src/net/net.cpp new file mode 100644 index 000000000..36e414643 --- /dev/null +++ b/src/net/net.cpp @@ -0,0 +1,198 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/net.h" + +#include "main.h" + +#include "net/adminhandler.h" +#include "net/beinghandler.h" +#include "net/buysellhandler.h" +#include "net/charhandler.h" +#include "net/chathandler.h" +#include "net/generalhandler.h" +#include "net/guildhandler.h" +#include "net/inventoryhandler.h" +#include "net/loginhandler.h" +#include "net/gamehandler.h" +#include "net/npchandler.h" +#include "net/partyhandler.h" +#include "net/playerhandler.h" +#include "net/specialhandler.h" +#include "net/tradehandler.h" + +#include "net/tmwa/generalhandler.h" + +#include "net/manaserv/generalhandler.h" + +Net::AdminHandler *adminHandler = NULL; +Net::CharHandler *charHandler = NULL; +Net::ChatHandler *chatHandler = NULL; +Net::GeneralHandler *generalHandler = NULL; +Net::InventoryHandler *inventoryHandler = NULL; +Net::LoginHandler *loginHandler = NULL; +Net::GameHandler *gameHandler = NULL; +Net::GuildHandler *guildHandler = NULL; +Net::NpcHandler *npcHandler = NULL; +Net::PartyHandler *partyHandler = NULL; +Net::PlayerHandler *playerHandler = NULL; +Net::SpecialHandler *specialHandler = NULL; +Net::TradeHandler *tradeHandler = NULL; +Net::BeingHandler *beingHandler = NULL; +Net::BuySellHandler *buySellHandler = NULL; + +Net::AdminHandler *Net::getAdminHandler() +{ + return adminHandler; +} + +Net::CharHandler *Net::getCharHandler() +{ + return charHandler; +} + +Net::ChatHandler *Net::getChatHandler() +{ + return chatHandler; +} + +Net::GameHandler *Net::getGameHandler() +{ + return gameHandler; +} + +Net::GeneralHandler *Net::getGeneralHandler() +{ + return generalHandler; +} + +Net::GuildHandler *Net::getGuildHandler() +{ + return guildHandler; +} + +Net::InventoryHandler *Net::getInventoryHandler() +{ + return inventoryHandler; +} + +Net::LoginHandler *Net::getLoginHandler() +{ + return loginHandler; +} + +Net::NpcHandler *Net::getNpcHandler() +{ + return npcHandler; +} + +Net::PartyHandler *Net::getPartyHandler() +{ + return partyHandler; +} + +Net::PlayerHandler *Net::getPlayerHandler() +{ + return playerHandler; +} + +Net::SpecialHandler *Net::getSpecialHandler() +{ + return specialHandler; +} + +Net::TradeHandler *Net::getTradeHandler() +{ + return tradeHandler; +} + +Net::BeingHandler *Net::getBeingHandler() +{ + return beingHandler; +} + +Net::BuySellHandler *Net::getBuySellHandler() +{ + return buySellHandler; +} + +namespace Net +{ +ServerInfo::Type networkType = ServerInfo::UNKNOWN; + +void connectToServer(const ServerInfo &server) +{ + if (server.type == ServerInfo::UNKNOWN) + { + // TODO: Query the server about itself and choose the netcode based on + // that + } + + if (networkType == server.type && getGeneralHandler() != NULL) + { + getGeneralHandler()->reload(); + } + else + { + if (networkType != ServerInfo::UNKNOWN && getGeneralHandler() != NULL) + getGeneralHandler()->unload(); + + switch (server.type) + { + case ServerInfo::MANASERV: + new ManaServ::GeneralHandler; + break; + + case ServerInfo::TMWATHENA: + new TmwAthena::GeneralHandler; + break; + + default: + // Shouldn't happen... + break; + } + + getGeneralHandler()->load(); + + networkType = server.type; + } + + if (getLoginHandler()) + { + getLoginHandler()->setServer(server); + getLoginHandler()->connect(); + } +} + +void unload() +{ + GeneralHandler *handler = getGeneralHandler(); + if (handler) + handler->unload(); +} + +ServerInfo::Type getNetworkType() +{ + return networkType; +} + +} // namespace Net + diff --git a/src/net/net.h b/src/net/net.h new file mode 100644 index 000000000..95fe04f36 --- /dev/null +++ b/src/net/net.h @@ -0,0 +1,81 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_H +#define NET_H + +/** + * \namespace Net + * + * The network communication layer. It is composed of a host of interfaces that + * interact with different aspects of the game. They have different + * implementations depending on the type of server the client is connecting to. + */ + +#include "net/serverinfo.h" + +namespace Net +{ + +class AdminHandler; +class BeingHandler; +class CharHandler; +class ChatHandler; +class GameHandler; +class GeneralHandler; +class GuildHandler; +class InventoryHandler; +class LoginHandler; +class NpcHandler; +class PartyHandler; +class PlayerHandler; +class SpecialHandler; +class TradeHandler; +class BuySellHandler; + +AdminHandler *getAdminHandler(); +BeingHandler *getBeingHandler(); +CharHandler *getCharHandler(); +ChatHandler *getChatHandler(); +GameHandler *getGameHandler(); +GeneralHandler *getGeneralHandler(); +GuildHandler *getGuildHandler(); +InventoryHandler *getInventoryHandler(); +LoginHandler *getLoginHandler(); +NpcHandler *getNpcHandler(); +PartyHandler *getPartyHandler(); +PlayerHandler *getPlayerHandler(); +SpecialHandler *getSpecialHandler(); +TradeHandler *getTradeHandler(); +BuySellHandler *getBuySellHandler(); + +ServerInfo::Type getNetworkType(); + +/** + * Handles server detection and connection + */ +void connectToServer(const ServerInfo &server); + +void unload(); + +} // namespace Net + +#endif // NET_H diff --git a/src/net/npchandler.h b/src/net/npchandler.h new file mode 100644 index 000000000..1b08a83ec --- /dev/null +++ b/src/net/npchandler.h @@ -0,0 +1,66 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NPCHANDLER_H +#define NPCHANDLER_H + +#include + +namespace Net +{ + +class NpcHandler +{ + public: + virtual ~NpcHandler() + { } + + virtual void talk(int npcId) = 0; + + virtual void nextDialog(int npcId) = 0; + + virtual void closeDialog(int npcId) = 0; + + virtual void listInput(int npcId, unsigned char value) = 0; + + virtual void integerInput(int npcId, int value) = 0; + + virtual void stringInput(int npcId, const std::string &value) = 0; + + virtual void sendLetter(int npcId, const std::string &recipient, + const std::string &text) = 0; + + virtual void startShopping(int beingId) = 0; + + virtual void buy(int beingId) = 0; + + virtual void sell(int beingId) = 0; + + virtual void buyItem(int beingId, int itemId, int amount) = 0; + + virtual void sellItem(int beingId, int itemId, int amount) = 0; + + virtual void endShopping(int beingId) = 0; +}; + +} // namespace Net + +#endif // NPCHANDLER_H diff --git a/src/net/packetcounters.cpp b/src/net/packetcounters.cpp new file mode 100644 index 000000000..40ab72024 --- /dev/null +++ b/src/net/packetcounters.cpp @@ -0,0 +1,128 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/packetcounters.h" + +extern volatile int cur_time; +extern volatile bool runCounters; + +int PacketCounters::mInCurrentSec = 0; +int PacketCounters::mInBytes = 0; +int PacketCounters::mInBytesCalc = 0; +int PacketCounters::mInPackets = 0; +int PacketCounters::mInPacketsCalc = 0; +int PacketCounters::mOutCurrentSec = 0; +int PacketCounters::mOutBytes = 0; +int PacketCounters::mOutBytesCalc = 0; +int PacketCounters::mOutPackets = 0; +int PacketCounters::mOutPacketsCalc = 0; + +void PacketCounters::incInBytes(int cnt) +{ + if (!runCounters) + return; + + updateCounter(PacketCounters::mInCurrentSec, PacketCounters::mInBytesCalc, + PacketCounters::mInBytes); + + PacketCounters::mInBytes += cnt; +} + +void PacketCounters::incInPackets() +{ + if (!runCounters) + return; + + updateCounter(PacketCounters::mInCurrentSec, + PacketCounters::mInPacketsCalc, PacketCounters::mInPackets); + + PacketCounters::mInPackets ++; +} + +int PacketCounters::getInBytes() +{ + return PacketCounters::mInBytesCalc; +} + +int PacketCounters::getInPackets() +{ + return PacketCounters::mInPacketsCalc; +} + +void PacketCounters::incOutBytes(int cnt) +{ + if (!runCounters) + return; + + updateCounter(PacketCounters::mOutCurrentSec, + PacketCounters::mOutBytesCalc, PacketCounters::mOutBytes); + + PacketCounters::mOutBytes += cnt; +} + +void PacketCounters::incOutPackets() +{ + if (!runCounters) + return; + + updateCounter(PacketCounters::mOutCurrentSec, + PacketCounters::mOutPacketsCalc, + PacketCounters::mOutPackets); + + PacketCounters::mOutPackets ++; +} + +int PacketCounters::getOutBytes() +{ + return PacketCounters::mOutBytesCalc; +} + +int PacketCounters::getOutPackets() +{ + return PacketCounters::mOutPacketsCalc; +} + + +void PacketCounters::updateCounter(int ¤tSec, int &calc, int &counter) +{ + int idx = cur_time % 60; + if (currentSec != idx) + { + currentSec = idx; + calc = counter; + counter = 0; + } +} + +void PacketCounters::update() +{ + if (!runCounters) + return; + + updateCounter(PacketCounters::mInCurrentSec, PacketCounters::mInBytesCalc, + PacketCounters::mInBytes); + updateCounter(PacketCounters::mInCurrentSec, + PacketCounters::mInPacketsCalc, PacketCounters::mInPackets); + updateCounter(PacketCounters::mOutCurrentSec, + PacketCounters::mOutBytesCalc, PacketCounters::mOutBytes); + updateCounter(PacketCounters::mOutCurrentSec, + PacketCounters::mOutPacketsCalc, PacketCounters::mOutPackets); +} diff --git a/src/net/packetcounters.h b/src/net/packetcounters.h new file mode 100644 index 000000000..1e1aa7d83 --- /dev/null +++ b/src/net/packetcounters.h @@ -0,0 +1,55 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PACKETCOUNTERS_H +#define PACKETCOUNTERS_H + +class PacketCounters +{ +public: +// PacketCounters(); + static void incInBytes(int cnt); + static void incInPackets(); + static int getInBytes(); + static int getInPackets(); + static void incOutBytes(int cnt); + static void incOutPackets(); + static int getOutBytes(); + static int getOutPackets(); + static void update(); + + static int mInCurrentSec; + static int mInBytes; + static int mInBytesCalc; + static int mInPackets; + static int mInPacketsCalc; + static int mOutCurrentSec; + static int mOutBytes; + static int mOutBytesCalc; + static int mOutPackets; + static int mOutPacketsCalc; + +private: + static void updateCounter(int ¤tSec, int &calc, int &counter); + +}; + +#endif diff --git a/src/net/partyhandler.h b/src/net/partyhandler.h new file mode 100644 index 000000000..461334ece --- /dev/null +++ b/src/net/partyhandler.h @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PARTYHANDLER_H +#define PARTYHANDLER_H + +#include + +class Being; + +enum PartyShare +{ + PARTY_SHARE_UNKNOWN = -1, + PARTY_SHARE_NO, + PARTY_SHARE, + PARTY_SHARE_NOT_POSSIBLE = 2 +}; + +namespace Net +{ + +class PartyHandler +{ + public: + virtual ~PartyHandler() + { } + + virtual void create(const std::string &name = "") = 0; + + virtual void join(int partyId) = 0; + + virtual void invite(Being *player) = 0; + + virtual void invite(const std::string &name) = 0; + + virtual void inviteResponse(const std::string &inviter, + bool accept) = 0; + + virtual void leave() = 0; + + virtual void kick(Being *player) = 0; + + virtual void kick(const std::string &name) = 0; + + virtual void chat(const std::string &text) = 0; + + virtual void requestPartyMembers() = 0; + + virtual PartyShare getShareExperience() = 0; + + virtual void setShareExperience(PartyShare share) = 0; + + virtual PartyShare getShareItems() = 0; + + virtual void setShareItems(PartyShare share) = 0; + + // virtual void options() = 0; + + // virtual void message() = 0; +}; + +} // namespace Net + +#endif // PARTYHANDLER_H diff --git a/src/net/playerhandler.h b/src/net/playerhandler.h new file mode 100644 index 000000000..8fa84b38f --- /dev/null +++ b/src/net/playerhandler.h @@ -0,0 +1,75 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLAYERHANDLER_H +#define PLAYERHANDLER_H + +#include "being.h" +#include "flooritem.h" +#include "localplayer.h" + +namespace Net +{ + +class PlayerHandler +{ + public: + virtual ~PlayerHandler() + { } + + virtual void attack(int id, bool keep = false) = 0; + + virtual void stopAttack() = 0; + + virtual void emote(int emoteId) = 0; + + virtual void increaseAttribute(int attr) = 0; + + virtual void decreaseAttribute(int attr) = 0; + + virtual void increaseSkill(int skillId) = 0; + + virtual void pickUp(FloorItem *floorItem) = 0; + + virtual void setDirection(char direction) = 0; + + virtual void setDestination(int x, int y, int direction = -1) = 0; + + virtual void changeAction(Being::Action action) = 0; + + virtual void respawn() = 0; + + virtual void ignorePlayer(const std::string &player, bool ignore) = 0; + + virtual void ignoreAll(bool ignore) = 0; + + virtual bool canUseMagic() = 0; + + virtual bool canCorrectAttributes() = 0; + + virtual int getJobLocation() = 0; + + virtual Vector getDefaultWalkSpeed() = 0; +}; + +} // namespace Net + +#endif // PLAYERHANDLER_H diff --git a/src/net/serverinfo.h b/src/net/serverinfo.h new file mode 100644 index 000000000..113d8a9b2 --- /dev/null +++ b/src/net/serverinfo.h @@ -0,0 +1,117 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SERVERINFO_H +#define SERVERINFO_H + +#include "utils/stringutils.h" + +#include +#include + +class ServerInfo +{ +public: + enum Type + { + UNKNOWN = 0, + MANASERV, + TMWATHENA + }; + + typedef std::pair VersionString; + + Type type; + std::string name; + std::string hostname; + unsigned short port; + + std::string description; + VersionString version; + + bool save; + + ServerInfo() + { + type = TMWATHENA; + port = 0; + save = false; + version.first = 0; + } + + ServerInfo(const ServerInfo &info) + { + type = info.type; + name = info.name; + hostname = info.hostname; + port = info.port; + description = info.description; + version.first = info.version.first; + version.second = info.version.second; + save = info.save; + } + + bool isValid() const + { + return !(hostname.empty() || port == 0 || type == UNKNOWN); + } + + void clear() + { + type = UNKNOWN; + name.clear(); + hostname.clear(); + port = 0; + description.clear(); + version.first = 0; + version.second.clear(); + save = false; + } + + bool operator==(const ServerInfo &other) const + { + return (type == other.type && hostname == other.hostname && + port == other.port); + } + + bool operator!=(const ServerInfo &other) const + { + return (type != other.type || hostname != other.hostname || + port != other.port); + } + + static Type parseType(const std::string &type) + { + if (compareStrI(type, "tmwathena") == 0) + return TMWATHENA; + // Used for backward compatibility + else if (compareStrI(type, "eathena") == 0) + return TMWATHENA; + else if (compareStrI(type, "manaserv") == 0) + return MANASERV; + + return UNKNOWN; + } +}; + +typedef std::vector ServerInfos; + +#endif // SERVERINFO_H diff --git a/src/net/specialhandler.h b/src/net/specialhandler.h new file mode 100644 index 000000000..cc3a09356 --- /dev/null +++ b/src/net/specialhandler.h @@ -0,0 +1,45 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SPECIALHANDLER_H +#define SPECIALHANDLER_H + +#include + +namespace Net +{ +class SpecialHandler +{ + public: + virtual ~SpecialHandler () + { } + + virtual void use(int id) = 0; + + virtual void use(int id, int level, int beingId) = 0; + + virtual void use(int id, int level, int x, int y) = 0; + + virtual void use(int id, const std::string &map) = 0; +}; +} + +#endif // SPECIALHANDLER_H diff --git a/src/net/tmwa/adminhandler.cpp b/src/net/tmwa/adminhandler.cpp new file mode 100644 index 000000000..114c9a897 --- /dev/null +++ b/src/net/tmwa/adminhandler.cpp @@ -0,0 +1,135 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/adminhandler.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "event.h" +#include "game.h" +#include "playerrelations.h" + +#include "net/chathandler.h" +#include "net/net.h" + +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +extern Net::AdminHandler *adminHandler; + +namespace TmwAthena +{ + +AdminHandler::AdminHandler() +{ + static const Uint16 _messages[] = + { + SMSG_ADMIN_KICK_ACK, + 0 + }; + handledMessages = _messages; + adminHandler = this; +} + +void AdminHandler::handleMessage(Net::MessageIn &msg) +{ + int id; + switch (msg.getId()) + { + case SMSG_ADMIN_KICK_ACK: + id = msg.readInt32(); + if (id == 0) + SERVER_NOTICE(_("Kick failed!")) + else + SERVER_NOTICE(_("Kick succeeded!")) + default: + break; + } +} + +void AdminHandler::announce(const std::string &text) +{ + MessageOut outMsg(CMSG_ADMIN_ANNOUNCE); + outMsg.writeInt16(static_cast(text.length() + 4)); + outMsg.writeString(text, static_cast(text.length())); +} + +void AdminHandler::localAnnounce(const std::string &text) +{ + MessageOut outMsg(CMSG_ADMIN_LOCAL_ANNOUNCE); + outMsg.writeInt16(static_cast(text.length() + 4)); + outMsg.writeString(text, static_cast(text.length())); +} + +void AdminHandler::hide(bool hide _UNUSED_) +{ + MessageOut outMsg(CMSG_ADMIN_HIDE); + outMsg.writeInt32(0); //unused +} + +void AdminHandler::kick(int playerId) +{ + MessageOut outMsg(CMSG_ADMIN_KICK); + outMsg.writeInt32(playerId); +} + +void AdminHandler::kick(const std::string &name) +{ + Net::getChatHandler()->talk("@kick " + name); +} + +void AdminHandler::ban(int playerId _UNUSED_) +{ + // Not supported +} + +void AdminHandler::ban(const std::string &name) +{ + Net::getChatHandler()->talk("@ban " + name); +} + +void AdminHandler::unban(int playerId _UNUSED_) +{ + // Not supported +} + +void AdminHandler::unban(const std::string &name) +{ + Net::getChatHandler()->talk("@unban " + name); +} + +void AdminHandler::mute(int playerId _UNUSED_, int type _UNUSED_, + int limit _UNUSED_) +{ + return; // Still looking into this +/* + MessageOut outMsg(CMSG_ADMIN_MUTE); + outMsg.writeInt32(playerId); + outMsg.writeInt8(type); + outMsg.writeInt16(limit); +*/ +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/adminhandler.h b/src/net/tmwa/adminhandler.h new file mode 100644 index 000000000..a7febf441 --- /dev/null +++ b/src/net/tmwa/adminhandler.h @@ -0,0 +1,69 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_ADMINHANDLER_H +#define NET_TA_ADMINHANDLER_H + +#include "net/adminhandler.h" +#include "net/net.h" + +#include "net/tmwa/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class AdminHandler : public MessageHandler, public Net::AdminHandler +{ + public: + AdminHandler(); + + void handleMessage(Net::MessageIn &msg); + + void announce(const std::string &text); + + void localAnnounce(const std::string &text); + + void hide(bool hide); + + void kick(int playerId); + + void kick(const std::string &name); + + void ban(int playerId); + + void ban(const std::string &name); + + void unban(int playerId); + + void unban(const std::string &name); + + void mute(int playerId, int type, int limit); +}; + +} // namespace TmwAthena + +#endif // NET_TA_ADMINHANDLER_H diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp new file mode 100644 index 000000000..ea4978a20 --- /dev/null +++ b/src/net/tmwa/beinghandler.cpp @@ -0,0 +1,1075 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/beinghandler.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "client.h" +#include "effectmanager.h" +#include "guild.h" +#include "keyboardconfig.h" +#include "localplayer.h" +#include "log.h" +#include "party.h" +#include "playerrelations.h" +#include "configuration.h" + +#include "gui/botcheckerwindow.h" +#include "gui/outfitwindow.h" +#include "gui/socialwindow.h" +#include "gui/killstats.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "net/playerhandler.h" +#include "net/net.h" + +#include "net/tmwa/protocol.h" + +#include "resources/colordb.h" + +#include + +extern Net::BeingHandler *beingHandler; + +namespace TmwAthena +{ + +const int EMOTION_TIME = 500; /**< Duration of emotion icon */ + +Being *createBeing(int id, short job); + +BeingHandler::BeingHandler(bool enableSync): + mSync(enableSync) +{ + static const Uint16 _messages[] = + { + SMSG_BEING_VISIBLE, + SMSG_BEING_MOVE, + SMSG_BEING_MOVE2, + SMSG_BEING_REMOVE, + SMSG_SKILL_DAMAGE, + SMSG_BEING_ACTION, + SMSG_BEING_SELFEFFECT, + SMSG_BEING_EMOTION, + SMSG_BEING_CHANGE_LOOKS, + SMSG_BEING_CHANGE_LOOKS2, + SMSG_BEING_NAME_RESPONSE, + SMSG_PLAYER_GUILD_PARTY_INFO, + SMSG_BEING_CHANGE_DIRECTION, + SMSG_PLAYER_UPDATE_1, + SMSG_PLAYER_UPDATE_2, + SMSG_PLAYER_MOVE, + SMSG_PLAYER_STOP, + SMSG_PLAYER_MOVE_TO_ATTACK, + SMSG_PLAYER_STATUS_CHANGE, + SMSG_BEING_STATUS_CHANGE, + SMSG_BEING_RESURRECT, + SMSG_SOLVE_CHAR_NAME, + SMSG_BEING_SPAWN, + SMSG_SKILL_CASTING, + SMSG_SKILL_CAST_CANCEL, + SMSG_SKILL_NO_DAMAGE, + SMSG_BEING_IP_RESPONSE, + SMSG_PVP_MAP_MODE, + SMSG_PVP_SET, + 0 + }; + handledMessages = _messages; + beingHandler = this; +} + +Being *createBeing(int id, short job) +{ + if (!actorSpriteManager) + return 0; + + ActorSprite::Type type = ActorSprite::UNKNOWN; + if (job <= 25 || (job >= 4001 && job <= 4049)) + type = ActorSprite::PLAYER; + else if (job >= 46 && job <= 1000) + type = ActorSprite::NPC; + else if (job > 1000 && job <= 2000) + type = ActorSprite::MONSTER; + else if (job == 45) + type = ActorSprite::PORTAL; + + Being *being = actorSpriteManager->createBeing(id, type, job); + + if (type == ActorSprite::PLAYER || type == ActorSprite::NPC) + { + if (!being->updateFromCache()) + { + MessageOut outMsg(0x0094); + outMsg.writeInt32(id); //readLong(2)); + } + else + { + if (player_node) + player_node->checkNewName(being); + } + } + if (type == Being::PLAYER) + { + if (botCheckerWindow) + botCheckerWindow->updateList(); + if (socialWindow) + socialWindow->updateActiveList(); + } + return being; +} + +void BeingHandler::requestNameById(int id) +{ + MessageOut outMsg(0x0094); + outMsg.writeInt32(id); //readLong(2)); +} + +void BeingHandler::handleMessage(Net::MessageIn &msg) +{ + if (!actorSpriteManager) + return; + + int id; + short job, speed, gender; + Uint16 headTop, headMid, headBottom; + Uint16 shoes, gloves; + Uint16 weapon, shield; + Uint16 gmstatus; + int param1; + Uint16 stunMode; + int level; + Uint32 statusEffects; + int type, guild; + Uint16 status; + Being *srcBeing, *dstBeing; + int hairStyle, hairColor, flag; + int hp, maxHP, oldHP; + + switch (msg.getId()) + { + case SMSG_BEING_VISIBLE: + case SMSG_BEING_MOVE: + // Information about a being in range + id = msg.readInt32(); + speed = msg.readInt16(); + stunMode = msg.readInt16(); // opt1 + statusEffects = msg.readInt16(); // opt2 + statusEffects |= ((Uint32)msg.readInt16()) << 16; // option + job = msg.readInt16(); // class + + dstBeing = actorSpriteManager->findBeing(id); + + if (dstBeing && dstBeing->getType() == Being::MONSTER + && !dstBeing->isAlive()) + { + actorSpriteManager->destroy(dstBeing); + actorSpriteManager->erase(dstBeing); + dstBeing = 0; + } + + if (!dstBeing) + { + // Being with id >= 110000000 and job 0 are better + // known as ghosts, so don't create those. + if (job == 0 && id >= 110000000) + break; + + if (actorSpriteManager->isBlocked(id) == true) + break; + + dstBeing = createBeing(id, job); + + if (!dstBeing) + break; + + if (job == 1022 && killStats) + killStats->jackoAlive(dstBeing->getId()); + } + else + { + // undeleting marked for deletion being + if (dstBeing->getType() == Being::NPC) + { + actorSpriteManager->undelete(dstBeing); + } + } + + if (dstBeing->getType() == Being::PLAYER) + dstBeing->setMoveTime(); + + if (msg.getId() == SMSG_BEING_VISIBLE) + { + dstBeing->clearPath(); + dstBeing->setActionTime(tick_time); + dstBeing->setAction(Being::STAND); + } + + + // Prevent division by 0 when calculating frame + if (speed == 0) + speed = 150; + + dstBeing->setWalkSpeed(Vector(speed, speed, 0)); + dstBeing->setSubtype(job); + if (dstBeing->getType() == ActorSprite::MONSTER && player_node) + player_node->checkNewName(dstBeing); + + hairStyle = msg.readInt16(); + weapon = msg.readInt16(); + headBottom = msg.readInt16(); + + if (msg.getId() == SMSG_BEING_MOVE) + msg.readInt32(); // server tick + + shield = msg.readInt16(); + headTop = msg.readInt16(); + headMid = msg.readInt16(); + hairColor = msg.readInt16(); + shoes = msg.readInt16(); // clothes color - "abused" as shoes + + if (dstBeing->getType() == ActorSprite::MONSTER) + { + hp = msg.readInt32(); + maxHP = msg.readInt32(); + if (hp && maxHP) + { + oldHP = dstBeing->getHP(); + if (!oldHP || oldHP > hp) + dstBeing->setHP(hp); + dstBeing->setMaxHP(maxHP); + } + gloves = 0; + guild = 0; + } + else + { + gloves = msg.readInt16(); // head dir - "abused" as gloves + guild = msg.readInt32(); // guild + msg.readInt16(); // guild emblem + } +// logger->log("being guild: " + toString(guild)); +/* + if (guild == 0) + dstBeing->clearGuilds(); + else + dstBeing->setGuild(Guild::getGuild(static_cast(guild))); +*/ + + msg.readInt16(); // manner + dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 + msg.readInt8(); // karma + gender = msg.readInt8(); + + if (dstBeing->getType() == ActorSprite::PLAYER) + { + dstBeing->setGender((gender == 0) + ? GENDER_FEMALE : GENDER_MALE); + // Set these after the gender, as the sprites may be gender-specific + dstBeing->setSprite(SPRITE_HAIR, hairStyle * -1, + ColorDB::get(hairColor)); + dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, headBottom); + dstBeing->setSprite(SPRITE_TOPCLOTHES, headMid); + dstBeing->setSprite(SPRITE_HAT, headTop); + dstBeing->setSprite(SPRITE_SHOE, shoes); + dstBeing->setSprite(SPRITE_GLOVES, gloves); + dstBeing->setSprite(SPRITE_WEAPON, weapon, "", true); + if (!config.getBoolValue("hideShield")) + dstBeing->setSprite(SPRITE_SHIELD, shield); + } + + if (msg.getId() == SMSG_BEING_MOVE) + { + Uint16 srcX, srcY, dstX, dstY; + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + dstBeing->setAction(Being::STAND); + dstBeing->setTileCoords(srcX, srcY); + dstBeing->setDestination(dstX, dstY); + +// if (player_node && player_node->getTarget() == dstBeing) +// player_node->targetMoved(); + } + else + { + Uint8 dir; + Uint16 x, y; + msg.readCoordinates(x, y, dir); + dstBeing->setTileCoords(x, y); + + if (job == 45 && socialWindow && outfitWindow) + { + int num = socialWindow->getPortalIndex(x, y); + if (num >= 0) + { + dstBeing->setName(keyboard.getKeyShortString( + outfitWindow->keyName(num))); + } + else + { + dstBeing->setName(""); + } + } + + dstBeing->setDirection(getDirection(dir)); + } + + msg.readInt8(); // unknown + msg.readInt8(); // unknown +// msg.readInt8(); // unknown / sit + msg.readInt16(); + + dstBeing->setStunMode(stunMode); + dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); + dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + break; + + case SMSG_BEING_MOVE2: + /* + * A simplified movement packet, used by the + * later versions of eAthena for both mobs and + * players + */ + dstBeing = actorSpriteManager->findBeing(msg.readInt32()); + + /* + * This packet doesn't have enough info to actually + * create a new being, so if the being isn't found, + * we'll just pretend the packet didn't happen + */ + + if (!dstBeing) + break; + + Uint16 srcX, srcY, dstX, dstY; + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + msg.readInt32(); // Server tick + + dstBeing->setAction(Being::STAND); + dstBeing->setTileCoords(srcX, srcY); + dstBeing->setDestination(dstX, dstY); + if (dstBeing->getType() == Being::PLAYER) + dstBeing->setMoveTime(); + +// if (player_node && player_node->getTarget() == dstBeing) +// { +// logger->log("SMSG_BEING_MOVE2"); +// player_node->targetMoved(); +// } + + break; + + case SMSG_BEING_SPAWN: + // skipping this packet + msg.readInt32(); // id + msg.readInt16(); // speed + msg.readInt16(); // opt1 + msg.readInt16(); // opt2 + msg.readInt16(); // option + msg.readInt16(); // disguise + break; + + case SMSG_BEING_REMOVE: + // A being should be removed or has died + + id = msg.readInt32(); + dstBeing = actorSpriteManager->findBeing(id); + if (!dstBeing) + break; + + player_node->followMoveTo(dstBeing, player_node->getNextDestX(), + player_node->getNextDestY()); + + // If this is player's current target, clear it. + if (dstBeing == player_node->getTarget()) + player_node->stopAttack(); + + if (msg.readInt8() == 1) + { + dstBeing->setAction(Being::DEAD); + if (dstBeing->getName() == "Jack O" && killStats) + killStats->jackoDead(id); + } + else + { + if (dstBeing->getType() == Being::PLAYER) + { + if (botCheckerWindow) + botCheckerWindow->updateList(); + if (socialWindow) + socialWindow->updateActiveList(); + } + actorSpriteManager->destroy(dstBeing); + } + break; + + case SMSG_BEING_RESURRECT: + // A being changed mortality status + + id = msg.readInt32(); + dstBeing = actorSpriteManager->findBeing(id); + if (!dstBeing) + break; + + // If this is player's current target, clear it. + if (dstBeing == player_node->getTarget()) + player_node->stopAttack(); + + if (msg.readInt8() == 1) + dstBeing->setAction(Being::STAND); + + break; + + case SMSG_SKILL_DAMAGE: + msg.readInt16(); // Skill Id + srcBeing = actorSpriteManager->findBeing(msg.readInt32()); + dstBeing = actorSpriteManager->findBeing(msg.readInt32()); + msg.readInt32(); // Server tick + msg.readInt32(); // src speed + msg.readInt32(); // dst speed + param1 = msg.readInt32(); // Damage + msg.readInt16(); // Skill level + msg.readInt16(); // Div + msg.readInt8(); // Skill hit/type (?) + if (dstBeing) + { + // Perhaps a new skill attack type should be created and used? + dstBeing->takeDamage(srcBeing, param1, Being::HIT); + } + if (srcBeing) + srcBeing->handleAttack(dstBeing, param1, Being::HIT); + break; + + case SMSG_BEING_ACTION: + srcBeing = actorSpriteManager->findBeing(msg.readInt32()); + dstBeing = actorSpriteManager->findBeing(msg.readInt32()); + + msg.readInt32(); // server tick + msg.readInt32(); // src speed + msg.readInt32(); // dst speed + param1 = msg.readInt16(); + msg.readInt16(); // param 2 + type = msg.readInt8(); + msg.readInt16(); // param 3 + + switch (type) + { + case Being::HIT: // Damage + case Being::CRITICAL: // Critical Damage + case Being::MULTI: // Critical Damage + case Being::REFLECT: // Reflected Damage + case Being::FLEE: // Lucky Dodge + if (dstBeing) + { + dstBeing->takeDamage(srcBeing, param1, + (Being::AttackType)type); + } + if (srcBeing) + { + srcBeing->handleAttack(dstBeing, param1, + (Being::AttackType)type); + if (srcBeing->getType() == Being::PLAYER) + srcBeing->setAttackTime(); + } + break; + + case 0x02: // Sit + if (srcBeing) + { + srcBeing->setAction(Being::SIT); + if (srcBeing->getType() == Being::PLAYER) + { + srcBeing->setMoveTime(); + if (player_node) + { + player_node->imitateAction( + srcBeing, Being::SIT); + } + } + } + break; + + case 0x03: // Stand up + if (srcBeing) + { + srcBeing->setAction(Being::STAND); + if (srcBeing->getType() == Being::PLAYER) + { + srcBeing->setMoveTime(); + if (player_node) + { + player_node->imitateAction( + srcBeing, Being::STAND); + } + } + } + break; + default: + break; +/* + logger->log("QQQ1 SMSG_BEING_ACTION:"); + if (srcBeing) + logger->log("srcBeing:" + toString(srcBeing->getId())); + if (dstBeing) + logger->log("dstBeing:" + toString(dstBeing->getId())); + logger->log("type: " + toString(type)); +*/ + } + break; + + case SMSG_BEING_SELFEFFECT: + { + if (!effectManager) + return; + + id = (Uint32)msg.readInt32(); + Being* being = actorSpriteManager->findBeing(id); + if (!being) + break; + + int effectType = msg.readInt32(); + + effectManager->trigger(effectType, being); + + if (being && effectType == 3 + && being->getType() == Being::PLAYER + && socialWindow) + { //reset received damage + socialWindow->resetDamage(being->getName()); + } + + break; + } + + case SMSG_BEING_EMOTION: + if (!player_node) + break; + + if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) + break; + + if (player_relations.hasPermission(dstBeing, + PlayerRelation::EMOTE)) + { + unsigned char emote = msg.readInt8(); + if (emote) + { + dstBeing->setEmote(emote, EMOTION_TIME); + player_node->imitateEmote(dstBeing, emote); + } + } + if (dstBeing->getType() == Being::PLAYER) + dstBeing->setOtherTime(); + + break; + + case SMSG_BEING_CHANGE_LOOKS: + case SMSG_BEING_CHANGE_LOOKS2: + { + /* + * SMSG_BEING_CHANGE_LOOKS (0x00c3) and + * SMSG_BEING_CHANGE_LOOKS2 (0x01d7) do basically the same + * thing. The difference is that ...LOOKS carries a single + * 8 bit value, where ...LOOKS2 carries two 16 bit values. + * + * If type = 2, then the first 16 bit value is the weapon ID, + * and the second 16 bit value is the shield ID. If no + * shield is equipped, or type is not 2, then the second + * 16 bit value will be 0. + */ + + if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) + break; + + int type = msg.readInt8(); + int id = 0; + int id2 = 0; + + if (msg.getId() == SMSG_BEING_CHANGE_LOOKS) + { + id = msg.readInt8(); + } + else + { // SMSG_BEING_CHANGE_LOOKS2 + id = msg.readInt16(); + id2 = msg.readInt16(); + } + + if (dstBeing->getType() == Being::PLAYER) + dstBeing->setOtherTime(); + + if (!player_node) + break; + + switch (type) + { + case 0: // change race + dstBeing->setSubtype(id); + break; + case 1: // eAthena LOOK_HAIR + dstBeing->setSpriteID(SPRITE_HAIR, id *-1); + break; + case 2: // Weapon ID in id, Shield ID in id2 + dstBeing->setSprite(SPRITE_WEAPON, id, "", true); + if (!config.getBoolValue("hideShield")) + dstBeing->setSprite(SPRITE_SHIELD, id2); + player_node->imitateOutfit(dstBeing, SPRITE_SHIELD); + break; + case 3: // Change lower headgear for eAthena, pants for us + dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, id); + player_node->imitateOutfit(dstBeing, SPRITE_BOTTOMCLOTHES); + break; + case 4: // Change upper headgear for eAthena, hat for us + dstBeing->setSprite(SPRITE_HAT, id); + player_node->imitateOutfit(dstBeing, SPRITE_HAT); + break; + case 5: // Change middle headgear for eathena, armor for us + dstBeing->setSprite(SPRITE_TOPCLOTHES, id); + player_node->imitateOutfit(dstBeing, SPRITE_TOPCLOTHES); + break; + case 6: // eAthena LOOK_HAIR_COLOR + dstBeing->setSpriteColor(SPRITE_HAIR, ColorDB::get(id)); + break; + case 8: // eAthena LOOK_SHIELD + if (!config.getBoolValue("hideShield")) + dstBeing->setSprite(SPRITE_SHIELD, id); + player_node->imitateOutfit(dstBeing, SPRITE_SHIELD); + break; + case 9: // eAthena LOOK_SHOES + dstBeing->setSprite(SPRITE_SHOE, id); + player_node->imitateOutfit(dstBeing, SPRITE_SHOE); + break; + case 10: // LOOK_GLOVES + dstBeing->setSprite(SPRITE_GLOVES, id); + player_node->imitateOutfit(dstBeing, SPRITE_GLOVES); + break; + case 11: // LOOK_CAPE + dstBeing->setSprite(SPRITE_CAPE, id); + player_node->imitateOutfit(dstBeing, SPRITE_CAPE); + break; + case 12: + dstBeing->setSprite(SPRITE_MISC1, id); + player_node->imitateOutfit(dstBeing, SPRITE_MISC1); + break; + case 13: + dstBeing->setSprite(SPRITE_MISC2, id); + player_node->imitateOutfit(dstBeing, SPRITE_MISC2); + break; + default: + logger->log("QQQ3 CHANGE_LOOKS: unsupported type: " + "%d, id: %d", type, id); + if (dstBeing) + { + logger->log("ID: " + toString(dstBeing->getId())); + logger->log("name: " + toString(dstBeing->getName())); + } + break; + } + } + break; + + case SMSG_BEING_NAME_RESPONSE: + { + int beingId = msg.readInt32(); + if ((dstBeing = actorSpriteManager->findBeing(beingId))) + { + if (beingId == player_node->getId()) + { + player_node->pingResponse(); + } + else + { + dstBeing->setName(msg.readString(24)); + dstBeing->updateGuild(); + dstBeing->addToCache(); + + if (dstBeing->getType() == Being::PLAYER) + dstBeing->updateColors(); + + if (player_node) + { + Party *party = player_node->getParty(); + if (party && party->isMember(dstBeing->getId())) + { + PartyMember *member = party->getMember( + dstBeing->getId()); + + if (member) + member->setName(dstBeing->getName()); + } + player_node->checkNewName(dstBeing); + } + } + } + } + break; + case SMSG_BEING_IP_RESPONSE: + { + if ((dstBeing = actorSpriteManager->findBeing( + msg.readInt32()))) + { + dstBeing->setIp(ipToString(msg.readInt32())); + } + } + break; + case SMSG_SOLVE_CHAR_NAME: + { + logger->log1("SMSG_SOLVE_CHAR_NAME"); + logger->log(toString(msg.readInt32())); + logger->log(msg.readString(24)); + } + break; + case SMSG_PLAYER_GUILD_PARTY_INFO: + if ((dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) + { + dstBeing->setPartyName(msg.readString(24)); + dstBeing->setGuildName(msg.readString(24)); + dstBeing->setGuildPos(msg.readString(24)); + dstBeing->addToCache(); + msg.readString(24); // Discard this + } + break; + case SMSG_BEING_CHANGE_DIRECTION: + { + if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) + break; + + msg.readInt16(); // unused + + unsigned char dir = getDirection(msg.readInt8()); + dstBeing->setDirection(dir); + if (player_node) + player_node->imitateDirection(dstBeing, dir); + break; + } + case SMSG_PLAYER_UPDATE_1: + case SMSG_PLAYER_UPDATE_2: + case SMSG_PLAYER_MOVE: + if (!actorSpriteManager || !player_node) + break; + + // An update about a player, potentially including movement. + id = msg.readInt32(); + speed = msg.readInt16(); + stunMode = msg.readInt16(); // opt1; Aethyra use this as cape + statusEffects = msg.readInt16(); // opt2; Aethyra use this as misc1 + statusEffects |= ((Uint32) msg.readInt16()) + << 16; // status.options; Aethyra uses this as misc2 + job = msg.readInt16(); + + dstBeing = actorSpriteManager->findBeing(id); + + if (!dstBeing) + { + if (actorSpriteManager->isBlocked(id) == true) + break; + + dstBeing = createBeing(id, job); + + if (!dstBeing) + break; + } + + if (Party *party = player_node->getParty()) + { + if (party->isMember(id)) + dstBeing->setParty(party); + } + + dstBeing->setWalkSpeed(Vector(speed, speed, 0)); + dstBeing->setSubtype(job); + hairStyle = msg.readInt16(); + weapon = msg.readInt16(); + shield = msg.readInt16(); + headBottom = msg.readInt16(); + + if (msg.getId() == SMSG_PLAYER_MOVE) + msg.readInt32(); // server tick + + headTop = msg.readInt16(); + headMid = msg.readInt16(); + hairColor = msg.readInt16(); + shoes = msg.readInt16(); + gloves = msg.readInt16(); //sd->head_dir + guild = msg.readInt32(); // guild + + if (guild == 0) + dstBeing->clearGuilds(); + else + dstBeing->setGuild(Guild::getGuild(static_cast(guild))); + + msg.readInt16(); // emblem + msg.readInt16(); // manner + dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 + msg.readInt8(); // karma + dstBeing->setGender((msg.readInt8() == 0) + ? GENDER_FEMALE : GENDER_MALE); + + // Set these after the gender, as the sprites may be gender-specific + dstBeing->setSprite(SPRITE_WEAPON, weapon, "", true); + if (!config.getBoolValue("hideShield")) + dstBeing->setSprite(SPRITE_SHIELD, shield); + //dstBeing->setSprite(SPRITE_SHOE, shoes); + dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, headBottom); + dstBeing->setSprite(SPRITE_TOPCLOTHES, headMid); + dstBeing->setSprite(SPRITE_HAT, headTop); + //dstBeing->setSprite(SPRITE_GLOVES, gloves); + //dstBeing->setSprite(SPRITE_CAPE, cape); + //dstBeing->setSprite(SPRITE_MISC1, misc1); + //dstBeing->setSprite(SPRITE_MISC2, misc2); + dstBeing->setSprite(SPRITE_HAIR, hairStyle * -1, + ColorDB::get(hairColor)); + + player_node->imitateOutfit(dstBeing); + + if (msg.getId() == SMSG_PLAYER_MOVE) + { + Uint16 srcX, srcY, dstX, dstY; + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + + player_node->followMoveTo(dstBeing, srcX, srcY, dstX, dstY); + + dstBeing->setTileCoords(srcX, srcY); + dstBeing->setDestination(dstX, dstY); + + if (player_node->getCurrentAction() != Being::STAND) + player_node->imitateAction(dstBeing, Being::STAND); + if (player_node->getDirection() != dstBeing->getDirection()) + { + player_node->imitateDirection(dstBeing, + dstBeing->getDirection()); + } + } + else + { + Uint8 dir; + Uint16 x, y; + msg.readCoordinates(x, y, dir); + dstBeing->setTileCoords(x, y); + dir = getDirection(dir); + dstBeing->setDirection(dir); + + player_node->imitateDirection(dstBeing, dir); + } + + gmstatus = msg.readInt16(); + + if (gmstatus & 0x80) + dstBeing->setGM(true); + + if (msg.getId() == SMSG_PLAYER_UPDATE_1) + { + int type = msg.readInt8(); + switch (type) + { + case 0: + dstBeing->setAction(Being::STAND); + player_node->imitateAction(dstBeing, Being::STAND); + break; + + case 1: + dstBeing->setAction(Being::DEAD); + break; + + case 2: + dstBeing->setAction(Being::SIT); + player_node->imitateAction(dstBeing, Being::SIT); + break; + + default: + //need set stay state? + logger->log("QQQ2 SMSG_PLAYER_UPDATE_1:" + + toString(id) + " " + toString(type)); + if (dstBeing) + { + logger->log("dstBeing id:" + + toString(dstBeing->getId())); + logger->log("dstBeing name:" + + dstBeing->getName()); + } + break; + + } + } + else if (msg.getId() == SMSG_PLAYER_MOVE) + { + msg.readInt8(); // unknown + } + + level = msg.readInt8(); // Lv + if (level) + dstBeing->setLevel(level); + + msg.readInt8(); // unknown + + dstBeing->setActionTime(tick_time); + dstBeing->reset(); + + dstBeing->setStunMode(stunMode); + dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); + dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + + if (msg.getId() == SMSG_PLAYER_MOVE + && dstBeing->getType() == Being::PLAYER) + { + dstBeing->setMoveTime(); + } + + break; + case SMSG_PLAYER_STOP: + /* + * Instruction from server to stop walking at x, y. + * + * Some people like having this enabled. Others absolutely + * despise it. So I'm setting to so that it only affects the + * local player if the person has set a key "EnableSync" to "1" + * in their config.xml file. + * + * This packet will be honored for all other beings, regardless + * of the config setting. + */ + + id = msg.readInt32(); + + if (mSync || id != player_node->getId()) + { + dstBeing = actorSpriteManager->findBeing(id); + if (dstBeing) + { + Uint16 x, y; + x = msg.readInt16(); + y = msg.readInt16(); + dstBeing->setTileCoords(x, y); + if (dstBeing->getCurrentAction() == Being::MOVE) + dstBeing->setAction(Being::STAND); + } + } + break; + + case SMSG_PLAYER_MOVE_TO_ATTACK: + /* + * This is an *advisory* message, telling the client that + * it needs to move the character before attacking + * a target (out of range, obstruction in line of fire). + * We can safely ignore this... + */ + if (player_node) + player_node->fixAttackTarget(); + break; + + case SMSG_PLAYER_STATUS_CHANGE: + // Change in players' flags + + id = msg.readInt32(); + dstBeing = actorSpriteManager->findBeing(id); + if (!dstBeing) + break; + + stunMode = msg.readInt16(); + statusEffects = msg.readInt16(); + statusEffects |= ((Uint32) msg.readInt16()) << 16; + msg.readInt8(); // Unused? + + dstBeing->setStunMode(stunMode); + dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); + dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + break; + + case SMSG_BEING_STATUS_CHANGE: + // Status change + status = msg.readInt16(); + id = msg.readInt32(); + flag = msg.readInt8(); // 0: stop, 1: start + + dstBeing = actorSpriteManager->findBeing(id); + if (dstBeing) + dstBeing->setStatusEffect(status, flag); + break; + + case SMSG_SKILL_CASTING: + msg.readInt32(); // src id + msg.readInt32(); // dst id + msg.readInt16(); // dst x + msg.readInt16(); // dst y + msg.readInt16(); // skill num + msg.readInt32(); // skill get pl + msg.readInt32(); // cast time + break; + + case SMSG_SKILL_CAST_CANCEL: + msg.readInt32(); // id + break; + + case SMSG_SKILL_NO_DAMAGE: + msg.readInt16(); // skill id + msg.readInt16(); // heal + msg.readInt32(); // dst id + msg.readInt32(); // src id + msg.readInt8(); // fail + break; + + case SMSG_PVP_MAP_MODE: + { + Game *game = Game::instance(); + if (!game) + break; + + Map *map = game->getCurrentMap(); + if (map) + map->setPvpMode(msg.readInt16()); + break; + } + + case SMSG_PVP_SET: + { + int id = msg.readInt32(); // id + int rank = msg.readInt32(); // rank + msg.readInt32(); // num + dstBeing = actorSpriteManager->findBeing(id); + if (dstBeing) + dstBeing->setPvpRank(rank); + break; + } + + default: + break; + } +} + +Uint8 BeingHandler::getDirection(Uint8 dir) +{ + if (dir == 0) + dir = 8; + return dir; +} + +void BeingHandler::undress(Being *being) +{ + being->setSprite(SPRITE_BOTTOMCLOTHES, 0); + being->setSprite(SPRITE_TOPCLOTHES, 0); + being->setSprite(SPRITE_HAT, 0); + being->setSprite(SPRITE_SHOE, 0); + being->setSprite(SPRITE_GLOVES, 0); +// being->setSprite(SPRITE_WEAPON, 0, "", true); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/beinghandler.h b/src/net/tmwa/beinghandler.h new file mode 100644 index 000000000..f16b35178 --- /dev/null +++ b/src/net/tmwa/beinghandler.h @@ -0,0 +1,53 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_BEINGHANDLER_H +#define NET_TA_BEINGHANDLER_H + +#include "net/beinghandler.h" +#include "net/net.h" + +#include "net/tmwa/messagehandler.h" + +namespace TmwAthena +{ + +class BeingHandler : public MessageHandler, public Net::BeingHandler +{ + public: + BeingHandler(bool enableSync); + + virtual void handleMessage(Net::MessageIn &msg); + + virtual void requestNameById(int id); + + virtual void undress(Being *being); + + Uint8 getDirection(Uint8 dir); + + private: + // Should we honor server "Stop Walking" packets + bool mSync; +}; + +} // namespace TmwAthena + +#endif // NET_TA_BEINGHANDLER_H diff --git a/src/net/tmwa/buysellhandler.cpp b/src/net/tmwa/buysellhandler.cpp new file mode 100644 index 000000000..bdc372238 --- /dev/null +++ b/src/net/tmwa/buysellhandler.cpp @@ -0,0 +1,231 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/buysellhandler.h" + +#include "actorspritemanager.h" +#include "configuration.h" +#include "event.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "playerinfo.h" +#include "shopitem.h" + +#include "gui/buy.h" +#include "gui/buysell.h" +#include "gui/sell.h" +#include "gui/shopwindow.h" + +#include "gui/widgets/chattab.h" + +#include "net/chathandler.h" +#include "net/messagein.h" +#include "net/net.h" + +#include "net/tmwa/chathandler.h" +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" + +extern Net::BuySellHandler *buySellHandler; + +namespace TmwAthena +{ + +BuySellHandler::BuySellHandler() +{ + static const Uint16 _messages[] = + { + SMSG_NPC_BUY_SELL_CHOICE, + SMSG_NPC_BUY, + SMSG_NPC_SELL, + SMSG_NPC_BUY_RESPONSE, + SMSG_NPC_SELL_RESPONSE, + 0 + }; + mNpcId = 0; + handledMessages = _messages; + buySellHandler = this; +} + +void BuySellHandler::handleMessage(Net::MessageIn &msg) +{ + int n_items; + + switch (msg.getId()) + { + case SMSG_NPC_BUY_SELL_CHOICE: + if (!BuySellDialog::isActive()) + { + mNpcId = msg.readInt32(); + new BuySellDialog(mNpcId); + } + break; + + case SMSG_NPC_BUY: + msg.readInt16(); // length + n_items = (msg.getLength() - 4) / 11; + mBuyDialog = new BuyDialog(mNpcId); + mBuyDialog->setMoney(PlayerInfo::getAttribute(MONEY)); + + for (int k = 0; k < n_items; k++) + { + int value = msg.readInt32(); + msg.readInt32(); // DCvalue + msg.readInt8(); // type + int itemId = msg.readInt16(); + mBuyDialog->addItem(itemId, 0, value); + } + break; + + case SMSG_NPC_SELL: + msg.readInt16(); // length + n_items = (msg.getLength() - 4) / 10; + if (n_items > 0) + { + SellDialog *dialog = new SellDialog(mNpcId); + dialog->setMoney(PlayerInfo::getAttribute(MONEY)); + + for (int k = 0; k < n_items; k++) + { + int index = msg.readInt16() - INVENTORY_OFFSET; + int value = msg.readInt32(); + msg.readInt32(); // OCvalue + + Item *item = PlayerInfo::getInventory()->getItem(index); + + if (item && !(item->isEquipped())) + dialog->addItem(item, value); + } + } + else + { + SERVER_NOTICE(_("Nothing to sell.")) + } + break; + + case SMSG_NPC_BUY_RESPONSE: + if (msg.readInt8() == 0) + { + SERVER_NOTICE(_("Thanks for buying.")) + } + else + { + // Reset player money since buy dialog already assumed purchase + // would go fine + if (mBuyDialog) + mBuyDialog->setMoney(PlayerInfo::getAttribute(MONEY)); + SERVER_NOTICE(_("Unable to buy.")) + } + break; + + case SMSG_NPC_SELL_RESPONSE: + if (msg.readInt8() == 0) + SERVER_NOTICE(_("Thanks for selling.")) + else + SERVER_NOTICE(_("Unable to sell.")) + + break; + default: + break; + } + +} + +void BuySellHandler::requestSellList(std::string nick) +{ + if (nick.empty() != 0 || !shopWindow) + return; + + std::string data = "!selllist " + toString(tick_time); + shopWindow->setAcceptPlayer(nick); + + if (config.getBoolValue("hideShopMessages")) + { + Net::getChatHandler()->privateMessage(nick, data); + } + else + { + if (chatWindow) + chatWindow->whisper(nick, data, BY_PLAYER); + } +//was true +} + +void BuySellHandler::requestBuyList(std::string nick) +{ + if (nick.empty() || !shopWindow) + return; + + std::string data = "!buylist " + toString(tick_time); + shopWindow->setAcceptPlayer(nick); + + if (config.getBoolValue("hideShopMessages")) + { + Net::getChatHandler()->privateMessage(nick, data); + } + else + { + if (chatWindow) + chatWindow->whisper(nick, data, BY_PLAYER); + } +//was true +} + +void BuySellHandler::sendBuyRequest(std::string nick, ShopItem* item, + int amount) +{ + if (!chatWindow || nick.empty() || !item || + amount < 1 || amount > item->getQuantity()) + { + return; + } + std::string data = strprintf("!buyitem %d %d %d", + item->getId(), item->getPrice(), amount); + + if (config.getBoolValue("hideShopMessages")) + Net::getChatHandler()->privateMessage(nick, data); + else + chatWindow->whisper(nick, data, BY_PLAYER); +//was true +} + +void BuySellHandler::sendSellRequest(std::string nick, ShopItem* item, + int amount) +{ + if (!chatWindow || nick.empty() || !item || + amount < 1 || amount > item->getQuantity()) + { + return; + } + + std::string data = strprintf("!sellitem %d %d %d", + item->getId(), item->getPrice(), amount); + + if (config.getBoolValue("hideShopMessages")) + Net::getChatHandler()->privateMessage(nick, data); + else + chatWindow->whisper(nick, data, BY_PLAYER); +//was true +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/buysellhandler.h b/src/net/tmwa/buysellhandler.h new file mode 100644 index 000000000..e9ae2b9ff --- /dev/null +++ b/src/net/tmwa/buysellhandler.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_BUYSELLHANDLER_H +#define NET_TA_BUYSELLHANDLER_H + +#include "net/buysellhandler.h" + +#include "being.h" + +#include "net/net.h" + +#include "net/tmwa/messagehandler.h" + +class BuyDialog; + +namespace TmwAthena +{ + +class BuySellHandler : public MessageHandler, public Net::BuySellHandler +{ + public: + BuySellHandler(); + + virtual void handleMessage(Net::MessageIn &msg); + + virtual void requestSellList(std::string nick); + virtual void requestBuyList(std::string nick); + virtual void sendBuyRequest(std::string nick, ShopItem* item, + int amount); + virtual void sendSellRequest(std::string nick, ShopItem* item, + int amount); + + private: + int mNpcId; + BuyDialog *mBuyDialog; +}; + +} // namespace TmwAthena + +#endif // NET_TA_BUYSELLHANDLER_H diff --git a/src/net/tmwa/charserverhandler.cpp b/src/net/tmwa/charserverhandler.cpp new file mode 100644 index 000000000..e500f667b --- /dev/null +++ b/src/net/tmwa/charserverhandler.cpp @@ -0,0 +1,386 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/charserverhandler.h" + +#include "client.h" +#include "configuration.h" +#include "game.h" +#include "log.h" + +#include "gui/charcreatedialog.h" +#include "gui/okdialog.h" + +#include "net/logindata.h" +#include "net/messagein.h" +#include "net/messageout.h" +#include "net/net.h" + +#include "net/tmwa/gamehandler.h" +#include "net/tmwa/loginhandler.h" +#include "net/tmwa/network.h" +#include "net/tmwa/protocol.h" + +#include "resources/colordb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern Net::CharHandler *charHandler; + +namespace TmwAthena +{ + +extern ServerInfo charServer; +extern ServerInfo mapServer; + +CharServerHandler::CharServerHandler() +{ + static const Uint16 _messages[] = + { + SMSG_CHAR_LOGIN, + SMSG_CHAR_LOGIN_ERROR, + SMSG_CHAR_CREATE_SUCCEEDED, + SMSG_CHAR_CREATE_FAILED, + SMSG_CHAR_DELETE_SUCCEEDED, + SMSG_CHAR_DELETE_FAILED, + SMSG_CHAR_MAP_INFO, + SMSG_CHANGE_MAP_SERVER, + 0 + }; + handledMessages = _messages; + charHandler = this; +} + +void CharServerHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_CHAR_LOGIN: + { + msg.skip(2); // Length word + msg.skip(20); // Unused + + delete_all(mCharacters); + mCharacters.clear(); + + // Derive number of characters from message length + const int count = (msg.getLength() - 24) / 106; + + for (int i = 0; i < count; ++i) + { + Net::Character *character = new Net::Character; + readPlayerData(msg, character); + mCharacters.push_back(character); + logger->log("CharServer: Player: %s (%d)", + character->dummy->getName().c_str(), character->slot); + } + + Client::setState(STATE_CHAR_SELECT); + } + break; + + case SMSG_CHAR_LOGIN_ERROR: + switch (msg.readInt8()) + { + case 0: + errorMessage = _("Access denied. Most likely, there are " + "too many players on this server."); + break; + case 1: + errorMessage = _("Cannot use this ID."); + break; + default: + errorMessage = _("Unknown char-server failure."); + break; + } + Client::setState(STATE_ERROR); + break; + + case SMSG_CHAR_CREATE_SUCCEEDED: + { + Net::Character *character = new Net::Character; + readPlayerData(msg, character); + mCharacters.push_back(character); + + updateCharSelectDialog(); + + // Close the character create dialog + if (mCharCreateDialog) + { + mCharCreateDialog->scheduleDelete(); + mCharCreateDialog = 0; + } + } + break; + + case SMSG_CHAR_CREATE_FAILED: + new OkDialog(_("Error"), _("Failed to create character. Most " + "likely the name is already taken.")); + if (mCharCreateDialog) + mCharCreateDialog->unlock(); + break; + + case SMSG_CHAR_DELETE_SUCCEEDED: + delete mSelectedCharacter; + mCharacters.remove(mSelectedCharacter); + mSelectedCharacter = 0; + updateCharSelectDialog(); + unlockCharSelectDialog(); + new OkDialog(_("Info"), _("Character deleted.")); + break; + + case SMSG_CHAR_DELETE_FAILED: + unlockCharSelectDialog(); + new OkDialog(_("Error"), _("Failed to delete character.")); + break; + + case SMSG_CHAR_MAP_INFO: + { +// msg.skip(4); // CharID, must be the same as player_node->charID + PlayerInfo::setCharId(msg.readInt32()); + GameHandler *gh = static_cast(Net::getGameHandler()); + gh->setMap(msg.readString(16)); + mapServer.hostname = ipToString(msg.readInt32()); + mapServer.port = msg.readInt16(); + + // Prevent the selected local player from being deleted + player_node = mSelectedCharacter->dummy; + PlayerInfo::setBackend(mSelectedCharacter->data); + + mSelectedCharacter->dummy = 0; + + delete_all(mCharacters); + mCharacters.clear(); + updateCharSelectDialog(); + + if (mNetwork) + mNetwork->disconnect(); + Client::setState(STATE_CONNECT_GAME); + } + break; + + case SMSG_CHANGE_MAP_SERVER: + { + GameHandler *gh = static_cast(Net::getGameHandler()); + if (!gh || !mNetwork) + return; + gh->setMap(msg.readString(16)); + int x = msg.readInt16(); + int y = msg.readInt16(); + mapServer.hostname = ipToString(msg.readInt32()); + mapServer.port = msg.readInt16(); + + mNetwork->disconnect(); + Client::setState(STATE_CHANGE_MAP); + if (player_node) + { + player_node->setTileCoords(x, y); + player_node->setMap(0); + } + } + break; + + default: + break; + } +} + +void CharServerHandler::readPlayerData(Net::MessageIn &msg, + Net::Character *character) +{ + const Token &token = + static_cast(Net::getLoginHandler())->getToken(); + + LocalPlayer *tempPlayer = new LocalPlayer(msg.readInt32(), 0); + tempPlayer->setGender(token.sex); + + character->data.mAttributes[EXP] = msg.readInt32(); + character->data.mAttributes[MONEY] = msg.readInt32(); + character->data.mStats[JOB].exp = msg.readInt32(); + + int temp = msg.readInt32(); + character->data.mStats[JOB].base = temp; + character->data.mStats[JOB].mod = temp; + + tempPlayer->setSprite(SPRITE_SHOE, msg.readInt16()); + tempPlayer->setSprite(SPRITE_GLOVES, msg.readInt16()); + tempPlayer->setSprite(SPRITE_CAPE, msg.readInt16()); + tempPlayer->setSprite(SPRITE_MISC1, msg.readInt16()); + + msg.readInt32(); // option + msg.readInt32(); // karma + msg.readInt32(); // manner + msg.skip(2); // unknown + + character->data.mAttributes[HP] = msg.readInt16(); + character->data.mAttributes[MAX_HP] = msg.readInt16(); + character->data.mAttributes[MP] = msg.readInt16(); + character->data.mAttributes[MAX_MP] = msg.readInt16(); + + msg.readInt16(); // speed + tempPlayer->setSubtype(msg.readInt16()); // class (used for race) + int hairStyle = msg.readInt16(); + Uint16 weapon = msg.readInt16(); + tempPlayer->setSprite(SPRITE_WEAPON, weapon, "", true); + + character->data.mAttributes[LEVEL] = msg.readInt16(); + + msg.readInt16(); // skill point + tempPlayer->setSprite(SPRITE_BOTTOMCLOTHES, msg.readInt16()); + //to avoid show error (error.xml) need remove this sprite + if (!config.getBoolValue("hideShield")) + tempPlayer->setSprite(SPRITE_SHIELD, msg.readInt16()); + else + msg.readInt16(); + + tempPlayer->setSprite(SPRITE_HAT, msg.readInt16()); // head option top + tempPlayer->setSprite(SPRITE_TOPCLOTHES, msg.readInt16()); + tempPlayer->setSprite(SPRITE_HAIR, hairStyle * -1, + ColorDB::get(msg.readInt16())); + tempPlayer->setSprite(SPRITE_MISC2, msg.readInt16()); + tempPlayer->setName(msg.readString(24)); + + character->dummy = tempPlayer; + + for (int i = 0; i < 6; i++) + character->data.mStats[i + STR].base = msg.readInt8(); + + character->slot = msg.readInt8(); // character slot + msg.readInt8(); // unknown +} + +void CharServerHandler::setCharSelectDialog(CharSelectDialog *window) +{ + mCharSelectDialog = window; + updateCharSelectDialog(); +} + +void CharServerHandler::setCharCreateDialog(CharCreateDialog *window) +{ + mCharCreateDialog = window; + + if (!mCharCreateDialog) + return; + + std::vector attributes; + attributes.push_back(_("Strength:")); + attributes.push_back(_("Agility:")); + attributes.push_back(_("Vitality:")); + attributes.push_back(_("Intelligence:")); + attributes.push_back(_("Dexterity:")); + attributes.push_back(_("Luck:")); + + const Token &token = + static_cast(Net::getLoginHandler())->getToken(); + + mCharCreateDialog->setAttributes(attributes, 30, 1, 9); + mCharCreateDialog->setFixedGender(true, token.sex); +} + +void CharServerHandler::requestCharacters() +{ + connect(); +} + +void CharServerHandler::chooseCharacter(Net::Character *character) +{ + mSelectedCharacter = character; + mCharSelectDialog = 0; + + MessageOut outMsg(CMSG_CHAR_SELECT); + outMsg.writeInt8(static_cast(mSelectedCharacter->slot)); +} + +void CharServerHandler::newCharacter(const std::string &name, int slot, + bool gender _UNUSED_, int hairstyle, + int hairColor, + const std::vector &stats) +{ + MessageOut outMsg(CMSG_CHAR_CREATE); + outMsg.writeString(name, 24); + for (int i = 0; i < 6; i++) + outMsg.writeInt8(static_cast(stats[i])); + + outMsg.writeInt8(static_cast(slot)); + outMsg.writeInt16(static_cast(hairColor)); + outMsg.writeInt16(static_cast(hairstyle)); +} + +void CharServerHandler::deleteCharacter(Net::Character *character) +{ + if (!character) + return; + + mSelectedCharacter = character; + + MessageOut outMsg(CMSG_CHAR_DELETE); + outMsg.writeInt32(mSelectedCharacter->dummy->getId()); + outMsg.writeString("a@a.com", 40); +} + +void CharServerHandler::switchCharacter() +{ + // This is really a map-server packet + MessageOut outMsg(CMSG_PLAYER_RESTART); + outMsg.writeInt8(1); +} + +unsigned int CharServerHandler::baseSprite() const +{ + return SPRITE_BASE; +} + +unsigned int CharServerHandler::hairSprite() const +{ + return SPRITE_HAIR; +} + +unsigned int CharServerHandler::maxSprite() const +{ + return SPRITE_VECTOREND; +} + +void CharServerHandler::connect() +{ + const Token &token = + static_cast(Net::getLoginHandler())->getToken(); + + if (!mNetwork) + return; + + mNetwork->disconnect(); + mNetwork->connect(charServer); + MessageOut outMsg(CMSG_CHAR_SERVER_CONNECT); + outMsg.writeInt32(token.account_ID); + outMsg.writeInt32(token.session_ID1); + outMsg.writeInt32(token.session_ID2); + // [Fate] The next word is unused by the old char server, so we squeeze in + // mana client version information + outMsg.writeInt16(CLIENT_PROTOCOL_VERSION); + outMsg.writeInt8((token.sex == GENDER_MALE) ? 1 : 0); + + // We get 4 useless bytes before the real answer comes in (what are these?) + mNetwork->skip(4); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/charserverhandler.h b/src/net/tmwa/charserverhandler.h new file mode 100644 index 000000000..d6ce0a2dc --- /dev/null +++ b/src/net/tmwa/charserverhandler.h @@ -0,0 +1,87 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_CHARSERVERHANDLER_H +#define NET_TA_CHARSERVERHANDLER_H + +#include "net/charhandler.h" +#include "net/serverinfo.h" + +#include "net/tmwa/messagehandler.h" +#include "net/tmwa/token.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class LoginData; + +namespace TmwAthena +{ + +/** + * Deals with incoming messages from the character server. + */ +class CharServerHandler : public MessageHandler, public Net::CharHandler +{ + public: + CharServerHandler(); + + virtual void handleMessage(Net::MessageIn &msg); + + void setCharSelectDialog(CharSelectDialog *window); + + /** + * Sets the character create dialog. The handler will clean up this + * dialog when a new character is succesfully created, and will unlock + * the dialog when a new character failed to be created. + */ + void setCharCreateDialog(CharCreateDialog *window); + + void requestCharacters(); + + void chooseCharacter(Net::Character *character); + + void newCharacter(const std::string &name, int slot, + bool gender, int hairstyle, int hairColor, + const std::vector &stats); + + void deleteCharacter(Net::Character *character); + + void switchCharacter(); + + unsigned int baseSprite() const; + + unsigned int hairSprite() const; + + unsigned int maxSprite() const; + + void connect(); + + private: + void readPlayerData(Net::MessageIn &msg, Net::Character *character); +}; + +} // namespace TmwAthena + +#endif // NET_TA_CHARSERVERHANDLER_H diff --git a/src/net/tmwa/chathandler.cpp b/src/net/tmwa/chathandler.cpp new file mode 100644 index 000000000..4dcfb2b34 --- /dev/null +++ b/src/net/tmwa/chathandler.cpp @@ -0,0 +1,552 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/chathandler.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "configuration.h" +#include "event.h" +#include "game.h" +#include "localplayer.h" +#include "playerrelations.h" +#include "log.h" + +#include "gui/chat.h" +#include "gui/shopwindow.h" + +#include "gui/widgets/chattab.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +extern Net::ChatHandler *chatHandler; + +namespace TmwAthena +{ + +ChatHandler::ChatHandler() +{ + static const Uint16 _messages[] = + { + SMSG_BEING_CHAT, + SMSG_PLAYER_CHAT, + SMSG_WHISPER, + SMSG_WHISPER_RESPONSE, + SMSG_GM_CHAT, + SMSG_MVP, // MVP + 0 + }; + handledMessages = _messages; + chatHandler = this; +} + +void ChatHandler::handleMessage(Net::MessageIn &msg) +{ + if (!localChatTab) + return; + + Being *being; + std::string chatMsg; + std::string nick; + int chatMsgLength; + + switch (msg.getId()) + { + case SMSG_WHISPER_RESPONSE: + { + if (mSentWhispers.empty()) + { + nick = "user"; + } + else + { + nick = mSentWhispers.front(); + mSentWhispers.pop(); + } + + int type = msg.readInt8(); + switch (type) + { + case 0x00: + // Success (don't need to report) + break; + case 0x01: + if (chatWindow) + { + chatWindow->whisper(nick, + strprintf(_("Whisper could not be " + "sent, %s is offline."), nick.c_str()), BY_SERVER); + } + break; + case 0x02: + if (chatWindow) + { + chatWindow->whisper(nick, + strprintf(_("Whisper could not " + "be sent, ignored by %s."), nick.c_str()), + BY_SERVER); + } + break; + default: + if (logger) + { + logger->log("QQQ SMSG_WHISPER_RESPONSE:" + + toString(type)); + } + } + break; + } + + // Received whisper + case SMSG_WHISPER: + { + chatMsgLength = msg.readInt16() - 28; + nick = msg.readString(24); + + if (chatMsgLength <= 0) + break; + + chatMsg = msg.readString(chatMsgLength); + + if (nick != "Server") + { + if (player_relations.hasPermission( + nick, PlayerRelation::WHISPER)) + { + bool tradeBot = config.getBoolValue("tradebot"); + bool showMsg = !config.getBoolValue("hideShopMessages"); + if (player_relations.hasPermission( + nick, PlayerRelation::TRADE)) + { + if (shopWindow) + { //commands to shop from player + if (chatMsg.find("!selllist ") == 0) + { + if (tradeBot) + { + if (showMsg && chatWindow) + chatWindow->whisper(nick, chatMsg); + shopWindow->giveList(nick, + ShopWindow::SELL); + } + } + else if (chatMsg.find("!buylist ") == 0) + { + if (tradeBot) + { + if (showMsg && chatWindow) + chatWindow->whisper(nick, chatMsg); + shopWindow->giveList(nick, + ShopWindow::BUY); + } + } + else if (chatMsg.find("!buyitem ") == 0) + { + if (showMsg && chatWindow) + chatWindow->whisper(nick, chatMsg); + if (tradeBot) + { + shopWindow->processRequest(nick, chatMsg, + ShopWindow::BUY); + } + } + else if (chatMsg.find("!sellitem ") == 0) + { + if (showMsg && chatWindow) + chatWindow->whisper(nick, chatMsg); + if (tradeBot) + { + shopWindow->processRequest(nick, chatMsg, + ShopWindow::SELL); + } + } + else if (chatMsg.length() > 3 + && chatMsg.find("\302\202") == 0) + { + chatMsg = chatMsg.erase(0, 2); + if (showMsg && chatWindow) + chatWindow->whisper(nick, chatMsg); + if (chatMsg.find("B1") == 0 + || chatMsg.find("S1") == 0) + { + shopWindow->showList(nick, chatMsg); + } + } + else if (chatWindow) + { + chatWindow->whisper(nick, chatMsg); + } + } + else if (chatWindow) + { + chatWindow->whisper(nick, chatMsg); + } + } + else + { + if (chatWindow && (showMsg + || (chatMsg.find("!selllist") + != 0 && chatMsg.find("!buylist") != 0))) + { + chatWindow->whisper(nick, chatMsg); + } + } + } + } + else if (localChatTab) + { + localChatTab->chatLog(chatMsg, BY_SERVER); + } + + break; + } + + // Received speech from being + case SMSG_BEING_CHAT: + { + if (!actorSpriteManager) + return; + + chatMsgLength = msg.readInt16() - 8; + being = actorSpriteManager->findBeing(msg.readInt32()); + + if (!being || chatMsgLength <= 0) + break; + + std::string str2; + chatMsg = msg.readRawString(chatMsgLength); + + if (being->getType() == Being::PLAYER) + being->setTalkTime(); + + std::string::size_type pos = chatMsg.find(" : ", 0); + std::string sender_name = ((pos == std::string::npos) + ? "" : chatMsg.substr(0, pos)); + + if (sender_name != being->getName() + && being->getType() == Being::PLAYER) + { + if (!being->getName().empty()) + sender_name = being->getName(); + } + else + { + chatMsg.erase(0, pos + 3); + } + + trim(chatMsg); + + // We use getIgnorePlayer instead of ignoringPlayer here + // because ignorePlayer' side effects are triggered + // right below for Being::IGNORE_SPEECH_FLOAT. + if (player_relations.checkPermissionSilently(sender_name, + PlayerRelation::SPEECH_LOG) && chatWindow) + { + chatWindow->resortChatLog(removeColors(sender_name) + " : " + + chatMsg, BY_OTHER); + } + + if (player_relations.hasPermission(sender_name, + PlayerRelation::SPEECH_FLOAT)) + { + being->setSpeech(chatMsg, SPEECH_TIME); + } + break; + } + + case SMSG_PLAYER_CHAT: + case SMSG_GM_CHAT: + { + chatMsgLength = msg.readInt16() - 4; + + if (chatMsgLength <= 0) + break; + + std::string str2; + chatMsg = msg.readRawString(chatMsgLength); + std::string::size_type pos = chatMsg.find(" : ", 0); + + if (msg.getId() == SMSG_PLAYER_CHAT) + { + if (chatWindow) + chatWindow->resortChatLog(chatMsg, BY_PLAYER); +// if (localChatTab) +// localChatTab->chatLog(chatMsg, BY_PLAYER); + + const std::string senseStr = "You sense the following: "; + if (actorSpriteManager && !chatMsg.find(senseStr)) + { + actorSpriteManager->parseLevels( + chatMsg.substr(senseStr.size())); + } + + if (pos != std::string::npos) + chatMsg.erase(0, pos + 3); + + trim(chatMsg); + + if (player_node) + player_node->setSpeech(chatMsg, SPEECH_TIME); + } + else if (localChatTab) + { + localChatTab->chatLog(chatMsg, BY_GM); + } + break; + } + + case SMSG_MVP: + { + // Display MVP player + int id = msg.readInt32(); // id + if (localChatTab && actorSpriteManager) + { + being = actorSpriteManager->findBeing(id); + if (!being) + { + localChatTab->chatLog(_("MVP player."), BY_SERVER); + } + else + { + localChatTab->chatLog(_("MVP player: ") + + being->getName(), BY_SERVER); + } + } + break; + } + + default: + break; + } +} + +void ChatHandler::talk(const std::string &text) +{ + if (!player_node) + return; + + std::string mes = player_node->getName() + " : " + text; +// std::string mes = player_node->getName() + "zzzz : " + text; + + MessageOut outMsg(CMSG_CHAT_MESSAGE); + // Added + 1 in order to let eAthena parse admin commands correctly + outMsg.writeInt16(static_cast(mes.length() + 4 + 1)); + outMsg.writeString(mes, static_cast(mes.length() + 1)); +} + +void ChatHandler::talkRaw(const std::string &mes) +{ + MessageOut outMsg(CMSG_CHAT_MESSAGE); + // Added + 1 in order to let eAthena parse admin commands correctly + outMsg.writeInt16(static_cast(mes.length() + 4 + 1)); + outMsg.writeString(mes, static_cast(mes.length() + 1)); +} + +void ChatHandler::me(const std::string &text) +{ + std::string action = strprintf("*%s*", text.c_str()); + + talk(action); +} + +void ChatHandler::privateMessage(const std::string &recipient, + const std::string &text) +{ + MessageOut outMsg(CMSG_CHAT_WHISPER); + outMsg.writeInt16(static_cast(text.length() + 28)); + outMsg.writeString(recipient, 24); + outMsg.writeString(text, static_cast(text.length())); + mSentWhispers.push(recipient); +} + +void ChatHandler::channelList() +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::enterChannel(const std::string &channel _UNUSED_, + const std::string &password _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::quitChannel(int channelId _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::sendToChannel(int channelId _UNUSED_, + const std::string &text _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::userList(const std::string &channel _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::setChannelTopic(int channelId _UNUSED_, + const std::string &text _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::setUserMode(int channelId _UNUSED_, + const std::string &name _UNUSED_, + int mode _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::kickUser(int channelId _UNUSED_, + const std::string &name _UNUSED_) +{ + SERVER_NOTICE(_("Channels are not supported!")) +} + +void ChatHandler::who() +{ + MessageOut outMsg(CMSG_WHO_REQUEST); +} + +void ChatHandler::sendRaw(const std::string &args) +{ + std::string line = args; + std::string str; + MessageOut *outMsg = 0; + + if (line == "") + return; + + std::string::size_type pos = line.find(" "); + if (pos != std::string::npos) + { + str = line.substr(0, pos); + outMsg = new MessageOut(static_cast(atoi(str.c_str()))); + line = line.substr(pos + 1); + pos = line.find(" "); + } + else + { + outMsg = new MessageOut(static_cast(atoi(line.c_str()))); + delete outMsg; + return; + } + + while (pos != std::string::npos) + { + str = line.substr(0, pos); + processRaw(*outMsg, str); + line = line.substr(pos + 1); + pos = line.find(" "); + } + if (outMsg) + { + if (line != "") + processRaw(*outMsg, line); + delete outMsg; + } +} + +void ChatHandler::processRaw(MessageOut &outMsg, std::string &line) +{ + std::string::size_type pos = line.find(":"); + if (pos == std::string::npos) + { + int i = atoi(line.c_str()); + if (line.length() <= 3) + outMsg.writeInt8(static_cast(i)); + else if (line.length() <= 5) + outMsg.writeInt16(static_cast(i)); + else + outMsg.writeInt32(i); + } + else + { + std::string header = line.substr(0, pos); + std::string data = line.substr(pos + 1); + if (header.length() != 1) + return; + + int i = 0; + + switch (header[0]) + { + case '1': + case '2': + case '4': + i = atoi(data.c_str()); + break; + default: + break; + } + + switch (header[0]) + { + case '1': + outMsg.writeInt8(static_cast(i)); + break; + case '2': + outMsg.writeInt16(static_cast(i)); + break; + case '4': + outMsg.writeInt32(i); + break; + case 'c': + { + pos = line.find(","); + if (pos != std::string::npos) + { + unsigned short x = static_cast( + atoi(data.substr(0, pos).c_str())); + data = data.substr(pos + 1); + pos = line.find(","); + if (pos == std::string::npos) + break; + + unsigned short y = static_cast( + atoi(data.substr(0, pos).c_str())); + int dir = atoi(data.substr(pos + 1).c_str()); + outMsg.writeCoordinates(x, y, + static_cast(dir)); + } + break; + } + case 't': + outMsg.writeString(data, static_cast(data.length())); + break; + default: + break; + } + } +} + +} // namespace TmwAthena + diff --git a/src/net/tmwa/chathandler.h b/src/net/tmwa/chathandler.h new file mode 100644 index 000000000..1432da3a2 --- /dev/null +++ b/src/net/tmwa/chathandler.h @@ -0,0 +1,87 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_CHATHANDLER_H +#define NET_TA_CHATHANDLER_H + +#include "net/chathandler.h" +#include "net/net.h" + +#include "net/tmwa/messagehandler.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class ChatHandler : public MessageHandler, public Net::ChatHandler +{ + public: + ChatHandler(); + + void handleMessage(Net::MessageIn &msg); + + void talk(const std::string &text); + + void talkRaw(const std::string &text); + + void me(const std::string &text); + + void privateMessage(const std::string &recipient, + const std::string &text); + + void channelList(); + + void enterChannel(const std::string &channel, + const std::string &password); + + void quitChannel(int channelId); + + void sendToChannel(int channelId, const std::string &text); + + void userList(const std::string &channel); + + void setChannelTopic(int channelId, const std::string &text); + + void setUserMode(int channelId, const std::string &name, int mode); + + void kickUser(int channelId, const std::string &name); + + void who(); + + void sendRaw(const std::string &args); + + void processRaw(MessageOut &outMsg, std::string &line); + + private: + typedef std::queue WhisperQueue; + WhisperQueue mSentWhispers; +}; + +} // namespace TmwAthena + +#endif // NET_TA_CHATHANDLER_H diff --git a/src/net/tmwa/gamehandler.cpp b/src/net/tmwa/gamehandler.cpp new file mode 100644 index 000000000..5f949ce6e --- /dev/null +++ b/src/net/tmwa/gamehandler.cpp @@ -0,0 +1,196 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/gamehandler.h" + +#include "client.h" +#include "event.h" +#include "game.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/okdialog.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/loginhandler.h" +#include "net/tmwa/network.h" +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern Net::GameHandler *gameHandler; + +namespace TmwAthena +{ + +extern ServerInfo mapServer; + +GameHandler::GameHandler() +{ + static const Uint16 _messages[] = + { + SMSG_MAP_LOGIN_SUCCESS, + SMSG_SERVER_PING, + SMSG_WHO_ANSWER, + SMSG_CHAR_SWITCH_RESPONSE, + SMSG_MAP_QUIT_RESPONSE, + 0 + }; + handledMessages = _messages; + gameHandler = this; + + listen(CHANNEL_GAME); +} + +void GameHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_MAP_LOGIN_SUCCESS: + { + unsigned char direction; + Uint16 x, y; + msg.readInt32(); // server tick + msg.readCoordinates(x, y, direction); + msg.skip(2); // unknown + logger->log("Protocol: Player start position: (%d, %d)," + " Direction: %d", x, y, direction); + // Switch now or we'll have problems + Client::setState(STATE_GAME); + if (player_node) + player_node->setTileCoords(x, y); + break; + } + + case SMSG_SERVER_PING: + // We ignore this for now + // int tick = msg.readInt32() + break; + + case SMSG_WHO_ANSWER: + SERVER_NOTICE(strprintf(_("Online users: %d"), msg.readInt32())) + break; + + case SMSG_CHAR_SWITCH_RESPONSE: + if (msg.readInt8()) + Client::setState(STATE_SWITCH_CHARACTER); + break; + + case SMSG_MAP_QUIT_RESPONSE: + if (msg.readInt8()) + new OkDialog(_("Game"), _("Request to quit denied!"), NULL); + break; + + default: + break; + } +} + +void GameHandler::event(Channels channel, const Mana::Event &event) +{ + if (channel == CHANNEL_GAME) + { + if (event.getName() == EVENT_ENGINESINITALIZED) + Game::instance()->changeMap(mMap); + else if (event.getName() == EVENT_MAPLOADED) + MessageOut outMsg(CMSG_MAP_LOADED); + } +} + +void GameHandler::connect() +{ + if (!mNetwork) + return; + + mNetwork->connect(mapServer); + + const Token &token = + static_cast(Net::getLoginHandler())->getToken(); + + + if (Client::getState() == STATE_CONNECT_GAME) + { + mCharID = player_node->getId(); + // Change the player's ID to the account ID to match what eAthena uses + player_node->setId(token.account_ID); + } + + // Send login infos + MessageOut outMsg(CMSG_MAP_SERVER_CONNECT); + outMsg.writeInt32(token.account_ID); + outMsg.writeInt32(mCharID); + outMsg.writeInt32(token.session_ID1); + outMsg.writeInt32(token.session_ID2); + outMsg.writeInt8((token.sex == GENDER_MALE) ? 1 : 0); + +/* + if (player_node) + { + // Change the player's ID to the account ID to match what eAthena uses + player_node->setId(token.account_ID); + } +*/ + // We get 4 useless bytes before the real answer comes in (what are these?) + mNetwork->skip(4); +} + +bool GameHandler::isConnected() +{ + if (!mNetwork) + return false; + return mNetwork->isConnected(); +} + +void GameHandler::disconnect() +{ + if (mNetwork) + mNetwork->disconnect(); +} + +void GameHandler::who() +{ +} + +void GameHandler::quit() +{ + MessageOut outMsg(CMSG_CLIENT_QUIT); +} + +void GameHandler::ping(int tick) +{ + MessageOut msg(CMSG_CLIENT_PING); + msg.writeInt32(tick); +} + +void GameHandler::setMap(const std::string map) +{ + mMap = map.substr(0, map.rfind(".")); +} + +void GameHandler::disconnect2() +{ + MessageOut outMsg(CMSG_CLIENT_DISCONNECT); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/gamehandler.h b/src/net/tmwa/gamehandler.h new file mode 100644 index 000000000..d59320bfa --- /dev/null +++ b/src/net/tmwa/gamehandler.h @@ -0,0 +1,85 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_MAPHANDLER_H +#define NET_TA_MAPHANDLER_H + +#include "listener.h" + +#include "net/gamehandler.h" +#include "net/net.h" +#include "net/serverinfo.h" + +#include "net/tmwa/messagehandler.h" +#include "net/tmwa/token.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class GameHandler : public MessageHandler, public Net::GameHandler, + public Mana::Listener +{ + public: + GameHandler(); + + void handleMessage(Net::MessageIn &msg); + + void event(Channels channel, const Mana::Event &event); + + void connect(); + + bool isConnected(); + + void disconnect(); + + void who(); + + void quit(); + + void ping(int tick); + + bool removeDeadBeings() const + { return true; } + + void clear(); + + void setMap(const std::string map); + + /** The tmwAthena protocol is making use of the MP status bar. */ + bool canUseMagicBar() const + { return true; } + + void disconnect2(); + + private: + std::string mMap; + int mCharID; /// < Saved for map-server switching +}; + +} // namespace TmwAthena + +#endif // NET_TA_MAPHANDLER_H diff --git a/src/net/tmwa/generalhandler.cpp b/src/net/tmwa/generalhandler.cpp new file mode 100644 index 000000000..83d8ceb8d --- /dev/null +++ b/src/net/tmwa/generalhandler.cpp @@ -0,0 +1,285 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/generalhandler.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" + +#include "gui/charselectdialog.h" +#include "gui/inventorywindow.h" +#include "gui/register.h" +#include "gui/skilldialog.h" +#include "gui/socialwindow.h" +#include "gui/statuswindow.h" + +#include "net/messagein.h" +#include "net/messageout.h" +#include "net/serverinfo.h" + +#include "net/tmwa/adminhandler.h" +#include "net/tmwa/beinghandler.h" +#include "net/tmwa/buysellhandler.h" +#include "net/tmwa/chathandler.h" +#include "net/tmwa/charserverhandler.h" +#include "net/tmwa/gamehandler.h" +#include "net/tmwa/guildhandler.h" +#include "net/tmwa/inventoryhandler.h" +#include "net/tmwa/itemhandler.h" +#include "net/tmwa/loginhandler.h" +#include "net/tmwa/network.h" +#include "net/tmwa/npchandler.h" +#include "net/tmwa/partyhandler.h" +#include "net/tmwa/playerhandler.h" +#include "net/tmwa/protocol.h" +#include "net/tmwa/tradehandler.h" +#include "net/tmwa/specialhandler.h" + +#include "net/tmwa/gui/guildtab.h" +#include "net/tmwa/gui/partytab.h" + +#include "resources/itemdb.h" + +#include "utils/gettext.h" + +#include +#include + +extern Net::GeneralHandler *generalHandler; + +namespace TmwAthena +{ + +ServerInfo charServer; +ServerInfo mapServer; + +extern Guild *taGuild; +extern Party *taParty; + +GeneralHandler::GeneralHandler(): + mAdminHandler(new AdminHandler), + mBeingHandler(new BeingHandler(config.getBoolValue("EnableSync"))), + mBuySellHandler(new BuySellHandler), + mCharHandler(new CharServerHandler), + mChatHandler(new ChatHandler), + mGameHandler(new GameHandler), + mGuildHandler(new GuildHandler), + mInventoryHandler(new InventoryHandler), + mItemHandler(new ItemHandler), + mLoginHandler(new LoginHandler), + mNpcHandler(new NpcHandler), + mPartyHandler(new PartyHandler), + mPlayerHandler(new PlayerHandler), + mSpecialHandler(new SpecialHandler), + mTradeHandler(new TradeHandler) +{ + static const Uint16 _messages[] = + { + SMSG_CONNECTION_PROBLEM, + 0 + }; + handledMessages = _messages; + generalHandler = this; + + std::list stats; + stats.push_back(ItemDB::Stat("str", _("Strength %+d"))); + stats.push_back(ItemDB::Stat("agi", _("Agility %+d"))); + stats.push_back(ItemDB::Stat("vit", _("Vitality %+d"))); + stats.push_back(ItemDB::Stat("int", _("Intelligence %+d"))); + stats.push_back(ItemDB::Stat("dex", _("Dexterity %+d"))); + stats.push_back(ItemDB::Stat("luck", _("Luck %+d"))); + + ItemDB::setStatsList(stats); + + listen(CHANNEL_GAME); +} + +GeneralHandler::~GeneralHandler() +{ + delete mNetwork; + mNetwork = 0; +} + +void GeneralHandler::handleMessage(Net::MessageIn &msg) +{ + int code; + + switch (msg.getId()) + { + case SMSG_CONNECTION_PROBLEM: + code = msg.readInt8(); + logger->log("Connection problem: %i", code); + + switch (code) + { + case 0: + errorMessage = _("Authentication failed."); + break; + case 1: + errorMessage = _("No servers available."); + break; + case 2: + if (Client::getState() == STATE_GAME) + errorMessage = _("Someone else is trying to use this " + "account."); + else + errorMessage = _("This account is already logged in."); + break; + case 3: + errorMessage = _("Speed hack detected."); + break; + case 8: + errorMessage = _("Duplicated login."); + break; + default: + errorMessage = _("Unknown connection error."); + break; + } + Client::setState(STATE_ERROR); + break; + + default: + break; + } +} + +void GeneralHandler::load() +{ + (new Network)->registerHandler(this); + + if (!mNetwork) + return; + + mNetwork->registerHandler(mAdminHandler.get()); + mNetwork->registerHandler(mBeingHandler.get()); + mNetwork->registerHandler(mBuySellHandler.get()); + mNetwork->registerHandler(mChatHandler.get()); + mNetwork->registerHandler(mCharHandler.get()); + mNetwork->registerHandler(mGameHandler.get()); + mNetwork->registerHandler(mGuildHandler.get()); + mNetwork->registerHandler(mInventoryHandler.get()); + mNetwork->registerHandler(mItemHandler.get()); + mNetwork->registerHandler(mLoginHandler.get()); + mNetwork->registerHandler(mNpcHandler.get()); + mNetwork->registerHandler(mPlayerHandler.get()); + mNetwork->registerHandler(mSpecialHandler.get()); + mNetwork->registerHandler(mTradeHandler.get()); + mNetwork->registerHandler(mPartyHandler.get()); +} + +void GeneralHandler::reload() +{ + if (mNetwork) + mNetwork->disconnect(); + + static_cast(mLoginHandler.get())->clearWorlds(); + static_cast( + mCharHandler.get())->setCharCreateDialog(0); + static_cast( + mCharHandler.get())->setCharSelectDialog(0); +} + +void GeneralHandler::unload() +{ + if (mNetwork) + mNetwork->clearHandlers(); +} + +void GeneralHandler::flushNetwork() +{ + if (!mNetwork) + return; + + mNetwork->flush(); + mNetwork->dispatchMessages(); + + if (mNetwork->getState() == Network::NET_ERROR) + { + if (!mNetwork->getError().empty()) + errorMessage = mNetwork->getError(); + else + errorMessage = _("Got disconnected from server!"); + + Client::setState(STATE_ERROR); + } +} + +void GeneralHandler::clearHandlers() +{ + if (mNetwork) + mNetwork->clearHandlers(); +} + +void GeneralHandler::event(Channels channel, + const Mana::Event &event) +{ + if (channel == CHANNEL_GAME) + { + if (event.getName() == EVENT_GUIWINDOWSLOADED) + { + if (inventoryWindow) + inventoryWindow->setSplitAllowed(false); + if (skillDialog) + skillDialog->loadSkills("ea-skills.xml"); + + if (!statusWindow) + return; + + statusWindow->addAttribute(STR, _("Strength"), true, ""); + statusWindow->addAttribute(AGI, _("Agility"), true, ""); + statusWindow->addAttribute(VIT, _("Vitality"), true, ""); + statusWindow->addAttribute(INT, _("Intelligence"), true, ""); + statusWindow->addAttribute(DEX, _("Dexterity"), true, ""); + statusWindow->addAttribute(LUK, _("Luck"), true, ""); + + statusWindow->addAttribute(ATK, _("Attack"), false, ""); + statusWindow->addAttribute(DEF, _("Defense"), false, ""); + statusWindow->addAttribute(MATK, _("M.Attack"), false, ""); + statusWindow->addAttribute(MDEF, _("M.Defense"), false, ""); + statusWindow->addAttribute(HIT, _("% Accuracy"), false, ""); + statusWindow->addAttribute(FLEE, _("% Evade"), false, ""); + statusWindow->addAttribute(CRIT, _("% Critical"), false, ""); + statusWindow->addAttribute(ATTACK_SPEED, _("Attack Delay"), + false, ""); + statusWindow->addAttribute(WALK_SPEED, _("Walk Delay"), + false, ""); + statusWindow->addAttribute(ATTACK_RANGE, _("Attack Range"), + false, ""); + } + else if (event.getName() == EVENT_GUIWINDOWSUNLOADING) + { + if (socialWindow) + { + socialWindow->removeTab(taGuild); + socialWindow->removeTab(taParty); + } + + delete guildTab; + guildTab = 0; + + delete partyTab; + partyTab = 0; + } + } +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/generalhandler.h b/src/net/tmwa/generalhandler.h new file mode 100644 index 000000000..eded556d0 --- /dev/null +++ b/src/net/tmwa/generalhandler.h @@ -0,0 +1,83 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TMWA_GENERALHANDLER_H +#define NET_TMWA_GENERALHANDLER_H + +#include "listener.h" + +#include "net/generalhandler.h" +#include "net/net.h" + +#include "net/tmwa/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class GeneralHandler : public MessageHandler, public Net::GeneralHandler, + public Mana::Listener +{ + public: + GeneralHandler(); + + ~GeneralHandler(); + + void handleMessage(Net::MessageIn &msg); + + void load(); + + void reload(); + + void unload(); + + void flushNetwork(); + + void clearHandlers(); + + void event(Channels channel, const Mana::Event &event); + + protected: + MessageHandlerPtr mAdminHandler; + MessageHandlerPtr mBeingHandler; + MessageHandlerPtr mBuySellHandler; + MessageHandlerPtr mCharHandler; + MessageHandlerPtr mChatHandler; + MessageHandlerPtr mGameHandler; + MessageHandlerPtr mGuildHandler; + MessageHandlerPtr mInventoryHandler; + MessageHandlerPtr mItemHandler; + MessageHandlerPtr mLoginHandler; + MessageHandlerPtr mNpcHandler; + MessageHandlerPtr mPartyHandler; + MessageHandlerPtr mPlayerHandler; + MessageHandlerPtr mSpecialHandler; + MessageHandlerPtr mTradeHandler; +}; + +} // namespace TmwAthena + +#endif // NET_TA_GENERALHANDLER_H diff --git a/src/net/tmwa/gui/guildtab.cpp b/src/net/tmwa/gui/guildtab.cpp new file mode 100644 index 000000000..4b080277c --- /dev/null +++ b/src/net/tmwa/gui/guildtab.cpp @@ -0,0 +1,150 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/gui/guildtab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "guild.h" +#include "localplayer.h" + +#include "gui/theme.h" + +#include "net/net.h" +#include "net/guildhandler.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +namespace TmwAthena +{ + +extern Guild *taGuild; + +GuildTab::GuildTab() : + ChatTab(_("Guild")) +{ + setTabColor(&Theme::getThemeColor(Theme::GUILD_CHAT_TAB)); +} + +GuildTab::~GuildTab() +{ +} + +void GuildTab::handleInput(const std::string &msg) +{ + if (!taGuild) + return; + + if (chatWindow) + { + Net::getGuildHandler()->chat(taGuild->getId(), + chatWindow->doReplace(msg)); + } + else + { + Net::getGuildHandler()->chat(taGuild->getId(), msg); + } +} + +void GuildTab::showHelp() +{ + chatLog(_("/help > Display this help.")); + chatLog(_("/invite > Invite a player to your guild")); + chatLog(_("/leave > Leave the guild you are in")); + chatLog(_("/kick > Kick some one from the guild you are in")); +} + +bool GuildTab::handleCommand(const std::string &type, const std::string &args) +{ + if (type == "help") + { + if (args == "invite") + { + chatLog(_("Command: /invite ")); + chatLog(_("This command invites to the guild you're in.")); + chatLog(_("If the has spaces in it, enclose it in " + "double quotes (\").")); + } + else if (args == "leave") + { + chatLog(_("Command: /leave")); + chatLog(_("This command causes the player to leave the guild.")); + } + else + return false; + } +/* + else if (type == "create" || type == "new") + { + if (args.empty()) + chatLog(_("Guild name is missing."), BY_SERVER); + else + Net::getGuildHandler()->create(args); + } +*/ + else if (type == "invite" && taGuild) + { + Net::getGuildHandler()->invite(taGuild->getId(), args); + } + else if (type == "leave" && taGuild) + { + Net::getGuildHandler()->leave(taGuild->getId()); + } + else if (type == "kick" && taGuild) + { + Net::getGuildHandler()->kick(taGuild->getMember(args)); + } + else if (type == "notice" && taGuild) + { + std::string str1 = args.substr(0, 60); + std::string str2 = ""; + if (args.size() > 60) + str2 = args.substr(60); + Net::getGuildHandler()->changeNotice(taGuild->getId(), str1, str2); + } + else + { + return false; + } + + return true; +} + +void GuildTab::getAutoCompleteList(std::vector &names) const +{ + if (taGuild) + taGuild->getNames(names); + names.push_back("/notice "); +} + +void GuildTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log("#Guild", msg); +} + +} // namespace TmwAthena + diff --git a/src/net/tmwa/gui/guildtab.h b/src/net/tmwa/gui/guildtab.h new file mode 100644 index 000000000..cce9eb596 --- /dev/null +++ b/src/net/tmwa/gui/guildtab.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TA_GUILDTAB_H +#define TA_GUILDTAB_H + +#include "gui/widgets/chattab.h" + +namespace TmwAthena +{ + +/** + * A tab for a guild chat channel. + */ +class GuildTab : public ChatTab +{ + public: + GuildTab(); + ~GuildTab(); + + void showHelp(); + + bool handleCommand(const std::string &type, const std::string &args); + + void saveToLogFile(std::string &msg); + + int getType() const { return ChatTab::TAB_GUILD; } + + protected: + void handleInput(const std::string &msg); + + void getAutoCompleteList(std::vector &names) const; +}; + +extern GuildTab *guildTab; + +} // namespace TmwAthena + +#endif // TA_GUILDTAB_H diff --git a/src/net/tmwa/gui/partytab.cpp b/src/net/tmwa/gui/partytab.cpp new file mode 100644 index 000000000..9cfb4670f --- /dev/null +++ b/src/net/tmwa/gui/partytab.cpp @@ -0,0 +1,242 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/gui/partytab.h" + +#include "chatlog.h" +#include "commandhandler.h" +#include "localplayer.h" +#include "party.h" + +#include "gui/theme.h" + +#include "net/net.h" +#include "net/partyhandler.h" + +#include "resources/iteminfo.h" +#include "resources/itemdb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "net/chathandler.h" + +namespace TmwAthena +{ + +PartyTab::PartyTab() : + ChatTab(_("Party")) +{ + setTabColor(&Theme::getThemeColor(Theme::PARTY_CHAT_TAB)); +} + +PartyTab::~PartyTab() +{ +} + +void PartyTab::handleInput(const std::string &msg) +{ + if (chatWindow) + Net::getPartyHandler()->chat(chatWindow->doReplace(msg)); + else + Net::getPartyHandler()->chat(msg); +} + +void PartyTab::showHelp() +{ + chatLog(_("/help > Display this help.")); + chatLog(_("/invite > Invite a player to your party")); + chatLog(_("/leave > Leave the party you are in")); + chatLog(_("/kick > Kick some one from the party you are in")); + chatLog(_("/item > Show/change party item sharing options")); + chatLog(_("/exp > Show/change party experience sharing options")); +} + +bool PartyTab::handleCommand(const std::string &type, const std::string &args) +{ + if (type == "help") + { + if (args == "invite") + { + chatLog(_("Command: /invite ")); + chatLog(_("This command invites to party with you.")); + chatLog(_("If the has spaces in it, enclose it in " + "double quotes (\").")); + } + else if (args == "leave") + { + chatLog(_("Command: /leave")); + chatLog(_("This command causes the player to leave the party.")); + } + else if (args == "item") + { + chatLog(_("Command: /item ")); + chatLog( + _("This command changes the party's item sharing policy.")); + chatLog(_(" can be one of \"1\", \"yes\", \"true\" to " + "enable item sharing, or \"0\", \"no\", \"false\" to " + "disable item sharing.")); + chatLog(_("Command: /item")); + chatLog(_("This command displays the party's" + " current item sharing policy.")); + } + else if (args == "exp") + { + chatLog(_("Command: /exp ")); + chatLog(_("This command changes the party's " + "experience sharing policy.")); + chatLog(_(" can be one of \"1\", \"yes\", \"true\" to " + "enable experience sharing, or \"0\"," + " \"no\", \"false\" to disable experience sharing.")); + chatLog(_("Command: /exp")); + chatLog(_("This command displays the party's current " + "experience sharing policy.")); + } + else + { + return false; + } + } + else if (type == "create" || type == "new") + { + if (args.empty()) + chatLog(_("Party name is missing."), BY_SERVER); + else + Net::getPartyHandler()->create(args); + } + else if (type == "invite") + { + Net::getPartyHandler()->invite(args); + } + else if (type == "leave") + { + Net::getPartyHandler()->leave(); + } + else if (type == "kick") + { + Net::getPartyHandler()->kick(args); + } + else if (type == "item") + { + if (args.empty()) + { + switch (Net::getPartyHandler()->getShareItems()) + { + case PARTY_SHARE: + chatLog(_("Item sharing enabled."), BY_SERVER); + return true; + case PARTY_SHARE_NO: + chatLog(_("Item sharing disabled."), BY_SERVER); + return true; + case PARTY_SHARE_NOT_POSSIBLE: + chatLog(_("Item sharing not possible."), BY_SERVER); + return true; + case PARTY_SHARE_UNKNOWN: + chatLog(_("Item sharing unknown."), BY_SERVER); + return true; + default: + break; + } + } + + char opt = CommandHandler::parseBoolean(args); + + switch (opt) + { + case 1: + Net::getPartyHandler()->setShareItems(PARTY_SHARE); + break; + case 0: + Net::getPartyHandler()->setShareItems(PARTY_SHARE_NO); + break; + case -1: + chatLog(strprintf(BOOLEAN_OPTIONS, "item")); + default: + break; + } + } + else if (type == "exp") + { + if (args.empty()) + { + switch (Net::getPartyHandler()->getShareExperience()) + { + case PARTY_SHARE: + chatLog(_("Experience sharing enabled."), BY_SERVER); + return true; + case PARTY_SHARE_NO: + chatLog(_("Experience sharing disabled."), BY_SERVER); + return true; + case PARTY_SHARE_NOT_POSSIBLE: + chatLog(_("Experience sharing not possible."), BY_SERVER); + return true; + case PARTY_SHARE_UNKNOWN: + chatLog(_("Experience sharing unknown."), BY_SERVER); + return true; + default: + break; + } + } + + char opt = CommandHandler::parseBoolean(args); + + switch (opt) + { + case 1: + Net::getPartyHandler()->setShareExperience(PARTY_SHARE); + break; + case 0: + Net::getPartyHandler()->setShareExperience(PARTY_SHARE_NO); + break; + case -1: + chatLog(strprintf(BOOLEAN_OPTIONS, "exp")); + default: + break; + } + } + else + { + return false; + } + + return true; +} + +void PartyTab::getAutoCompleteList(std::vector &names) const +{ + if (!player_node) + return; + + Party *p = player_node->getParty(); + + if (p) + p->getNames(names); +} + +void PartyTab::saveToLogFile(std::string &msg) +{ + if (chatLogger) + chatLogger->log("#Party", msg); +} + +} // namespace TmwAthena + diff --git a/src/net/tmwa/gui/partytab.h b/src/net/tmwa/gui/partytab.h new file mode 100644 index 000000000..d5f4436db --- /dev/null +++ b/src/net/tmwa/gui/partytab.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TA_PARTYTAB_H +#define TA_PARTYTAB_H + +#include "gui/widgets/chattab.h" + +namespace TmwAthena +{ + +/** + * A tab for a party chat channel. + */ +class PartyTab : public ChatTab +{ + public: + PartyTab(); + ~PartyTab(); + + void showHelp(); + + bool handleCommand(const std::string &type, const std::string &args); + + int getType() const { return ChatTab::TAB_PARTY; } + + void saveToLogFile(std::string &msg); + + protected: + void handleInput(const std::string &msg); + + virtual void getAutoCompleteList(std::vector&) const; +}; + +extern PartyTab *partyTab; + +} // namespace TmwAthena + +#endif // TA_PARTYTAB_H diff --git a/src/net/tmwa/guildhandler.cpp b/src/net/tmwa/guildhandler.cpp new file mode 100644 index 000000000..88cd88009 --- /dev/null +++ b/src/net/tmwa/guildhandler.cpp @@ -0,0 +1,780 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/guildhandler.h" + +#include "actorspritemanager.h" +#include "guild.h" +#include "event.h" +#include "localplayer.h" +#include "log.h" +#include "playerinfo.h" + +#include "gui/socialwindow.h" + +#include "net/tmwa/messagein.h" +#include "net/tmwa/protocol.h" + +#include "net/tmwa/gui/guildtab.h" + +#include "utils/gettext.h" + +extern Net::GuildHandler *guildHandler; + +namespace TmwAthena +{ + +GuildTab *guildTab = 0; +Guild *taGuild; +bool showBasicInfo(false); + +GuildHandler::GuildHandler() +{ + static const Uint16 _messages[] = + { + SMSG_GUILD_CREATE_RESPONSE, + SMSG_GUILD_POSITION_INFO, + SMSG_GUILD_MEMBER_LOGIN, + SMSG_GUILD_MASTER_OR_MEMBER, + SMSG_GUILD_BASIC_INFO, + SMSG_GUILD_ALIANCE_INFO, + SMSG_GUILD_MEMBER_LIST, + SMSG_GUILD_POS_NAME_LIST, + SMSG_GUILD_POS_INFO_LIST, + SMSG_GUILD_POSITION_CHANGED, + SMSG_GUILD_MEMBER_POS_CHANGE, + SMSG_GUILD_EMBLEM, + SMSG_GUILD_SKILL_INFO, + SMSG_GUILD_NOTICE, + SMSG_GUILD_INVITE, + SMSG_GUILD_INVITE_ACK, + SMSG_GUILD_LEAVE, + SMSG_GUILD_EXPULSION, + SMSG_GUILD_EXPULSION_LIST, + SMSG_GUILD_MESSAGE, + SMSG_GUILD_SKILL_UP, + SMSG_GUILD_REQ_ALLIANCE, + SMSG_GUILD_REQ_ALLIANCE_ACK, + SMSG_GUILD_DEL_ALLIANCE, + SMSG_GUILD_OPPOSITION_ACK, + SMSG_GUILD_BROKEN, + 0 + }; + handledMessages = _messages; + + guildHandler = this; +} + +GuildHandler::~GuildHandler() +{ + delete guildTab; + guildTab = 0; +} + +void GuildHandler::handleMessage(Net::MessageIn &msg) +{ + DEBUGLOG("guild message"); + + switch (msg.getId()) + { + case SMSG_GUILD_CREATE_RESPONSE: + { + int flag = msg.readInt8(); + + if (flag == 0) + { + // Success + SERVER_NOTICE(_("Guild created.")) + } + else if (flag == 1) + { + // Already in a guild + SERVER_NOTICE(_("You already in guild.")) + } + else if (flag == 2) + { + // Unable to make (likely name already in use) + SERVER_NOTICE(_("You already in guild.")) + } + else if (flag == 3) + { + // Emperium check failed + SERVER_NOTICE(_("Emperium check failed.")) + } + else + { + // Unknown response + SERVER_NOTICE(_("Unknown server response.")) + } + } + break; + + case SMSG_GUILD_POSITION_INFO: + { + int guildId = msg.readInt32(); + int emblem = msg.readInt32(); + int posMode = msg.readInt32(); + msg.readInt32(); // Unused + msg.readInt8(); // Unused + std::string guildName = msg.readString(24); + + Guild *g = Guild::getGuild(guildId); + if (!g) + break; + + g->setName(guildName); + g->setEmblemId(emblem); + if (!taGuild) + taGuild = g; + if (!guildTab && chatWindow) + { + guildTab = new GuildTab(); + if (player_node) + player_node->addGuild(taGuild); + memberList(guildId); + } + + if (player_node) + { + player_node->setGuild(g); + player_node->setGuildName(g->getName()); + } + + logger->log("Guild position info: %d %d %d %s\n", guildId, + emblem, posMode, guildName.c_str()); + } + break; + + case SMSG_GUILD_MEMBER_LOGIN: + { + int accountId = msg.readInt32(); // Account ID + int charId = msg.readInt32(); // Char ID + int online = msg.readInt32(); // Flag + if (taGuild) + { + GuildMember *m = taGuild->getMember(accountId, charId); + if (m) + m->setOnline(online); + } + break; + } + + case SMSG_GUILD_MASTER_OR_MEMBER: + msg.readInt32(); // Type (0x57 for member, 0xd7 for master) + break; + + case SMSG_GUILD_BASIC_INFO: + { + int guildId = msg.readInt32(); // Guild ID + int level = msg.readInt32(); // Guild level + int members = msg.readInt32(); // 'Connect member' + int maxMembers = msg.readInt32(); // 'Max member' + int avgLevel = msg.readInt32(); // Average level + int exp = msg.readInt32(); // Exp + int nextExp = msg.readInt32(); // Next exp + msg.skip(16); // unused + std::string name = msg.readString(24); // Name + std::string master = msg.readString(24); // Master's name + std::string castle = msg.readString(20); // Castles + // (ie: "Six Castles" or "None Taken") + + if (guildTab && showBasicInfo) + { + showBasicInfo = false; + guildTab->chatLog(strprintf( + _("Guild name: %s"), name.c_str()), BY_SERVER); + guildTab->chatLog(strprintf( + _("Guild master: %s"), master.c_str()), BY_SERVER); + guildTab->chatLog(strprintf( + _("Guild level: %d"), level), BY_SERVER); + guildTab->chatLog(strprintf( + _("Online members: %d"), members), BY_SERVER); + guildTab->chatLog(strprintf( + _("Max members: %d"), maxMembers), BY_SERVER); + guildTab->chatLog(strprintf( + _("Average level: %d"), avgLevel), BY_SERVER); + guildTab->chatLog(strprintf( + _("Guild exp: %d"), exp), BY_SERVER); + guildTab->chatLog(strprintf( + _("Guild next exp: %d"), nextExp), BY_SERVER); + guildTab->chatLog(strprintf( + _("Guild castle: %s"), castle.c_str()), BY_SERVER); + } + + Guild *g = Guild::getGuild(guildId); + if (!g) + break; + g->setName(name); + } + break; + + case SMSG_GUILD_ALIANCE_INFO: + { + int length = msg.readInt16(); + int count = (length - 4) / 32; + + for (int i = 0; i < count; i++) + { + msg.readInt32(); // 'Opposition' + msg.readInt32(); // Other guild ID + msg.readString(24); // Other guild name + } + } + break; + + case SMSG_GUILD_MEMBER_LIST: + { + int length = msg.readInt16(); + int count = (length - 4) / 104; + if (!taGuild) + { + logger->log1("!taGuild"); + break; + } + + taGuild->clearMembers(); + + for (int i = 0; i < count; i++) + { + int id = msg.readInt32(); // Account ID + int charId = msg.readInt32(); // Char ID + msg.readInt16(); // Hair + msg.readInt16(); // Hair color + int gender = msg.readInt16(); // Gender + int race = msg.readInt16(); // Class + int level = msg.readInt16(); // Level + int exp = msg.readInt32(); // Exp + int online = msg.readInt32(); // Online + int pos = msg.readInt32(); // Position + msg.skip(50); // unused + std::string name = msg.readString(24); // Name + + GuildMember *m = taGuild->addMember(id, charId, name); + if (m) + { + m->setOnline(online); + m->setID(id); + m->setCharId(charId); + if (!gender) + m->setGender(GENDER_FEMALE); + else if (gender == 1) + m->setGender(GENDER_MALE); + else + m->setGender(GENDER_UNSPECIFIED); + + m->setLevel(level); + m->setExp(exp); + m->setPos(pos); + m->setRace(race); +// m->setDisplayBold(!pos); + if (actorSpriteManager) + { + Being *being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + if (being) + { + being->setGuildName(taGuild->getName()); + if (being->getLevel() != level) + { + being->setLevel(level); + being->updateName(); + } + } + } + } + } + taGuild->sort(); + if (actorSpriteManager) + { + actorSpriteManager->updatePlayerGuild(); + actorSpriteManager->updatePlayerColors(); + } + } + break; + + case SMSG_GUILD_POS_NAME_LIST: + { + if (!taGuild) + { + logger->log1("!taGuild"); + break; + } + + int length = msg.readInt16(); + int count = (length - 4) / 28; + + for (int i = 0; i < count; i++) + { + int id = msg.readInt32(); // ID + std::string name = msg.readString(24); // Position name + taGuild->addPos(id, name); + } + } + break; + + case SMSG_GUILD_POS_INFO_LIST: + { + int length = msg.readInt16(); + int count = (length - 4) / 16; + + for (int i = 0; i < count; i++) + { + msg.readInt32(); // ID + msg.readInt32(); // Mode + msg.readInt32(); // Same ID + msg.readInt32(); // Exp mode + } + } + break; + + case SMSG_GUILD_POSITION_CHANGED: + msg.readInt16(); // Always 44 + msg.readInt32(); // ID + msg.readInt32(); // Mode + msg.readInt32(); // Same ID + msg.readInt32(); // Exp mode + msg.readString(24); // Name + break; + + case SMSG_GUILD_MEMBER_POS_CHANGE: + { + msg.readInt16(); // Always 16 + int accountId = msg.readInt32(); // Account ID + int charId = msg.readInt32(); // Char ID + int pos = msg.readInt32(); // Position + if (taGuild) + { + GuildMember *m = taGuild->getMember(accountId, charId); + if (m) + m->setPos(pos); + } + break; + } + + case SMSG_GUILD_EMBLEM: + { + int length = msg.readInt16(); + + msg.readInt32(); // Guild ID + msg.readInt32(); // Emblem ID + msg.skip(length - 12); // Emblem data (unknown format) + } + break; + + case SMSG_GUILD_SKILL_INFO: + { + int length = msg.readInt16(); + int count = (length - 6) / 37; + + msg.readInt16(); // 'Skill point' + + for (int i = 0; i < count; i++) + { + msg.readInt16(); // ID + msg.readInt16(); // 'Info' (unknown atm) + msg.readInt16(); // unused + msg.readInt16(); // Level + msg.readInt16(); // SP + msg.readInt16(); // 'Range' + msg.skip(24); // unused + msg.readInt8(); // Can be increased + } + } + break; + + case SMSG_GUILD_NOTICE: + { + std::string msg1 = msg.readString(60); // Mes1 + std::string msg2 = msg.readString(120); // Mes2 + if (guildTab) + { + guildTab->chatLog(msg1, BY_SERVER); + guildTab->chatLog(msg2, BY_SERVER); + } + break; + } + + case SMSG_GUILD_INVITE: + { + int guildId = msg.readInt32(); + std::string guildName = msg.readString(24); + + if (socialWindow) + socialWindow->showGuildInvite(guildName, guildId, ""); + break; + } + + case SMSG_GUILD_INVITE_ACK: + { + int flag = msg.readInt8(); + if (!guildTab) + break; + + switch (flag) + { + case 0: + guildTab->chatLog(_("Could not inivte user to guild."), + BY_SERVER); + break; + + case 1: + guildTab->chatLog(_("User rejected guild invite."), + BY_SERVER); + break; + + case 2: + guildTab->chatLog(_("User is now part of your guild."), + BY_SERVER); + break; + + case 3: + guildTab->chatLog(_("Your guild is full."), + BY_SERVER); + break; + + default: + guildTab->chatLog(_("Unknown guild invite response."), + BY_SERVER); + break; + } + } + break; + + case SMSG_GUILD_LEAVE: + { + std::string nick = msg.readString(24); // Name + std::string message = msg.readString(40); // Message + + if (taGuild) + taGuild->removeMember(nick); + + if (player_node && nick == player_node->getName()) + { + if (taGuild) + { + taGuild->removeFromMembers(); + taGuild->clearMembers(); + } + SERVER_NOTICE(_("You have left the guild.")) + delete guildTab; + guildTab = 0; + + if (socialWindow && taGuild) + socialWindow->removeTab(taGuild); + if (actorSpriteManager) + actorSpriteManager->updatePlayerColors(); + } + else + { + if (guildTab) + { + guildTab->chatLog(strprintf( + _("%s has left your guild."), + nick.c_str()), BY_SERVER); + } + if (actorSpriteManager) + { + Being *b = actorSpriteManager->findBeingByName( + nick, Being::PLAYER); + + if (b) + b->clearGuilds(); + if (taGuild) + taGuild->removeMember(nick); + } + } + break; + } + + case SMSG_GUILD_EXPULSION: + { + std::string nick = msg.readString(24); // Name (of expulsed?) + std::string message = msg.readString(40); // Message + msg.skip(24); // unused ("dummy") + if (taGuild) + taGuild->removeMember(nick); + + if (player_node && nick == player_node->getName()) + { + if (taGuild) + { + taGuild->removeFromMembers(); + taGuild->clearMembers(); + } + SERVER_NOTICE(_("You was kicked from guild.")); + delete guildTab; + guildTab = 0; + + if (socialWindow && taGuild) + socialWindow->removeTab(taGuild); + if (actorSpriteManager) + actorSpriteManager->updatePlayerColors(); + } + else + { + if (guildTab) + { + guildTab->chatLog(strprintf( + _("%s has kicked from your guild."), + nick.c_str()), BY_SERVER); + } + + if (actorSpriteManager) + { + Being *b = actorSpriteManager->findBeingByName( + nick, Being::PLAYER); + + if (b) + b->clearGuilds(); + if (taGuild) + taGuild->removeMember(nick); + } + } + break; + } + + case SMSG_GUILD_EXPULSION_LIST: + { + int length = msg.readInt16(); + int count = (length - 4) / 88; + + for (int i = 0; i < count; i++) + { + msg.readString(24); // Name (of expulsed?) + msg.readString(24); // 'Acc' (name of expulser?) + msg.readString(24); // Message + } + } + break; + + case SMSG_GUILD_MESSAGE: + { + int msgLength = msg.readInt16() - 4; + + if (msgLength <= 0) + return; + if (guildTab) + { + std::string chatMsg = msg.readString(msgLength); + + std::string::size_type pos = chatMsg.find(" : ", 0); + if (pos != std::string::npos) + { + std::string sender_name = ((pos == std::string::npos) + ? "" : chatMsg.substr(0, pos)); + + chatMsg.erase(0, pos + 3); + + trim(chatMsg); + guildTab->chatLog(sender_name, chatMsg); + } + else + { + guildTab->chatLog(chatMsg); + } + } + } + break; + + case SMSG_GUILD_SKILL_UP: + msg.readInt16(); // Skill ID + msg.readInt16(); // Level + msg.readInt16(); // SP + msg.readInt16(); // 'Range' + msg.readInt8(); // unused? (always 1) + break; + + case SMSG_GUILD_REQ_ALLIANCE: + msg.readInt32(); // Account ID + msg.readString(24); // Name + break; + + case SMSG_GUILD_REQ_ALLIANCE_ACK: + msg.readInt32(); // Flag + break; + + case SMSG_GUILD_DEL_ALLIANCE: + msg.readInt32(); // Guild ID + msg.readInt32(); // Flag + break; + + case SMSG_GUILD_OPPOSITION_ACK: + msg.readInt8(); // Flag + break; + + case SMSG_GUILD_BROKEN: + msg.readInt32(); // Flag + break; + + default: + break; + } +} + +void GuildHandler::create(const std::string &name) +{ + MessageOut msg(CMSG_GUILD_CREATE); + msg.writeInt32(0); // Unused + msg.writeString(name, 24); +} + +void GuildHandler::invite(int guildId _UNUSED_, + const std::string &name _UNUSED_) +{ + if (!actorSpriteManager) + return; + + Being* being = actorSpriteManager->findBeingByName(name, Being::PLAYER); + if (being) + { + MessageOut msg(CMSG_GUILD_INVITE); + msg.writeInt32(being->getId()); + msg.writeInt32(0); // Unused + msg.writeInt32(0); // Unused + } +} + +void GuildHandler::invite(int guildId _UNUSED_, Being *being) +{ + if (!being) + return; + + MessageOut msg(CMSG_GUILD_INVITE); + msg.writeInt32(being->getId()); + msg.writeInt32(0); // Unused + msg.writeInt32(0); // Unused +} + +void GuildHandler::inviteResponse(int guildId, bool response) +{ + MessageOut msg(CMSG_GUILD_INVITE_REPLY); + msg.writeInt32(guildId); + msg.writeInt8(response); + msg.writeInt8(0); // Unused + msg.writeInt16(0); // Unused +} + +void GuildHandler::leave(int guildId) +{ + if (!player_node) + return; + + MessageOut msg(CMSG_GUILD_LEAVE); + msg.writeInt32(guildId); + msg.writeInt32(player_node->getId()); // Account ID + msg.writeInt32(PlayerInfo::getCharId()); // Char ID + msg.writeString("", 40); // Message +} + +void GuildHandler::kick(GuildMember *member, std::string reason) +{ + if (!member || !member->getGuild()) + return; + + MessageOut msg(CMSG_GUILD_EXPULSION); + msg.writeInt32(member->getGuild()->getId()); + msg.writeInt32(member->getID()); // Account ID + msg.writeInt32(member->getCharId()); // Char ID + msg.writeString(reason, 40); // Message +} + +void GuildHandler::chat(int guildId _UNUSED_, const std::string &text) +{ + if (!player_node) + return; + + std::string str = player_node->getName() + " : " + text; + MessageOut msg(CMSG_GUILD_MESSAGE); + msg.writeInt16(str.size() + 4); + msg.writeString(str, str.length()); +} + +void GuildHandler::memberList(int guildId _UNUSED_) +{ + // TODO four types of info requests: + // 0 = basic info + alliance info + // 1 = position name list + member list + // 2 = position name list + position info list + // 3 = skill info + // 4 = expulsion list + + MessageOut msg(CMSG_GUILD_REQUEST_INFO); + msg.writeInt32(1); // Request member list +} + +void GuildHandler::info(int guildId _UNUSED_) +{ + // TODO four types of info requests: + // 0 = basic info + alliance info + // 1 = position name list + member list + // 2 = position name list + position info list + // 3 = skill info + // 4 = expulsion list + + showBasicInfo = true; + MessageOut msg(CMSG_GUILD_REQUEST_INFO); + msg.writeInt32(0); // Request basic info +} + +void GuildHandler::changeMemberPostion(GuildMember *member, int level) +{ + if (!member || !member->getGuild()) + return; + + MessageOut msg(CMSG_GUILD_CHANGE_MEMBER_POS); + msg.writeInt16(16); // size less then 16 <= 4 + 12 + msg.writeInt32(member->getID()); // Account ID + msg.writeInt32(member->getCharId()); // Char ID + msg.writeInt32(level); // pos +} + +void GuildHandler::requestAlliance(int guildId _UNUSED_, + int otherGuildId _UNUSED_) +{ + // TODO +} + +void GuildHandler::requestAllianceResponse(int guildId _UNUSED_, + int otherGuildId _UNUSED_, + bool response _UNUSED_) +{ + // TODO +} + +void GuildHandler::endAlliance(int guildId _UNUSED_, int otherGuildId _UNUSED_) +{ + // TODO +} + +void GuildHandler::changeNotice(int guildId, std::string msg1, + std::string msg2) +{ + MessageOut msg(CMSG_GUILD_CHANGE_NOTICE); + msg.writeInt32(guildId); + msg.writeString(msg1, 60); // msg1 + msg.writeString(msg2, 120); // msg2 +} + +bool GuildHandler::isSupported() +{ + return true; +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/guildhandler.h b/src/net/tmwa/guildhandler.h new file mode 100644 index 000000000..80b03bd01 --- /dev/null +++ b/src/net/tmwa/guildhandler.h @@ -0,0 +1,84 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_GUILDHANDLER_H +#define NET_TA_GUILDHANDLER_H + +#include "net/guildhandler.h" + +#include "net/tmwa/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class GuildHandler : public Net::GuildHandler, public MessageHandler +{ + public: + GuildHandler(); + + ~GuildHandler(); + + void handleMessage(Net::MessageIn &msg); + + void create(const std::string &name); + + void invite(int guildId, const std::string &name); + + void invite(int guildId, Being *being); + + void inviteResponse(int guildId, bool response); + + void leave(int guildId); + + void kick(GuildMember *member, std::string reason = ""); + + void chat(int guildId, const std::string &text); + + void memberList(int guildId); + + void info(int guildId _UNUSED_); + + void changeMemberPostion(GuildMember *member, int level); + + void requestAlliance(int guildId, int otherGuildId); + + void requestAllianceResponse(int guildId, int otherGuildId, + bool response); + + void endAlliance(int guildId, int otherGuildId); + + void changeNotice(int guildId, std::string msg1, std::string msg2); + + bool isSupported(); + + private: + // TmwAthena (and eAthena) only supports one guild per player + Guild *mGuild; +}; + +} + +#endif // NET_TA_GUILDHANDLER_H diff --git a/src/net/tmwa/inventoryhandler.cpp b/src/net/tmwa/inventoryhandler.cpp new file mode 100644 index 000000000..b4b38e623 --- /dev/null +++ b/src/net/tmwa/inventoryhandler.cpp @@ -0,0 +1,609 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/inventoryhandler.h" + +#include "configuration.h" +#include "equipment.h" +#include "event.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/widgets/chattab.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" + +#include "resources/iteminfo.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include + +extern Net::InventoryHandler *inventoryHandler; + +const Equipment::Slot EQUIP_POINTS[Equipment::EQUIP_VECTOREND] = +{ + Equipment::EQUIP_LEGS_SLOT, + Equipment::EQUIP_FIGHT1_SLOT, + Equipment::EQUIP_GLOVES_SLOT, + Equipment::EQUIP_RING2_SLOT, + Equipment::EQUIP_RING1_SLOT, + Equipment::EQUIP_FIGHT2_SLOT, + Equipment::EQUIP_FEET_SLOT, + Equipment::EQUIP_NECK_SLOT, + Equipment::EQUIP_HEAD_SLOT, + Equipment::EQUIP_TORSO_SLOT, + Equipment::EQUIP_EVOL_RING1_SLOT, + Equipment::EQUIP_EVOL_RING2_SLOT, + Equipment::EQUIP_PROJECTILE_SLOT, +}; + +const Equipment::Slot EQUIP_CONVERT[] = +{ + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_FEET_SLOT, // SPRITE_SHOE + Equipment::EQUIP_LEGS_SLOT, // SPRITE_BOTTOMCLOTHES + Equipment::EQUIP_TORSO_SLOT, // SPRITE_TOPCLOTHES + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_HEAD_SLOT, // SPRITE_HAT + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_GLOVES_SLOT, // SPRITE_GLOVES + Equipment::EQUIP_FIGHT1_SLOT, // SPRITE_WEAPON + Equipment::EQUIP_FIGHT2_SLOT, // SPRITE_SHIELD + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_PROJECTILE_SLOT, // 0 + Equipment::EQUIP_PROJECTILE_SLOT, // 0 +}; + +namespace TmwAthena +{ + +int getSlot(int eAthenaSlot); + +int getSlot(int eAthenaSlot) +{ + if (eAthenaSlot == 0) + return Equipment::EQUIP_VECTOREND; + + if (eAthenaSlot & 0x8000) + return Equipment::EQUIP_PROJECTILE_SLOT; + + int mask = 1; + int position = 0; + while (!(eAthenaSlot & mask)) + { + mask <<= 1; + position++; + } + return EQUIP_POINTS[position]; +} + +enum +{ + debugInventory = 1 +}; + +InventoryHandler::InventoryHandler() +{ + static const Uint16 _messages[] = + { + SMSG_PLAYER_INVENTORY, + SMSG_PLAYER_INVENTORY_ADD, + SMSG_PLAYER_INVENTORY_REMOVE, + SMSG_PLAYER_INVENTORY_USE, + SMSG_ITEM_USE_RESPONSE, + SMSG_PLAYER_STORAGE_ITEMS, + SMSG_PLAYER_STORAGE_EQUIP, + SMSG_PLAYER_STORAGE_STATUS, + SMSG_PLAYER_STORAGE_ADD, + SMSG_PLAYER_STORAGE_REMOVE, + SMSG_PLAYER_STORAGE_CLOSE, + SMSG_PLAYER_EQUIPMENT, + SMSG_PLAYER_EQUIP, + SMSG_PLAYER_UNEQUIP, + SMSG_PLAYER_ARROW_EQUIP, + SMSG_PLAYER_ATTACK_RANGE, + 0 + }; + handledMessages = _messages; + inventoryHandler = this; + + mStorage = 0; + mStorageWindow = 0; +} + +InventoryHandler::~InventoryHandler() +{ + if (mStorageWindow) + { + mStorageWindow->close(); + mStorageWindow = 0; + } + + delete mStorage; + mStorage = 0; +} + +void InventoryHandler::handleMessage(Net::MessageIn &msg) +{ + int number, flag; + int index, amount, itemId, equipType, arrow, refine; + int identified, cards[4], itemType; + Inventory *inventory = 0; + if (player_node) + inventory = PlayerInfo::getInventory(); + + switch (msg.getId()) + { + case SMSG_PLAYER_INVENTORY: + case SMSG_PLAYER_STORAGE_ITEMS: + if (msg.getId() == SMSG_PLAYER_INVENTORY) + { + // Clear inventory - this will be a complete refresh + mEquips.clear(); + PlayerInfo::getEquipment()->setBackend(&mEquips); + + if (inventory) + inventory->clear(); + } + else + { + mInventoryItems.clear(); + } + + msg.readInt16(); // length + number = (msg.getLength() - 4) / 18; + + for (int loop = 0; loop < number; loop++) + { + index = msg.readInt16(); + itemId = msg.readInt16(); + itemType = msg.readInt8(); + identified = msg.readInt8(); + amount = msg.readInt16(); + arrow = msg.readInt16(); + for (int i = 0; i < 4; i++) + cards[i] = msg.readInt16(); + + index -= (msg.getId() == SMSG_PLAYER_INVENTORY) ? + INVENTORY_OFFSET : STORAGE_OFFSET; + + if (debugInventory) + { + logger->log("Index: %d, ID: %d, Type: %d, Identified: %d, " + "Qty: %d, Cards: %d, %d, %d, %d", + index, itemId, itemType, identified, amount, + cards[0], cards[1], cards[2], cards[3]); + } + + if (msg.getId() == SMSG_PLAYER_INVENTORY) + { + // Trick because arrows are not considered equipment + bool isEquipment = arrow & 0x8000; + + if (inventory) + { + inventory->setItem(index, itemId, amount, + 0, isEquipment); + } + } + else + { + mInventoryItems.push_back(InventoryItem(index, itemId, + amount, 0, false)); + } + } + break; + + case SMSG_PLAYER_STORAGE_EQUIP: + msg.readInt16(); // length + number = (msg.getLength() - 4) / 20; + + for (int loop = 0; loop < number; loop++) + { + index = msg.readInt16() - STORAGE_OFFSET; + itemId = msg.readInt16(); + itemType = msg.readInt8(); + identified = msg.readInt8(); + amount = 1; + msg.readInt16(); // Equip Point? + msg.readInt16(); // Another Equip Point? + msg.readInt8(); // Attribute (broken) + refine = msg.readInt8(); + for (int i = 0; i < 4; i++) + cards[i] = msg.readInt16(); + + if (debugInventory) + { + logger->log("Index: %d, ID: %d, Type: %d, Identified: %d, " + "Qty: %d, Cards: %d, %d, %d, %d, Refine: %d", + index, itemId, itemType, identified, amount, + cards[0], cards[1], cards[2], cards[3], + refine); + } + + mInventoryItems.push_back(InventoryItem(index, itemId, amount, + refine, false)); + } + break; + + case SMSG_PLAYER_INVENTORY_ADD: + index = msg.readInt16() - INVENTORY_OFFSET; + amount = msg.readInt16(); + itemId = msg.readInt16(); + identified = msg.readInt8(); + msg.readInt8(); // attribute + refine = msg.readInt8(); + for (int i = 0; i < 4; i++) + cards[i] = msg.readInt16(); + equipType = msg.readInt16(); + itemType = msg.readInt8(); + + { + const ItemInfo &itemInfo = ItemDB::get(itemId); + + if (msg.readInt8() > 0) + { + if (player_node) + player_node->pickedUp(itemInfo, 0); + } + else + { + if (player_node) + player_node->pickedUp(itemInfo, amount); + + if (inventory) + { + Item *item = inventory->getItem(index); + + if (item && item->getId() == itemId) + amount += inventory->getItem(index)->getQuantity(); + + inventory->setItem(index, itemId, amount, refine, + equipType != 0); + } + } + } break; + + case SMSG_PLAYER_INVENTORY_REMOVE: + index = msg.readInt16() - INVENTORY_OFFSET; + amount = msg.readInt16(); + if (inventory) + { + if (Item *item = inventory->getItem(index)) + { + item->increaseQuantity(-amount); + if (item->getQuantity() == 0) + inventory->removeItemAt(index); + } + } + break; + + case SMSG_PLAYER_INVENTORY_USE: + index = msg.readInt16() - INVENTORY_OFFSET; + msg.readInt16(); // item id + msg.readInt32(); // id + amount = msg.readInt16(); + msg.readInt8(); // type + + if (inventory) + { + if (Item *item = inventory->getItem(index)) + { + if (amount) + item->setQuantity(amount); + else + inventory->removeItemAt(index); + } + } + break; + + case SMSG_ITEM_USE_RESPONSE: + index = msg.readInt16() - INVENTORY_OFFSET; + amount = msg.readInt16(); + + if (msg.readInt8() == 0) + { + SERVER_NOTICE(_("Failed to use item.")) + } + else + { + if (inventory) + { + if (Item *item = inventory->getItem(index)) + { + if (amount) + item->setQuantity(amount); + else + inventory->removeItemAt(index); + } + } + } + break; + + case SMSG_PLAYER_STORAGE_STATUS: + /* + * This is the closest we get to an "Open Storage" packet from the + * server. It always comes after the two SMSG_PLAYER_STORAGE_... + * packets that update storage contents. + */ + { + msg.readInt16(); // Used count + int size = msg.readInt16(); // Max size + + if (!mStorage) + mStorage = new Inventory(Inventory::STORAGE, size); + + InventoryItems::iterator it = mInventoryItems.begin(); + InventoryItems::iterator it_end = mInventoryItems.end(); + for (; it != it_end; it++) + mStorage->setItem((*it).slot, (*it).id, (*it).quantity, + (*it).equip); + mInventoryItems.clear(); + + if (!mStorageWindow) + mStorageWindow = new InventoryWindow(mStorage); + } + break; + + case SMSG_PLAYER_STORAGE_ADD: + // Move an item into storage + index = msg.readInt16() - STORAGE_OFFSET; + amount = msg.readInt32(); + itemId = msg.readInt16(); + identified = msg.readInt8(); + msg.readInt8(); // attribute + refine = msg.readInt8(); + for (int i = 0; i < 4; i++) + cards[i] = msg.readInt16(); + + if (Item *item = mStorage->getItem(index)) + { + item->setId(itemId); + item->increaseQuantity(amount); + } + else + { + if (mStorage) + mStorage->setItem(index, itemId, amount, false); + } + break; + + case SMSG_PLAYER_STORAGE_REMOVE: + // Move an item out of storage + index = msg.readInt16() - STORAGE_OFFSET; + amount = msg.readInt16(); + if (mStorage) + { + if (Item *item = mStorage->getItem(index)) + { + item->increaseQuantity(-amount); + if (item->getQuantity() == 0) + mStorage->removeItemAt(index); + } + } + break; + + case SMSG_PLAYER_STORAGE_CLOSE: + // Storage access has been closed + + // Storage window deletes itself + mStorageWindow = 0; + + if (mStorage) + mStorage->clear(); + + delete mStorage; + mStorage = 0; + break; + + case SMSG_PLAYER_EQUIPMENT: + msg.readInt16(); // length + number = (msg.getLength() - 4) / 20; + + for (int loop = 0; loop < number; loop++) + { + index = msg.readInt16() - INVENTORY_OFFSET; + itemId = msg.readInt16(); + msg.readInt8(); // type + msg.readInt8(); // identify flag + msg.readInt16(); // equip type + equipType = msg.readInt16(); + msg.readInt8(); // attribute + refine = msg.readInt8(); + msg.skip(8); // card + + if (inventory) + inventory->setItem(index, itemId, 1, refine, true); + + if (equipType) + mEquips.setEquipment(getSlot(equipType), index); + } + break; + + case SMSG_PLAYER_EQUIP: + index = msg.readInt16() - INVENTORY_OFFSET; + equipType = msg.readInt16(); + flag = msg.readInt8(); + + if (!flag) + SERVER_NOTICE(_("Unable to equip.")) + else + mEquips.setEquipment(getSlot(equipType), index); + break; + + case SMSG_PLAYER_UNEQUIP: + index = msg.readInt16() - INVENTORY_OFFSET; + equipType = msg.readInt16(); + flag = msg.readInt8(); + + if (flag) + mEquips.setEquipment(getSlot(equipType), -1); + break; + + case SMSG_PLAYER_ATTACK_RANGE: + { + int range = msg.readInt16(); + if (player_node) + player_node->setAttackRange(range); + PlayerInfo::setStatBase(ATTACK_RANGE, range); + PlayerInfo::setStatMod(ATTACK_RANGE, 0); + break; + } + + case SMSG_PLAYER_ARROW_EQUIP: + index = msg.readInt16(); + + if (index <= 1) + break; + + index -= INVENTORY_OFFSET; + + logger->log("Arrows equipped: %i", index); + mEquips.setEquipment(Equipment::EQUIP_PROJECTILE_SLOT, index); + break; + + default: + break; + } +} + +void InventoryHandler::equipItem(const Item *item) +{ + if (!item) + return; + + MessageOut outMsg(CMSG_PLAYER_EQUIP); + outMsg.writeInt16(static_cast( + item->getInvIndex() + INVENTORY_OFFSET)); + outMsg.writeInt16(0); +} + +void InventoryHandler::unequipItem(const Item *item) +{ + if (!item) + return; + + MessageOut outMsg(CMSG_PLAYER_UNEQUIP); + outMsg.writeInt16(static_cast( + item->getInvIndex() + INVENTORY_OFFSET)); +} + +void InventoryHandler::useItem(const Item *item) +{ + if (!item) + return; + + MessageOut outMsg(CMSG_PLAYER_INVENTORY_USE); + outMsg.writeInt16(static_cast( + item->getInvIndex() + INVENTORY_OFFSET)); + outMsg.writeInt32(item->getId()); // unused +} + +void InventoryHandler::dropItem(const Item *item, int amount) +{ + if (!item) + return; + + // TODO: Fix wrong coordinates of drops, serverside? (what's wrong here?) + MessageOut outMsg(CMSG_PLAYER_INVENTORY_DROP); + outMsg.writeInt16(static_cast( + item->getInvIndex() + INVENTORY_OFFSET)); + outMsg.writeInt16(static_cast(amount)); +} + +bool InventoryHandler::canSplit(const Item *item _UNUSED_) +{ + return false; +} + +void InventoryHandler::splitItem(const Item *item _UNUSED_, + int amount _UNUSED_) +{ + // Not implemented for eAthena (possible?) +} + +void InventoryHandler::moveItem(int oldIndex _UNUSED_, int newIndex _UNUSED_) +{ + // Not implemented for eAthena (possible?) +} + +void InventoryHandler::openStorage(int type _UNUSED_) +{ + // Doesn't apply to eAthena, since opening happens through NPCs? +} + +void InventoryHandler::closeStorage(int type _UNUSED_) +{ + MessageOut outMsg(CMSG_CLOSE_STORAGE); +} + +void InventoryHandler::moveItem(int source, int slot, int amount, + int destination) +{ + if (source == Inventory::INVENTORY && destination == Inventory::STORAGE) + { + MessageOut outMsg(CMSG_MOVE_TO_STORAGE); + outMsg.writeInt16(slot + INVENTORY_OFFSET); + outMsg.writeInt32(amount); + } + else if (source == Inventory::STORAGE + && destination == Inventory::INVENTORY) + { + MessageOut outMsg(CSMG_MOVE_FROM_STORAGE); + outMsg.writeInt16(slot + STORAGE_OFFSET); + outMsg.writeInt32(amount); + } +} + +size_t InventoryHandler::getSize(int type) const +{ + switch (type) + { + case Inventory::INVENTORY: + return 100; + case Inventory::STORAGE: + return 0; // Comes from server after items + case Inventory::TRADE: + return 12; + case GUILD_STORAGE: + return 0; // Comes from server after items + default: + return 0; + } +} +int InventoryHandler::convertFromServerSlot(int serverSlot) +{ + if (serverSlot < 0 || serverSlot > 13) + return 0; + + return EQUIP_CONVERT[serverSlot]; +} +} // namespace TmwAthena diff --git a/src/net/tmwa/inventoryhandler.h b/src/net/tmwa/inventoryhandler.h new file mode 100644 index 000000000..1a10b6ba0 --- /dev/null +++ b/src/net/tmwa/inventoryhandler.h @@ -0,0 +1,177 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_INVENTORYHANDLER_H +#define NET_TA_INVENTORYHANDLER_H + +#include "equipment.h" +#include "inventory.h" +#include "playerinfo.h" + +#include "gui/inventorywindow.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "net/tmwa/messagehandler.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class EquipBackend : public Equipment::Backend +{ + public: + EquipBackend() + { + memset(mEquipment, -1, sizeof(mEquipment)); + } + + Item *getEquipment(int index) const + { + int invyIndex = mEquipment[index]; + if (invyIndex == -1) + { + return NULL; + } + return PlayerInfo::getInventory()->getItem(invyIndex); + } + + void clear() + { + for (int i = 0; i < EQUIPMENT_SIZE; i++) + { + if (mEquipment[i] != -1) + { + Item* item = PlayerInfo::getInventory()->getItem(i); + if (item) + { + item->setEquipped(false); + } + } + + mEquipment[i] = -1; + } + } + + void setEquipment(int index, int inventoryIndex) + { + // Unequip existing item + Item* item = PlayerInfo::getInventory() + ->getItem(mEquipment[index]); + + if (item) + item->setEquipped(false); + + mEquipment[index] = inventoryIndex; + + item = PlayerInfo::getInventory()->getItem(inventoryIndex); + if (item) + item->setEquipped(true); + } + + private: + int mEquipment[EQUIPMENT_SIZE]; +}; + +/** + * Used to cache storage data until we get size data for it. + */ +class InventoryItem +{ + public: + int slot; + int id; + int quantity; + int refine; + bool equip; + + InventoryItem(int slot, int id, int quantity, int refine, bool equip) + { + this->slot = slot; + this->id = id; + this->quantity = quantity; + this->refine = refine; + this->equip = equip; + } +}; + +typedef std::list InventoryItems; + +class InventoryHandler : public MessageHandler, public Net::InventoryHandler +{ + public: + enum + { + GUILD_STORAGE = Inventory::TYPE_END, + CART + }; + + InventoryHandler(); + + ~InventoryHandler(); + + void handleMessage(Net::MessageIn &msg); + + void equipItem(const Item *item); + + void unequipItem(const Item *item); + + void useItem(const Item *item); + + void dropItem(const Item *item, int amount); + + bool canSplit(const Item *item); + + void splitItem(const Item *item, int amount); + + void moveItem(int oldIndex, int newIndex); + + void openStorage(int type); + + void closeStorage(int type); + + void moveItem(int source, int slot, int amount, + int destination); + + size_t getSize(int type) const; + + int convertFromServerSlot(int serverSlot); + + private: + EquipBackend mEquips; + InventoryItems mInventoryItems; + Inventory *mStorage; + InventoryWindow *mStorageWindow; +}; + +} // namespace TmwAthena + +int getSlot(int eAthenaSlot); + +#endif // NET_TA_INVENTORYHANDLER_H diff --git a/src/net/tmwa/itemhandler.cpp b/src/net/tmwa/itemhandler.cpp new file mode 100644 index 000000000..7ae124646 --- /dev/null +++ b/src/net/tmwa/itemhandler.cpp @@ -0,0 +1,93 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/itemhandler.h" + +#include "actorspritemanager.h" + +#include "net/messagein.h" + +#include "net/tmwa/protocol.h" + +namespace TmwAthena +{ + +ItemHandler::ItemHandler() +{ + static const Uint16 _messages[] = + { + SMSG_ITEM_VISIBLE, + SMSG_ITEM_DROPPED, + SMSG_ITEM_REMOVE, + 0 + }; + handledMessages = _messages; +} + +void ItemHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_ITEM_VISIBLE: + case SMSG_ITEM_DROPPED: + { + int id = msg.readInt32(); + int itemId = msg.readInt16(); + msg.readInt8(); // identify flag + int x = msg.readInt16(); + int y = msg.readInt16(); +// msg.skip(4); // amount,subX,subY / subX,subY,amount + int amount1 = msg.readInt16(); + int amount2 = msg.readInt16(); + + if (actorSpriteManager) + { + if (msg.getId() == SMSG_ITEM_VISIBLE) + { + actorSpriteManager->createItem(id, itemId, + x, y, amount1); + } + else + { + actorSpriteManager->createItem(id, itemId, + x, y, amount2); + } + } + } + break; + + case SMSG_ITEM_REMOVE: + if (actorSpriteManager) + { + if (FloorItem *item = actorSpriteManager->findItem( + msg.readInt32())) + { + actorSpriteManager->destroy(item); + } + } + break; + + default: + break; + } +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/itemhandler.h b/src/net/tmwa/itemhandler.h new file mode 100644 index 000000000..0f740fe58 --- /dev/null +++ b/src/net/tmwa/itemhandler.h @@ -0,0 +1,40 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_ITEMHANDLER_H +#define NET_TA_ITEMHANDLER_H + +#include "net/tmwa/messagehandler.h" + +namespace TmwAthena +{ + +class ItemHandler : public MessageHandler +{ + public: + ItemHandler(); + + virtual void handleMessage(Net::MessageIn &msg); +}; + +} // namespace TmwAthena + +#endif // NET_TA_ITEMHANDLER_H diff --git a/src/net/tmwa/loginhandler.cpp b/src/net/tmwa/loginhandler.cpp new file mode 100644 index 000000000..73fec1322 --- /dev/null +++ b/src/net/tmwa/loginhandler.cpp @@ -0,0 +1,342 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/loginhandler.h" + +#include "client.h" +#include "log.h" +#include "configuration.h" + +#include "net/logindata.h" +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/network.h" +#include "net/tmwa/protocol.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern Net::LoginHandler *loginHandler; + +namespace TmwAthena +{ + +extern ServerInfo charServer; + +LoginHandler::LoginHandler(): + mVersionResponse(false), + mRegistrationEnabled(true) +{ + static const Uint16 _messages[] = + { + SMSG_UPDATE_HOST, + SMSG_LOGIN_DATA, + SMSG_LOGIN_ERROR, + SMSG_CHAR_PASSWORD_RESPONSE, + SMSG_SERVER_VERSION_RESPONSE, + 0 + }; + handledMessages = _messages; + loginHandler = this; +} + +LoginHandler::~LoginHandler() +{ + delete_all(mWorlds); +} + +void LoginHandler::handleMessage(Net::MessageIn &msg) +{ + int code, worldCount; + + switch (msg.getId()) + { + case SMSG_CHAR_PASSWORD_RESPONSE: + { + // 0: acc not found, 1: success, 2: password mismatch, 3: pass too short + int errMsg = msg.readInt8(); + // Successful pass change + if (errMsg == 1) + { + Client::setState(STATE_CHANGEPASSWORD_SUCCESS); + } + // pass change failed + else + { + switch (errMsg) + { + case 0: + errorMessage = + _("Account was not found. Please re-login."); + break; + case 2: + errorMessage = _("Old password incorrect."); + break; + case 3: + errorMessage = _("New password too short."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + + case SMSG_UPDATE_HOST: + int len; + + len = msg.readInt16() - 4; + mUpdateHost = msg.readString(len); + loginData.updateHost = mUpdateHost; + + logger->log("Received update host \"%s\" from login server.", + mUpdateHost.c_str()); + break; + + case SMSG_LOGIN_DATA: + // Skip the length word + msg.skip(2); + + clearWorlds(); + + worldCount = (msg.getLength() - 47) / 32; + + mToken.session_ID1 = msg.readInt32(); + mToken.account_ID = msg.readInt32(); + mToken.session_ID2 = msg.readInt32(); + msg.skip(30); // unknown + mToken.sex = msg.readInt8() ? GENDER_MALE : GENDER_FEMALE; + + for (int i = 0; i < worldCount; i++) + { + WorldInfo *world = new WorldInfo; + + world->address = msg.readInt32(); + world->port = msg.readInt16(); + world->name = msg.readString(20); + world->online_users = msg.readInt32(); + config.setValue("updatehost", mUpdateHost); + world->updateHost = mUpdateHost; + msg.skip(2); // unknown + + logger->log("Network: Server: %s (%s:%d)", + world->name.c_str(), + ipToString(world->address), + world->port); + + mWorlds.push_back(world); + } + Client::setState(STATE_WORLD_SELECT); + break; + + case SMSG_LOGIN_ERROR: + code = msg.readInt8(); + logger->log("Login::error code: %i", code); + + switch (code) + { + case 0: + errorMessage = _("Unregistered ID."); + break; + case 1: + errorMessage = _("Wrong password."); + break; + case 2: + errorMessage = _("Account expired."); + break; + case 3: + errorMessage = _("Rejected from server."); + break; + case 4: + errorMessage = _("You have been permanently banned from " + "the game. Please contact the GM team."); + break; + case 6: + errorMessage = strprintf(_("You have been temporarily " + "banned from the game until " + "%s.\nPlease contact the GM " + "team via the forums."), + msg.readString(20).c_str()); + break; + case 9: + errorMessage = _("This user name is already taken."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ERROR); + break; + + case SMSG_SERVER_VERSION_RESPONSE: + { + // TODO: verify these! + + msg.readInt8(); // -1 + msg.readInt8(); // T + msg.readInt8(); // M + msg.readInt8(); // W + + unsigned int options = msg.readInt32(); + + mRegistrationEnabled = options; +// mRegistrationEnabled = (options & 1); + + // Leave this last + mVersionResponse = true; + } + break; + + default: + break; + } +} + +void LoginHandler::connect() +{ + if (!mNetwork) + return; + + mNetwork->connect(mServer); + MessageOut outMsg(CMSG_SERVER_VERSION_REQUEST); +} + +bool LoginHandler::isConnected() +{ + if (!mNetwork) + return false; + + return mVersionResponse && mNetwork->isConnected(); +} + +void LoginHandler::disconnect() +{ + if (mNetwork && mNetwork->getServer() == mServer) + mNetwork->disconnect(); +} + +bool LoginHandler::isRegistrationEnabled() +{ + return mRegistrationEnabled; +} + +void LoginHandler::getRegistrationDetails() +{ + // Not supported, so move on + Client::setState(STATE_REGISTER); +} + +void LoginHandler::loginAccount(LoginData *loginData) +{ + + if (loginData) + { + // Since we're attempting to use the tAthena protocol, + // let's reset the character slots to the good value, + // in case we just logged out a Manaserv server + // with a different config. + loginData->resetCharacterSlots(); + + sendLoginRegister(loginData->username, loginData->password); + } +} + +void LoginHandler::logout() +{ + // TODO +} + +void LoginHandler::changeEmail(const std::string &email _UNUSED_) +{ + // TODO +} + +void LoginHandler::changePassword(const std::string &username _UNUSED_, + const std::string &oldPassword, + const std::string &newPassword) +{ + MessageOut outMsg(CMSG_CHAR_PASSWORD_CHANGE); + outMsg.writeString(oldPassword, 24); + outMsg.writeString(newPassword, 24); +} + +void LoginHandler::chooseServer(unsigned int server) +{ + if (server >= mWorlds.size() || !mWorlds[server]) + return; + + charServer.clear(); + charServer.hostname = ipToString(mWorlds[server]->address); + charServer.port = mWorlds[server]->port; + + Client::setState(STATE_UPDATE); +} + +void LoginHandler::registerAccount(LoginData *loginData) +{ + if (!loginData) + return; + + std::string username = loginData->username; + username.append((loginData->gender == GENDER_FEMALE) ? "_F" : "_M"); + + sendLoginRegister(username, loginData->password); +} + +void LoginHandler::unregisterAccount(const std::string &username _UNUSED_, + const std::string &password _UNUSED_) +{ + // TODO +} + +void LoginHandler::sendLoginRegister(const std::string &username, + const std::string &password) +{ + MessageOut outMsg(0x0064); + outMsg.writeInt32(0); // client version + outMsg.writeString(username, 24); + outMsg.writeString(password, 24); + + /* + * eAthena calls the last byte "client version 2", but it isn't used at + * at all. We're retasking it, as a bit mask: + * 0 - can handle the 0x63 "update host" packet + * 1 - defaults to the first char-server (instead of the last) + */ + outMsg.writeInt8(0x03); +} + +Worlds LoginHandler::getWorlds() const +{ + return mWorlds; +} + +void LoginHandler::clearWorlds() +{ + delete_all(mWorlds); + mWorlds.clear(); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/loginhandler.h b/src/net/tmwa/loginhandler.h new file mode 100644 index 000000000..977b4ce0a --- /dev/null +++ b/src/net/tmwa/loginhandler.h @@ -0,0 +1,105 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TMWA_LOGINHANDLER_H +#define NET_TMWA_LOGINHANDLER_H + +#include "net/loginhandler.h" + +#include "net/tmwa/messagehandler.h" +#include "net/tmwa/token.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +struct LoginData; + +namespace TmwAthena +{ + +class LoginHandler : public MessageHandler, public Net::LoginHandler +{ + public: + LoginHandler(); + + ~LoginHandler(); + + void handleMessage(Net::MessageIn &msg); + + void connect(); + + bool isConnected(); + + void disconnect(); + + int supportedOptionalActions() const + { return SetGenderOnRegister; } + + bool isRegistrationEnabled(); + + void getRegistrationDetails(); + + unsigned int getMaxPasswordLength() const + { return 25; } + + void loginAccount(LoginData *loginData); + + void logout(); + + void changeEmail(const std::string &email); + + void changePassword(const std::string &username, + const std::string &oldPassword, + const std::string &newPassword); + + void chooseServer(unsigned int server); + + void registerAccount(LoginData *loginData); + + void unregisterAccount(const std::string &username, + const std::string &password); + + Worlds getWorlds() const; + + void clearWorlds(); + + const Token &getToken() const + { return mToken; } + + private: + void sendLoginRegister(const std::string &username, + const std::string &password); + + bool mVersionResponse; + bool mRegistrationEnabled; + std::string mUpdateHost; + Worlds mWorlds; + Token mToken; +}; + +} // namespace TmwAthena + +#endif // NET_TA_LOGINHANDLER_H diff --git a/src/net/tmwa/messagehandler.cpp b/src/net/tmwa/messagehandler.cpp new file mode 100644 index 000000000..85a0caeae --- /dev/null +++ b/src/net/tmwa/messagehandler.cpp @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/messagehandler.h" + +#include "net/tmwa/network.h" + +#include + +namespace TmwAthena +{ + +MessageHandler::MessageHandler() + : mNetwork(NULL) +{ +} + +MessageHandler::~MessageHandler() +{ + if (mNetwork) + mNetwork->unregisterHandler(this); +} + +void MessageHandler::setNetwork(Network *network) +{ + assert(!(network && mNetwork)); + mNetwork = network; +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/messagehandler.h b/src/net/tmwa/messagehandler.h new file mode 100644 index 000000000..d4b0c8094 --- /dev/null +++ b/src/net/tmwa/messagehandler.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_MESSAGEHANDLER_H +#define NET_TA_MESSAGEHANDLER_H + +#include "net/messagehandler.h" +#include "net/messagein.h" + +#include "net/tmwa/messageout.h" + +#include + +#include + +namespace TmwAthena +{ + +class Network; + +/** + * \ingroup Network + */ +class MessageHandler : public Net::MessageHandler +{ + public: + MessageHandler(); + + ~MessageHandler(); + + void setNetwork(Network *network); + + protected: + Network *mNetwork; +}; + +typedef const std::auto_ptr MessageHandlerPtr; + +} + +#endif // NET_TA_MESSAGEHANDLER_H diff --git a/src/net/tmwa/messagein.cpp b/src/net/tmwa/messagein.cpp new file mode 100644 index 000000000..23f5e09c1 --- /dev/null +++ b/src/net/tmwa/messagein.cpp @@ -0,0 +1,85 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/messagein.h" + +#include "net/packetcounters.h" + +#include "log.h" + +#include "utils/stringutils.h" + +#include +#include + +#define MAKEWORD(low, high) \ + ((unsigned short)(((unsigned char)(low)) | \ + ((unsigned short)((unsigned char)(high))) << 8)) + +namespace TmwAthena +{ + +MessageIn::MessageIn(const char *data, unsigned int length): + Net::MessageIn(data, length) +{ + // Read the message ID + mId = readInt16(); +} + +Sint16 MessageIn::readInt16() +{ + Sint16 value = -1; + if (mPos + 2 <= mLength) + { +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + Sint16 swap; + memcpy(&swap, mData + mPos, sizeof(Sint16)); + value = SDL_Swap16(swap); +#else + memcpy(&value, mData + mPos, sizeof(Sint16)); +#endif + } + mPos += 2; + PacketCounters::incInBytes(2); + DEBUGLOG("readInt16: " + toString((int)value)); + return value; +} + +int MessageIn::readInt32() +{ + Sint32 value = -1; + if (mPos + 4 <= mLength) + { +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + Sint32 swap; + memcpy(&swap, mData + mPos, sizeof(Sint32)); + value = SDL_Swap32(swap); +#else + memcpy(&value, mData + mPos, sizeof(Sint32)); +#endif + } + mPos += 4; + PacketCounters::incInBytes(4); + DEBUGLOG(strprintf("readInt32: %u", value)); + return value; +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/messagein.h b/src/net/tmwa/messagein.h new file mode 100644 index 000000000..b3dedc7af --- /dev/null +++ b/src/net/tmwa/messagein.h @@ -0,0 +1,52 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_MESSAGEIN_H +#define NET_TA_MESSAGEIN_H + +#include "net/messagein.h" + +#include +#include + +namespace TmwAthena +{ + +/** + * Used for parsing an incoming message. + * + * \ingroup Network + */ + class MessageIn : public Net::MessageIn +{ + public: + /** + * Constructor. + */ + MessageIn(const char *data, unsigned int length); + + Sint16 readInt16(); /**< Reads a short. */ + int readInt32(); /**< Reads a long. */ +}; + +} + +#endif // NET_TA_MESSAGEIN_H diff --git a/src/net/tmwa/messageout.cpp b/src/net/tmwa/messageout.cpp new file mode 100644 index 000000000..42d4f582c --- /dev/null +++ b/src/net/tmwa/messageout.cpp @@ -0,0 +1,142 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/messageout.h" + +#include "net/packetcounters.h" + +#include "net/tmwa/network.h" + +#include "log.h" + +#include "utils/stringutils.h" + +#include +#include + +#include +#include + +namespace TmwAthena +{ + +MessageOut::MessageOut(short id): + Net::MessageOut(id) +{ + mNetwork = TmwAthena::Network::instance(); + mData = mNetwork->mOutBuffer + mNetwork->mOutSize; + writeInt16(id); +} + +void MessageOut::expand(size_t bytes) +{ + mNetwork->mOutSize += static_cast(bytes); + PacketCounters::incOutBytes(static_cast(bytes)); +} + +void MessageOut::writeInt16(Sint16 value) +{ + DEBUGLOG("writeInt16: " + toString((int)value)); + expand(2); +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + Sint16 swap = SDL_Swap16(value); + memcpy(mData + mPos, &swap, sizeof(Sint16)); +#else + memcpy(mData + mPos, &value, sizeof(Sint16)); +#endif + mPos += 2; + PacketCounters::incOutBytes(2); +} + +void MessageOut::writeInt32(Sint32 value) +{ + DEBUGLOG("writeInt32: " + toString(value)); + expand(4); +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + Sint32 swap = SDL_Swap32(value); + memcpy(mData + mPos, &swap, sizeof(Sint32)); +#else + memcpy(mData + mPos, &value, sizeof(Sint32)); +#endif + mPos += 4; + PacketCounters::incOutBytes(4); +} + +#define LOBYTE(w) ((unsigned char)(w)) +#define HIBYTE(w) ((unsigned char)(((unsigned short)(w)) >> 8)) + +void MessageOut::writeCoordinates(unsigned short x, unsigned short y, + unsigned char direction) +{ + DEBUGLOG(strprintf("writeCoordinates: %u,%u %u", x, y, direction)); + char *data = mData + mPos; + mNetwork->mOutSize += 3; + mPos += 3; + + short temp; + temp = x; + temp <<= 6; + data[0] = 0; + data[1] = 1; + data[2] = 2; + data[0] = HIBYTE(temp); + data[1] = (unsigned char) temp; + temp = y; + temp <<= 4; + data[1] |= HIBYTE(temp); + data[2] = LOBYTE(temp); + + // Translate direction to eAthena format + switch (direction) + { + case 1: + direction = 0; + break; + case 3: + direction = 1; + break; + case 2: + direction = 2; + break; + case 6: + direction = 3; + break; + case 4: + direction = 4; + break; + case 12: + direction = 5; + break; + case 8: + direction = 6; + break; + case 9: + direction = 7; + break; + default: + // OOPSIE! Impossible or unknown + direction = (unsigned char) -1; + } + data[2] |= direction; + PacketCounters::incOutBytes(3); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/messageout.h b/src/net/tmwa/messageout.h new file mode 100644 index 000000000..ae0fc9a49 --- /dev/null +++ b/src/net/tmwa/messageout.h @@ -0,0 +1,65 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_MESSAGEOUT_H +#define NET_TA_MESSAGEOUT_H + +#include "net/messageout.h" + +#include +#include + +namespace TmwAthena +{ + +class Network; + +/** + * Used for building an outgoing message. + * + * \ingroup Network + */ +class MessageOut : public Net::MessageOut +{ + public: + /** + * Constructor. + */ + MessageOut(short id); + + void writeInt16(Sint16 value); /**< Writes a short. */ + void writeInt32(Sint32 value); /**< Writes a long. */ + + /** + * Encodes coordinates and direction in 3 bytes. + */ + void writeCoordinates(unsigned short x, unsigned short y, + unsigned char direction); + + private: + void expand(size_t size); + + Network *mNetwork; +}; + +} + +#endif // NET_TA_MESSAGEOUT_H diff --git a/src/net/tmwa/network.cpp b/src/net/tmwa/network.cpp new file mode 100644 index 000000000..7aa9ad7d2 --- /dev/null +++ b/src/net/tmwa/network.cpp @@ -0,0 +1,483 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/network.h" + +#include "log.h" + +#include "net/messagehandler.h" +#include "net/messagein.h" + +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include +#include + +/** Warning: buffers and other variables are shared, + so there can be only one connection active at a time */ + +short packet_lengths[] = +{ + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// #0x0040 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 50, 3, -1, 55, 17, 3, 37, 46, -1, 23, -1, 3, 108, 3, 2, + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +// #0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, +// #0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 6, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, +// #0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, +// #0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14, 186, 182, + 14, 30, 10, 3, -1, 6, 106, -1, 4, 5, 4, -1, 6, 7, -1, -1, +// #0x0180 + 6, 3, 106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, + 90, 86, 24, 6, 30, 102, 9, 4, 8, 4, 14, 10, 4, 6, 2, 6, + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, + 11, 7, -1, 67, 12, 18, 114, 6, 3, 6, 26, 26, 26, 26, 2, 3, +// #0x01C0 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 0, 9, 9, 29, 6, 28, + 8, 14, 10, 35, 6, 8, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, +// #0x2000 + 26, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 19, 10, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const unsigned int BUFFER_SIZE = 655360; + +namespace TmwAthena +{ + +int networkThread(void *data) +{ + Network *network = static_cast(data); + + if (!network || !network->realConnect()) + return -1; + + network->receive(); + + return 0; +} + +Network *Network::mInstance = 0; + +Network::Network(): + mSocket(0), + mInBuffer(new char[BUFFER_SIZE]), + mOutBuffer(new char[BUFFER_SIZE]), + mInSize(0), mOutSize(0), + mToSkip(0), + mState(IDLE), + mWorkerThread(0) +{ + SDLNet_Init(); + + mMutex = SDL_CreateMutex(); + mInstance = this; +} + +Network::~Network() +{ + clearHandlers(); + + if (mState != IDLE && mState != NET_ERROR) + disconnect(); + + SDL_DestroyMutex(mMutex); + mInstance = 0; + + delete[] mInBuffer; + delete[] mOutBuffer; + + SDLNet_Quit(); +} + +bool Network::connect(ServerInfo server) +{ + if (mState != IDLE && mState != NET_ERROR) + { + logger->log1("Tried to connect an already connected socket!"); + assert(false); + return false; + } + + if (server.hostname.empty()) + { + setError(_("Empty address given to Network::connect()!")); + return false; + } + + logger->log("Network::Connecting to %s:%i", server.hostname.c_str(), + server.port); + + mServer.hostname = server.hostname; + mServer.port = server.port; + + // Reset to sane values + mOutSize = 0; + mInSize = 0; + mToSkip = 0; + + mState = CONNECTING; + mWorkerThread = SDL_CreateThread(networkThread, this); + if (!mWorkerThread) + { + setError("Unable to create network worker thread"); + return false; + } + + return true; +} + +void Network::disconnect() +{ + mState = IDLE; + + if (mWorkerThread && SDL_GetThreadID(mWorkerThread)) + { + SDL_WaitThread(mWorkerThread, NULL); + mWorkerThread = NULL; + } + + if (mSocket) + { + // need call SDLNet_TCP_DelSocket? + SDLNet_TCP_Close(mSocket); + mSocket = 0; + } +} + +void Network::registerHandler(MessageHandler *handler) +{ + if (!handler) + return; + + for (const Uint16 *i = handler->handledMessages; *i; ++i) + mMessageHandlers[*i] = handler; + + handler->setNetwork(this); +} + +void Network::unregisterHandler(MessageHandler *handler) +{ + if (!handler) + return; + + for (const Uint16 *i = handler->handledMessages; *i; ++i) + mMessageHandlers.erase(*i); + + handler->setNetwork(0); +} + +void Network::clearHandlers() +{ + MessageHandlerIterator i; + for (i = mMessageHandlers.begin(); i != mMessageHandlers.end(); ++i) + { + if (i->second) + i->second->setNetwork(0); + } + mMessageHandlers.clear(); +} + +void Network::dispatchMessages() +{ + while (messageReady()) + { + MessageIn msg = getNextMessage(); + + MessageHandlerIterator iter = mMessageHandlers.find(msg.getId()); + + if (msg.getLength() == 0) + logger->error("Zero length packet received. Exiting."); + + if (iter != mMessageHandlers.end()) + { + if (iter->second) + iter->second->handleMessage(msg); + } + else + { + logger->log("Unhandled packet: %x", msg.getId()); + } + + skip(msg.getLength()); + } +} + +void Network::flush() +{ + if (!mOutSize || mState != CONNECTED) + return; + + int ret; + + SDL_mutexP(mMutex); + ret = SDLNet_TCP_Send(mSocket, mOutBuffer, mOutSize); + if (ret < (int)mOutSize) + { + setError("Error in SDLNet_TCP_Send(): " + + std::string(SDLNet_GetError())); + } + mOutSize = 0; + SDL_mutexV(mMutex); +} + +void Network::skip(int len) +{ + SDL_mutexP(mMutex); + mToSkip += len; + if (!mInSize) + { + SDL_mutexV(mMutex); + return; + } + + if (mInSize >= mToSkip) + { + mInSize -= mToSkip; + memmove(mInBuffer, mInBuffer + mToSkip, mInSize); + mToSkip = 0; + } + else + { + mToSkip -= mInSize; + mInSize = 0; + } + SDL_mutexV(mMutex); +} + +bool Network::messageReady() +{ + int len = -1, msgId; + + SDL_mutexP(mMutex); + if (mInSize >= 2) + { + msgId = readWord(0); + if (msgId == SMSG_SERVER_VERSION_RESPONSE) + len = 10; + else + len = packet_lengths[msgId]; + + if (len == -1 && mInSize > 4) + len = readWord(2); + + } + + bool ret = (mInSize >= static_cast(len)); + SDL_mutexV(mMutex); + + return ret; +} + +MessageIn Network::getNextMessage() +{ + while (!messageReady()) + { + if (mState == NET_ERROR) + break; + } + + SDL_mutexP(mMutex); + int msgId = readWord(0); + int len; + if (msgId == SMSG_SERVER_VERSION_RESPONSE) + len = 10; + else + len = packet_lengths[msgId]; + + if (len == -1) + len = readWord(2); + +#ifdef DEBUG + logger->log("Received packet 0x%x of length %d\n", msgId, len); +#endif + + MessageIn msg(mInBuffer, len); + SDL_mutexV(mMutex); + + return msg; +} + +bool Network::realConnect() +{ + IPaddress ipAddress; + + if (SDLNet_ResolveHost(&ipAddress, mServer.hostname.c_str(), + mServer.port) == -1) + { + std::string errorMessage = _("Unable to resolve host \"") + + mServer.hostname + "\""; + setError(errorMessage); + logger->log("SDLNet_ResolveHost: %s", errorMessage.c_str()); + return false; + } + + mState = CONNECTING; + + mSocket = SDLNet_TCP_Open(&ipAddress); + if (!mSocket) + { + logger->log("Error in SDLNet_TCP_Open(): %s", SDLNet_GetError()); + setError(SDLNet_GetError()); + return false; + } + + logger->log("Network::Started session with %s:%i", + ipToString(ipAddress.host), ipAddress.port); + + mState = CONNECTED; + + return true; +} + +void Network::receive() +{ + SDLNet_SocketSet set; + + if (!(set = SDLNet_AllocSocketSet(1))) + { + setError("Error in SDLNet_AllocSocketSet(): " + + std::string(SDLNet_GetError())); + return; + } + + if (SDLNet_TCP_AddSocket(set, mSocket) == -1) + { + setError("Error in SDLNet_AddSocket(): " + + std::string(SDLNet_GetError())); + } + + while (mState == CONNECTED) + { + // TODO Try to get this to block all the time while still being able + // to escape the loop + int numReady = SDLNet_CheckSockets(set, ((Uint32)500)); + int ret; + switch (numReady) + { + case -1: + logger->log1("Error: SDLNet_CheckSockets"); + // FALLTHROUGH + case 0: + break; + + case 1: + // Receive data from the socket + SDL_mutexP(mMutex); + ret = SDLNet_TCP_Recv(mSocket, mInBuffer + mInSize, + BUFFER_SIZE - mInSize); + + if (!ret) + { + // We got disconnected + mState = IDLE; + logger->log1("Disconnected."); + } + else if (ret < 0) + { + setError(_("Connection to server terminated. ") + + std::string(SDLNet_GetError())); + } + else + { + mInSize += ret; + if (mToSkip) + { + if (mInSize >= mToSkip) + { + mInSize -= mToSkip; + memmove(mInBuffer, mInBuffer + mToSkip, mInSize); + mToSkip = 0; + } + else + { + mToSkip -= mInSize; + mInSize = 0; + } + } + } + SDL_mutexV(mMutex); + break; + + default: + // more than one socket is ready.. + // this should not happen since we only listen once socket. + std::stringstream errorStream; + errorStream << "Error in SDLNet_TCP_Recv(), " << numReady + << " sockets are ready: " << SDLNet_GetError(); + setError(errorStream.str()); + break; + } + } + + if (SDLNet_TCP_DelSocket(set, mSocket) == -1) + logger->log("Error in SDLNet_DelSocket(): %s", SDLNet_GetError()); + + SDLNet_FreeSocketSet(set); +} + +Network *Network::instance() +{ + return mInstance; +} + +void Network::setError(const std::string &error) +{ + logger->log("Network error: %s", error.c_str()); + mError = error; + mState = NET_ERROR; +} + +Uint16 Network::readWord(int pos) +{ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + return SDL_Swap16((*(Uint16*)(mInBuffer + (pos)))); +#else + return (*(Uint16*)(mInBuffer + (pos))); +#endif +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/network.h b/src/net/tmwa/network.h new file mode 100644 index 000000000..bdafd6a09 --- /dev/null +++ b/src/net/tmwa/network.h @@ -0,0 +1,136 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_NETWORK_H +#define NET_TA_NETWORK_H + +#include "net/serverinfo.h" + +#include "net/tmwa/messagehandler.h" +#include "net/tmwa/messagein.h" +#include "net/tmwa/messageout.h" + +#include +#include + +#include +#include + +/** + * Protocol version, reported to the eAthena char and mapserver who can adjust + * the protocol accordingly. + */ +#define CLIENT_PROTOCOL_VERSION 1 + +namespace TmwAthena +{ + +class Network +{ + public: + Network(); + + ~Network(); + + bool connect(ServerInfo server); + + void disconnect(); + + ServerInfo getServer() const + { return mServer; } + + void registerHandler(MessageHandler *handler); + + void unregisterHandler(MessageHandler *handler); + + void clearHandlers(); + + int getState() const + { return mState; } + + const std::string &getError() const + { return mError; } + + bool isConnected() const + { return mState == CONNECTED; } + + int getInSize() const + { return mInSize; } + + void skip(int len); + + bool messageReady(); + + MessageIn getNextMessage(); + + void dispatchMessages(); + + void flush(); + + // ERROR replaced by NET_ERROR because already defined in Windows + enum + { + IDLE = 0, + CONNECTED, + CONNECTING, + DATA, + NET_ERROR + }; + + protected: + friend int networkThread(void *data); + friend class MessageOut; + + static Network *instance(); + + void setError(const std::string &error); + + Uint16 readWord(int pos); + + bool realConnect(); + + void receive(); + + TCPsocket mSocket; + + ServerInfo mServer; + + char *mInBuffer, *mOutBuffer; + unsigned int mInSize, mOutSize; + + unsigned int mToSkip; + + int mState; + std::string mError; + + SDL_Thread *mWorkerThread; + SDL_mutex *mMutex; + + typedef std::map MessageHandlers; + typedef MessageHandlers::iterator MessageHandlerIterator; + MessageHandlers mMessageHandlers; + + static Network *mInstance; +}; + +} // namespace TmwAthena + +#endif // NET_TA_NETWORK_H diff --git a/src/net/tmwa/npchandler.cpp b/src/net/tmwa/npchandler.cpp new file mode 100644 index 000000000..84fe1789c --- /dev/null +++ b/src/net/tmwa/npchandler.cpp @@ -0,0 +1,249 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/npchandler.h" + +#include "actorspritemanager.h" +#include "localplayer.h" + +#include "gui/npcdialog.h" + +#include "net/messagein.h" +#include "net/messageout.h" +#include "net/net.h" +#include "net/npchandler.h" + +#include "net/tmwa/protocol.h" + +#include + +extern Net::NpcHandler *npcHandler; + +namespace TmwAthena +{ + +NpcHandler::NpcHandler() +{ + static const Uint16 _messages[] = + { + SMSG_NPC_CHOICE, + SMSG_NPC_MESSAGE, + SMSG_NPC_NEXT, + SMSG_NPC_CLOSE, + SMSG_NPC_INT_INPUT, + SMSG_NPC_STR_INPUT, + 0 + }; + handledMessages = _messages; + npcHandler = this; +} + +void NpcHandler::handleMessage(Net::MessageIn &msg) +{ + if (msg.getId() == SMSG_NPC_CHOICE || msg.getId() == SMSG_NPC_MESSAGE) + msg.readInt16(); // length + + int npcId = msg.readInt32(); + NpcDialogs::iterator diag = mNpcDialogs.find(npcId); + NpcDialog *dialog = 0; + + if (diag == mNpcDialogs.end()) + { + // Empty dialogs don't help + if (msg.getId() == SMSG_NPC_CLOSE) + { + closeDialog(npcId); + return; + } + else if (msg.getId() == SMSG_NPC_NEXT) + { + nextDialog(npcId); + return; + } + else + { + dialog = new NpcDialog(npcId); + Wrapper wrap; + wrap.dialog = dialog; + mNpcDialogs[npcId] = wrap; + } + } + else + { + dialog = diag->second.dialog; + } + + switch (msg.getId()) + { + case SMSG_NPC_CHOICE: + if (dialog) + { + dialog->choiceRequest(); + dialog->parseListItems(msg.readString(msg.getLength() - 8)); + } + else + { + msg.readString(msg.getLength() - 8); + } + break; + + case SMSG_NPC_MESSAGE: + if (dialog) + dialog->addText(msg.readString(msg.getLength() - 8)); + else + msg.readString(msg.getLength() - 8); + break; + + case SMSG_NPC_CLOSE: + // Show the close button + if (dialog) + dialog->showCloseButton(); + break; + + case SMSG_NPC_NEXT: + // Show the next button + if (dialog) + dialog->showNextButton(); + break; + + case SMSG_NPC_INT_INPUT: + // Request for an integer + if (dialog) + dialog->integerRequest(0); + break; + + case SMSG_NPC_STR_INPUT: + // Request for a string + if (dialog) + dialog->textRequest(""); + break; + + default: + break; + } + + if (player_node && player_node->getCurrentAction() != Being::SIT) + player_node->setAction(Being::STAND); +} + +void NpcHandler::talk(int npcId) +{ + MessageOut outMsg(CMSG_NPC_TALK); + outMsg.writeInt32(npcId); + outMsg.writeInt8(0); // Unused +} + +void NpcHandler::nextDialog(int npcId) +{ + MessageOut outMsg(CMSG_NPC_NEXT_REQUEST); + outMsg.writeInt32(npcId); +} + +void NpcHandler::closeDialog(int npcId) +{ + MessageOut outMsg(CMSG_NPC_CLOSE); + outMsg.writeInt32(npcId); + + NpcDialogs::iterator it = mNpcDialogs.find(npcId); + if (it != mNpcDialogs.end()) + { + if ((*it).second.dialog) + (*it).second.dialog->close(); + mNpcDialogs.erase(it); + } +} + +void NpcHandler::listInput(int npcId, unsigned char value) +{ + MessageOut outMsg(CMSG_NPC_LIST_CHOICE); + outMsg.writeInt32(npcId); + outMsg.writeInt8(static_cast(value)); +} + +void NpcHandler::integerInput(int npcId, int value) +{ + MessageOut outMsg(CMSG_NPC_INT_RESPONSE); + outMsg.writeInt32(npcId); + outMsg.writeInt32(value); +} + +void NpcHandler::stringInput(int npcId, const std::string &value) +{ + MessageOut outMsg(CMSG_NPC_STR_RESPONSE); + outMsg.writeInt16(value.length() + 9); + outMsg.writeInt32(npcId); + outMsg.writeString(value, value.length()); + outMsg.writeInt8(0); // Prevent problems with string reading +} + +void NpcHandler::sendLetter(int npcId _UNUSED_, + const std::string &recipient _UNUSED_, + const std::string &text _UNUSED_) +{ + // TODO +} + +void NpcHandler::startShopping(int beingId _UNUSED_) +{ + // TODO +} + +void NpcHandler::buy(int beingId) +{ + MessageOut outMsg(CMSG_NPC_BUY_SELL_REQUEST); + outMsg.writeInt32(beingId); + outMsg.writeInt8(0); // Buy +} + +void NpcHandler::sell(int beingId) +{ + MessageOut outMsg(CMSG_NPC_BUY_SELL_REQUEST); + outMsg.writeInt32(beingId); + outMsg.writeInt8(1); // Sell +} + +void NpcHandler::buyItem(int beingId _UNUSED_, int itemId, int amount) +{ + MessageOut outMsg(CMSG_NPC_BUY_REQUEST); + outMsg.writeInt16(8); // One item (length of packet) + outMsg.writeInt16(amount); + outMsg.writeInt16(itemId); +} + +void NpcHandler::sellItem(int beingId _UNUSED_, int itemId, int amount) +{ + MessageOut outMsg(CMSG_NPC_SELL_REQUEST); + outMsg.writeInt16(8); // One item (length of packet) + outMsg.writeInt16(itemId + INVENTORY_OFFSET); + outMsg.writeInt16(amount); +} + +void NpcHandler::endShopping(int beingId _UNUSED_) +{ + // TODO +} + +void NpcHandler::clearDialogs() +{ + mNpcDialogs.clear(); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/npchandler.h b/src/net/tmwa/npchandler.h new file mode 100644 index 000000000..7931a3d8b --- /dev/null +++ b/src/net/tmwa/npchandler.h @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_NPCHANDLER_H +#define NET_TA_NPCHANDLER_H + +#include "net/net.h" +#include "net/npchandler.h" + +#include "net/tmwa/messagehandler.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class NpcDialog; + +namespace TmwAthena +{ + +class NpcHandler : public MessageHandler, public Net::NpcHandler +{ + public: + NpcHandler(); + + void handleMessage(Net::MessageIn &msg); + + void talk(int npcId); + + void nextDialog(int npcId); + + void closeDialog(int npcId); + + void listInput(int npcId, unsigned char value); + + void integerInput(int npcId, int value); + + void stringInput(int npcId, const std::string &value); + + void sendLetter(int npcId, const std::string &recipient, + const std::string &text); + + void startShopping(int beingId); + + void buy(int beingId); + + void sell(int beingId); + + void buyItem(int beingId, int itemId, int amount); + + void sellItem(int beingId, int itemId, int amount); + + void endShopping(int beingId); + + void clearDialogs(); + + private: + typedef struct + { + NpcDialog* dialog; + } Wrapper; + typedef std::map NpcDialogs; + NpcDialogs mNpcDialogs; +}; + +} // namespace TmwAthena + +#endif // NET_TA_NPCHANDLER_H diff --git a/src/net/tmwa/partyhandler.cpp b/src/net/tmwa/partyhandler.cpp new file mode 100644 index 000000000..eecbf0c0e --- /dev/null +++ b/src/net/tmwa/partyhandler.cpp @@ -0,0 +1,547 @@ +/* + * The Mana Client + * Copyright (C) 2008 Lloyd Bryant + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/partyhandler.h" + +#include "actorspritemanager.h" +#include "event.h" +#include "localplayer.h" +#include "log.h" + +#include "gui/socialwindow.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" + +#include "net/tmwa/gui/partytab.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#define PARTY_ID 1 + +extern Net::PartyHandler *partyHandler; + +namespace TmwAthena +{ + +PartyTab *partyTab = 0; +Party *taParty; + +PartyHandler::PartyHandler(): + mShareExp(PARTY_SHARE_UNKNOWN), mShareItems(PARTY_SHARE_UNKNOWN) +{ + static const Uint16 _messages[] = + { + SMSG_PARTY_CREATE, + SMSG_PARTY_INFO, + SMSG_PARTY_INVITE_RESPONSE, + SMSG_PARTY_INVITED, + SMSG_PARTY_SETTINGS, + SMSG_PARTY_MOVE, + SMSG_PARTY_LEAVE, + SMSG_PARTY_UPDATE_HP, + SMSG_PARTY_UPDATE_COORDS, + SMSG_PARTY_MESSAGE, + 0 + }; + handledMessages = _messages; + partyHandler = this; + taParty = Party::getParty(1); +} + +PartyHandler::~PartyHandler() +{ + delete partyTab; + partyTab = 0; +} + +void PartyHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_PARTY_CREATE: + if (msg.readInt8()) + SERVER_NOTICE(_("Could not create party.")) + else + SERVER_NOTICE(_("Party successfully created.")) + break; + case SMSG_PARTY_INFO: + { + if (!taParty) + { + logger->log1("error: party empty in SMSG_PARTY_INFO"); + taParty = Party::getParty(1); + } + if (!player_node) + logger->log1("error: player_node==0 in SMSG_PARTY_INFO"); + + if (taParty) + taParty->clearMembers(); + + int length = msg.readInt16(); + if (taParty) + taParty->setName(msg.readString(24)); + + int count = (length - 28) / 46; + if (player_node && taParty) + { + player_node->setParty(taParty); + player_node->setPartyName(taParty->getName()); + } + + for (int i = 0; i < count; i++) + { + int id = msg.readInt32(); + std::string nick = msg.readString(24); + std::string map = msg.readString(16); + bool leader = msg.readInt8() == 0; + bool online = msg.readInt8() == 0; + + if (taParty) + { + PartyMember *member = taParty->addMember(id, nick); + if (member) + { + member->setLeader(leader); + member->setOnline(online); + member->setMap(map); + } + } + } + + if (taParty) + taParty->sort(); + + if (player_node && taParty) + { + player_node->setParty(taParty); + player_node->setPartyName(taParty->getName()); + } + } + break; + case SMSG_PARTY_INVITE_RESPONSE: + { + if (!partyTab) + break; + + std::string nick = msg.readString(24); + + switch (msg.readInt8()) + { + case 0: + partyTab->chatLog(strprintf( + _("%s is already a member of a party."), + nick.c_str()), BY_SERVER); + break; + case 1: + partyTab->chatLog(strprintf( + _("%s refused your invitation."), + nick.c_str()), BY_SERVER); + break; + case 2: + partyTab->chatLog(strprintf( + _("%s is now a member of your party."), + nick.c_str()), BY_SERVER); + break; + case 3: + partyTab->chatLog(strprintf( + _("%s cant joid your party because party is " + "full."), nick.c_str()), BY_SERVER); + break; + default: + partyTab->chatLog(strprintf( + _("QQQ Unknown invite response for %s."), + nick.c_str()), BY_SERVER); + break; + } + break; + } + case SMSG_PARTY_INVITED: + { + int id = msg.readInt32(); + std::string partyName = msg.readString(24); + std::string nick = ""; + Being *being; + + if (actorSpriteManager) + { + if ((being = actorSpriteManager->findBeing(id))) + { + if (being && being->getType() == Being::PLAYER) + nick = being->getName(); + } + } + + if (socialWindow) + socialWindow->showPartyInvite(partyName, nick); + break; + } + case SMSG_PARTY_SETTINGS: + { + if (!partyTab) + { + if (!chatWindow) + break; + + partyTab = new PartyTab(); + } + + // These seem to indicate the sharing mode for exp and items + short exp = msg.readInt16(); + short item = msg.readInt16(); + + if (!partyTab) + break; + + switch (exp) + { + case PARTY_SHARE: + if (mShareExp == PARTY_SHARE) + break; + mShareExp = PARTY_SHARE; + if (partyTab) + { + partyTab->chatLog( + _("Experience sharing enabled."), BY_SERVER); + } + break; + case PARTY_SHARE_NO: + if (mShareExp == PARTY_SHARE_NO) + break; + mShareExp = PARTY_SHARE_NO; + if (partyTab) + { + partyTab->chatLog( + _("Experience sharing disabled."), BY_SERVER); + } + break; + case PARTY_SHARE_NOT_POSSIBLE: + if (mShareExp == PARTY_SHARE_NOT_POSSIBLE) + break; + mShareExp = PARTY_SHARE_NOT_POSSIBLE; + if (partyTab) + { + partyTab->chatLog( + _("Experience sharing not possible."), + BY_SERVER); + } + break; + default: + logger->log("QQQ Unknown party exp option: %d\n", exp); + break; + } + + switch (item) + { + case PARTY_SHARE: + if (mShareItems == PARTY_SHARE) + break; + mShareItems = PARTY_SHARE; + if (partyTab) + { + partyTab->chatLog( + _("Item sharing enabled."), BY_SERVER); + } + break; + case PARTY_SHARE_NO: + if (mShareItems == PARTY_SHARE_NO) + break; + mShareItems = PARTY_SHARE_NO; + if (partyTab) + { + partyTab->chatLog( + _("Item sharing disabled."), BY_SERVER); + } + break; + case PARTY_SHARE_NOT_POSSIBLE: + if (mShareItems == PARTY_SHARE_NOT_POSSIBLE) + break; + mShareItems = PARTY_SHARE_NOT_POSSIBLE; + if (partyTab) + { + partyTab->chatLog( + _("Item sharing not possible."), BY_SERVER); + } + break; + default: + logger->log("QQQ Unknown party item option: %d\n", + exp); + break; + } + break; + } + case SMSG_PARTY_MOVE: + { + int id = msg.readInt32(); // id + PartyMember *m = 0; + if (taParty) + m = taParty->getMember(id); + if (m) + { + msg.skip(4); + m->setX(msg.readInt16()); // x + m->setY(msg.readInt16()); // y + m->setOnline(msg.readInt8()); // online (if 0) + msg.readString(24); // party + msg.readString(24); // nick + m->setMap(msg.readString(16)); // map + } + else + { + msg.skip(4); + msg.readInt16(); // x + msg.readInt16(); // y + msg.readInt8(); // online (if 0) + msg.readString(24); // party + msg.readString(24); // nick + msg.readString(16); // map + } + } + break; + case SMSG_PARTY_LEAVE: + { + int id = msg.readInt32(); + std::string nick = msg.readString(24); + msg.readInt8(); // fail + if (player_node && id == player_node->getId()) + { + if (taParty) + { + taParty->removeFromMembers(); + taParty->clearMembers(); + } + SERVER_NOTICE(_("You have left the party.")) + delete partyTab; + partyTab = 0; + + if (socialWindow && taParty) + socialWindow->removeTab(taParty); + } + else + { + if (partyTab) + { + partyTab->chatLog(strprintf( + _("%s has left your party."), + nick.c_str()), BY_SERVER); + } + if (actorSpriteManager) + { + Being *b = actorSpriteManager->findBeing(id); + if (b && b->getType() == Being::PLAYER) + b->setParty(0); + } + if (taParty) + taParty->removeMember(id); + } + break; + } + case SMSG_PARTY_UPDATE_HP: + { + int id = msg.readInt32(); + int hp = msg.readInt16(); + int maxhp = msg.readInt16(); + PartyMember *m = 0; + if (taParty) + m = taParty->getMember(id); + if (m) + { + m->setHp(hp); + m->setMaxHp(maxhp); + } + + // The server only sends this when the member is in range, so + // lets make sure they get the party hilight. + if (actorSpriteManager && taParty) + { + if (Being *b = actorSpriteManager->findBeing(id)) + b->setParty(taParty); + } + } + break; + case SMSG_PARTY_UPDATE_COORDS: + { + int id = msg.readInt32(); // id + PartyMember *m = 0; + if (taParty) + m = taParty->getMember(id); + if (m) + { + m->setX(msg.readInt16()); // x + m->setY(msg.readInt16()); // y + } + else + { + msg.readInt16(); // x + msg.readInt16(); // y + } + } + break; + case SMSG_PARTY_MESSAGE: + { + int msgLength = msg.readInt16() - 8; + if (msgLength <= 0) + return; + + int id = msg.readInt32(); + std::string chatMsg = msg.readString(msgLength); + + if (taParty) + { + PartyMember *member = taParty->getMember(id); + if (partyTab) + { + if (member) + { + partyTab->chatLog(member->getName(), chatMsg); + } + else + { + partyTab->chatLog(strprintf( + _("An unknown member tried to say: %s"), + chatMsg.c_str()), BY_SERVER); + } + } + } + } + break; + + default: + break; + } +} + +void PartyHandler::create(const std::string &name) +{ + MessageOut outMsg(CMSG_PARTY_CREATE); + outMsg.writeString(name.substr(0, 23), 24); +} + +void PartyHandler::join(int partyId _UNUSED_) +{ + // TODO? +} + +void PartyHandler::invite(Being *being) +{ + if (being) + { + MessageOut outMsg(CMSG_PARTY_INVITE); + outMsg.writeInt32(being->getId()); + } +} + +void PartyHandler::invite(const std::string &name) +{ + if (!actorSpriteManager) + return; + + Being* being = actorSpriteManager->findBeingByName(name, Being::PLAYER); + if (being) + { + MessageOut outMsg(CMSG_PARTY_INVITE); + outMsg.writeInt32(being->getId()); + } +} + +void PartyHandler::inviteResponse(const std::string &inviter _UNUSED_, + bool accept) +{ + if (player_node) + { + MessageOut outMsg(CMSG_PARTY_INVITED); + outMsg.writeInt32(player_node->getId()); + outMsg.writeInt32(accept ? 1 : 0); + } +} + +void PartyHandler::leave() +{ + MessageOut outMsg(CMSG_PARTY_LEAVE); +} + +void PartyHandler::kick(Being *being) +{ + if (being) + { + MessageOut outMsg(CMSG_PARTY_KICK); + outMsg.writeInt32(being->getId()); + outMsg.writeString("", 24); //Unused + } +} + +void PartyHandler::kick(const std::string &name) +{ + if (!taParty) + return; + + PartyMember *m = taParty->getMember(name); + if (!m) + { + if (partyTab) + { + partyTab->chatLog(strprintf(_("%s is not in your party!"), + name.c_str()), BY_SERVER); + } + return; + } + + MessageOut outMsg(CMSG_PARTY_KICK); + outMsg.writeInt32(m->getID()); + outMsg.writeString(name, 24); //Unused +} + +void PartyHandler::chat(const std::string &text) +{ + MessageOut outMsg(CMSG_PARTY_MESSAGE); + outMsg.writeInt16(text.length() + 4); + outMsg.writeString(text, text.length()); +} + +void PartyHandler::requestPartyMembers() +{ + // Our eAthena doesn't have this message + // Not needed anyways +} + +void PartyHandler::setShareExperience(PartyShare share) +{ + if (share == PARTY_SHARE_NOT_POSSIBLE) + return; + + MessageOut outMsg(CMSG_PARTY_SETTINGS); + outMsg.writeInt16(share); + outMsg.writeInt16(mShareItems); +} + +void PartyHandler::setShareItems(PartyShare share) +{ + if (share == PARTY_SHARE_NOT_POSSIBLE) + return; + + MessageOut outMsg(CMSG_PARTY_SETTINGS); + outMsg.writeInt16(mShareExp); + outMsg.writeInt16(share); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/partyhandler.h b/src/net/tmwa/partyhandler.h new file mode 100644 index 000000000..8a199ded3 --- /dev/null +++ b/src/net/tmwa/partyhandler.h @@ -0,0 +1,85 @@ +/* + * The Mana Client + * Copyright (C) 2008 Lloyd Bryant + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_PARTYHANDLER_H +#define NET_TA_PARTYHANDLER_H + +#include "net/net.h" +#include "net/partyhandler.h" + +#include "net/tmwa/messagehandler.h" + +#include "party.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class PartyHandler : public MessageHandler, public Net::PartyHandler +{ + public: + PartyHandler(); + + ~PartyHandler(); + + void handleMessage(Net::MessageIn &msg); + + void create(const std::string &name = ""); + + void join(int partyId); + + void invite(Being *being); + + void invite(const std::string &name); + + void inviteResponse(const std::string &inviter, bool accept); + + void leave(); + + void kick(Being *being); + + void kick(const std::string &name); + + void chat(const std::string &text); + + void requestPartyMembers(); + + PartyShare getShareExperience() + { return mShareExp; } + + void setShareExperience(PartyShare share); + + PartyShare getShareItems() + { return mShareItems; } + + void setShareItems(PartyShare share); + + private: + PartyShare mShareExp, mShareItems; +}; + +} // namespace TmwAthena + +#endif // NET_TA_PARTYHANDLER_H diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp new file mode 100644 index 000000000..1d0d3e67d --- /dev/null +++ b/src/net/tmwa/playerhandler.cpp @@ -0,0 +1,752 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/playerhandler.h" + +#include "event.h" +#include "game.h" +#include "localplayer.h" +#include "log.h" +#include "party.h" +#include "playerinfo.h" +#include "units.h" + +#include "gui/buy.h" +#include "gui/buysell.h" +#include "gui/gui.h" +#include "gui/npcdialog.h" +#include "gui/okdialog.h" +#include "gui/sell.h" +#include "gui/statuswindow.h" +#include "gui/viewport.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" +#include "net/tmwa/npchandler.h" + +#include "utils/stringutils.h" +#include "utils/gettext.h" + +extern OkDialog *weightNotice; +extern OkDialog *deathNotice; + +// Max. distance we are willing to scroll after a teleport; +// everything beyond will reset the port hard. +static const int MAP_TELEPORT_SCROLL_DISTANCE = 8; + +// TODO Move somewhere else +namespace +{ + /** + * Listener used for handling the overweigth message. + */ + struct WeightListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event _UNUSED_) + { + weightNotice = NULL; + } + } weightListener; + + /** + * Listener used for handling death message. + */ + struct DeathListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event _UNUSED_) + { + Net::getPlayerHandler()->respawn(); + deathNotice = NULL; + + BuyDialog::closeAll(); + BuySellDialog::closeAll(); + NpcDialog::closeAll(); + SellDialog::closeAll(); + + if (viewport) + viewport->closePopupMenu(); + + TmwAthena::NpcHandler *handler = + static_cast(Net::getNpcHandler()); + if (handler) + handler->clearDialogs(); + if (player_node) + player_node->respawn(); + } + } deathListener; + +} // anonymous namespace + +static const char *randomDeathMessage() +{ + static char const *const deadMsg[] = + { + N_("You are dead."), + N_("We regret to inform you that your character was killed in " + "battle."), + N_("You are not that alive anymore."), + N_("The cold hands of the grim reaper are grabbing for your soul."), + N_("Game Over!"), + N_("Insert coin to continue."), + N_("No, kids. Your character did not really die. It... " + "err... went to a better place."), + N_("Your plan of breaking your enemies weapon by " + "bashing it with your throat failed."), + N_("I guess this did not run too well."), + // NetHack reference: + N_("Do you want your possessions identified?"), + // Secret of Mana reference: + N_("Sadly, no trace of you was ever found..."), + // Final Fantasy VI reference: + N_("Annihilated."), + // Earthbound reference: + N_("Looks like you got your head handed to you."), + // Leisure Suit Larry 1 reference: + N_("You screwed up again, dump your body down the tubes " + "and get you another one."), + // Monty Python references (Dead Parrot sketch mostly): + N_("You're not dead yet. You're just resting."), + N_("You are no more."), + N_("You have ceased to be."), + N_("You've expired and gone to meet your maker."), + N_("You're a stiff."), + N_("Bereft of life, you rest in peace."), + N_("If you weren't so animated, you'd be pushing up the daisies."), + N_("Your metabolic processes are now history."), + N_("You're off the twig."), + N_("You've kicked the bucket."), + N_("You've shuffled off your mortal coil, run down the " + "curtain and joined the bleedin' choir invisibile."), + N_("You are an ex-player."), + N_("You're pining for the fjords.") + }; + + const int random = rand() % (sizeof(deadMsg) / sizeof(deadMsg[0])); + return gettext(deadMsg[random]); +} + +extern Net::PlayerHandler *playerHandler; + +namespace TmwAthena +{ + +PlayerHandler::PlayerHandler() +{ + static const Uint16 _messages[] = + { + SMSG_WALK_RESPONSE, + SMSG_PLAYER_WARP, + SMSG_PLAYER_STAT_UPDATE_1, + SMSG_PLAYER_STAT_UPDATE_2, + SMSG_PLAYER_STAT_UPDATE_3, + SMSG_PLAYER_STAT_UPDATE_4, + SMSG_PLAYER_STAT_UPDATE_5, + SMSG_PLAYER_STAT_UPDATE_6, + SMSG_PLAYER_ARROW_MESSAGE, + 0 + }; + handledMessages = _messages; + playerHandler = this; +} + +void PlayerHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_WALK_RESPONSE: + /* + * This client assumes that all walk messages succeed, + * and that the server will send a correction notice + * otherwise. + */ + Uint16 srcX, srcY, dstX, dstY; + msg.readInt32(); //tick + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + if (player_node) + player_node->setRealPos(dstX, dstY); +// if (debugChatTab) +// debugChatTab->chatLog("move resp: " + toString((int)srcX) + "," + toString((int)srcY) + " " +// + toString((int)dstX) + "," + toString((int)dstY)); + + break; + + case SMSG_PLAYER_WARP: + { + std::string mapPath = msg.readString(16); + int x = msg.readInt16(); + int y = msg.readInt16(); + + logger->log("Warping to %s (%d, %d)", mapPath.c_str(), x, y); + + if (!player_node) + logger->log1("SMSG_PLAYER_WARP player_node null"); + + /* + * We must clear the local player's target *before* the call + * to changeMap, as it deletes all beings. + */ + if (player_node) + player_node->stopAttack(); + + Game *game = Game::instance(); + + const std::string ¤tMapName = game->getCurrentMapName(); + bool sameMap = (currentMapName == mapPath); + + // Switch the actual map, deleting the previous one if necessary + mapPath = mapPath.substr(0, mapPath.rfind(".")); + game->changeMap(mapPath); + + float scrollOffsetX = 0.0f; + float scrollOffsetY = 0.0f; + + if (player_node) + { + /* Scroll if neccessary */ + if (!sameMap + || (abs(x - player_node->getTileX()) + > MAP_TELEPORT_SCROLL_DISTANCE) + || (abs(y - player_node->getTileY()) + > MAP_TELEPORT_SCROLL_DISTANCE)) + { + Map *map = game->getCurrentMap(); + if (map) + { + scrollOffsetX = (x - player_node->getTileX()) + * map->getTileWidth(); + scrollOffsetY = (y - player_node->getTileY()) + * map->getTileHeight(); + } + } + + player_node->setAction(Being::STAND); + player_node->setTileCoords(x, y); + player_node->naviageClean(); +// player_node->updateNavigateList(); + } + + logger->log("Adjust scrolling by %d:%d", (int) scrollOffsetX, + (int) scrollOffsetY); + + if (viewport) + viewport->scrollBy(scrollOffsetX, scrollOffsetY); + } + break; + + case SMSG_PLAYER_STAT_UPDATE_1: + { + int type = msg.readInt16(); + int value = msg.readInt32(); + if (!player_node) + return; + + switch (type) + { + case 0x0000: + player_node->setWalkSpeed(Vector(value, value, 0)); + PlayerInfo::setStatBase(WALK_SPEED, value); + PlayerInfo::setStatMod(WALK_SPEED, 0); + break; + case 0x0004: break; // manner + case 0x0005: + PlayerInfo::setAttribute(HP, value); + if (player_node->isInParty() && Party::getParty(1)) + { + PartyMember *m = Party::getParty(1) + ->getMember(player_node->getId()); + if (m) + { + m->setHp(value); + m->setMaxHp(PlayerInfo::getAttribute(MAX_HP)); + } + } + break; + case 0x0006: + PlayerInfo::setAttribute(MAX_HP, value); + + if (player_node->isInParty() && Party::getParty(1)) + { + PartyMember *m = Party::getParty(1)->getMember( + player_node->getId()); + if (m) + { + m->setHp(PlayerInfo::getAttribute(HP)); + m->setMaxHp(value); + } + } + break; + case 0x0007: + PlayerInfo::setAttribute(MP, value); + break; + case 0x0008: + PlayerInfo::setAttribute(MAX_MP, value); + break; + case 0x0009: + PlayerInfo::setAttribute(CHAR_POINTS, value); + break; + case 0x000b: + PlayerInfo::setAttribute(LEVEL, value); + if (player_node) + { + player_node->setLevel(value); + player_node->updateName(); + } + break; + case 0x000c: + PlayerInfo::setAttribute(SKILL_POINTS, value); + break; + case 0x0018: + if (!weightNotice) + { + const int max + = PlayerInfo::getAttribute(MAX_WEIGHT) / 2; + const int total + = PlayerInfo::getAttribute(TOTAL_WEIGHT); + if (value >= max && total < max) + { + weightNotice = new OkDialog(_("Message"), + _("You are carrying more than " + "half your weight. You are " + "unable to regain health.")); + weightNotice->addActionListener( + &weightListener); + } + else if (value < max && total >= max) + { + weightNotice = new OkDialog(_("Message"), + _("You are carrying less than " + "half your weight. You are " + "can regain health.")); + weightNotice->addActionListener( + &weightListener); + } + } + PlayerInfo::setAttribute(TOTAL_WEIGHT, value); + break; + case 0x0019: + PlayerInfo::setAttribute(MAX_WEIGHT, value); + break; + + case 0x0029: + PlayerInfo::setStatBase(ATK, value); + break; + case 0x002a: + PlayerInfo::setStatMod(ATK, value); + break; + + case 0x002b: + PlayerInfo::setStatBase(MATK, value); + break; + case 0x002c: + PlayerInfo::setStatMod(MATK, value); + break; + + case 0x002d: + PlayerInfo::setStatBase(DEF, value); + break; + case 0x002e: + PlayerInfo::setStatMod(DEF, value); + break; + + case 0x002f: + PlayerInfo::setStatBase(MDEF, value); + break; + case 0x0030: + PlayerInfo::setStatMod(MDEF, value); + break; + + case 0x0031: + PlayerInfo::setStatBase(HIT, value); + break; + + case 0x0032: + PlayerInfo::setStatBase(FLEE, value); + break; + case 0x0033: + PlayerInfo::setStatMod(FLEE, value); + break; + + case 0x0034: + PlayerInfo::setStatBase(CRIT, value); + break; + + case 0x0035: + player_node->setAttackSpeed(value); + PlayerInfo::setStatBase(ATTACK_SPEED, value); + PlayerInfo::setStatMod(ATTACK_SPEED, 0); + break; + + case 0x0037: + PlayerInfo::setStatBase(JOB, value); + break; + + case 500: + player_node->setGMLevel(value); + break; + + default: + logger->log("QQQQ PLAYER_STAT_UPDATE_1 " + + toString(type) + "," + toString(value)); + break; + } + + if (PlayerInfo::getAttribute(HP) == 0 && !deathNotice) + { + deathNotice = new OkDialog(_("Message"), + randomDeathMessage(), + false); + deathNotice->addActionListener(&deathListener); + player_node->setAction(Being::DEAD); + } + } + break; + + case SMSG_PLAYER_STAT_UPDATE_2: + { + int type = msg.readInt16(); + switch (type) + { + case 0x0001: + PlayerInfo::setAttribute(EXP, msg.readInt32()); + break; + case 0x0002: + PlayerInfo::setStatExperience(JOB, msg.readInt32(), + PlayerInfo::getStatExperience(JOB).second); + break; + case 0x0014: + { + int oldMoney = PlayerInfo::getAttribute(MONEY); + int newMoney = msg.readInt32(); + if (newMoney > oldMoney) + { + SERVER_NOTICE(strprintf(_("You picked up %s."), + Units::formatCurrency(newMoney - + oldMoney).c_str())) + } + else if (newMoney < oldMoney) + { + SERVER_NOTICE(strprintf(_("You spent %s."), + Units::formatCurrency(oldMoney - + newMoney).c_str())) + } + + PlayerInfo::setAttribute(MONEY, newMoney); + break; + } + case 0x0016: + PlayerInfo::setAttribute(EXP_NEEDED, msg.readInt32()); + break; + case 0x0017: + PlayerInfo::setStatExperience(JOB, + PlayerInfo::getStatExperience(JOB).first, + msg.readInt32()); + break; + default: + logger->log("QQQQ PLAYER_STAT_UPDATE_2 " + toString(type)); + break; + } + break; + } + case SMSG_PLAYER_STAT_UPDATE_3: // Update a base attribute + { + int type = msg.readInt32(); + int base = msg.readInt32(); + int bonus = msg.readInt32(); + + PlayerInfo::setStatBase(type, base, false); + PlayerInfo::setStatMod(type, bonus); + } + break; + + case SMSG_PLAYER_STAT_UPDATE_4: // Attribute increase ack + { + int type = msg.readInt16(); + int ok = msg.readInt8(); + int value = msg.readInt8(); + + if (ok != 1) + { + int oldValue = PlayerInfo::getStatBase(type); + int points = PlayerInfo::getAttribute(CHAR_POINTS); + points += oldValue - value; + PlayerInfo::setAttribute(CHAR_POINTS, points); + SERVER_NOTICE(_("Cannot raise skill!")) + } + + PlayerInfo::setStatBase(type, value); + } + break; + + // Updates stats and status points + case SMSG_PLAYER_STAT_UPDATE_5: + PlayerInfo::setAttribute(CHAR_POINTS, msg.readInt16()); + { + int val = msg.readInt8(); + PlayerInfo::setStatBase(STR, val); + if (statusWindow) + statusWindow->setPointsNeeded(STR, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(AGI, val); + if (statusWindow) + statusWindow->setPointsNeeded(AGI, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(VIT, val); + if (statusWindow) + statusWindow->setPointsNeeded(VIT, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(INT, val); + if (statusWindow) + statusWindow->setPointsNeeded(INT, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(DEX, val); + if (statusWindow) + statusWindow->setPointsNeeded(DEX, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(LUK, val); + if (statusWindow) + statusWindow->setPointsNeeded(LUK, msg.readInt8()); + else + msg.readInt8(); + + PlayerInfo::setStatBase(ATK, msg.readInt16(), false); + PlayerInfo::setStatMod(ATK, msg.readInt16()); + + val = msg.readInt16(); + PlayerInfo::setStatBase(MATK, val, false); + + val = msg.readInt16(); + PlayerInfo::setStatMod(MATK, val); + + PlayerInfo::setStatBase(DEF, msg.readInt16(), false); + PlayerInfo::setStatMod(DEF, msg.readInt16()); + + PlayerInfo::setStatBase(MDEF, msg.readInt16(), false); + PlayerInfo::setStatMod(MDEF, msg.readInt16()); + + PlayerInfo::setStatBase(HIT, msg.readInt16()); + + PlayerInfo::setStatBase(FLEE, msg.readInt16(), false); + PlayerInfo::setStatMod(FLEE, msg.readInt16()); + + PlayerInfo::setStatBase(CRIT, msg.readInt16()); + } + + msg.readInt16(); // manner + break; + + case SMSG_PLAYER_STAT_UPDATE_6: + { + int type = msg.readInt16(); + if (statusWindow) + { + switch (type) + { + case 0x0020: + statusWindow->setPointsNeeded(STR, msg.readInt8()); + break; + case 0x0021: + statusWindow->setPointsNeeded(AGI, msg.readInt8()); + break; + case 0x0022: + statusWindow->setPointsNeeded(VIT, msg.readInt8()); + break; + case 0x0023: + statusWindow->setPointsNeeded(INT, msg.readInt8()); + break; + case 0x0024: + statusWindow->setPointsNeeded(DEX, msg.readInt8()); + break; + case 0x0025: + statusWindow->setPointsNeeded(LUK, msg.readInt8()); + break; + default: + logger->log("QQQQ PLAYER_STAT_UPDATE_6 " + + toString(type)); + break; + } + } + break; + } + case SMSG_PLAYER_ARROW_MESSAGE: + { + int type = msg.readInt16(); + + switch (type) + { + case 0: + { + SERVER_NOTICE(_("Equip arrows first.")) + } + break; + case 3: + // arrows equiped + break; + default: + logger->log("QQQQ 0x013b: Unhandled message %i", type); + break; + } + } + break; + + default: + break; + } +} + +void PlayerHandler::attack(int id, bool keep) +{ + MessageOut outMsg(CMSG_PLAYER_ATTACK); + outMsg.writeInt32(id); + if (keep) + outMsg.writeInt8(7); + else + outMsg.writeInt8(0); +} + +void PlayerHandler::stopAttack() +{ + MessageOut outMsg(CMSG_PLAYER_STOP_ATTACK); +} + +void PlayerHandler::emote(int emoteId) +{ + MessageOut outMsg(CMSG_PLAYER_EMOTE); + outMsg.writeInt8(emoteId); +} + +void PlayerHandler::increaseAttribute(int attr) +{ + if (attr >= STR && attr <= LUK) + { + MessageOut outMsg(CMSG_STAT_UPDATE_REQUEST); + outMsg.writeInt16(attr); + outMsg.writeInt8(1); + } +} + +void PlayerHandler::decreaseAttribute(int attr _UNUSED_) +{ + // Supported by eA? +} + +void PlayerHandler::increaseSkill(int skillId) +{ + if (PlayerInfo::getAttribute(SKILL_POINTS) <= 0) + return; + + MessageOut outMsg(CMSG_SKILL_LEVELUP_REQUEST); + outMsg.writeInt16(skillId); +} + +void PlayerHandler::pickUp(FloorItem *floorItem) +{ + if (!floorItem) + return; + + MessageOut outMsg(CMSG_ITEM_PICKUP); + outMsg.writeInt32(floorItem->getId()); +} + +void PlayerHandler::setDirection(char direction) +{ + MessageOut outMsg(CMSG_PLAYER_CHANGE_DIR); + outMsg.writeInt16(0); + outMsg.writeInt8(direction); +} + +void PlayerHandler::setDestination(int x, int y, int direction) +{ + MessageOut outMsg(CMSG_PLAYER_CHANGE_DEST); + outMsg.writeCoordinates(x, y, direction); +} + +void PlayerHandler::changeAction(Being::Action action) +{ + char type; + switch (action) + { + case Being::SIT: type = 2; break; + case Being::STAND: type = 3; break; + default: return; + } + + MessageOut outMsg(CMSG_PLAYER_CHANGE_ACT); + outMsg.writeInt32(0); + outMsg.writeInt8(type); +} + +void PlayerHandler::respawn() +{ + MessageOut outMsg(CMSG_PLAYER_RESTART); + outMsg.writeInt8(0); +} + +void PlayerHandler::ignorePlayer(const std::string &player _UNUSED_, + bool ignore _UNUSED_) +{ + // TODO +} + +void PlayerHandler::ignoreAll(bool ignore _UNUSED_) +{ + // TODO +} + +bool PlayerHandler::canUseMagic() +{ + return PlayerInfo::getStatEffective(MATK) > 0; +} + +bool PlayerHandler::canCorrectAttributes() +{ + return false; +} + +int PlayerHandler::getJobLocation() +{ + return JOB; +} + +Vector PlayerHandler::getDefaultWalkSpeed() +{ + // Return an normalized speed for any side + // as the offset is calculated elsewhere. + return Vector(150, 150, 0); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/playerhandler.h b/src/net/tmwa/playerhandler.h new file mode 100644 index 000000000..6e1c6dc78 --- /dev/null +++ b/src/net/tmwa/playerhandler.h @@ -0,0 +1,74 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_PLAYERHANDLER_H +#define NET_TA_PLAYERHANDLER_H + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "net/tmwa/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class PlayerHandler : public MessageHandler, public Net::PlayerHandler +{ + public: + PlayerHandler(); + + void handleMessage(Net::MessageIn &msg); + + void attack(int id, bool keep = false); + void stopAttack(); + void emote(int emoteId); + + void increaseAttribute(int attr); + void decreaseAttribute(int attr); + void increaseSkill(int skillId); + + void pickUp(FloorItem *floorItem); + void setDirection(char direction); + void setDestination(int x, int y, int direction = -1); + void changeAction(Being::Action action); + + void respawn(); + + void ignorePlayer(const std::string &player, bool ignore); + void ignoreAll(bool ignore); + + bool canUseMagic(); + bool canCorrectAttributes(); + + int getJobLocation(); + + Vector getDefaultWalkSpeed(); +}; + +} // namespace TmwAthena + +#endif // NET_TA_PLAYERHANDLER_H diff --git a/src/net/tmwa/protocol.h b/src/net/tmwa/protocol.h new file mode 100644 index 000000000..6001fae3b --- /dev/null +++ b/src/net/tmwa/protocol.h @@ -0,0 +1,327 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TA_PROTOCOL_H +#define TA_PROTOCOL_H + +enum +{ + JOB = 0xa, + + STR = 0xd, + AGI, + VIT, + INT, + DEX, + LUK, + + ATK, + DEF, + MATK, + MDEF, + HIT, + FLEE, + CRIT + +// KARMA, +// MANNER +}; + +enum +{ + SPRITE_BASE = 0, + SPRITE_SHOE, + SPRITE_BOTTOMCLOTHES, + SPRITE_TOPCLOTHES, + SPRITE_MISC1, + SPRITE_MISC2, + SPRITE_HAIR, + SPRITE_HAT, + SPRITE_CAPE, + SPRITE_GLOVES, + SPRITE_WEAPON, + SPRITE_SHIELD, + SPRITE_VECTOREND +}; + +static const int INVENTORY_OFFSET = 2; +static const int STORAGE_OFFSET = 1; + +/********************************* + * Packets from server to client * + *********************************/ +#define SMSG_SERVER_VERSION_RESPONSE 0x7531 + +#define SMSG_SERVER_PING 0x007f /**< Contains server tick */ +#define SMSG_CONNECTION_PROBLEM 0x0081 + +#define SMSG_UPDATE_HOST 0x0063 /**< Custom update host packet */ +#define SMSG_LOGIN_DATA 0x0069 +#define SMSG_LOGIN_ERROR 0x006a + +#define SMSG_CHAR_LOGIN 0x006b +#define SMSG_CHAR_LOGIN_ERROR 0x006c +#define SMSG_CHAR_CREATE_SUCCEEDED 0x006d +#define SMSG_CHAR_CREATE_FAILED 0x006e +#define SMSG_CHAR_DELETE_SUCCEEDED 0x006f +#define SMSG_CHAR_DELETE_FAILED 0x0070 +#define SMSG_CHAR_MAP_INFO 0x0071 +#define SMSG_CHAR_PASSWORD_RESPONSE 0x0062 /**< Custom packet reply to password change request */ + +#define SMSG_CHAR_SWITCH_RESPONSE 0x00b3 +#define SMSG_CHANGE_MAP_SERVER 0x0092 + +#define SMSG_MAP_LOGIN_SUCCESS 0x0073 /**< Contains starting location */ +#define SMSG_MAP_QUIT_RESPONSE 0x018b +#define SMSG_PLAYER_UPDATE_1 0x01d8 +#define SMSG_PLAYER_UPDATE_2 0x01d9 +#define SMSG_PLAYER_MOVE 0x01da /**< A nearby player moves */ +#define SMSG_PLAYER_STOP 0x0088 /**< Stop walking, set position */ +#define SMSG_PLAYER_MOVE_TO_ATTACK 0x0139 /**< Move to within attack range */ +#define SMSG_PLAYER_STAT_UPDATE_1 0x00b0 +#define SMSG_PLAYER_STAT_UPDATE_2 0x00b1 +#define SMSG_PLAYER_STAT_UPDATE_3 0x0141 +#define SMSG_PLAYER_STAT_UPDATE_4 0x00bc +#define SMSG_PLAYER_STAT_UPDATE_5 0x00bd +#define SMSG_PLAYER_STAT_UPDATE_6 0x00be +#define SMSG_WHO_ANSWER 0x00c2 +#define SMSG_PLAYER_WARP 0x0091 /**< Warp player to map/location */ +#define SMSG_PLAYER_INVENTORY 0x01ee +#define SMSG_PLAYER_INVENTORY_ADD 0x00a0 +#define SMSG_PLAYER_INVENTORY_REMOVE 0x00af +#define SMSG_PLAYER_INVENTORY_USE 0x01c8 +#define SMSG_PLAYER_EQUIPMENT 0x00a4 +#define SMSG_PLAYER_EQUIP 0x00aa +#define SMSG_PLAYER_UNEQUIP 0x00ac +#define SMSG_PLAYER_ATTACK_RANGE 0x013a +#define SMSG_PLAYER_ARROW_EQUIP 0x013c +#define SMSG_PLAYER_ARROW_MESSAGE 0x013b +#define SMSG_PLAYER_SKILLS 0x010f +#define SMSG_PLAYER_SKILL_UP 0x010e +#define SMSG_SKILL_FAILED 0x0110 +#define SMSG_SKILL_DAMAGE 0x01de +#define SMSG_ITEM_USE_RESPONSE 0x00a8 +#define SMSG_ITEM_VISIBLE 0x009d /**< An item is on the floor */ +#define SMSG_ITEM_DROPPED 0x009e /**< An item is dropped */ +#define SMSG_ITEM_REMOVE 0x00a1 /**< An item disappers */ +#define SMSG_BEING_VISIBLE 0x0078 +#define SMSG_BEING_MOVE 0x007b /**< A nearby monster moves */ +#define SMSG_BEING_SPAWN 0x007c /**< A being spawns nearby */ +#define SMSG_BEING_MOVE2 0x0086 /**< New eAthena being moves */ +#define SMSG_BEING_REMOVE 0x0080 +#define SMSG_BEING_CHANGE_LOOKS 0x00c3 +#define SMSG_BEING_CHANGE_LOOKS2 0x01d7 /**< Same as 0x00c3, but 16 bit ID */ +#define SMSG_BEING_SELFEFFECT 0x019b +#define SMSG_BEING_EMOTION 0x00c0 +#define SMSG_BEING_ACTION 0x008a /**< Attack, sit, stand up, ... */ +#define SMSG_BEING_CHAT 0x008d /**< A being talks */ +#define SMSG_BEING_NAME_RESPONSE 0x0095 /**< Has to be requested */ +#define SMSG_BEING_CHANGE_DIRECTION 0x009c +#define SMSG_BEING_RESURRECT 0x0148 + +#define SMSG_PLAYER_STATUS_CHANGE 0x0119 +#define SMSG_PLAYER_GUILD_PARTY_INFO 0x0195 +#define SMSG_BEING_STATUS_CHANGE 0x0196 + +#define SMSG_NPC_MESSAGE 0x00b4 +#define SMSG_NPC_NEXT 0x00b5 +#define SMSG_NPC_CLOSE 0x00b6 +#define SMSG_NPC_CHOICE 0x00b7 /**< Display a choice */ +#define SMSG_NPC_BUY_SELL_CHOICE 0x00c4 +#define SMSG_NPC_BUY 0x00c6 +#define SMSG_NPC_SELL 0x00c7 +#define SMSG_NPC_BUY_RESPONSE 0x00ca +#define SMSG_NPC_SELL_RESPONSE 0x00cb +#define SMSG_NPC_INT_INPUT 0x0142 /**< Integer input */ +#define SMSG_NPC_STR_INPUT 0x01d4 /**< String input */ +#define SMSG_PLAYER_CHAT 0x008e /**< Player talks */ +#define SMSG_WHISPER 0x0097 /**< Whisper Recieved */ +#define SMSG_WHISPER_RESPONSE 0x0098 +#define SMSG_GM_CHAT 0x009a /**< GM announce */ +#define SMSG_WALK_RESPONSE 0x0087 + +#define SMSG_TRADE_REQUEST 0x00e5 /**< Receiving a request to trade */ +#define SMSG_TRADE_RESPONSE 0x00e7 +#define SMSG_TRADE_ITEM_ADD 0x00e9 +#define SMSG_TRADE_ITEM_ADD_RESPONSE 0x01b1 /**< Not standard eAthena! */ +#define SMSG_TRADE_OK 0x00ec +#define SMSG_TRADE_CANCEL 0x00ee +#define SMSG_TRADE_COMPLETE 0x00f0 + +#define SMSG_PARTY_CREATE 0x00fa +#define SMSG_PARTY_INFO 0x00fb +#define SMSG_PARTY_INVITE_RESPONSE 0x00fd +#define SMSG_PARTY_INVITED 0x00fe +#define SMSG_PARTY_SETTINGS 0x0101 +#define SMSG_PARTY_MOVE 0x0104 +#define SMSG_PARTY_LEAVE 0x0105 +#define SMSG_PARTY_UPDATE_HP 0x0106 +#define SMSG_PARTY_UPDATE_COORDS 0x0107 +#define SMSG_PARTY_MESSAGE 0x0109 + +#define SMSG_PLAYER_STORAGE_ITEMS 0x01f0 /**< Item list for storage */ +#define SMSG_PLAYER_STORAGE_EQUIP 0x00a6 /**< Equipment list for storage */ +#define SMSG_PLAYER_STORAGE_STATUS 0x00f2 /**< Slots used and total slots */ +#define SMSG_PLAYER_STORAGE_ADD 0x00f4 /**< Add item/equip to storage */ +#define SMSG_PLAYER_STORAGE_REMOVE 0x00f6 /**< Remove item/equip from storage */ +#define SMSG_PLAYER_STORAGE_CLOSE 0x00f8 /**< Storage access closed */ + +#define SMSG_ADMIN_KICK_ACK 0x00cd + +#define SMSG_GUILD_CREATE_RESPONSE 0x0167 +#define SMSG_GUILD_POSITION_INFO 0x016c +#define SMSG_GUILD_MEMBER_LOGIN 0x016d +#define SMSG_GUILD_MASTER_OR_MEMBER 0x014e +#define SMSG_GUILD_BASIC_INFO 0x01b6 +#define SMSG_GUILD_ALIANCE_INFO 0x014c +#define SMSG_GUILD_MEMBER_LIST 0x0154 +#define SMSG_GUILD_POS_NAME_LIST 0x0166 +#define SMSG_GUILD_POS_INFO_LIST 0x0160 +#define SMSG_GUILD_POSITION_CHANGED 0x0174 +#define SMSG_GUILD_MEMBER_POS_CHANGE 0x0156 +#define SMSG_GUILD_EMBLEM 0x0152 +#define SMSG_GUILD_SKILL_INFO 0x0162 +#define SMSG_GUILD_NOTICE 0x016f +#define SMSG_GUILD_INVITE 0x016a +#define SMSG_GUILD_INVITE_ACK 0x0169 +#define SMSG_GUILD_LEAVE 0x015a +#define SMSG_GUILD_EXPULSION 0x015c +#define SMSG_GUILD_EXPULSION_LIST 0x0163 +#define SMSG_GUILD_MESSAGE 0x017f +#define SMSG_GUILD_SKILL_UP 0x010e +#define SMSG_GUILD_REQ_ALLIANCE 0x0171 +#define SMSG_GUILD_REQ_ALLIANCE_ACK 0x0173 +#define SMSG_GUILD_DEL_ALLIANCE 0x0184 +#define SMSG_GUILD_OPPOSITION_ACK 0x0181 +#define SMSG_GUILD_BROKEN 0x015e + +#define SMSG_MVP 0x010c + +/********************************** + * Packets from client to server * + **********************************/ +#define CMSG_SERVER_VERSION_REQUEST 0x7530 + +#define CMSG_CHAR_PASSWORD_CHANGE 0x0061 /**< Custom change password packet */ +#define CMSG_CHAR_SERVER_CONNECT 0x0065 +#define CMSG_CHAR_SELECT 0x0066 +#define CMSG_CHAR_CREATE 0x0067 +#define CMSG_CHAR_DELETE 0x0068 + +#define CMSG_MAP_SERVER_CONNECT 0x0072 +#define CMSG_CLIENT_PING 0x007e /**< Send to server with tick */ +#define CMSG_MAP_LOADED 0x007d +#define CMSG_CLIENT_QUIT 0x018A + +#define CMSG_CHAT_MESSAGE 0x008c +#define CMSG_CHAT_WHISPER 0x0096 +#define CMSG_CHAT_ANNOUNCE 0x0099 +#define CMSG_CHAT_WHO 0x00c1 + +#define CMSG_SKILL_LEVELUP_REQUEST 0x0112 +#define CMSG_STAT_UPDATE_REQUEST 0x00bb +#define CMSG_SKILL_USE_BEING 0x0113 +#define CMSG_SKILL_USE_POSITION 0x0116 +// Variant of 0x116 with 80 char string at end (unsure of use) +#define CMSG_SKILL_USE_POSITION_MORE 0x0190 +#define CMSG_SKILL_USE_MAP 0x011b + +#define CMSG_PLAYER_INVENTORY_USE 0x00a7 +#define CMSG_PLAYER_INVENTORY_DROP 0x00a2 +#define CMSG_PLAYER_EQUIP 0x00a9 +#define CMSG_PLAYER_UNEQUIP 0x00ab + +#define CMSG_ITEM_PICKUP 0x009f +#define CMSG_PLAYER_CHANGE_DIR 0x009b +#define CMSG_PLAYER_CHANGE_DEST 0x0085 +#define CMSG_PLAYER_CHANGE_ACT 0x0089 +#define CMSG_PLAYER_RESTART 0x00b2 +#define CMSG_PLAYER_EMOTE 0x00bf +#define CMSG_PLAYER_ATTACK 0x0089 +#define CMSG_PLAYER_STOP_ATTACK 0x0118 +#define CMSG_WHO_REQUEST 0x00c1 + +#define CMSG_NPC_TALK 0x0090 +#define CMSG_NPC_NEXT_REQUEST 0x00b9 +#define CMSG_NPC_CLOSE 0x0146 +#define CMSG_NPC_LIST_CHOICE 0x00b8 +#define CMSG_NPC_INT_RESPONSE 0x0143 +#define CMSG_NPC_STR_RESPONSE 0x01d5 +#define CMSG_NPC_BUY_SELL_REQUEST 0x00c5 +#define CMSG_NPC_BUY_REQUEST 0x00c8 +#define CMSG_NPC_SELL_REQUEST 0x00c9 + +#define CMSG_TRADE_REQUEST 0x00e4 +#define CMSG_TRADE_RESPONSE 0x00e6 +#define CMSG_TRADE_ITEM_ADD_REQUEST 0x00e8 +#define CMSG_TRADE_CANCEL_REQUEST 0x00ed +#define CMSG_TRADE_ADD_COMPLETE 0x00eb +#define CMSG_TRADE_OK 0x00ef + +#define CMSG_PARTY_CREATE 0x00f9 +#define CMSG_PARTY_INVITE 0x00fc +#define CMSG_PARTY_INVITED 0x00ff +#define CMSG_PARTY_LEAVE 0x0100 +#define CMSG_PARTY_SETTINGS 0x0102 +#define CMSG_PARTY_KICK 0x0103 +#define CMSG_PARTY_MESSAGE 0x0108 + +#define CMSG_MOVE_TO_STORAGE 0x00f3 /** Move item to storage */ +#define CSMG_MOVE_FROM_STORAGE 0x00f5 /** Remove item from storage */ +#define CMSG_CLOSE_STORAGE 0x00f7 /** Request storage close */ + +#define CMSG_ADMIN_ANNOUNCE 0x0099 +#define CMSG_ADMIN_LOCAL_ANNOUNCE 0x019C +#define CMSG_ADMIN_HIDE 0x019D +#define CMSG_ADMIN_KICK 0x00CC +#define CMSG_ADMIN_MUTE 0x0149 + +#define CMSG_GUILD_CHECK_MASTER 0x014d +#define CMSG_GUILD_REQUEST_INFO 0x014f +#define CMSG_GUILD_REQUEST_EMBLEM 0x0151 +#define CMSG_GUILD_CHANGE_EMBLEM 0x0153 +#define CMSG_GUILD_CHANGE_MEMBER_POS 0x0155 +#define CMSG_GUILD_LEAVE 0x0159 +#define CMSG_GUILD_EXPULSION 0x015b +#define CMSG_GUILD_BREAK 0x015d +#define CMSG_GUILD_CHANGE_POS_INFO 0x0161 +#define CMSG_GUILD_CREATE 0x0165 +#define CMSG_GUILD_INVITE 0x0168 +#define CMSG_GUILD_INVITE_REPLY 0x016b +#define CMSG_GUILD_CHANGE_NOTICE 0x016e +#define CMSG_GUILD_ALLIANCE_REQUEST 0x0170 +#define CMSG_GUILD_ALLIANCE_REPLY 0x0172 +#define CMSG_GUILD_MESSAGE 0x017e +#define CMSG_GUILD_OPPOSITION 0x0180 +#define CMSG_GUILD_ALLIANCE_DELETE 0x0183 + +#define CMSG_SOLVE_CHAR_NAME 0x0193 +#define SMSG_SOLVE_CHAR_NAME 0x0194 +#define CMSG_CLIENT_DISCONNECT 0x7532 +#define SMSG_SKILL_CASTING 0x013e +#define SMSG_SKILL_CAST_CANCEL 0x01b9 +#define SMSG_SKILL_NO_DAMAGE 0x011a + +#define SMSG_BEING_IP_RESPONSE 0x020c +#define SMSG_PVP_MAP_MODE 0x0199 +#define SMSG_PVP_SET 0x019a + +#endif diff --git a/src/net/tmwa/specialhandler.cpp b/src/net/tmwa/specialhandler.cpp new file mode 100644 index 000000000..d502cc85f --- /dev/null +++ b/src/net/tmwa/specialhandler.cpp @@ -0,0 +1,273 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/specialhandler.h" + +#include "log.h" +#include "playerinfo.h" + +#include "gui/skilldialog.h" + +#include "gui/widgets/chattab.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" + +/** job dependend identifiers (?) */ +#define SKILL_BASIC 0x0001 +#define SKILL_WARP 0x001b +#define SKILL_STEAL 0x0032 +#define SKILL_ENVENOM 0x0034 + +/** basic skills identifiers */ +#define BSKILL_TRADE 0x0000 +#define BSKILL_EMOTE 0x0001 +#define BSKILL_SIT 0x0002 +#define BSKILL_CREATECHAT 0x0003 +#define BSKILL_JOINPARTY 0x0004 +#define BSKILL_SHOUT 0x0005 +#define BSKILL_PK 0x0006 // ?? +#define BSKILL_SETALLIGN 0x0007 // ?? + +/** reasons why action failed */ +#define RFAIL_SKILLDEP 0x00 +#define RFAIL_INSUFSP 0x01 +#define RFAIL_INSUFHP 0x02 +#define RFAIL_NOMEMO 0x03 +#define RFAIL_SKILLDELAY 0x04 +#define RFAIL_ZENY 0x05 +#define RFAIL_WEAPON 0x06 +#define RFAIL_REDGEM 0x07 +#define RFAIL_BLUEGEM 0x08 +#define RFAIL_OVERWEIGHT 0x09 +#define RFAIL_GENERIC 0x0a + +/** should always be zero if failed */ +#define SKILL_FAILED 0x00 + +extern Net::SpecialHandler *specialHandler; + +namespace TmwAthena +{ + +SpecialHandler::SpecialHandler() +{ + static const Uint16 _messages[] = + { + SMSG_PLAYER_SKILLS, + SMSG_SKILL_FAILED, + SMSG_PLAYER_SKILL_UP, + 0 + }; + handledMessages = _messages; + specialHandler = this; +} + +void SpecialHandler::handleMessage(Net::MessageIn &msg) +{ + int skillCount; + int skillId; + + switch (msg.getId()) + { + case SMSG_PLAYER_SKILLS: + { + msg.readInt16(); // length + skillCount = (msg.getLength() - 4) / 37; + + for (int k = 0; k < skillCount; k++) + { + skillId = msg.readInt16(); + msg.readInt16(); // target type + msg.skip(2); // unused + int level = msg.readInt16(); + msg.readInt16(); // sp + msg.readInt16(); // range + msg.skip(24); // unused + int up = msg.readInt8(); + + PlayerInfo::setStatBase(skillId, level); + if (skillDialog) + skillDialog->setModifiable(skillId, up); + } + break; + } + case SMSG_PLAYER_SKILL_UP: + { + skillId = msg.readInt16(); + int level = msg.readInt16(); + msg.readInt16(); // sp + msg.readInt16(); // range + int up = msg.readInt8(); + + PlayerInfo::setStatBase(skillId, level); + if (skillDialog) + skillDialog->setModifiable(skillId, up); + } + break; + + case SMSG_SKILL_FAILED: + { + // Action failed (ex. sit because you have not reached the + // right level) + skillId = msg.readInt16(); + short bskill = msg.readInt16(); + msg.readInt16(); // btype + char success = msg.readInt8(); + char reason = msg.readInt8(); + if (success != SKILL_FAILED && bskill == BSKILL_EMOTE) + logger->log("Action: %d/%d", bskill, success); + + std::string msg; + if (success == SKILL_FAILED && skillId == SKILL_BASIC) + { + switch (bskill) + { + case BSKILL_TRADE: + msg = _("Trade failed!"); + break; + case BSKILL_EMOTE: + msg = _("Emote failed!"); + break; + case BSKILL_SIT: + msg = _("Sit failed!"); + break; + case BSKILL_CREATECHAT: + msg = _("Chat creating failed!"); + break; + case BSKILL_JOINPARTY: + msg = _("Could not join party!"); + break; + case BSKILL_SHOUT: + msg = _("Cannot shout!"); + break; + default: + logger->log("QQQ SMSG_SKILL_FAILED: bskill " + + toString(bskill)); + break; + } + + msg += " "; + + switch (reason) + { + case RFAIL_SKILLDEP: + msg += _("You have not yet reached a high enough " + "lvl!"); + break; + case RFAIL_INSUFHP: + msg += _("Insufficient HP!"); + break; + case RFAIL_INSUFSP: + msg += _("Insufficient SP!"); + break; + case RFAIL_NOMEMO: + msg += _("You have no memos!"); + break; + case RFAIL_SKILLDELAY: + msg += _("You cannot do that right now!"); + break; + case RFAIL_ZENY: + msg += _("Seems you need more money... ;-)"); + break; + case RFAIL_WEAPON: + msg += _("You cannot use this skill with that " + "kind of weapon!"); + break; + case RFAIL_REDGEM: + msg += _("You need another red gem!"); + break; + case RFAIL_BLUEGEM: + msg += _("You need another blue gem!"); + break; + case RFAIL_OVERWEIGHT: + msg += _("You're carrying to much to do this!"); + break; + default: + msg += _("Huh? What's that?"); + logger->log("QQQ SMSG_SKILL_FAILED: reason " + + toString(reason)); + break; + } + } + else + { + switch (skillId) + { + case SKILL_WARP : + msg = _("Warp failed..."); + break; + case SKILL_STEAL : + msg = _("Could not steal anything..."); + break; + case SKILL_ENVENOM : + msg = _("Poison had no effect..."); + break; + default: + logger->log("QQQ SMSG_SKILL_FAILED: skillId " + + toString(skillId)); + break; + } + } + + if (localChatTab) + localChatTab->chatLog(msg); + } + break; + default: + break; + } +} + +void SpecialHandler::use(int id _UNUSED_) +{ + // TODO +} + +void SpecialHandler::use(int id, int level, int beingId) +{ + MessageOut outMsg(CMSG_SKILL_USE_BEING); + outMsg.writeInt16(level); + outMsg.writeInt16(id); + outMsg.writeInt16(beingId); +} + +void SpecialHandler::use(int id, int level, int x, int y) +{ + MessageOut outMsg(CMSG_SKILL_USE_POSITION); + outMsg.writeInt16(level); + outMsg.writeInt16(id); + outMsg.writeInt16(x); + outMsg.writeInt16(y); +} + +void SpecialHandler::use(int id, const std::string &map) +{ + MessageOut outMsg(CMSG_SKILL_USE_MAP); + outMsg.writeInt16(id); + outMsg.writeString(map, 16); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/specialhandler.h b/src/net/tmwa/specialhandler.h new file mode 100644 index 000000000..e4e65c607 --- /dev/null +++ b/src/net/tmwa/specialhandler.h @@ -0,0 +1,57 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_SKILLHANDLER_H +#define NET_TA_SKILLHANDLER_H + +#include "net/net.h" +#include "net/specialhandler.h" + +#include "net/tmwa/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class SpecialHandler : public MessageHandler, public Net::SpecialHandler +{ + public: + SpecialHandler(); + + void handleMessage(Net::MessageIn &msg); + + void use(int id); + + void use(int id, int level, int beingId); + + void use(int id, int level, int x, int y); + + void use(int id, const std::string &map); +}; + +} // namespace TmwAthena + +#endif // NET_TA_SKILLHANDLER_H diff --git a/src/net/tmwa/token.h b/src/net/tmwa/token.h new file mode 100644 index 000000000..3e781cd89 --- /dev/null +++ b/src/net/tmwa/token.h @@ -0,0 +1,43 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "being.h" + +#ifndef NET_TA_TOKEN_H +#define NET_TA_TOKEN_H + +struct Token +{ + int account_ID; + int session_ID1; + int session_ID2; + Gender sex; + + void clear() + { + account_ID = 0; + session_ID1 = 0; + session_ID2 = 0; + sex = GENDER_UNSPECIFIED; + } +}; + +#endif // NET_TA_TOKEN_H diff --git a/src/net/tmwa/tradehandler.cpp b/src/net/tmwa/tradehandler.cpp new file mode 100644 index 000000000..6c48d5e6c --- /dev/null +++ b/src/net/tmwa/tradehandler.cpp @@ -0,0 +1,347 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "net/tmwa/tradehandler.h" + +#include "event.h" +#include "inventory.h" +#include "item.h" +#include "localplayer.h" +#include "log.h" +#include "playerinfo.h" +#include "playerrelations.h" + +#include "gui/confirmdialog.h" +#include "gui/trade.h" + +#include "net/inventoryhandler.h" +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +extern std::string tradePartnerName; +ConfirmDialog *confirmDlg; + +/** + * Listener for request trade dialogs + */ +namespace +{ + struct RequestTradeListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event) + { + confirmDlg = 0; + if (event.getId() == "ignore") + player_relations.ignoreTrade(tradePartnerName); + Net::getTradeHandler()->respond(event.getId() == "yes"); + } + } listener; +} + +extern Net::TradeHandler *tradeHandler; + +namespace TmwAthena +{ + +TradeHandler::TradeHandler() +{ + static const Uint16 _messages[] = + { + SMSG_TRADE_REQUEST, + SMSG_TRADE_RESPONSE, + SMSG_TRADE_ITEM_ADD, + SMSG_TRADE_ITEM_ADD_RESPONSE, + SMSG_TRADE_OK, + SMSG_TRADE_CANCEL, + SMSG_TRADE_COMPLETE, + 0 + }; + handledMessages = _messages; + tradeHandler = this; + confirmDlg = 0; +} + + +void TradeHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_TRADE_REQUEST: + { + // If a trade window or request window is already open, send a + // trade cancel to any other trade request. + // + // Note that it would be nice if the server would prevent this + // situation, and that the requesting player would get a + // special message about the player being occupied. + std::string tradePartnerNameTemp = msg.readString(24); + + if (player_relations.hasPermission(tradePartnerName, + PlayerRelation::TRADE)) + { + if (PlayerInfo::isTrading() || confirmDlg) + { + Net::getTradeHandler()->respond(false); + break; + } + + tradePartnerName = tradePartnerNameTemp; + PlayerInfo::setTrading(true); + if (tradeWindow) + { + if (tradePartnerName.empty() + || tradeWindow->getAutoTradeNick() + != tradePartnerName) + { + tradeWindow->clear(); + confirmDlg = new ConfirmDialog( + _("Request for Trade"), + strprintf(_("%s wants to trade with you, do" + " you accept?"), tradePartnerName.c_str()), + true); + confirmDlg->addActionListener(&listener); + } + else + { + Net::getTradeHandler()->respond(true); + } + } + } + else + { + Net::getTradeHandler()->respond(false); + break; + } + } + break; + + case SMSG_TRADE_RESPONSE: + switch (msg.readInt8()) + { + case 0: // Too far away + SERVER_NOTICE(_("Trading isn't possible. Trade " + "partner is too far away.")) + break; + case 1: // Character doesn't exist + SERVER_NOTICE(_("Trading isn't possible. Character " + "doesn't exist.")) + break; + case 2: // Invite request check failed... + SERVER_NOTICE(_("Trade cancelled due to an unknown " + "reason.")) + break; + case 3: // Trade accepted + if (tradeWindow) + { + tradeWindow->reset(); + tradeWindow->setCaption(strprintf( + _("Trade: You and %s"), + tradePartnerName.c_str())); + tradeWindow->initTrade(tradePartnerName); + tradeWindow->setVisible(true); + } + break; + case 4: // Trade cancelled + if (player_relations.hasPermission(tradePartnerName, + PlayerRelation::SPEECH_LOG)) + { + SERVER_NOTICE(strprintf(_("Trade with %s cancelled."), + tradePartnerName.c_str())) + } + // otherwise ignore silently + + if (tradeWindow) + { + tradeWindow->setVisible(false); +// tradeWindow->clear(); + } + PlayerInfo::setTrading(false); + break; + default: // Shouldn't happen as well, but to be sure + SERVER_NOTICE(_("Unhandled trade cancel packet.")) + if (tradeWindow) + tradeWindow->clear(); + break; + } + break; + + case SMSG_TRADE_ITEM_ADD: + { + int amount = msg.readInt32(); + int type = msg.readInt16(); + msg.readInt8(); // identified flag + msg.readInt8(); // attribute + msg.readInt8(); // refine + msg.skip(8); // card (4 shorts) + + // TODO: handle also identified, etc + if (tradeWindow) + { + if (type == 0) + tradeWindow->setMoney(amount); + else + tradeWindow->addItem(type, false, amount, false); + } + } + break; + + case SMSG_TRADE_ITEM_ADD_RESPONSE: + // Trade: New Item add response (was 0x00ea, now 01b1) + { + const int index = msg.readInt16() - INVENTORY_OFFSET; + Item *item = PlayerInfo::getInventory()->getItem(index); + if (!item) + { + if (tradeWindow) + tradeWindow->receivedOk(true); + return; + } + int quantity = msg.readInt16(); + + int res = msg.readInt8(); + switch (res) + { + case 0: + // Successfully added item + if (item->isEquipment() && item->isEquipped()) + Net::getInventoryHandler()->unequipItem(item); + + if (tradeWindow) + { + tradeWindow->addItem(item->getId(), true, quantity, + item->isEquipment()); + } + item->increaseQuantity(-quantity); + break; + case 1: + // Add item failed - player overweighted + SERVER_NOTICE(_("Failed adding item. Trade " + "partner is over weighted.")) + break; + case 2: + // Add item failed - player has no free slot + SERVER_NOTICE(_("Failed adding item. Trade " + "partner has no free slot.")) + break; + default: + SERVER_NOTICE(_("Failed adding item for " + "unknown reason.")) + logger->log("QQQ SMSG_TRADE_ITEM_ADD_RESPONSE: " + + toString(res)); + break; + } + } + break; + + case SMSG_TRADE_OK: + // 0 means ok from myself, 1 means ok from other; + if (tradeWindow) + tradeWindow->receivedOk(msg.readInt8() == 0); + else + msg.readInt8(); + break; + + case SMSG_TRADE_CANCEL: + SERVER_NOTICE(_("Trade canceled.")) + if (tradeWindow) + { + tradeWindow->setVisible(false); + tradeWindow->reset(); + } + PlayerInfo::setTrading(false); + break; + + case SMSG_TRADE_COMPLETE: + SERVER_NOTICE(_("Trade completed.")) + if (tradeWindow) + { + tradeWindow->setVisible(false); + tradeWindow->reset(); + } + PlayerInfo::setTrading(false); + break; + + default: + break; + } +} + +void TradeHandler::request(Being *being) +{ + if (!being) + return; + + MessageOut outMsg(CMSG_TRADE_REQUEST); + outMsg.writeInt32(being->getId()); +} + +void TradeHandler::respond(bool accept) +{ + if (!accept) + PlayerInfo::setTrading(false); + + MessageOut outMsg(CMSG_TRADE_RESPONSE); + outMsg.writeInt8(accept ? 3 : 4); +} + +void TradeHandler::addItem(Item *item, int amount) +{ + if (!item) + return; + + MessageOut outMsg(CMSG_TRADE_ITEM_ADD_REQUEST); + outMsg.writeInt16(item->getInvIndex() + INVENTORY_OFFSET); + outMsg.writeInt32(amount); +} + +void TradeHandler::removeItem(int slotNum _UNUSED_, int amount _UNUSED_) +{ + // TODO +} + +void TradeHandler::setMoney(int amount) +{ + MessageOut outMsg(CMSG_TRADE_ITEM_ADD_REQUEST); + outMsg.writeInt16(0); + outMsg.writeInt32(amount); +} + +void TradeHandler::confirm() +{ + MessageOut outMsg(CMSG_TRADE_ADD_COMPLETE); +} + +void TradeHandler::finish() +{ + MessageOut outMsg(CMSG_TRADE_OK); +} + +void TradeHandler::cancel() +{ + MessageOut outMsg(CMSG_TRADE_CANCEL_REQUEST); +} + +} // namespace TmwAthena diff --git a/src/net/tmwa/tradehandler.h b/src/net/tmwa/tradehandler.h new file mode 100644 index 000000000..39364e52d --- /dev/null +++ b/src/net/tmwa/tradehandler.h @@ -0,0 +1,65 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NET_TA_TRADEHANDLER_H +#define NET_TA_TRADEHANDLER_H + +#include "net/net.h" +#include "net/tradehandler.h" + +#include "net/tmwa/messagehandler.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace TmwAthena +{ + +class TradeHandler : public MessageHandler, public Net::TradeHandler +{ + public: + TradeHandler(); + + void handleMessage(Net::MessageIn &msg); + + void request(Being *being); + + void respond(bool accept); + + void addItem(Item *item, int amount); + + void removeItem(int slotNum, int amount); + + void setMoney(int amount); + + void confirm(); + + void finish(); + + void cancel(); +}; + +} // namespace TmwAthena + +#endif // NET_TA_TRADEHANDLER_H diff --git a/src/net/tradehandler.h b/src/net/tradehandler.h new file mode 100644 index 000000000..d433cacbf --- /dev/null +++ b/src/net/tradehandler.h @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRADEHANDLER_H +#define TRADEHANDLER_H + +#include "being.h" + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +namespace Net +{ +class TradeHandler +{ + public: + virtual void request(Being *being _UNUSED_) + { } +// virtual ~TradeHandler() {} + + virtual void respond(bool accept _UNUSED_) + { } + + virtual void addItem(Item *item _UNUSED_, int amount _UNUSED_) + { } + + virtual void removeItem(int slotNum _UNUSED_, int amount _UNUSED_) + { } + + virtual void setMoney(int amount _UNUSED_) + { } + + virtual void confirm() + { } + + virtual void finish() + { } + + virtual void cancel() + { } +}; +} + +#endif // TRADEHANDLER_H diff --git a/src/net/worldinfo.h b/src/net/worldinfo.h new file mode 100644 index 000000000..a92a5735e --- /dev/null +++ b/src/net/worldinfo.h @@ -0,0 +1,39 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WORLD_INFO_H +#define WORLD_INFO_H + +#include +#include + +struct WorldInfo +{ + int address; + std::string name; + short port; + short online_users; + std::string updateHost; +}; + +typedef std::vector Worlds; + +#endif // WORLD_INFO_H diff --git a/src/opengl1graphics.cpp b/src/opengl1graphics.cpp new file mode 100644 index 000000000..acd4c9d13 --- /dev/null +++ b/src/opengl1graphics.cpp @@ -0,0 +1,603 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "log.h" +#include "opengl1graphics.h" + +#include "resources/image.h" + +#ifdef USE_OPENGL + +#ifdef __APPLE__ +#include +#endif + +#include + +#ifndef GL_TEXTURE_RECTANGLE_ARB +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +GLuint OpenGL1Graphics::mLastImage = 0; + +OpenGL1Graphics::OpenGL1Graphics(): + mAlpha(false), mTexture(false), mColorAlpha(false), + mSync(false) +{ +} + +OpenGL1Graphics::~OpenGL1Graphics() +{ +} + +void OpenGL1Graphics::setSync(bool sync) +{ + mSync = sync; +} + +bool OpenGL1Graphics::setVideoMode(int w, int h, int bpp, + bool fs, bool hwaccel) +{ + logger->log("Setting video mode %dx%d %s", + w, h, fs ? "fullscreen" : "windowed"); + + int displayFlags = SDL_ANYFORMAT | SDL_OPENGL; + + mWidth = w; + mHeight = h; + mBpp = bpp; + mFullscreen = fs; + mHWAccel = hwaccel; + + if (fs) + displayFlags |= SDL_FULLSCREEN; + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + if (!(mTarget = SDL_SetVideoMode(w, h, bpp, displayFlags))) + return false; + +#ifdef __APPLE__ + if (mSync) + { + const GLint VBL = 1; + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &VBL); + } +#endif + + // Setup OpenGL + glViewport(0, 0, w, h); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + int gotDoubleBuffer; + SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &gotDoubleBuffer); + logger->log("Using OpenGL %s double buffering.", + (gotDoubleBuffer ? "with" : "without")); + + char const *glExtensions = (char const *)glGetString(GL_EXTENSIONS); + GLint texSize; + bool rectTex = strstr(glExtensions, "GL_ARB_texture_rectangle"); + if (rectTex) + { + Image::mTextureType = GL_TEXTURE_RECTANGLE_ARB; + glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB, &texSize); + } + else + { + Image::mTextureType = GL_TEXTURE_2D; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize); + } + Image::mTextureSize = texSize; + logger->log("OpenGL texture size: %d pixels%s", Image::mTextureSize, + rectTex ? " (rectangle textures)" : ""); + + return true; +} + +static inline void drawQuad(Image *image, + int srcX, int srcY, int dstX, int dstY, + int width, int height) +{ + if (image->getTextureType() == GL_TEXTURE_2D) + { + // Find OpenGL normalized texture coordinates. + float texX1 = srcX / (float) image->getTextureWidth(); + float texY1 = srcY / (float) image->getTextureHeight(); + float texX2 = (srcX + width) / (float) image->getTextureWidth(); + float texY2 = (srcY + height) / (float) image->getTextureHeight(); + + glTexCoord2f(texX1, texY1); + glVertex2i(dstX, dstY); + glTexCoord2f(texX2, texY1); + glVertex2i(dstX + width, dstY); + glTexCoord2f(texX2, texY2); + glVertex2i(dstX + width, dstY + height); + glTexCoord2f(texX1, texY2); + glVertex2i(dstX, dstY + height); + } + else + { + glTexCoord2i(srcX, srcY); + glVertex2i(dstX, dstY); + glTexCoord2i(srcX + width, srcY); + glVertex2i(dstX + width, dstY); + glTexCoord2i(srcX + width, srcY + height); + glVertex2i(dstX + width, dstY + height); + glTexCoord2i(srcX, srcY + height); + glVertex2i(dstX, dstY + height); + } +} + +static inline void drawRescaledQuad(Image *image, int srcX, int srcY, + int dstX, int dstY, int width, int height, + int desiredWidth, int desiredHeight) +{ + if (image->getTextureType() == GL_TEXTURE_2D) + { + // Find OpenGL normalized texture coordinates. + float texX1 = srcX / (float) image->getTextureWidth(); + float texY1 = srcY / (float) image->getTextureHeight(); + float texX2 = (srcX + width) / (float) image->getTextureWidth(); + float texY2 = (srcY + height) / (float) image->getTextureHeight(); + + glTexCoord2f(texX1, texY1); + glVertex2i(dstX, dstY); + glTexCoord2f(texX2, texY1); + glVertex2i(dstX + desiredWidth, dstY); + glTexCoord2f(texX2, texY2); + glVertex2i(dstX + desiredWidth, dstY + desiredHeight); + glTexCoord2f(texX1, texY2); + glVertex2i(dstX, dstY + desiredHeight); + } + else + { + glTexCoord2i(srcX, srcY); + glVertex2i(dstX, dstY); + glTexCoord2i(srcX + width, srcY); + glVertex2i(dstX + desiredWidth, dstY); + glTexCoord2i(srcX + width, srcY + height); + glVertex2i(dstX + desiredWidth, dstY + desiredHeight); + glTexCoord2i(srcX, srcY + height); + glVertex2i(dstX, dstY + desiredHeight); + } +} + + +bool OpenGL1Graphics::drawImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, bool useColor) +{ + if (!image) + return false; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + if (!useColor) + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + // Draw a textured quad. + glBegin(GL_QUADS); + drawQuad(image, srcX, srcY, dstX, dstY, width, height); + glEnd(); + + if (!useColor) + { + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); + } + + return true; +} + +bool OpenGL1Graphics::drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor) +{ + return drawRescaledImage(image, srcX, srcY, + dstX, dstY, + width, height, + desiredWidth, desiredHeight, + useColor, true); +} + +bool OpenGL1Graphics::drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor, bool smooth) +{ + if (!image) + return false; + + // Just draw the image normally when no resizing is necessary, + if (width == desiredWidth && height == desiredHeight) + { + return drawImage(image, srcX, srcY, dstX, dstY, + width, height, useColor); + } + + // When the desired image is smaller than the current one, + // disable smooth effect. + if (width > desiredWidth && height > desiredHeight) + smooth = false; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + if (!useColor) + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + // Draw a textured quad. + glBegin(GL_QUADS); + drawRescaledQuad(image, srcX, srcY, dstX, dstY, width, height, + desiredWidth, desiredHeight); + + if (smooth) // A basic smooth effect... + { + glColor4f(1.0f, 1.0f, 1.0f, 0.2f); + drawRescaledQuad(image, srcX, srcY, dstX - 1, dstY - 1, width, height, + desiredWidth + 1, desiredHeight + 1); + drawRescaledQuad(image, srcX, srcY, dstX + 1, dstY + 1, width, height, + desiredWidth - 1, desiredHeight - 1); + + drawRescaledQuad(image, srcX, srcY, dstX + 1, dstY, width, height, + desiredWidth - 1, desiredHeight); + drawRescaledQuad(image, srcX, srcY, dstX, dstY + 1, width, height, + desiredWidth, desiredHeight - 1); + } + + glEnd(); + + if (!useColor) + { + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); + } + + return true; +} + +/* Optimising the functions that Graphics::drawImagePattern would call, + * so that glBegin...glEnd are outside the main loop. */ +void OpenGL1Graphics::drawImagePattern(Image *image, int x, int y, + int w, int h) +{ + if (!image) + return; + + const int srcX = image->mBounds.x; + const int srcY = image->mBounds.y; + + const int iw = image->getWidth(); + const int ih = image->getHeight(); + if (iw == 0 || ih == 0) + return; + + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + // Draw a set of textured rectangles + glBegin(GL_QUADS); + + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y + py; + for (int px = 0; px < w; px += iw) + { + int width = (px + iw >= w) ? w - px : iw; + int dstX = x + px; + + drawQuad(image, srcX, srcY, dstX, dstY, width, height); + } + } + + glEnd(); + + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); +} + +void OpenGL1Graphics::drawRescaledImagePattern(Image *image, int x, int y, + int w, int h, + int scaledWidth, + int scaledHeight) +{ + if (!image) + return; + + const int srcX = image->mBounds.x; + const int srcY = image->mBounds.y; + + const int iw = scaledWidth; + const int ih = scaledHeight; + if (iw == 0 || ih == 0) + return; + + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + // Draw a set of textured rectangles + glBegin(GL_QUADS); + + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y + py; + for (int px = 0; px < w; px += iw) + { + int width = (px + iw >= w) ? w - px : iw; + int dstX = x + px; + + drawRescaledQuad(image, srcX, srcY, dstX, dstY, + width, height, scaledWidth, scaledHeight); + } + } + + glEnd(); + + glColor4ub(mColor.r, mColor.g, mColor.b, mColor.a); +} + +void OpenGL1Graphics::updateScreen() +{ + glFlush(); + glFinish(); + SDL_GL_SwapBuffers(); +} + +void OpenGL1Graphics::_beginDraw() +{ + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho(0.0, (double)mTarget->w, (double)mTarget->h, 0.0, -1.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glEnable(GL_SCISSOR_TEST); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + pushClipArea(gcn::Rectangle(0, 0, mTarget->w, mTarget->h)); +} + +void OpenGL1Graphics::_endDraw() +{ +} + +SDL_Surface* OpenGL1Graphics::getScreenshot() +{ + int h = mTarget->h; + int w = mTarget->w; + + SDL_Surface *screenshot = SDL_CreateRGBSurface( + SDL_SWSURFACE, + w, h, 24, + 0xff0000, 0x00ff00, 0x0000ff, 0x000000); + + if (SDL_MUSTLOCK(screenshot)) + SDL_LockSurface(screenshot); + + // Grap the pixel buffer and write it to the SDL surface + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, screenshot->pixels); + + // Flip the screenshot, as OpenGL has 0,0 in bottom left + unsigned int lineSize = 3 * w; + GLubyte* buf = (GLubyte*)malloc(lineSize); + + for (int i = 0; i < (h / 2); i++) + { + GLubyte *top = (GLubyte*)screenshot->pixels + lineSize * i; + GLubyte *bot = (GLubyte*)screenshot->pixels + lineSize * (h - 1 - i); + + memcpy(buf, top, lineSize); + memcpy(top, bot, lineSize); + memcpy(bot, buf, lineSize); + } + + free(buf); + + if (SDL_MUSTLOCK(screenshot)) + SDL_UnlockSurface(screenshot); + + return screenshot; +} + +bool OpenGL1Graphics::pushClipArea(gcn::Rectangle area) +{ + int transX = 0; + int transY = 0; + + if (!mClipStack.empty()) + { + transX = -mClipStack.top().xOffset; + transY = -mClipStack.top().yOffset; + } + + bool result = gcn::Graphics::pushClipArea(area); + + transX += mClipStack.top().xOffset; + transY += mClipStack.top().yOffset; + + glPushMatrix(); + glTranslatef(static_cast(transX), + static_cast(transY), 0); + glScissor(mClipStack.top().x, + mTarget->h - mClipStack.top().y - mClipStack.top().height, + mClipStack.top().width, + mClipStack.top().height); + + return result; +} + +void OpenGL1Graphics::popClipArea() +{ + gcn::Graphics::popClipArea(); + + if (mClipStack.empty()) + return; + + glPopMatrix(); + glScissor(mClipStack.top().x, + mTarget->h - mClipStack.top().y - mClipStack.top().height, + mClipStack.top().width, + mClipStack.top().height); +} + +void OpenGL1Graphics::setColor(const gcn::Color& color) +{ + mColor = color; + glColor4ub(static_cast(color.r), + static_cast(color.g), + static_cast(color.b), + static_cast(color.a)); + + mColorAlpha = (color.a != 255); +} + +void OpenGL1Graphics::drawPoint(int x, int y) +{ + setTexturingAndBlending(false); + + glBegin(GL_POINTS); + glVertex2i(x, y); + glEnd(); +} + +void OpenGL1Graphics::drawLine(int x1, int y1, int x2, int y2) +{ + setTexturingAndBlending(false); + + glBegin(GL_LINES); + glVertex2f(x1 + 0.5f, y1 + 0.5f); + glVertex2f(x2 + 0.5f, y2 + 0.5f); + glEnd(); + + glBegin(GL_POINTS); + glVertex2f(x2 + 0.5f, y2 + 0.5f); + glEnd(); +} + +void OpenGL1Graphics::drawRectangle(const gcn::Rectangle& rect) +{ + drawRectangle(rect, false); +} + +void OpenGL1Graphics::fillRectangle(const gcn::Rectangle& rect) +{ + drawRectangle(rect, true); +} + +void OpenGL1Graphics::setTargetPlane(int width _UNUSED_, int height _UNUSED_) +{ +} + +void OpenGL1Graphics::setTexturingAndBlending(bool enable) +{ + if (enable) + { + if (!mTexture) + { + glEnable(Image::mTextureType); + mTexture = true; + } + + if (!mAlpha) + { + glEnable(GL_BLEND); + mAlpha = true; + } + } + else + { + mLastImage = 0; + if (mAlpha && !mColorAlpha) + { + glDisable(GL_BLEND); + mAlpha = false; + } + else if (!mAlpha && mColorAlpha) + { + glEnable(GL_BLEND); + mAlpha = true; + } + + if (mTexture) + { + glDisable(Image::mTextureType); + mTexture = false; + } + } +} + +void OpenGL1Graphics::drawRectangle(const gcn::Rectangle& rect, bool filled) +{ + const float offset = filled ? 0 : 0.5f; + + setTexturingAndBlending(false); + + glBegin(filled ? GL_QUADS : GL_LINE_LOOP); + glVertex2f(rect.x + offset, rect.y + offset); + glVertex2f(rect.x + rect.width - offset, rect.y + offset); + glVertex2f(rect.x + rect.width - offset, rect.y + rect.height - offset); + glVertex2f(rect.x + offset, rect.y + rect.height - offset); + glEnd(); +} + +void OpenGL1Graphics::bindTexture(GLenum target, GLuint texture) +{ + if (mLastImage != texture) + { + mLastImage = texture; + glBindTexture(target, texture); + } +} + +#endif // USE_OPENGL diff --git a/src/opengl1graphics.h b/src/opengl1graphics.h new file mode 100644 index 000000000..4869244ef --- /dev/null +++ b/src/opengl1graphics.h @@ -0,0 +1,133 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPENGL1GRAPHICS_H +#define OPENGL1GRAPHICS_H + +#include "main.h" + +#include "graphics.h" + +#ifdef USE_OPENGL +#define NO_SDL_GLEXT + +#include +#endif + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class OpenGL1Graphics : public Graphics +{ + public: + OpenGL1Graphics(); + + ~OpenGL1Graphics(); + + /** + * Sets whether vertical refresh syncing is enabled. Takes effect after + * the next call to setVideoMode(). Only implemented on MacOS for now. + */ + void setSync(bool sync); + bool getSync() const + { return mSync; } + + bool setVideoMode(int w, int h, int bpp, bool fs, bool hwaccel); + + bool drawImage(Image *image, + int srcX, int srcY, + int dstX, int dstY, + int width, int height, + bool useColor); + + /** + * Draws a resclaled version of the image + */ + bool drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor); + + /** + * Used to get the smooth rescale option over the standard function. + */ + bool drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor, bool smooth); + + void drawImagePattern(Image *image, + int x, int y, + int w, int h); + + /** + * Draw a pattern based on a rescaled version of the given image... + */ + void drawRescaledImagePattern(Image *image, + int x, int y, int w, int h, + int scaledWidth, int scaledHeight); + + void updateScreen(); + + void _beginDraw(); + void _endDraw(); + + bool pushClipArea(gcn::Rectangle area); + void popClipArea(); + + void setColor(const gcn::Color &color); + + void drawPoint(int x, int y); + + void drawLine(int x1, int y1, int x2, int y2); + + void drawRectangle(const gcn::Rectangle &rect, bool filled); + + void drawRectangle(const gcn::Rectangle &rect); + + void fillRectangle(const gcn::Rectangle &rect); + + void setTargetPlane(int width, int height); + + /** + * Takes a screenshot and returns it as SDL surface. + */ + SDL_Surface *getScreenshot(); + + static void bindTexture(GLenum target, GLuint texture); + + static GLuint mLastImage; + + protected: + void setTexturingAndBlending(bool enable); + + private: + bool mAlpha, mTexture; + bool mColorAlpha; + bool mSync; +}; + +#endif diff --git a/src/openglgraphics.cpp b/src/openglgraphics.cpp new file mode 100644 index 000000000..f1bfffa35 --- /dev/null +++ b/src/openglgraphics.cpp @@ -0,0 +1,950 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "main.h" + +#ifdef USE_OPENGL + +#include "openglgraphics.h" + +#include "log.h" + +#include "resources/image.h" + +#include "utils/stringutils.h" + +#ifdef __APPLE__ +#include +#endif + +#include + +#ifndef GL_TEXTURE_RECTANGLE_ARB +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +const unsigned int vertexBufSize = 500; + +GLuint OpenGLGraphics::mLastImage = 0; + +OpenGLGraphics::OpenGLGraphics(): + mAlpha(false), mTexture(false), mColorAlpha(false), + mSync(false) +{ + mFloatTexArray = new GLfloat[vertexBufSize * 4 + 30]; + mIntTexArray = new GLint[vertexBufSize * 4 + 30]; + mIntVertArray = new GLint[vertexBufSize * 4 + 30]; +} + +OpenGLGraphics::~OpenGLGraphics() +{ + delete[] mFloatTexArray; + delete[] mIntTexArray; + delete[] mIntVertArray; +} + +void OpenGLGraphics::setSync(bool sync) +{ + mSync = sync; +} + +bool OpenGLGraphics::setVideoMode(int w, int h, int bpp, bool fs, bool hwaccel) +{ + logger->log("Setting video mode %dx%d %s", + w, h, fs ? "fullscreen" : "windowed"); + + int displayFlags = SDL_ANYFORMAT | SDL_OPENGL; + + mWidth = w; + mHeight = h; + mBpp = bpp; + mFullscreen = fs; + mHWAccel = hwaccel; + + if (fs) + displayFlags |= SDL_FULLSCREEN; + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + if (!(mTarget = SDL_SetVideoMode(w, h, bpp, displayFlags))) + return false; + +#ifdef __APPLE__ + if (mSync) + { + const GLint VBL = 1; + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &VBL); + } +#endif + + // Setup OpenGL + glViewport(0, 0, w, h); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + int gotDoubleBuffer; + SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &gotDoubleBuffer); + logger->log("Using OpenGL %s double buffering.", + (gotDoubleBuffer ? "with" : "without")); + + char const *glExtensions = (char const *)glGetString(GL_EXTENSIONS); + GLint texSize; + bool rectTex = strstr(glExtensions, "GL_ARB_texture_rectangle"); + if (rectTex) + { + Image::mTextureType = GL_TEXTURE_RECTANGLE_ARB; + glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB, &texSize); + } + else + { + Image::mTextureType = GL_TEXTURE_2D; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize); + } + Image::mTextureSize = texSize; + logger->log("OpenGL texture size: %d pixels%s", Image::mTextureSize, + rectTex ? " (rectangle textures)" : ""); + + return true; +} + +static inline void drawQuad(Image *image, + int srcX, int srcY, int dstX, int dstY, + int width, int height) +{ + if (image->getTextureType() == GL_TEXTURE_2D) + { + // Find OpenGL normalized texture coordinates. + const float texX1 = static_cast(srcX) / + static_cast(image->getTextureWidth()); + const float texY1 = static_cast(srcY) / + static_cast(image->getTextureHeight()); + const float texX2 = static_cast(srcX + width) / + static_cast(image->getTextureWidth()); + const float texY2 = static_cast(srcY + height) / + static_cast(image->getTextureHeight()); + + GLfloat tex[] = + { + texX1, texY1, + texX2, texY1, + texX2, texY2, + texX1, texY2 + }; + + GLint vert[] = + { + dstX, dstY, + dstX + width, dstY, + dstX + width, dstY + height, + dstX, dstY + height + }; + + glVertexPointer(2, GL_FLOAT, 0, &vert); + glTexCoordPointer(2, GL_INT, 0, &tex); + + glDrawArrays(GL_QUADS, 0, 4); + } + else + { + GLint tex[] = + { + srcX, srcY, + srcX + width, srcY, + srcX + width, srcY + height, + srcX, srcY + height + }; + GLint vert[] = + { + dstX, dstY, + dstX + width, dstY, + dstX + width, dstY + height, + dstX, dstY + height + }; + + glVertexPointer(2, GL_INT, 0, &vert); + glTexCoordPointer(2, GL_INT, 0, &tex); + + glDrawArrays(GL_QUADS, 0, 4); + } +} + +static inline void drawRescaledQuad(Image *image, + int srcX, int srcY, int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight) +{ + if (image->getTextureType() == GL_TEXTURE_2D) + { + // Find OpenGL normalized texture coordinates. + const float texX1 = static_cast(srcX) / + static_cast(image->getTextureWidth()); + const float texY1 = static_cast(srcY) / + static_cast(image->getTextureHeight()); + const float texX2 = static_cast(srcX + width) / + static_cast(image->getTextureWidth()); + const float texY2 = static_cast(srcY + height) / + static_cast(image->getTextureHeight()); + + GLfloat tex[] = + { + texX1, texY1, + texX2, texY1, + texX2, texY2, + texX1, texY2 + }; + + GLint vert[] = + { + dstX, dstY, + dstX + desiredWidth, dstY, + dstX + desiredWidth, dstY + desiredHeight, + dstX, dstY + desiredHeight + }; + + glVertexPointer(2, GL_FLOAT, 0, &vert); + glTexCoordPointer(2, GL_INT, 0, &tex); + + glDrawArrays(GL_QUADS, 0, 4); + } + else + { + GLint tex[] = + { + srcX, srcY, + srcX + width, srcY, + srcX + width, srcY + height, + srcX, srcY + height + }; + GLint vert[] = + { + dstX, dstY, + dstX + desiredWidth, dstY, + dstX + desiredWidth, dstY + desiredHeight, + dstX, dstY + desiredHeight + }; + + glVertexPointer(2, GL_INT, 0, &vert); + glTexCoordPointer(2, GL_INT, 0, &tex); + + glDrawArrays(GL_QUADS, 0, 4); + } +} + + +bool OpenGLGraphics::drawImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, bool useColor) +{ + if (!image) + return false; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + if (!useColor) + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + drawQuad(image, srcX, srcY, dstX, dstY, width, height); + + if (!useColor) + { + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); + } + + return true; +} + +bool OpenGLGraphics::drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor) +{ + return drawRescaledImage(image, srcX, srcY, + dstX, dstY, + width, height, + desiredWidth, desiredHeight, + useColor, true); +} + +bool OpenGLGraphics::drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor, bool smooth) +{ + if (!image) + return false; + + // Just draw the image normally when no resizing is necessary, + if (width == desiredWidth && height == desiredHeight) + { + return drawImage(image, srcX, srcY, dstX, dstY, + width, height, useColor); + } + + // When the desired image is smaller than the current one, + // disable smooth effect. + if (width > desiredWidth && height > desiredHeight) + smooth = false; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + if (!useColor) + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + // Draw a textured quad. + drawRescaledQuad(image, srcX, srcY, dstX, dstY, width, height, + desiredWidth, desiredHeight); + + if (smooth) // A basic smooth effect... + { + glColor4f(1.0f, 1.0f, 1.0f, 0.2f); + drawRescaledQuad(image, srcX, srcY, dstX - 1, dstY - 1, width, height, + desiredWidth + 1, desiredHeight + 1); + drawRescaledQuad(image, srcX, srcY, dstX + 1, dstY + 1, width, height, + desiredWidth - 1, desiredHeight - 1); + + drawRescaledQuad(image, srcX, srcY, dstX + 1, dstY, width, height, + desiredWidth - 1, desiredHeight); + drawRescaledQuad(image, srcX, srcY, dstX, dstY + 1, width, height, + desiredWidth, desiredHeight - 1); + } + + if (!useColor) + { + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); + } + + return true; +} + +void OpenGLGraphics::drawImagePattern(Image *image, int x, int y, int w, int h) +{ + if (!image) + return; + + const int srcX = image->mBounds.x; + const int srcY = image->mBounds.y; + + const int iw = image->getWidth(); + const int ih = image->getHeight(); + + if (iw == 0 || ih == 0) + return; + + const float tw = static_cast(image->getTextureWidth()); + const float th = static_cast(image->getTextureHeight()); + + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + unsigned int vp = 0; + const unsigned int vLimit = vertexBufSize * 4; + // Draw a set of textured rectangles + if (image->getTextureType() == GL_TEXTURE_2D) + { + float texX1 = static_cast(srcX) / tw; + float texY1 = static_cast(srcY) / th; + + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y + py; + for (int px = 0; px < w; px += iw) + { + int width = (px + iw >= w) ? w - px : iw; + int dstX = x + px; + + float texX2 = static_cast(srcX + width) / tw; + float texY2 = static_cast(srcY + height) / th; + + mFloatTexArray[vp + 0] = texX1; + mFloatTexArray[vp + 1] = texY1; + + mFloatTexArray[vp + 2] = texX2; + mFloatTexArray[vp + 3] = texY1; + + mFloatTexArray[vp + 4] = texX2; + mFloatTexArray[vp + 5] = texY2; + + mFloatTexArray[vp + 6] = texX1; + mFloatTexArray[vp + 7] = texY2; + + mIntVertArray[vp + 0] = dstX; + mIntVertArray[vp + 1] = dstY; + + mIntVertArray[vp + 2] = dstX + width; + mIntVertArray[vp + 3] = dstY; + + mIntVertArray[vp + 4] = dstX + width; + mIntVertArray[vp + 5] = dstY + height; + + mIntVertArray[vp + 6] = dstX; + mIntVertArray[vp + 7] = dstY + height; + + vp += 8; + if (vp >= vLimit) + { + drawQuadArrayfi(vp); + vp = 0; + } + } + } + if (vp > 0) + drawQuadArrayfi(vp); + } + else + { + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y + py; + for (int px = 0; px < w; px += iw) + { + int width = (px + iw >= w) ? w - px : iw; + int dstX = x + px; + + mIntTexArray[vp + 0] = srcX; + mIntTexArray[vp + 1] = srcY; + + mIntTexArray[vp + 2] = srcX + width; + mIntTexArray[vp + 3] = srcY; + + mIntTexArray[vp + 4] = srcX + width; + mIntTexArray[vp + 5] = srcY + height; + + mIntTexArray[vp + 6] = srcX; + mIntTexArray[vp + 7] = srcY + height; + + mIntVertArray[vp + 0] = dstX; + mIntVertArray[vp + 1] = dstY; + + mIntVertArray[vp + 2] = dstX + width; + mIntVertArray[vp + 3] = dstY; + + mIntVertArray[vp + 4] = dstX + width; + mIntVertArray[vp + 5] = dstY + height; + + mIntVertArray[vp + 6] = dstX; + mIntVertArray[vp + 7] = dstY + height; + + vp += 8; + if (vp >= vLimit) + { + drawQuadArrayii(vp); + vp = 0; + } + } + } + if (vp > 0) + drawQuadArrayii(vp); + } + + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); +} + +void OpenGLGraphics::drawRescaledImagePattern(Image *image, int x, int y, + int w, int h, int scaledWidth, + int scaledHeight) +{ + if (!image) + return; + + const int srcX = image->mBounds.x; + const int srcY = image->mBounds.y; + + const int iw = scaledWidth; + const int ih = scaledHeight; + if (iw == 0 || ih == 0) + return; + + const float tw = static_cast(image->getTextureWidth()); + const float th = static_cast(image->getTextureHeight()); + + glColor4f(1.0f, 1.0f, 1.0f, image->mAlpha); + + bindTexture(Image::mTextureType, image->mGLImage); + + setTexturingAndBlending(true); + + unsigned int vp = 0; + const unsigned int vLimit = vertexBufSize * 4; + + float texX1 = static_cast(srcX) / tw; + float texY1 = static_cast(srcY) / th; + + // Draw a set of textured rectangles + if (image->getTextureType() == GL_TEXTURE_2D) + { + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y + py; + for (int px = 0; px < w; px += iw) + { + int width = (px + iw >= w) ? w - px : iw; + int dstX = x + px; + + float texX2 = static_cast(srcX + width) / tw; + float texY2 = static_cast(srcY + height) / th; + + mFloatTexArray[vp + 0] = texX1; + mFloatTexArray[vp + 1] = texY1; + + mFloatTexArray[vp + 2] = texX2; + mFloatTexArray[vp + 3] = texY1; + + mFloatTexArray[vp + 4] = texX2; + mFloatTexArray[vp + 5] = texY2; + + mFloatTexArray[vp + 6] = texX1; + mFloatTexArray[vp + 7] = texY2; + + mIntVertArray[vp + 0] = dstX; + mIntVertArray[vp + 1] = dstY; + + mIntVertArray[vp + 2] = dstX + scaledWidth; + mIntVertArray[vp + 3] = dstY; + + mIntVertArray[vp + 4] = dstX + scaledWidth; + mIntVertArray[vp + 5] = dstY + scaledHeight; + + mIntVertArray[vp + 6] = dstX; + mIntVertArray[vp + 7] = dstY + scaledHeight; + + vp += 8; + if (vp >= vLimit) + { + drawQuadArrayfi(vp); + vp = 0; + } + } + } + if (vp > 0) + drawQuadArrayfi(vp); + } + else + { + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y + py; + for (int px = 0; px < w; px += iw) + { + int width = (px + iw >= w) ? w - px : iw; + int dstX = x + px; + + mIntTexArray[vp + 0] = srcX; + mIntTexArray[vp + 1] = srcY; + + mIntTexArray[vp + 2] = srcX + width; + mIntTexArray[vp + 3] = srcY; + + mIntTexArray[vp + 4] = srcX + width; + mIntTexArray[vp + 5] = srcY + height; + + mIntTexArray[vp + 6] = srcX; + mIntTexArray[vp + 7] = srcY + height; + + mIntVertArray[vp + 0] = dstX; + mIntVertArray[vp + 1] = dstY; + + mIntVertArray[vp + 2] = dstX + scaledWidth; + mIntVertArray[vp + 3] = dstY; + + mIntVertArray[vp + 4] = dstX + scaledWidth; + mIntVertArray[vp + 5] = dstY + scaledHeight; + + mIntVertArray[vp + 6] = dstX; + mIntVertArray[vp + 7] = dstY + scaledHeight; + + vp += 8; + if (vp >= vLimit) + { + drawQuadArrayii(vp); + vp = 0; + } + } + } + if (vp > 0) + drawQuadArrayii(vp); + } + + glColor4ub(static_cast(mColor.r), + static_cast(mColor.g), + static_cast(mColor.b), + static_cast(mColor.a)); +} + +void OpenGLGraphics::updateScreen() +{ + glFlush(); + glFinish(); + SDL_GL_SwapBuffers(); +} + +void OpenGLGraphics::_beginDraw() +{ + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho(0.0, (double)mTarget->w, (double)mTarget->h, 0.0, -1.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + glHint(GL_POINT_SMOOTH_HINT, GL_FASTEST); + glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST); + +#ifndef __MINGW32__ + glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST); +#endif + +// glScalef(0.5f, 0.5f, 0.5f); + + pushClipArea(gcn::Rectangle(0, 0, mTarget->w, mTarget->h)); +} + +void OpenGLGraphics::_endDraw() +{ +} + +SDL_Surface* OpenGLGraphics::getScreenshot() +{ + int h = mTarget->h; + int w = mTarget->w; + + SDL_Surface *screenshot = SDL_CreateRGBSurface( + SDL_SWSURFACE, + w, h, 24, + 0xff0000, 0x00ff00, 0x0000ff, 0x000000); + + if (SDL_MUSTLOCK(screenshot)) + SDL_LockSurface(screenshot); + + // Grap the pixel buffer and write it to the SDL surface + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, screenshot->pixels); + + // Flip the screenshot, as OpenGL has 0,0 in bottom left + unsigned int lineSize = 3 * w; + GLubyte* buf = (GLubyte*)malloc(lineSize); + + for (int i = 0; i < (h / 2); i++) + { + GLubyte *top = (GLubyte*)screenshot->pixels + lineSize * i; + GLubyte *bot = (GLubyte*)screenshot->pixels + lineSize * (h - 1 - i); + + memcpy(buf, top, lineSize); + memcpy(top, bot, lineSize); + memcpy(bot, buf, lineSize); + } + + free(buf); + + if (SDL_MUSTLOCK(screenshot)) + SDL_UnlockSurface(screenshot); + + return screenshot; +} + +bool OpenGLGraphics::pushClipArea(gcn::Rectangle area) +{ + int transX = 0; + int transY = 0; + + if (!mClipStack.empty()) + { + transX = -mClipStack.top().xOffset; + transY = -mClipStack.top().yOffset; + } + + bool result = gcn::Graphics::pushClipArea(area); + + transX += mClipStack.top().xOffset; + transY += mClipStack.top().yOffset; + + glPushMatrix(); + glTranslatef(static_cast(transX), + static_cast(transY), 0); + glScissor(mClipStack.top().x, + mTarget->h - mClipStack.top().y - mClipStack.top().height, + mClipStack.top().width, + mClipStack.top().height); + + return result; +} + +void OpenGLGraphics::popClipArea() +{ + gcn::Graphics::popClipArea(); + + if (mClipStack.empty()) + return; + + glPopMatrix(); + glScissor(mClipStack.top().x, + mTarget->h - mClipStack.top().y - mClipStack.top().height, + mClipStack.top().width, + mClipStack.top().height); +} + +void OpenGLGraphics::setColor(const gcn::Color& color) +{ + mColor = color; + glColor4ub(static_cast(color.r), + static_cast(color.g), + static_cast(color.b), + static_cast(color.a)); + + mColorAlpha = (color.a != 255); +} + +void OpenGLGraphics::drawPoint(int x, int y) +{ + setTexturingAndBlending(false); + + glBegin(GL_POINTS); + glVertex2i(x, y); + glEnd(); +} + +void OpenGLGraphics::drawLine(int x1, int y1, int x2, int y2) +{ + setTexturingAndBlending(false); + + float x3 = static_cast(x2) + 0.5f; + float y3 = static_cast(y2) + 0.5f; + + glBegin(GL_LINES); + glVertex2f(static_cast(x1) + 0.5f, static_cast(y1) + 0.5f); + glVertex2f(x3, y3); + glEnd(); + + glBegin(GL_POINTS); + glVertex2f(x3, y3); + glEnd(); +} + +void OpenGLGraphics::drawRectangle(const gcn::Rectangle& rect) +{ + drawRectangle(rect, false); +} + +void OpenGLGraphics::fillRectangle(const gcn::Rectangle& rect) +{ + drawRectangle(rect, true); +} + +void OpenGLGraphics::setTargetPlane(int width _UNUSED_, int height _UNUSED_) +{ +} + +void OpenGLGraphics::setTexturingAndBlending(bool enable) +{ + if (enable) + { + if (!mTexture) + { + glEnable(Image::mTextureType); + mTexture = true; + } + + if (!mAlpha) + { + glEnable(GL_BLEND); + mAlpha = true; + } + } + else + { + mLastImage = 0; + if (mAlpha && !mColorAlpha) + { + glDisable(GL_BLEND); + mAlpha = false; + } + else if (!mAlpha && mColorAlpha) + { + glEnable(GL_BLEND); + mAlpha = true; + } + + if (mTexture) + { + glDisable(Image::mTextureType); + mTexture = false; + } + } +} + +void OpenGLGraphics::drawRectangle(const gcn::Rectangle& rect, bool filled) +{ + const float offset = filled ? 0 : 0.5f; + const float x = static_cast(rect.x); + const float y = static_cast(rect.y); + const float width = static_cast(rect.width); + const float height = static_cast(rect.height); + + setTexturingAndBlending(false); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + GLfloat vert[] = + { + x + offset, y + offset, + x + width - offset, y + offset, + x + width - offset, y + height - offset, + x + offset, y + height - offset + }; + + glVertexPointer(2, GL_FLOAT, 0, &vert); + glDrawArrays(filled ? GL_QUADS : GL_LINE_LOOP, 0, 4); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); +} + +bool OpenGLGraphics::drawNet(int x1, int y1, int x2, int y2, + int width, int height) +{ + unsigned int vp = 0; + const unsigned int vLimit = vertexBufSize * 4; + + setTexturingAndBlending(false); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + const float xf1 = static_cast(x1); + const float xf2 = static_cast(x2); + const float yf1 = static_cast(y1); + const float yf2 = static_cast(y2); + + for (int y = y1; y < y2; y += height) + { + mFloatTexArray[vp + 0] = xf1; + mFloatTexArray[vp + 1] = static_cast(y); + + mFloatTexArray[vp + 2] = xf2; + mFloatTexArray[vp + 3] = static_cast(y); + + vp += 4; + if (vp >= vLimit) + { + drawLineArrayf(vp); + vp = 0; + } + } + + for (int x = x1; x < x2; x += width) + { + mFloatTexArray[vp + 0] = static_cast(x); + mFloatTexArray[vp + 1] = yf1; + + mFloatTexArray[vp + 2] = static_cast(x); + mFloatTexArray[vp + 3] = yf2; + + vp += 4; + if (vp >= vLimit) + { + drawLineArrayf(vp); + vp = 0; + } + } + + if (vp > 0) + drawLineArrayf(vp); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + return true; +} + +void OpenGLGraphics::bindTexture(GLenum target, GLuint texture) +{ + if (mLastImage != texture) + { + mLastImage = texture; + glBindTexture(target, texture); + } +} + +inline void OpenGLGraphics::drawQuadArrayfi(int size) +{ + glVertexPointer(2, GL_INT, 0, mIntVertArray); + glTexCoordPointer(2, GL_FLOAT, 0, mFloatTexArray); + + glDrawArrays(GL_QUADS, 0, size / 2); +} + +inline void OpenGLGraphics::drawQuadArrayii(int size) +{ + glVertexPointer(2, GL_INT, 0, mIntVertArray); + glTexCoordPointer(2, GL_INT, 0, mIntTexArray); + + glDrawArrays(GL_QUADS, 0, size / 2); +} + +inline void OpenGLGraphics::drawLineArrayi(int size) +{ + glVertexPointer(2, GL_INT, 0, mIntVertArray); + + glDrawArrays(GL_LINES, 0, size / 2); +} + +inline void OpenGLGraphics::drawLineArrayf(int size) +{ + glVertexPointer(2, GL_FLOAT, 0, mFloatTexArray); + + glDrawArrays(GL_LINES, 0, size / 2); +} + +#endif // USE_OPENGL diff --git a/src/openglgraphics.h b/src/openglgraphics.h new file mode 100644 index 000000000..9740d9bca --- /dev/null +++ b/src/openglgraphics.h @@ -0,0 +1,146 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPENGLGRAPHICS_H +#define OPENGLGRAPHICS_H + +#include "main.h" + +#include "graphics.h" + +#ifdef USE_OPENGL +#define NO_SDL_GLEXT + +#include +#endif + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class OpenGLGraphics : public Graphics +{ + public: + OpenGLGraphics(); + + ~OpenGLGraphics(); + + /** + * Sets whether vertical refresh syncing is enabled. Takes effect after + * the next call to setVideoMode(). Only implemented on MacOS for now. + */ + void setSync(bool sync); + bool getSync() const + { return mSync; } + + bool setVideoMode(int w, int h, int bpp, bool fs, bool hwaccel); + + bool drawImage(Image *image, + int srcX, int srcY, + int dstX, int dstY, + int width, int height, + bool useColor); + + /** + * Draws a resclaled version of the image + */ + bool drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor); + + /** + * Used to get the smooth rescale option over the standard function. + */ + bool drawRescaledImage(Image *image, int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor, bool smooth); + + void drawImagePattern(Image *image, + int x, int y, + int w, int h); + + /** + * Draw a pattern based on a rescaled version of the given image... + */ + void drawRescaledImagePattern(Image *image, + int x, int y, int w, int h, + int scaledWidth, int scaledHeight); + + void updateScreen(); + + void _beginDraw(); + void _endDraw(); + + bool pushClipArea(gcn::Rectangle area); + void popClipArea(); + + void setColor(const gcn::Color &color); + + void drawPoint(int x, int y); + + void drawLine(int x1, int y1, int x2, int y2); + + void drawRectangle(const gcn::Rectangle &rect, bool filled); + + void drawRectangle(const gcn::Rectangle &rect); + + void fillRectangle(const gcn::Rectangle &rect); + + void setTargetPlane(int width, int height); + + void drawQuadArrayfi(int size); + + void drawQuadArrayii(int size); + + void drawLineArrayi(int size); + + void drawLineArrayf(int size); + + /** + * Takes a screenshot and returns it as SDL surface. + */ + SDL_Surface *getScreenshot(); + + bool drawNet(int x1, int y1, int x2, int y2, int width, int height); + + static void bindTexture(GLenum target, GLuint texture); + + static GLuint mLastImage; + + protected: + void setTexturingAndBlending(bool enable); + + private: + GLfloat *mFloatTexArray; + GLint *mIntTexArray; + GLint *mIntVertArray; + bool mAlpha, mTexture; + bool mColorAlpha; + bool mSync; +}; + +#endif diff --git a/src/particle.cpp b/src/particle.cpp new file mode 100644 index 000000000..30ed7ea89 --- /dev/null +++ b/src/particle.cpp @@ -0,0 +1,450 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "animationparticle.h" +#include "configuration.h" +#include "imageparticle.h" +#include "log.h" +#include "map.h" +#include "particle.h" +#include "particleemitter.h" +#include "rotationalparticle.h" +#include "textparticle.h" + +#include "resources/resourcemanager.h" + +#include "utils/dtor.h" +#include "utils/mathutils.h" +#include "utils/xml.h" + +#include + +#include +#include + +#define SIN45 0.707106781f + +class Graphics; +class Image; + +int Particle::particleCount = 0; +int Particle::maxCount = 0; +int Particle::fastPhysics = 0; +int Particle::emitterSkip = 1; +bool Particle::enabled = true; +const float Particle::PARTICLE_SKY = 800.0f; + +Particle::Particle(Map *map): + mAlpha(1.0f), + mLifetimeLeft(-1), + mLifetimePast(0), + mFadeOut(0), + mFadeIn(0), + mAlive(ALIVE), + mAutoDelete(true), + mAllowSizeAdjust(false), + mDeathEffectConditions(0x00), + mGravity(0.0f), + mRandomness(0), + mBounce(0.0f), + mFollow(false), + mTarget(NULL), + mAcceleration(0.0f), + mInvDieDistance(-1.0f), + mMomentum(1.0f) +{ + setMap(map); + Particle::particleCount++; +} + +Particle::~Particle() +{ + // Delete child emitters and child particles + clear(); + //update particle count + Particle::particleCount--; +} + +void Particle::setupEngine() +{ + Particle::maxCount = config.getIntValue("particleMaxCount"); + Particle::fastPhysics = config.getIntValue("particleFastPhysics"); + Particle::emitterSkip = config.getIntValue("particleEmitterSkip") + 1; + Particle::enabled = config.getBoolValue("particleeffects"); + disableAutoDelete(); + logger->log1("Particle engine set up"); +} + +bool Particle::draw(Graphics *, int, int) const +{ + return false; +} + +bool Particle::update() +{ + if (!mMap) + return false; + + if (mLifetimeLeft == 0 && mAlive == ALIVE) + mAlive = DEAD_TIMEOUT; + + Vector oldPos = mPos; + + if (mAlive == ALIVE) + { + //calculate particle movement + if (mMomentum != 1.0f) + mVelocity *= mMomentum; + + if (mTarget && mAcceleration != 0.0f) + { + Vector dist = mPos - mTarget->getPosition(); + dist.x *= SIN45; + float invHypotenuse; + + switch (Particle::fastPhysics) + { + case 1: + invHypotenuse = fastInvSqrt( + dist.x * dist.x + dist.y * dist.y + dist.z * dist.z); + break; + case 2: + invHypotenuse = 0; + if (!dist.x) + break; + + invHypotenuse = 2.0f / static_cast(fabs(dist.x)) + + static_cast(fabs(dist.y)) + + static_cast(fabs(dist.z)); + break; + default: + invHypotenuse = 1.0f / sqrt( + dist.x * dist.x + dist.y * dist.y + dist.z * dist.z); + break; + } + + if (invHypotenuse) + { + if (mInvDieDistance > 0.0f && invHypotenuse > mInvDieDistance) + mAlive = DEAD_IMPACT; + float accFactor = invHypotenuse * mAcceleration; + mVelocity -= dist * accFactor; + } + } + + if (mRandomness > 0) + { + mVelocity.x += (rand() % mRandomness - rand() + % mRandomness) / 1000.0f; + mVelocity.y += (rand() % mRandomness - rand() + % mRandomness) / 1000.0f; + mVelocity.z += (rand() % mRandomness - rand() + % mRandomness) / 1000.0f; + } + + mVelocity.z -= mGravity; + + // Update position + mPos.x += mVelocity.x; + mPos.y += mVelocity.y * SIN45; + mPos.z += mVelocity.z * SIN45; + + // Update other stuff + if (mLifetimeLeft > 0) + mLifetimeLeft--; + + mLifetimePast++; + + if (mPos.z < 0.0f) + { + if (mBounce > 0.0f) + { + mPos.z *= -mBounce; + mVelocity *= mBounce; + mVelocity.z = -mVelocity.z; + } + else + { + mAlive = DEAD_FLOOR; + } + } else if (mPos.z > PARTICLE_SKY) + { + mAlive = DEAD_SKY; + } + + // Update child emitters + if ((mLifetimePast - 1) % Particle::emitterSkip == 0) + { + for (EmitterIterator e = mChildEmitters.begin(); + e != mChildEmitters.end(); e++) + { + Particles newParticles = (*e)->createParticles(mLifetimePast); + for (ParticleIterator p = newParticles.begin(); + p != newParticles.end(); p++) + { + (*p)->moveBy(mPos); + mChildParticles.push_back (*p); + } + } + } + } + + // create death effect when the particle died + if (mAlive != ALIVE && mAlive != DEAD_LONG_AGO) + { + if ((mAlive & mDeathEffectConditions) > 0x00 && !mDeathEffect.empty()) + { + Particle* deathEffect = particleEngine->addEffect( + mDeathEffect, 0, 0); + if (deathEffect) + deathEffect->moveBy(mPos); + } + mAlive = DEAD_LONG_AGO; + } + + Vector change = mPos - oldPos; + + // Update child particles + + for (ParticleIterator p = mChildParticles.begin(); + p != mChildParticles.end(); ) + { + //move particle with its parent if desired + if ((*p)->doesFollow()) + (*p)->moveBy(change); + + //update particle + if ((*p)->update()) + { + p++; + } + else + { + delete (*p); + p = mChildParticles.erase(p); + } + } + if (mAlive != ALIVE && mChildParticles.empty() && mAutoDelete) + return false; + + return true; +} + +void Particle::moveBy(const Vector &change) +{ + mPos += change; + for (ParticleIterator p = mChildParticles.begin(); + p != mChildParticles.end(); p++) + { + if ((*p)->doesFollow()) + (*p)->moveBy(change); + } +} + +void Particle::moveTo(float x, float y) +{ + moveTo(Vector(x, y, mPos.z)); +} + +Particle *Particle::createChild() +{ + Particle *newParticle = new Particle(mMap); + mChildParticles.push_back(newParticle); + return newParticle; +} + +Particle *Particle::addEffect(const std::string &particleEffectFile, + int pixelX, int pixelY, int rotation) +{ + Particle *newParticle = NULL; + + XML::Document doc(particleEffectFile); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "effect")) + { + logger->log("Error loading particle: %s", particleEffectFile.c_str()); + return NULL; + } + + ResourceManager *resman = ResourceManager::getInstance(); + + // Parse particles + for_each_xml_child_node(effectChildNode, rootNode) + { + // We're only interested in particles + if (!xmlStrEqual(effectChildNode->name, BAD_CAST "particle")) + continue; + + // Determine the exact particle type + xmlNodePtr node; + + // Animation + if ((node = XML::findFirstChildByName(effectChildNode, "animation"))) + { + newParticle = new AnimationParticle(mMap, node); + } + // Rotational + else if ((node = XML::findFirstChildByName( + effectChildNode, "rotation"))) + { + newParticle = new RotationalParticle(mMap, node); + } + // Image + else if ((node = XML::findFirstChildByName(effectChildNode, "image"))) + { + Image *img = resman->getImage((const char*) + node->xmlChildrenNode->content); + + newParticle = new ImageParticle(mMap, img); + } + // Other + else + { + newParticle = new Particle(mMap); + } + + // Read and set the basic properties of the particle + float offsetX = XML::getFloatProperty( + effectChildNode, "position-x", 0); + float offsetY = XML::getFloatProperty( + effectChildNode, "position-y", 0); + float offsetZ = XML::getFloatProperty( + effectChildNode, "position-z", 0); + Vector position (mPos.x + (float)pixelX + offsetX, + mPos.y + (float)pixelY + offsetY, + mPos.z + offsetZ); + newParticle->moveTo(position); + + int lifetime = XML::getProperty(effectChildNode, "lifetime", -1); + newParticle->setLifetime(lifetime); + bool resizeable = "false" != XML::getProperty(effectChildNode, + "size-adjustable", "false"); + + newParticle->setAllowSizeAdjust(resizeable); + + // Look for additional emitters for this particle + for_each_xml_child_node(emitterNode, effectChildNode) + { + if (xmlStrEqual(emitterNode->name, BAD_CAST "emitter")) + { + ParticleEmitter *newEmitter; + newEmitter = new ParticleEmitter( + emitterNode, newParticle, mMap, rotation); + newParticle->addEmitter(newEmitter); + } + else if (xmlStrEqual(emitterNode->name, BAD_CAST "deatheffect")) + { + std::string deathEffect = (const char*)emitterNode + ->xmlChildrenNode->content; + + char deathEffectConditions = 0x00; + if (XML::getBoolProperty(emitterNode, "on-floor", true)) + { + deathEffectConditions += Particle::DEAD_FLOOR; + } + if (XML::getBoolProperty(emitterNode, "on-sky", true)) + { + deathEffectConditions += Particle::DEAD_SKY; + } + if (XML::getBoolProperty(emitterNode, "on-other", false)) + { + deathEffectConditions += Particle::DEAD_OTHER; + } + if (XML::getBoolProperty(emitterNode, "on-impact", true)) + { + deathEffectConditions += Particle::DEAD_IMPACT; + } + if (XML::getBoolProperty(emitterNode, "on-timeout", true)) + { + deathEffectConditions += Particle::DEAD_TIMEOUT; + } + newParticle->setDeathEffect( + deathEffect, deathEffectConditions); + } + } + + mChildParticles.push_back(newParticle); + } + + return newParticle; +} + +Particle *Particle::addTextSplashEffect(const std::string &text, int x, int y, + const gcn::Color *color, + gcn::Font *font, bool outline) +{ + Particle *newParticle = new TextParticle(mMap, text, color, font, outline); + newParticle->moveTo(static_cast(x), static_cast(y)); + newParticle->setVelocity(((rand() % 100) - 50) / 200.0f, // X + ((rand() % 100) - 50) / 200.0f, // Y + ((rand() % 100) / 200.0f) + 4.0f); // Z + newParticle->setGravity(0.1f); + newParticle->setBounce(0.5f); + newParticle->setLifetime(200); + newParticle->setFadeOut(100); + + mChildParticles.push_back(newParticle); + + return newParticle; +} + +Particle *Particle::addTextRiseFadeOutEffect(const std::string &text, + int x, int y, + const gcn::Color *color, + gcn::Font *font, bool outline) +{ + Particle *newParticle = new TextParticle(mMap, text, color, font, outline); + newParticle->moveTo(static_cast(x), static_cast(y)); + newParticle->setVelocity(0.0f, 0.0f, 0.5f); + newParticle->setGravity(0.0015f); + newParticle->setLifetime(300); + newParticle->setFadeOut(50); + newParticle->setFadeIn(200); + + mChildParticles.push_back(newParticle); + + return newParticle; +} + +void Particle::adjustEmitterSize(int w, int h) +{ + if (mAllowSizeAdjust) + { + for (EmitterIterator e = mChildEmitters.begin(); + e != mChildEmitters.end(); e++) + { + (*e)->adjustSize(w, h); + } + } +} + +void Particle::clear() +{ + delete_all(mChildEmitters); + mChildEmitters.clear(); + + delete_all(mChildParticles); + mChildParticles.clear(); +} diff --git a/src/particle.h b/src/particle.h new file mode 100644 index 000000000..a512e1f61 --- /dev/null +++ b/src/particle.h @@ -0,0 +1,331 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PARTICLE_H +#define PARTICLE_H + +#include "actor.h" +#include "guichanfwd.h" +#include "vector.h" + +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Map; +class Particle; +class ParticleEmitter; + +typedef std::list Particles; +typedef Particles::iterator ParticleIterator; +typedef std::list Emitters; +typedef Emitters::iterator EmitterIterator; + +/** + * A particle spawned by a ParticleEmitter. + */ +class Particle : public Actor +{ + public: + enum AliveStatus + { + ALIVE = 0, + DEAD_TIMEOUT = 1, + DEAD_FLOOR = 2, + DEAD_SKY = 4, + DEAD_IMPACT = 8, + DEAD_OTHER = 16, + DEAD_LONG_AGO = 128 + }; + static const float PARTICLE_SKY; /**< Maximum Z position + of particles */ + static int fastPhysics; /**< Mode of squareroot calculation */ + static int particleCount; /**< Current number of particles */ + static int maxCount; /**< Maximum number of particles */ + static int emitterSkip; /**< Duration of pause between two + emitter updates in ticks */ + static bool enabled; /**< true when non-crucial particle effects + are disabled */ + + /** + * Constructor. + * + * @param map the map this particle will add itself to, may be NULL + */ + Particle(Map *map); + + /** + * Destructor. + */ + ~Particle(); + + /** + * Deletes all child particles and emitters. + */ + void clear(); + + /** + * Gives a particle the properties of an engine root particle and loads + * the particle-related config settings. + */ + void setupEngine(); + + /** + * Updates particle position, returns false when the particle should + * be deleted. + */ + virtual bool update(); + + /** + * Draws the particle image. + */ + virtual bool draw(Graphics *graphics, int offsetX, int offsetY) const; + + /** + * Necessary for sorting with the other sprites. + */ + virtual int getPixelY() const + { return static_cast(mPos.y + mPos.z) - 64; } + + /** + * Creates a blank particle as a child of the current particle + * Useful for creating target particles + */ + Particle *createChild(); + + /** + * Creates a child particle that hosts some emitters described in the + * particleEffectFile. + */ + Particle *addEffect(const std::string &particleEffectFile, + int pixelX, int pixelY, int rotation = 0); + + /** + * Creates a standalone text particle. + */ + Particle *addTextSplashEffect(const std::string &text, int x, int y, + const gcn::Color *color, gcn::Font *font, + bool outline = false); + + /** + * Creates a standalone text particle. + */ + Particle *addTextRiseFadeOutEffect(const std::string &text, + int x, int y, + const gcn::Color *color, + gcn::Font *font, + bool outline = false); + + /** + * Adds an emitter to the particle. + */ + void addEmitter (ParticleEmitter* emitter) + { mChildEmitters.push_back(emitter); } + + /** + * Sets the position in 3 dimensional space in pixels relative to map. + */ + void moveTo(const Vector &pos) + { moveBy (pos - mPos); } + + /** + * Sets the position in 2 dimensional space in pixels relative to map. + */ + void moveTo(float x, float y); + + /** + * Changes the particle position relative + */ + void moveBy (const Vector &change); + + /** + * Sets the time in game ticks until the particle is destroyed. + */ + void setLifetime(int lifetime) + { mLifetimeLeft = lifetime; mLifetimePast = 0; } + + /** + * Sets the age of the pixel in game ticks where the particle has + * faded in completely. + */ + void setFadeOut(int fadeOut) + { mFadeOut = fadeOut; } + + /** + * Sets the remaining particle lifetime where the particle starts to + * fade out. + */ + void setFadeIn(int fadeIn) + { mFadeIn = fadeIn; } + + /** + * Sets the current velocity in 3 dimensional space. + */ + void setVelocity(float x, float y, float z) + { mVelocity.x = x; mVelocity.y = y; mVelocity.z = z; } + + /** + * Sets the downward acceleration. + */ + void setGravity(float gravity) + { mGravity = gravity; } + + /** + * Sets the ammount of random vector changes + */ + void setRandomness(int r) + { mRandomness = r; } + + /** + * Sets the ammount of velocity particles retain after + * hitting the ground. + */ + void setBounce(float bouncieness) + { mBounce = bouncieness; } + + /** + * Sets the flag if the particle is supposed to be moved by its parent + */ + void setFollow(bool follow) + { mFollow = follow; } + + /** + * Gets the flag if the particle is supposed to be moved by its parent + */ + bool doesFollow() + { return mFollow; } + + /** + * Makes the particle move toward another particle with a + * given acceleration and momentum + */ + void setDestination(Particle *target, float accel, float moment) + { mTarget = target; mAcceleration = accel; mMomentum = moment; } + + /** + * Sets the distance in pixel the particle can come near the target + * particle before it is destroyed. Does only make sense after a target + * particle has been set using setDestination. + */ + void setDieDistance(float dist) + { mInvDieDistance = 1.0f / dist; } + + /** + * Changes the size of the emitters so that the effect fills a + * rectangle of this size + */ + void adjustEmitterSize(int w, int h); + + void setAllowSizeAdjust(bool adjust) + { mAllowSizeAdjust = adjust; } + + bool isAlive() const + { return mAlive == ALIVE; } + + /** + * Determines whether the particle and its children are all dead + */ + bool isExtinct() const + { return !isAlive() && mChildParticles.empty(); } + + /** + * Manually marks the particle for deletion. + */ + void kill() + { mAlive = DEAD_OTHER; mAutoDelete = true; } + + /** + * After calling this function the particle will only request + * deletion when kill() is called + */ + void disableAutoDelete() + { mAutoDelete = false; } + + /** We consider particles (at least for now) to be one layer-sprites */ + virtual int getNumberOfLayers() const + { return 1; } + + virtual float getAlpha() const + { return 1.0f; } + + virtual void setAlpha(float alpha _UNUSED_) {} + + virtual void setDeathEffect(const std::string &effectFile, + char conditions) + { mDeathEffect = effectFile; mDeathEffectConditions = conditions; } + + protected: + float mAlpha; /**< Opacity of the graphical + representation of the particle */ + int mLifetimeLeft; /**< Lifetime left in game ticks*/ + int mLifetimePast; /**< Age of the particle in game ticks*/ + int mFadeOut; /**< Lifetime in game ticks left where + fading out begins*/ + int mFadeIn; /**< Age in game ticks where fading in is + finished*/ + Vector mVelocity; /**< Speed in pixels per game-tick. */ + + private: + AliveStatus mAlive; /**< Is the particle supposed to be + drawn and updated?*/ + // generic properties + bool mAutoDelete; /**< May the particle request its deletion + by the parent particle? */ + Emitters mChildEmitters; /**< List of child emitters. */ + Particles mChildParticles; /**< List of particles controlled by this + particle */ + bool mAllowSizeAdjust; /**< Can the effect size be adjusted by + the object props in the map file? */ + std::string mDeathEffect; /**< Particle effect file to be spawned + when the particle dies */ + char mDeathEffectConditions; /**< Bitfield of death conditions which + trigger spawning of the death + particle */ + + // dynamic particle + float mGravity; /**< Downward acceleration in pixels per + game-tick. */ + int mRandomness; /**< Ammount of random vector change */ + float mBounce; /**< How much the particle bounces off when + hitting the ground */ + bool mFollow; /**< is this particle moved when its parent + particle moves? */ + + // follow-point particles + Particle *mTarget; /**< The particle that attracts + this particle*/ + float mAcceleration; /**< Acceleration towards the target + particle in pixels per game-tick*/ + float mInvDieDistance; /**< Distance in pixels from the target + particle that causes the destruction + of the particle*/ + float mMomentum; /**< How much speed the particle retains + after each game tick*/ +}; + +extern Particle *particleEngine; + +#endif diff --git a/src/particlecontainer.cpp b/src/particlecontainer.cpp new file mode 100644 index 000000000..68e5f64e4 --- /dev/null +++ b/src/particlecontainer.cpp @@ -0,0 +1,189 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "particle.h" +#include "particlecontainer.h" + +ParticleContainer::ParticleContainer(ParticleContainer *parent, + bool delParent): + mDelParent(delParent), + mNext(parent) +{} + +ParticleContainer::~ParticleContainer() +{ + clearLocally(); + if (mDelParent) + { + delete mNext; + mNext = 0; + } +} + +void ParticleContainer::clear() +{ + clearLocally(); + if (mNext) + mNext->clear(); +} + +void ParticleContainer::moveTo(float x, float y) +{ + if (mNext) + mNext->moveTo(x, y); +} + +// -- particle list ---------------------------------------- + +ParticleList::ParticleList(ParticleContainer *parent, bool delParent): + ParticleContainer(parent, delParent) +{} + +ParticleList::~ParticleList() +{} + +void ParticleList::addLocally(Particle *particle) +{ + if (particle) + { + // The effect may not die without the beings permission or we segfault + particle->disableAutoDelete(); + mElements.push_back(particle); + } +} + +void ParticleList::removeLocally(Particle *particle) +{ + std::list::iterator it, it_end; + for (it = mElements.begin(), it_end = mElements.end(); + it != it_end; ) + { + if (*it == particle) + { + (*it)->kill(); + it = mElements.erase(it); + } + else + { + it++; + } + } +} + +void ParticleList::clearLocally() +{ + for (std::list::iterator it = mElements.begin(); + it != mElements.end(); it++) + { + (*it)->kill(); + } + + mElements.clear(); +} + +void ParticleList::moveTo(float x, float y) +{ + ParticleContainer::moveTo(x, y); + + for (std::list::iterator it = mElements.begin(); + it != mElements.end(); ) + { + (*it)->moveTo(x, y); + if ((*it)->isExtinct()) + { + (*it)->kill(); + it = mElements.erase(it); + } + else + { + it++; + } + } +} + +// -- particle vector ---------------------------------------- + +ParticleVector::ParticleVector(ParticleContainer *parent, bool delParent): + ParticleContainer(parent, delParent) +{} + +ParticleVector::~ParticleVector() +{} + +void ParticleVector::setLocally(int index, Particle *particle) +{ + if (index < 0) + return; + + delLocally(index); + + if (mIndexedElements.size() <= (unsigned) index) + mIndexedElements.resize(index + 1, NULL); + + if (particle) + particle->disableAutoDelete(); + mIndexedElements[index] = particle; +} + +void ParticleVector::delLocally(int index) +{ + if (index < 0) + return; + + if (mIndexedElements.size() <= (unsigned) index) + return; + + Particle *p = mIndexedElements[index]; + if (p) + { + mIndexedElements[index] = NULL; + p->kill(); + } +} + +void ParticleVector::clearLocally() +{ + for (unsigned int i = 0; i < mIndexedElements.size(); i++) + delLocally(i); +} + +void ParticleVector::moveTo(float x, float y) +{ + ParticleContainer::moveTo(x, y); + + for (std::vector::iterator it = mIndexedElements.begin(); + it != mIndexedElements.end(); it++) + { + if (*it) + { + (*it)->moveTo(x, y); + + if ((*it)->isExtinct()) + { + (*it)->kill(); + *it = NULL; + } + } + } +} + diff --git a/src/particlecontainer.h b/src/particlecontainer.h new file mode 100644 index 000000000..6502e4afd --- /dev/null +++ b/src/particlecontainer.h @@ -0,0 +1,121 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PARTICLE_CONTAINER_H +#define PARTICLE_CONTAINER_H + +#include +#include + +class Particle; + +/** + * Set of particle effects. May be stacked with other ParticleContainers. All + * operations herein affect such stacked containers, unless the operations end + * in `Locally'. + */ +class ParticleContainer +{ +public: + /** + * Constructs a new particle container and assumes responsibility for + * its parent (for all operations defined herein, except when ending in `Locally') + * + * delParent means that the destructor should also free the parent. + */ + ParticleContainer(ParticleContainer *parent = NULL, bool delParent = true); + virtual ~ParticleContainer(); + + /** + * Kills and removes all particle effects + */ + void clear(); + + /** + * Kills and removes all particle effects (only in this container) + */ + virtual void clearLocally() {} + + /** + * Sets the positions of all elements + */ + virtual void moveTo(float x, float y); + +protected: + bool mDelParent; /**< Delete mNext in destructor */ + ParticleContainer *mNext; /**< Contained container, if any */ +}; + +/** + * Linked list of particle effects. + */ +class ParticleList : public ParticleContainer +{ +public: + ParticleList(ParticleContainer *parent = NULL, bool delParent = true); + virtual ~ParticleList(); + + /** + * Takes control of and adds a particle + */ + void addLocally(Particle *); + + /** + * `kills' and removes a particle + */ + void removeLocally(Particle *); + + virtual void clearLocally(); + + virtual void moveTo(float x, float y); + +protected: + std::list mElements; /**< Contained particle effects */ +}; + +/** + * Particle container with indexing facilities + */ +class ParticleVector : public ParticleContainer +{ +public: + ParticleVector(ParticleContainer *parent = NULL, bool delParent = true); + virtual ~ParticleVector(); + + /** + * Sets a particle at a specified index. Kills the previous particle + * there, if needed. + */ + virtual void setLocally(int index, Particle *particle); + + /** + * Removes a particle at a specified index + */ + virtual void delLocally(int index); + + virtual void clearLocally(); + virtual void moveTo(float x, float y); + +protected: + std::vector mIndexedElements; +}; + +#endif diff --git a/src/particleemitter.cpp b/src/particleemitter.cpp new file mode 100644 index 000000000..90b29422f --- /dev/null +++ b/src/particleemitter.cpp @@ -0,0 +1,568 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "animationparticle.h" +#include "imageparticle.h" +#include "log.h" +#include "particle.h" +#include "particleemitter.h" +#include "rotationalparticle.h" + +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include + +#define SIN45 0.707106781f +#define DEG_RAD_FACTOR 0.017453293f + +ParticleEmitter::ParticleEmitter(xmlNodePtr emitterNode, Particle *target, + Map *map, int rotation): + mOutputPauseLeft(0), + mParticleImage(0) +{ + mMap = map; + mParticleTarget = target; + + // Initializing default values + mParticlePosX.set(0.0f); + mParticlePosY.set(0.0f); + mParticlePosZ.set(0.0f); + mParticleAngleHorizontal.set(0.0f); + mParticleAngleVertical.set(0.0f); + mParticlePower.set(0.0f); + mParticleGravity.set(0.0f); + mParticleRandomness.set(0); + mParticleBounce.set(0.0f); + mParticleFollow = false; + mParticleAcceleration.set(0.0f); + mParticleDieDistance.set(-1.0f); + mParticleMomentum.set(1.0f); + mParticleLifetime.set(-1); + mParticleFadeOut.set(0); + mParticleFadeIn.set(0); + mOutput.set(1); + mOutputPause.set(0); + mParticleAlpha.set(1.0f); + + for_each_xml_child_node(propertyNode, emitterNode) + { + if (xmlStrEqual(propertyNode->name, BAD_CAST "property")) + { + std::string name = XML::getProperty(propertyNode, "name", ""); + + if (name == "position-x") + { + mParticlePosX = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "position-y") + { + + mParticlePosY = readParticleEmitterProp(propertyNode, 0.0f); + mParticlePosY.minVal *= SIN45; + mParticlePosY.maxVal *= SIN45; + mParticlePosY.changeAmplitude *= SIN45; + } + else if (name == "position-z") + { + mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0f); + mParticlePosZ.minVal *= SIN45; + mParticlePosZ.maxVal *= SIN45; + mParticlePosZ.changeAmplitude *= SIN45; + } + else if (name == "image") + { + std::string image = XML::getProperty( + propertyNode, "value", ""); + // Don't leak when multiple images are defined + if (!image.empty() && !mParticleImage) + { + ResourceManager *resman = ResourceManager::getInstance(); + mParticleImage = resman->getImage(image); + } + } + else if (name == "horizontal-angle") + { + mParticleAngleHorizontal = + readParticleEmitterProp(propertyNode, 0.0f); + mParticleAngleHorizontal.minVal + += static_cast(rotation); + mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR; + mParticleAngleHorizontal.maxVal + += static_cast(rotation); + mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR; + mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR; + } + else if (name == "vertical-angle") + { + mParticleAngleVertical = + readParticleEmitterProp(propertyNode, 0.0f); + mParticleAngleVertical.minVal *= DEG_RAD_FACTOR; + mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR; + mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR; + } + else if (name == "power") + { + mParticlePower = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "gravity") + { + mParticleGravity = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "randomnes" || name == "randomness") // legacy bug + { + mParticleRandomness = readParticleEmitterProp(propertyNode, 0); + } + else if (name == "bounce") + { + mParticleBounce = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "lifetime") + { + mParticleLifetime = readParticleEmitterProp(propertyNode, 0); + mParticleLifetime.minVal += 1; + } + else if (name == "output") + { + mOutput = readParticleEmitterProp(propertyNode, 0); + mOutput.maxVal += 1; + } + else if (name == "output-pause") + { + mOutputPause = readParticleEmitterProp(propertyNode, 0); + mOutputPauseLeft = mOutputPause.value(0); + } + else if (name == "acceleration") + { + mParticleAcceleration = readParticleEmitterProp( + propertyNode, 0.0f); + } + else if (name == "die-distance") + { + mParticleDieDistance = readParticleEmitterProp( + propertyNode, 0.0f); + } + else if (name == "momentum") + { + mParticleMomentum = readParticleEmitterProp( + propertyNode, 1.0f); + } + else if (name == "fade-out") + { + mParticleFadeOut = readParticleEmitterProp(propertyNode, 0); + } + else if (name == "fade-in") + { + mParticleFadeIn = readParticleEmitterProp(propertyNode, 0); + } + else if (name == "alpha") + { + mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0f); + } + else if (name == "follow-parent") + { + mParticleFollow = true; + } + else + { + logger->log("Particle Engine: Warning, " + "unknown emitter property \"%s\"", + name.c_str()); + } + } + else if (xmlStrEqual(propertyNode->name, BAD_CAST "emitter")) + { + ParticleEmitter newEmitter(propertyNode, mParticleTarget, map); + mParticleChildEmitters.push_back(newEmitter); + } + else if (xmlStrEqual(propertyNode->name, BAD_CAST "rotation")) + { + ImageSet *imageset = ResourceManager::getInstance()->getImageSet( + XML::getProperty(propertyNode, "imageset", ""), + XML::getProperty(propertyNode, "width", 0), + XML::getProperty(propertyNode, "height", 0) + ); + + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + + // Get animation frames + for_each_xml_child_node(frameNode, propertyNode) + { + int delay = XML::getProperty(frameNode, "delay", 0); + int offsetX = XML::getProperty(frameNode, "offsetX", 0); + int offsetY = XML::getProperty(frameNode, "offsetY", 0); + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + + offsetY -= imageset->getHeight() - 32; + offsetX -= imageset->getWidth() / 2 - 16; + + if (xmlStrEqual(frameNode->name, BAD_CAST "frame")) + { + int index = XML::getProperty(frameNode, "index", -1); + + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + mParticleRotation.addFrame(img, delay, offsetX, offsetY); + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence")) + { + int start = XML::getProperty(frameNode, "start", -1); + int end = XML::getProperty(frameNode, "end", -1); + + if (start < 0 || end < 0) + { + logger->log1("No valid value for 'start' or 'end'"); + continue; + } + + while (end >= start) + { + Image *img = imageset->get(start); + + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + mParticleRotation.addFrame(img, delay, + offsetX, offsetY); + start++; + } + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "end")) + { + mParticleRotation.addTerminator(); + } + } // for frameNode + } + else if (xmlStrEqual(propertyNode->name, BAD_CAST "animation")) + { + ImageSet *imageset = ResourceManager::getInstance()->getImageSet( + XML::getProperty(propertyNode, "imageset", ""), + XML::getProperty(propertyNode, "width", 0), + XML::getProperty(propertyNode, "height", 0) + ); + + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + + // Get animation frames + for_each_xml_child_node(frameNode, propertyNode) + { + int delay = XML::getProperty(frameNode, "delay", 0); + int offsetX = XML::getProperty(frameNode, "offsetX", 0); + int offsetY = XML::getProperty(frameNode, "offsetY", 0); + offsetY -= imageset->getHeight() - 32; + offsetX -= imageset->getWidth() / 2 - 16; + + if (xmlStrEqual(frameNode->name, BAD_CAST "frame")) + { + int index = XML::getProperty(frameNode, "index", -1); + + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + mParticleAnimation.addFrame(img, delay, offsetX, offsetY); + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence")) + { + int start = XML::getProperty(frameNode, "start", -1); + int end = XML::getProperty(frameNode, "end", -1); + + if (start < 0 || end < 0) + { + logger->log1("No valid value for 'start' or 'end'"); + continue; + } + + while (end >= start) + { + Image *img = imageset->get(start); + + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + mParticleAnimation.addFrame(img, delay, + offsetX, offsetY); + start++; + } + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "end")) + { + mParticleAnimation.addTerminator(); + } + } // for frameNode + } + else if (xmlStrEqual(propertyNode->name, BAD_CAST "deatheffect")) + { + mDeathEffect = (const char*)propertyNode->xmlChildrenNode->content; + mDeathEffectConditions = 0x00; + if (XML::getBoolProperty(propertyNode, "on-floor", true)) + mDeathEffectConditions += Particle::DEAD_FLOOR; + if (XML::getBoolProperty(propertyNode, "on-sky", true)) + mDeathEffectConditions += Particle::DEAD_SKY; + if (XML::getBoolProperty(propertyNode, "on-other", false)) + mDeathEffectConditions += Particle::DEAD_OTHER; + if (XML::getBoolProperty(propertyNode, "on-impact", true)) + mDeathEffectConditions += Particle::DEAD_IMPACT; + if (XML::getBoolProperty(propertyNode, "on-timeout", true)) + mDeathEffectConditions += Particle::DEAD_TIMEOUT; + } + } +} + +ParticleEmitter::ParticleEmitter(const ParticleEmitter &o) +{ + *this = o; +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o) +{ + mParticlePosX = o.mParticlePosX; + mParticlePosY = o.mParticlePosY; + mParticlePosZ = o.mParticlePosZ; + mParticleAngleHorizontal = o.mParticleAngleHorizontal; + mParticleAngleVertical = o.mParticleAngleVertical; + mParticlePower = o.mParticlePower; + mParticleGravity = o.mParticleGravity; + mParticleRandomness = o.mParticleRandomness; + mParticleBounce = o.mParticleBounce; + mParticleFollow = o.mParticleFollow; + mParticleTarget = o.mParticleTarget; + mParticleAcceleration = o.mParticleAcceleration; + mParticleDieDistance = o.mParticleDieDistance; + mParticleMomentum = o.mParticleMomentum; + mParticleLifetime = o.mParticleLifetime; + mParticleFadeOut = o.mParticleFadeOut; + mParticleFadeIn = o.mParticleFadeIn; + mParticleAlpha = o.mParticleAlpha; + mMap = o.mMap; + mOutput = o.mOutput; + mOutputPause = o.mOutputPause; + mParticleImage = o.mParticleImage; + mParticleAnimation = o.mParticleAnimation; + mParticleRotation = o.mParticleRotation; + mParticleChildEmitters = o.mParticleChildEmitters; + mDeathEffectConditions = o.mDeathEffectConditions; + + mOutputPauseLeft = 0; + + if (mParticleImage) + mParticleImage->incRef(); + + return *this; +} + + +ParticleEmitter::~ParticleEmitter() +{ + if (mParticleImage) + mParticleImage->decRef(); +} + + +template ParticleEmitterProp +ParticleEmitter::readParticleEmitterProp(xmlNodePtr propertyNode, T def) +{ + ParticleEmitterProp retval; + + def = (T) XML::getFloatProperty(propertyNode, "value", (double) def); + retval.set((T) XML::getFloatProperty(propertyNode, "min", (double) def), + (T) XML::getFloatProperty(propertyNode, "max", (double) def)); + + std::string change = XML::getProperty(propertyNode, "change-func", "none"); + T amplitude = (T) XML::getFloatProperty(propertyNode, + "change-amplitude", 0.0); + int period = XML::getProperty(propertyNode, "change-period", 0); + int phase = XML::getProperty(propertyNode, "change-phase", 0); + if (change == "saw" || change == "sawtooth") + retval.setFunction(FUNC_SAW, amplitude, period, phase); + else if (change == "sine" || change == "sinewave") + retval.setFunction(FUNC_SINE, amplitude, period, phase); + else if (change == "triangle") + retval.setFunction(FUNC_TRIANGLE, amplitude, period, phase); + else if (change == "square") + retval.setFunction(FUNC_SQUARE, amplitude, period, phase); + + return retval; +} + + +std::list ParticleEmitter::createParticles(int tick) +{ + std::list newParticles; + + if (mOutputPauseLeft > 0) + { + mOutputPauseLeft--; + return newParticles; + } + mOutputPauseLeft = mOutputPause.value(tick); + + for (int i = mOutput.value(tick); i > 0; i--) + { + // Limit maximum particles + if (Particle::particleCount > Particle::maxCount) + break; + + Particle *newParticle; + if (mParticleImage) + { + std::string name = mParticleImage->getIdPath(); + if (ImageParticle::imageParticleCountByName.find(name) == + ImageParticle::imageParticleCountByName.end()) + { + ImageParticle::imageParticleCountByName[name] = 0; + } + + if (ImageParticle::imageParticleCountByName[name] > 200) + break; + + newParticle = new ImageParticle(mMap, mParticleImage); + } + else if (mParticleRotation.getLength() > 0) + { + Animation *newAnimation = new Animation(mParticleRotation); + newParticle = new RotationalParticle(mMap, newAnimation); + } + else if (mParticleAnimation.getLength() > 0) + { + Animation *newAnimation = new Animation(mParticleAnimation); + newParticle = new AnimationParticle(mMap, newAnimation); + } + else + { + newParticle = new Particle(mMap); + } + + Vector position(mParticlePosX.value(tick), + mParticlePosY.value(tick), + mParticlePosZ.value(tick)); + newParticle->moveTo(position); + + float angleH = mParticleAngleHorizontal.value(tick); + float angleV = mParticleAngleVertical.value(tick); + float power = mParticlePower.value(tick); + newParticle->setVelocity( + static_cast(cos(angleH) * cos(angleV) * power), + static_cast(sin(angleH) * cos(angleV) * power), + static_cast(sin(angleV) * power)); + + newParticle->setRandomness(mParticleRandomness.value(tick)); + newParticle->setGravity(mParticleGravity.value(tick)); + newParticle->setBounce(mParticleBounce.value(tick)); + newParticle->setFollow(mParticleFollow); + + newParticle->setDestination(mParticleTarget, + mParticleAcceleration.value(tick), + mParticleMomentum.value(tick) + ); + newParticle->setDieDistance(mParticleDieDistance.value(tick)); + + newParticle->setLifetime(mParticleLifetime.value(tick)); + newParticle->setFadeOut(mParticleFadeOut.value(tick)); + newParticle->setFadeIn(mParticleFadeIn.value(tick)); + newParticle->setAlpha(mParticleAlpha.value(tick)); + + for (std::list::iterator + i = mParticleChildEmitters.begin(); + i != mParticleChildEmitters.end(); i++) + { + newParticle->addEmitter(new ParticleEmitter(*i)); + } + + if (!mDeathEffect.empty()) + { + newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions); + } + + newParticles.push_back(newParticle); + } + + return newParticles; +} + +void ParticleEmitter::adjustSize(int w, int h) +{ + if (w == 0 || h == 0) + return; // new dimensions are illegal + + // calculate the old rectangle + int oldWidth = static_cast(mParticlePosX.maxVal + - mParticlePosX.minVal); + int oldHeight = static_cast(mParticlePosX.maxVal + - mParticlePosY.minVal); + int oldArea = oldWidth * oldHeight; + if (oldArea == 0) + { + //when the effect has no dimension it is + //not designed to be resizeable + return; + } + + // set the new dimensions + mParticlePosX.set(0, static_cast(w)); + mParticlePosY.set(0, static_cast(h)); + int newArea = w * h; + // adjust the output so that the particle density stays the same + float outputFactor = (float)newArea / (float)oldArea; + mOutput.minVal *= static_cast(outputFactor); + mOutput.maxVal *= static_cast(outputFactor); +} diff --git a/src/particleemitter.h b/src/particleemitter.h new file mode 100644 index 000000000..dd67ca62f --- /dev/null +++ b/src/particleemitter.h @@ -0,0 +1,153 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PARTICLEEMITTER_H +#define PARTICLEEMITTER_H + +#include "particleemitterprop.h" + +#include "resources/animation.h" + +#include "utils/xml.h" + +#include + +class Image; +class Map; +class Particle; + +/** + * Every Particle can have one or more particle emitters that create new + * particles when they are updated + */ +class ParticleEmitter +{ + public: + /** + * Constructor. + */ + ParticleEmitter(xmlNodePtr emitterNode, Particle *target, + Map *map, int rotation = 0); + + /** + * Copy Constructor (necessary for reference counting of particle images) + */ + ParticleEmitter(const ParticleEmitter &o); + + /** + * Assignment operator that calls the copy constructor + */ + ParticleEmitter & operator=(const ParticleEmitter &o); + + /** + * Destructor. + */ + ~ParticleEmitter(); + + /** + * Spawns new particles + * @return: a list of created particles + */ + std::list createParticles(int tick); + + /** + * Sets the target of the particles that are created + */ + void setTarget(Particle *target) + { mParticleTarget = target; }; + + /** + * Changes the size of the emitter so that the effect fills a + * rectangle of this size + */ + void adjustSize(int w, int h); + + private: + template ParticleEmitterProp + readParticleEmitterProp(xmlNodePtr propertyNode, T def); + + /** + * initial position of particles: + */ + ParticleEmitterProp mParticlePosX, mParticlePosY, mParticlePosZ; + + /** + * initial vector of particles: + */ + ParticleEmitterProp mParticleAngleHorizontal, + mParticleAngleVertical; + + /** + * Initial velocity of particles + */ + ParticleEmitterProp mParticlePower; + + /* + * Vector changing of particles: + */ + ParticleEmitterProp mParticleGravity; + ParticleEmitterProp mParticleRandomness; + ParticleEmitterProp mParticleBounce; + bool mParticleFollow; + + /* + * Properties of targeting particles: + */ + Particle *mParticleTarget; + ParticleEmitterProp mParticleAcceleration; + ParticleEmitterProp mParticleDieDistance; + ParticleEmitterProp mParticleMomentum; + + /* + * Behavior over time of the particles: + */ + ParticleEmitterProp mParticleLifetime; + ParticleEmitterProp mParticleFadeOut; + ParticleEmitterProp mParticleFadeIn; + + Map *mMap; /**< Map the particles are spawned on */ + + ParticleEmitterProp mOutput; /**< Number of particles spawned + per update */ + ParticleEmitterProp mOutputPause; /**< Pause in frames between + two spawns */ + int mOutputPauseLeft; + + /* + * Graphical representation of the particles + */ + Image *mParticleImage; /**< Particle image, if used */ + Animation mParticleAnimation; /**< Filename of particle + animation file */ + Animation mParticleRotation; /**< Filename of particle rotation file */ + ParticleEmitterProp mParticleAlpha; /**< Opacity of the + graphical representation of the particles */ + + /* + * Death effect of the particles + */ + std::string mDeathEffect; + char mDeathEffectConditions; + + /** List of emitters the spawned particles are equipped with */ + std::list mParticleChildEmitters; +}; +#endif diff --git a/src/particleemitterprop.h b/src/particleemitterprop.h new file mode 100644 index 000000000..a54bd0c2f --- /dev/null +++ b/src/particleemitterprop.h @@ -0,0 +1,116 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +/** + * Returns a random numeric value that is larger than or equal min and smaller + * than max + */ + +enum ChangeFunc +{ + FUNC_NONE = 0, + FUNC_SINE, + FUNC_SAW, + FUNC_TRIANGLE, + FUNC_SQUARE +}; + +template struct ParticleEmitterProp +{ + ParticleEmitterProp(): + minVal(0), maxVal(0), changeFunc(FUNC_NONE), + changeAmplitude(0), changePeriod(0), changePhase(0) + { + } + + void set(T min, T max) + { + minVal = min; + maxVal = max; + } + + void set(T val) + { + set(val, val); + } + + void setFunction(ChangeFunc func, T amplitude, int period, int phase) + { + changeFunc = func; + changeAmplitude = amplitude; + changePeriod = period; + changePhase = phase; + } + + T value(int tick) + { + tick += changePhase; + T val = (T) (minVal + (maxVal - minVal) + * (rand() / ((double) RAND_MAX + 1))); + + switch (changeFunc) + { + case FUNC_SINE: + val += (T) std::sin(M_PI * 2 * ((double)(tick % changePeriod) + / (double)changePeriod)) * changeAmplitude; + break; + case FUNC_SAW: + val += (T) (changeAmplitude * ((double)(tick % changePeriod) + / (double)changePeriod)) * 2 - changeAmplitude; + break; + case FUNC_TRIANGLE: + if ((tick % changePeriod) * 2 < changePeriod) + { + val += changeAmplitude - (T)((tick % changePeriod) + / (double)changePeriod) * changeAmplitude * 4; + } + else + { + val += changeAmplitude * -3 + (T)((tick % changePeriod) + / (double)changePeriod) * changeAmplitude * 4; + // I have no idea why this works but it does + } + break; + case FUNC_SQUARE: + if ((tick % changePeriod) * 2 < changePeriod) + val += changeAmplitude; + else + val -= changeAmplitude; + break; + case FUNC_NONE: + default: + //nothing + break; + } + + return val; + } + + T minVal; + T maxVal; + + ChangeFunc changeFunc; + T changeAmplitude; + int changePeriod; + int changePhase; +}; diff --git a/src/party.cpp b/src/party.cpp new file mode 100644 index 000000000..3ed7e08aa --- /dev/null +++ b/src/party.cpp @@ -0,0 +1,258 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "party.h" + +#include "actorspritemanager.h" + +class SortPartyFunctor +{ + public: + bool operator() (PartyMember* p1, PartyMember* p2) + { + if (!p1 || !p2) + return false; + if (p1->getLeader()) + return true; + if (p2->getLeader()) + return false; + + return p1->getName() < p2->getName(); + } +} partySorter; + +PartyMember::PartyMember(Party *party, int id, const std::string &name): + Avatar(name), mParty(party), mLeader(false) +{ + mId = id; +} + +Party::PartyMap Party::parties; + +Party::Party(short id): + mCanInviteUsers(false) +{ + mId = id; + parties[id] = this; +} + +Party::~Party() +{ + clearMembers(); +} + +PartyMember *Party::addMember(int id, const std::string &name) +{ + PartyMember *m; + if ((m = getMember(id))) + return m; + + m = new PartyMember(this, id, name); + + mMembers.push_back(m); + + return m; +} + +PartyMember *Party::getMember(int id) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == id) + return (*itr); + ++itr; + } + + return NULL; +} + +PartyMember *Party::getMember(const std::string &name) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->getName() == name) + return (*itr); + + ++itr; + } + + return NULL; +} + +void Party::removeMember(PartyMember *member) +{ + if (!member) + return; + + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == member->mId && + (*itr)->getName() == member->getName()) + { + PartyMember *member = (*itr); + mMembers.erase(itr); + delete member; + } + ++itr; + } +} + +void Party::removeMember(int id) +{ + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == id) + { + PartyMember *member = (*itr); + mMembers.erase(itr); + delete member; + } + ++itr; + } +} + +void Party::removeMember(const std::string &name) +{ + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->getName() == name) + { + PartyMember *member = (*itr); + mMembers.erase(itr); + delete member; + } + ++itr; + } +} + +void Party::removeFromMembers() +{ + if (!actorSpriteManager) + return; + + MemberList::iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + Being *b = actorSpriteManager->findBeing((*itr)->getID()); + if (b) + b->setParty(0); + ++itr; + } +} + +Avatar *Party::getAvatarAt(int index) +{ + return mMembers[index]; +} + +void Party::setRights(short rights) +{ + // to invite, rights must be greater than 0 + if (rights > 0) + mCanInviteUsers = true; +} + +bool Party::isMember(PartyMember *member) const +{ + if (!member) + return false; + + if (member->mParty > 0 && member->mParty != this) + return false; + + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == member->mId && + (*itr)->getName() == member->getName()) + { + return true; + } + ++itr; + } + + return false; +} + +bool Party::isMember(int id) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->mId == id) + return true; + ++itr; + } + + return false; +} + +bool Party::isMember(const std::string &name) const +{ + MemberList::const_iterator itr = mMembers.begin(), + itr_end = mMembers.end(); + while (itr != itr_end) + { + if ((*itr)->getName() == name) + return true; + ++itr; + } + + return false; +} + +void Party::getNames(std::vector &names) const +{ + names.clear(); + MemberList::const_iterator it = mMembers.begin(), + it_end = mMembers.end(); + while (it != it_end) + { + names.push_back((*it)->getName()); + ++it; + } +} + +Party *Party::getParty(short id) +{ + PartyMap::iterator it = parties.find(id); + if (it != parties.end()) + return it->second; + + return new Party(id); +} + +void Party::sort() +{ + std::sort(mMembers.begin(), mMembers.end(), partySorter); +} \ No newline at end of file diff --git a/src/party.h b/src/party.h new file mode 100644 index 000000000..f5db617a0 --- /dev/null +++ b/src/party.h @@ -0,0 +1,168 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PARTY_H +#define PARTY_H + +#include "avatar.h" + +#include "gui/widgets/avatarlistbox.h" + +#include "utils/dtor.h" + +#include +#include +#include + +class Party; + +class PartyMember : public Avatar +{ +public: + Party *getParty() const + { return mParty; } + + bool getLeader() const + { return mLeader; } + + void setLeader(bool leader) + { mLeader = leader; setDisplayBold(leader); } + +protected: + friend class Party; + + PartyMember(Party *party, int id, const std::string &name); + + Party *mParty; + bool mLeader; +}; + +class Party : public AvatarListModel +{ +public: + + /** + * Set the party's name. + */ + void setName(const std::string &name) + { mName = name; } + + /** + * Adds member to the list. + */ + PartyMember *addMember(int id, const std::string &name); + + /** + * Find a member by ID. + * + * @return the member with the given ID, or NULL if they don't exist. + */ + PartyMember *getMember(int id) const; + + /** + * Find a member by name. + * + * @return the member with the given name, or NULL if they don't exist. + */ + PartyMember *getMember(const std::string &name) const; + + /** + * Get the name of the party. + * @return returns name of the party + */ + const std::string &getName() const + { return mName; } + + /** + * Get the id of the party. + * @return Returns the id of the party + */ + short getId() const + { return mId; } + + /** + * Removes a member from the party. + */ + void removeMember(PartyMember *member); + + /** + * Removes a member from the party. + */ + void removeMember(int id); + + /** + * Removes a member from the party. + */ + void removeMember(const std::string &name); + + void clearMembers() + { delete_all(mMembers); mMembers.clear(); } + + void removeFromMembers(); + + /** + * Get size of members list. + * @return Returns the number of members in the party. + */ + int getNumberOfElements() + { return static_cast(mMembers.size()); } + + Avatar *getAvatarAt(int i); + + /** + * Get whether user can invite users to this party. + * @return Returns true if user can invite users + */ + bool getInviteRights() const + { return mCanInviteUsers; } + + void setRights(short rights); + + bool isMember(PartyMember *member) const; + + bool isMember(int id) const; + + bool isMember(const std::string &name) const; + + void getNames(std::vector &names) const; + + void sort(); + + static Party *getParty(short id); + +private: + typedef std::map PartyMap; + static PartyMap parties; + + /** + * Constructor with party id passed to it. + */ + Party(short id); + + ~Party(); + + typedef std::vector MemberList; + MemberList mMembers; + std::string mName; + short mId; + bool mCanInviteUsers; +}; + +#endif // PARTY_H diff --git a/src/playerinfo.cpp b/src/playerinfo.cpp new file mode 100644 index 000000000..a9b8c1d28 --- /dev/null +++ b/src/playerinfo.cpp @@ -0,0 +1,330 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "playerinfo.h" + +#include "client.h" +#include "equipment.h" +#include "event.h" +#include "inventory.h" +#include "listener.h" +#include "log.h" + +#include "resources/itemdb.h" +#include "resources/iteminfo.h" + +namespace PlayerInfo +{ + +class PlayerInfoListener; + +PlayerInfoListener *mListener = 0; + +PlayerInfoBackend mData; +int mCharId = 0; + +Inventory *mInventory = 0; +Equipment *mEquipment = 0; + +std::map mSpecials; +char mSpecialRechargeUpdateNeeded = 0; + +bool mTrading = false; +int mLevelProgress = 0; + +// --- Triggers --------------------------------------------------------------- + +void triggerAttr(int id, int old) +{ + Mana::Event event(EVENT_UPDATEATTRIBUTE); + event.setInt("id", id); + event.setInt("oldValue", old); + event.setInt("newValue", mData.mAttributes.find(id)->second); + Mana::Event::trigger(CHANNEL_ATTRIBUTES, event); +} + +void triggerStat(int id, const std::string &changed, int old1, int old2) +{ + StatMap::iterator it = mData.mStats.find(id); + Mana::Event event(EVENT_UPDATESTAT); + event.setInt("id", id); + event.setInt("base", it->second.base); + event.setInt("mod", it->second.mod); + event.setInt("exp", it->second.exp); + event.setInt("expNeeded", it->second.expNeed); + event.setString("changed", changed); + event.setInt("oldValue1", old1); + event.setInt("oldValue2", old2); + Mana::Event::trigger(CHANNEL_ATTRIBUTES, event); +} + +// --- Attributes ------------------------------------------------------------- + +int getAttribute(int id) +{ + IntMap::const_iterator it = mData.mAttributes.find(id); + if (it != mData.mAttributes.end()) + return it->second; + else + return 0; +} + +void setAttribute(int id, int value, bool notify) +{ + int old = mData.mAttributes[id]; + mData.mAttributes[id] = value; + if (notify) + triggerAttr(id, old); +} + +// --- Stats ------------------------------------------------------------------ + +int getStatBase(int id) +{ + StatMap::const_iterator it = mData.mStats.find(id); + if (it != mData.mStats.end()) + return it->second.base; + else + return 0; +} + +void setStatBase(int id, int value, bool notify) +{ + int old = mData.mStats[id].base; + mData.mStats[id].base = value; + if (notify) + triggerStat(id, "base", old); +} + +int getStatMod(int id) +{ + StatMap::const_iterator it = mData.mStats.find(id); + if (it != mData.mStats.end()) + return it->second.mod; + else + return 0; +} + +void setStatMod(int id, int value, bool notify) +{ + int old = mData.mStats[id].mod; + mData.mStats[id].mod = value; + if (notify) + triggerStat(id, "mod", old); +} + +int getStatEffective(int id) +{ + StatMap::const_iterator it = mData.mStats.find(id); + if (it != mData.mStats.end()) + return it->second.base + it->second.mod; + else + return 0; +} + +std::pair getStatExperience(int id) +{ + StatMap::const_iterator it = mData.mStats.find(id); + int a, b; + if (it != mData.mStats.end()) + { + a = it->second.exp; + b = it->second.expNeed; + } + else + { + a = 0; + b = 0; + } + return std::pair(a, b); +} + +void setStatExperience(int id, int have, int need, bool notify) +{ + int oldExp = mData.mStats[id].exp; + int oldExpNeed = mData.mStats[id].expNeed; + mData.mStats[id].exp = have; + mData.mStats[id].expNeed = need; + if (notify) + triggerStat(id, "exp", oldExp, oldExpNeed); +} + +// --- Inventory / Equipment -------------------------------------------------- + +Inventory *getInventory() +{ + return mInventory; +} + +void clearInventory() +{ + if (mEquipment) + mEquipment->clear(); + if (mInventory) + mInventory->clear(); +} + +void setInventoryItem(int index, int id, int amount, int refine) +{ + bool equipment = false; + int itemType = ItemDB::get(id).getType(); + if (itemType != ITEM_UNUSABLE && itemType != ITEM_USABLE) + equipment = true; + if (mInventory) + mInventory->setItem(index, id, amount, refine, equipment); +} + +Equipment *getEquipment() +{ + return mEquipment; +} + +Item *getEquipment(unsigned int slot) +{ + if (mEquipment) + return mEquipment->getEquipment(slot); + else + return 0; +} + +void setEquipmentBackend(Equipment::Backend *backend) +{ + if (mEquipment) + mEquipment->setBackend(backend); +} + +// --- Specials --------------------------------------------------------------- + +void setSpecialStatus(int id, int current, int max, int recharge) +{ + logger->log("SpecialUpdate Skill #%d -- (%d/%d) -> %d", id, current, max, + recharge); + mSpecials[id].currentMana = current; + mSpecials[id].neededMana = max; + mSpecials[id].recharge = recharge; +} + +const SpecialsMap &getSpecialStatus() +{ + return mSpecials; +} + +// --- Misc ------------------------------------------------------------------- + +void setBackend(const PlayerInfoBackend &backend) +{ + mData = backend; +} + +void setCharId(int charId) +{ + mCharId = charId; +} + +int getCharId() +{ + return mCharId; +} + +void logic() +{ + if ((mSpecialRechargeUpdateNeeded % 11) == 0) + { + mSpecialRechargeUpdateNeeded = 0; + for (SpecialsMap::iterator it = mSpecials.begin(), + it_end = mSpecials.end(); it != it_end; it++) + { + it->second.currentMana += it->second.recharge; + if (it->second.currentMana > it->second.neededMana) + it->second.currentMana = it->second.neededMana; + } + } + mSpecialRechargeUpdateNeeded++; +} + +bool isTrading() +{ + return mTrading; +} + +void setTrading(bool trading) +{ + bool notify = mTrading != trading; + mTrading = trading; + + if (notify) + { + Mana::Event event(EVENT_TRADING); + event.setInt("trading", trading); + Mana::Event::trigger(CHANNEL_STATUS, event); + } +} + +class PlayerInfoListener : Mana::Listener +{ +public: + PlayerInfoListener() + { + listen(CHANNEL_CLIENT); + listen(CHANNEL_GAME); + } + + void event(Channels channel, const Mana::Event &event) + { + if (channel == CHANNEL_CLIENT) + { + if (event.getName() == EVENT_STATECHANGE) + { + int newState = event.getInt("newState"); + + if (newState == STATE_GAME) + { + if (mInventory == 0) + { + mInventory = new Inventory(Inventory::INVENTORY); + mEquipment = new Equipment(); + } + } + } + } + else if (channel == CHANNEL_GAME) + { + if (event.getName() == EVENT_DESTRUCTED) + { + delete mInventory; + delete mEquipment; + + mInventory = 0; + mEquipment = 0; + } + } + } +}; + +void init() +{ + if (mListener) + return; + + // may be need remove it? + mListener = new PlayerInfoListener(); +} + +} // namespace PlayerInfo diff --git a/src/playerinfo.h b/src/playerinfo.h new file mode 100644 index 000000000..f91d85197 --- /dev/null +++ b/src/playerinfo.h @@ -0,0 +1,238 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLAYERINFO_H +#define PLAYERINFO_H + +#include "equipment.h" + +#include +#include + +/** + * Standard attributes for players. + */ +enum Attribute +{ + LEVEL = 0, + HP, + MAX_HP, + MP, + MAX_MP, + EXP, + EXP_NEEDED, + MONEY, + TOTAL_WEIGHT, + MAX_WEIGHT, + SKILL_POINTS, + CHAR_POINTS, + CORR_POINTS, + ATTACK_SPEED = 100, + ATTACK_RANGE = 101, + WALK_SPEED = 102 +}; + +/** + * Stat information storage structure. + */ +struct Stat +{ + int base; + int mod; + int exp; + int expNeed; +}; + +typedef std::map IntMap; +typedef std::map StatMap; + +/** + * Backend for core player information. + */ +struct PlayerInfoBackend +{ + IntMap mAttributes; + StatMap mStats; +}; + +class Equipment; +class Inventory; +class Item; + +/** + * Special information storage structure. + */ +struct Special +{ + int currentMana; + int neededMana; + int recharge; +}; + +typedef std::map SpecialsMap; + +/** + * A database like namespace which holds global info about the localplayer + * + * NOTE: 'bool notify' is used to determine if a event is to be triggered. + */ +namespace PlayerInfo +{ + +// --- Attributes ------------------------------------------------------------- + + /** + * Returns the value of the given attribute. + */ + int getAttribute(int id); + + /** + * Changes the value of the given attribute. + */ + void setAttribute(int id, int value, bool notify = true); + +// --- Stats ------------------------------------------------------------------ + + /** + * Returns the base value of the given stat. + */ + int getStatBase(int id); + + /** + * Changes the base value of the given stat. + */ + void setStatBase(int id, int value, bool notify = true); + + /** + * Returns the modifier for the given stat. + */ + int getStatMod(int id); + + /** + * Changes the modifier for the given stat. + */ + void setStatMod(int id, int value, bool notify = true); + + /** + * Returns the current effective value of the given stat. Effective is base + * + mod + */ + int getStatEffective(int id); + + /** + * Changes the level of the given stat. + */ + void setStatLevel(int id, int value, bool notify = true); + + /** + * Returns the experience of the given stat. + */ + std::pair getStatExperience(int id); + + /** + * Changes the experience of the given stat. + */ + void setStatExperience(int id, int have, int need, bool notify = true); + +// --- Inventory / Equipment -------------------------------------------------- + + /** + * Returns the player's inventory. + */ + Inventory *getInventory(); + + /** + * Clears the player's inventory and equipment. + */ + void clearInventory(); + + /** + * Changes the inventory item at the given slot. + */ + void setInventoryItem(int index, int id, int amount, int refine); + + /** + * Returns the player's equipment. + */ + Equipment *getEquipment(); + + /** + * Returns the player's equipment at the given slot. + */ + Item *getEquipment(unsigned int slot); + +// --- Specials --------------------------------------------------------------- + + /** + * Changes the status of the given special. + */ + void setSpecialStatus(int id, int current, int max, int recharge); + + /** + * Returns the status of the given special. + */ + const SpecialsMap &getSpecialStatus(); + +// --- Misc ------------------------------------------------------------------- + + /** + * Changes the internal PlayerInfoBackend reference; + */ + void setBackend(const PlayerInfoBackend &backend); + + void setCharId(int charId); + + int getCharId(); + + /** + * Does necessary updates every tick. + */ + void logic(); + + /** + * Returns true if the player is involved in a trade at the moment, false + * otherwise. + */ + bool isTrading(); + + /** + * Sets whether the player is currently involved in trade or not. + */ + void setTrading(bool trading); + + /** + * Initializes some internals. + */ + void init(); + + void triggerAttr(int id); + + void triggerAttr(int id, int old); + + void triggerStat(int id); + + void triggerStat(int id, const std::string &changed, + int old1, int old2 = 0); + + void setEquipmentBackend(Equipment::Backend *backend); + +} // namespace PlayerInfo + +#endif diff --git a/src/playerrelations.cpp b/src/playerrelations.cpp new file mode 100644 index 000000000..740da61a6 --- /dev/null +++ b/src/playerrelations.cpp @@ -0,0 +1,496 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "actorspritemanager.h" +#include "being.h" +#include "configuration.h" +#include "graphics.h" +#include "playerrelations.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +#define PLAYER_IGNORE_STRATEGY_NOP "nop" +#define PLAYER_IGNORE_STRATEGY_EMOTE0 "emote0" +#define DEFAULT_IGNORE_STRATEGY PLAYER_IGNORE_STRATEGY_EMOTE0 + +#define NAME "name" // constant for xml serialisation +#define RELATION "relation" // constant for xml serialisation + +#define IGNORE_EMOTE_TIME 100 + +// (De)serialisation class +class PlayerConfSerialiser : + public ConfigurationListManager, + std::map *> +{ + virtual ConfigurationObject *writeConfigItem( + std::pair value, + ConfigurationObject *cobj) + { + if (!value.second) + return NULL; + cobj->setValue(NAME, value.first); + cobj->setValue(RELATION, toString( + static_cast(value.second->mRelation))); + + return cobj; + } + + virtual std::map * + readConfigItem(ConfigurationObject *cobj, + std::map *container) + { + std::string name = cobj->getValue(NAME, ""); + if (name.empty()) + return container; + + if (!(*container)[name]) + { + int v = cobj->getValueInt(RELATION, + static_cast(PlayerRelation::NEUTRAL)); + + (*container)[name] = new PlayerRelation( + static_cast(v)); + } + // otherwise ignore the duplicate entry + + return container; + } +}; + +static PlayerConfSerialiser player_conf_serialiser; // stateless singleton + +const unsigned int PlayerRelation::RELATION_PERMISSIONS[RELATIONS_NR] = { + /* NEUTRAL */ 0, // we always fall back to the defaults anyway + /* FRIEND */ EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE, + /* DISREGARDED*/ EMOTE | SPEECH_FLOAT, + /* IGNORED */ 0, + /* ERASED */ INVISIBLE +}; + +PlayerRelation::PlayerRelation(Relation relation) +{ + mRelation = relation; +} + +PlayerRelationsManager::PlayerRelationsManager() : + mPersistIgnores(false), + mDefaultPermissions(PlayerRelation::DEFAULT), + mIgnoreStrategy(0) +{ +} + +PlayerRelationsManager::~PlayerRelationsManager() +{ + delete_all(mIgnoreStrategies); + + for (std::map::const_iterator it = mRelations.begin(); + it != mRelations.end(); it++) + { + delete it->second; + } +} + +void PlayerRelationsManager::clear() +{ + std::vector *names = getPlayers(); + for (std::vector::const_iterator + it = names->begin(); it != names->end(); it++) + { + removePlayer(*it); + } + delete names; + names = 0; +} + +#define PERSIST_IGNORE_LIST "persistent-player-list" +#define PLAYER_IGNORE_STRATEGY "player-ignore-strategy" +#define DEFAULT_PERMISSIONS "default-player-permissions" + +int PlayerRelationsManager::getPlayerIgnoreStrategyIndex( + const std::string &name) +{ + std::vector *strategies + = getPlayerIgnoreStrategies(); + + for (unsigned int i = 0; i < strategies->size(); i++) + { + if ((*strategies)[i]->mShortName == name) + return i; + } + + return -1; +} + +void PlayerRelationsManager::load(bool oldConfig) +{ + Configuration *cfg; + if (oldConfig) + cfg = &config; + else + cfg = &serverConfig; + clear(); + + mPersistIgnores = cfg->getValue(PERSIST_IGNORE_LIST, 1); + mDefaultPermissions = (int) cfg->getValue(DEFAULT_PERMISSIONS, + mDefaultPermissions); + + std::string ignore_strategy_name = cfg->getValue(PLAYER_IGNORE_STRATEGY, + DEFAULT_IGNORE_STRATEGY); + int ignore_strategy_index = getPlayerIgnoreStrategyIndex( + ignore_strategy_name); + + if (ignore_strategy_index >= 0) + { + setPlayerIgnoreStrategy((*getPlayerIgnoreStrategies()) + [ignore_strategy_index]); + } + + cfg->getList, + std::map *> + ("player", &(mRelations), &player_conf_serialiser); +} + + +void PlayerRelationsManager::init() +{ + load(); + + if (!mPersistIgnores) + { + clear(); // Yes, we still keep them around in the config file + // until the next update. + } + + for (std::list::const_iterator + it = mListeners.begin(); it != mListeners.end(); it++) + { + (*it)->updateAll(); + } +} + +void PlayerRelationsManager::store() +{ + serverConfig.setList::const_iterator, + std::pair, + std::map *> + ("player", + mRelations.begin(), mRelations.end(), + &player_conf_serialiser); + + serverConfig.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions); + serverConfig.setValue(PERSIST_IGNORE_LIST, mPersistIgnores); + serverConfig.setValue(PLAYER_IGNORE_STRATEGY, + mIgnoreStrategy ? mIgnoreStrategy->mShortName + : DEFAULT_IGNORE_STRATEGY); + + serverConfig.write(); +} + +void PlayerRelationsManager::signalUpdate(const std::string &name) +{ +// store(); + + for (std::list::const_iterator + it = mListeners.begin(); it != mListeners.end(); it++) + { + (*it)->updatedPlayer(name); + } + + if (actorSpriteManager) + { + Being* being = actorSpriteManager->findBeingByName( + name, Being::PLAYER); + + if (being && being->getType() == Being::PLAYER) + being->updateColors(); + } +} + +unsigned int PlayerRelationsManager::checkPermissionSilently( + const std::string &player_name, unsigned int flags) +{ + PlayerRelation *r = mRelations[player_name]; + if (!r) + { + logger->log("checkPermissionSilently1: " + + toString(mDefaultPermissions & flags)); + return mDefaultPermissions & flags; + } + else + { + unsigned int permissions = + PlayerRelation::RELATION_PERMISSIONS[r->mRelation]; + + logger->log("r->mRelation: " + + toString(static_cast(r->mRelation))); + logger->log("permissions: " + toString(permissions)); + switch (r->mRelation) + { + case PlayerRelation::NEUTRAL: + permissions = mDefaultPermissions; + break; + + case PlayerRelation::FRIEND: + permissions |= mDefaultPermissions; // widen + break; + + default: + permissions &= mDefaultPermissions; // narrow + } + + logger->log("checkPermissionSilently2: " + + toString(permissions & flags)); + return permissions & flags; + } +} + +bool PlayerRelationsManager::hasPermission(Being *being, unsigned int flags) +{ + if (!being) + return false; + + if (being->getType() == ActorSprite::PLAYER) + return hasPermission(being->getName(), flags) == flags; + return true; +} + +bool PlayerRelationsManager::hasPermission(const std::string &name, + unsigned int flags) +{ + if (!actorSpriteManager) + return false; + + unsigned int rejections = flags & ~checkPermissionSilently(name, flags); + logger->log1("PlayerRelationsManager::hasPermission"); + logger->log("name: " + name); + logger->log("flags: " + toString(flags)); + logger->log("rejections: " + toString(rejections)); + bool permitted = (rejections == 0); + + if (!permitted) + { + // execute `ignore' strategy, if possible + if (mIgnoreStrategy) + { + Being *b = actorSpriteManager->findBeingByName( + name, ActorSprite::PLAYER); + + if (b && b->getType() == ActorSprite::PLAYER) + mIgnoreStrategy->ignore(b, rejections); + } + } + + return permitted; +} + +void PlayerRelationsManager::setRelation(const std::string &player_name, + PlayerRelation::Relation relation) +{ + PlayerRelation *r = mRelations[player_name]; + if (r == NULL) + mRelations[player_name] = new PlayerRelation(relation); + else + r->mRelation = relation; + + signalUpdate(player_name); +} + +std::vector * PlayerRelationsManager::getPlayers() +{ + std::vector *retval = new std::vector(); + + for (std::map::const_iterator it = mRelations.begin(); + it != mRelations.end(); it++) + { + if (it->second) + retval->push_back(it->first); + } + + sort(retval->begin(), retval->end()); + + return retval; +} + +void PlayerRelationsManager::removePlayer(const std::string &name) +{ + if (mRelations[name]) + delete mRelations[name]; + + mRelations.erase(name); + + signalUpdate(name); +} + + +PlayerRelation::Relation PlayerRelationsManager::getRelation( + const std::string &name) +{ + if (mRelations[name]) + return mRelations[name]->mRelation; + + return PlayerRelation::NEUTRAL; +} + +//////////////////////////////////////// +// defaults + +unsigned int PlayerRelationsManager::getDefault() const +{ + return mDefaultPermissions; +} + +void PlayerRelationsManager::setDefault(unsigned int permissions) +{ + mDefaultPermissions = permissions; + + store(); + signalUpdate(""); +} + +void PlayerRelationsManager::ignoreTrade(std::string name) +{ + if (name.empty()) + return; + + PlayerRelation::Relation relation = getRelation(name); + + if (relation == PlayerRelation::IGNORED + || relation == PlayerRelation::DISREGARDED + || relation == PlayerRelation::ERASED) + { + return; + } + else + { + player_relations.setRelation(name, PlayerRelation::DISREGARDED); + } +} + + +//////////////////////////////////////// +// ignore strategies + + +class PIS_nothing : public PlayerIgnoreStrategy +{ +public: + PIS_nothing() + { + mDescription = _("Completely ignore"); + mShortName = PLAYER_IGNORE_STRATEGY_NOP; + } + + virtual void ignore(Being *being _UNUSED_, unsigned int flags _UNUSED_) + { + } +}; + +class PIS_dotdotdot : public PlayerIgnoreStrategy +{ +public: + PIS_dotdotdot() + { + mDescription = _("Print '...'"); + mShortName = "dotdotdot"; + } + + virtual void ignore(Being *being, unsigned int flags _UNUSED_) + { + if (!being) + return; + + logger->log("ignoring: " + being->getName()); + being->setSpeech("...", 500); + } +}; + + +class PIS_blinkname : public PlayerIgnoreStrategy +{ +public: + PIS_blinkname() + { + mDescription = _("Blink name"); + mShortName = "blinkname"; + } + + virtual void ignore(Being *being, unsigned int flags _UNUSED_) + { + if (!being) + return; + + logger->log("ignoring: " + being->getName()); + being->flashName(200); + } +}; + +class PIS_emote : public PlayerIgnoreStrategy +{ +public: + PIS_emote(Uint8 emote_nr, const std::string &description, + const std::string &shortname) : + mEmotion(emote_nr) + { + mDescription = description; + mShortName = shortname; + } + + virtual void ignore(Being *being, unsigned int flags _UNUSED_) + { + if (!being) + return; + + logger->log("ignoring: " + being->getName()); + being->setEmote(mEmotion, IGNORE_EMOTE_TIME); + } + Uint8 mEmotion; +}; + + + +std::vector * +PlayerRelationsManager::getPlayerIgnoreStrategies() +{ + if (mIgnoreStrategies.size() == 0) + { + // not initialised yet? + mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE, + _("Floating '...' bubble"), + PLAYER_IGNORE_STRATEGY_EMOTE0)); + mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE + 1, + _("Floating bubble"), + "emote1")); + mIgnoreStrategies.push_back(new PIS_nothing()); + mIgnoreStrategies.push_back(new PIS_dotdotdot()); + mIgnoreStrategies.push_back(new PIS_blinkname()); + } + return &mIgnoreStrategies; +} + + +PlayerRelationsManager player_relations; diff --git a/src/playerrelations.h b/src/playerrelations.h new file mode 100644 index 000000000..1c7864580 --- /dev/null +++ b/src/playerrelations.h @@ -0,0 +1,251 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PLAYER_RELATIONS_H +#define PLAYER_RELATIONS_H + +#include +#include +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Being; + +struct PlayerRelation +{ + static const unsigned int EMOTE = (1 << 0); + static const unsigned int SPEECH_FLOAT = (1 << 1); + static const unsigned int SPEECH_LOG = (1 << 2); + static const unsigned int WHISPER = (1 << 3); + static const unsigned int TRADE = (1 << 4); + static const unsigned int INVISIBLE = (1 << 5); + + static const unsigned int RELATIONS_NR = 5; + static const unsigned int RELATION_PERMISSIONS[RELATIONS_NR]; + + static const unsigned int DEFAULT = EMOTE + | SPEECH_FLOAT + | SPEECH_LOG + | WHISPER + | TRADE; + enum Relation + { + NEUTRAL = 0, + FRIEND = 1, + DISREGARDED = 2, + IGNORED = 3, + ERASED = 4 + }; + + PlayerRelation(Relation relation); + + Relation mRelation; // bitmask for all of the above +}; + + +/** + * Ignore strategy: describes how we should handle ignores. + */ +class PlayerIgnoreStrategy +{ + public: + std::string mDescription; + std::string mShortName; + + virtual ~PlayerIgnoreStrategy() + { } + + /** + * Handle the ignoring of the indicated action by the indicated player. + */ + virtual void ignore(Being *being, unsigned int flags) = 0; +}; + +class PlayerRelationsListener +{ + public: + PlayerRelationsListener() + { } + + virtual ~PlayerRelationsListener() + { } + + virtual void updatedPlayer(const std::string &name) = 0; + virtual void updateAll() = 0; +}; + +/** + * Player relations class, represents any particular relations and/or + * preferences the user of the local client has wrt other players (identified + * by std::string). + */ +class PlayerRelationsManager +{ + public: + PlayerRelationsManager(); + ~PlayerRelationsManager(); + + /** + * Initialise player relations manager (load config file etc.) + */ + void init(); + + /** + * Load configuration from our config file, or substitute defaults. + */ + void load(bool oldConfig = false); + + /** + * Save configuration to our config file. + */ + void store(); + + /** + * Determines whether the player in question is being ignored, filtered by + * the specified flags. + */ + unsigned int checkPermissionSilently(const std::string &player_name, + unsigned int flags); + + /** + * Tests whether the player in question is being ignored for any of the + * actions in the specified flags. If so, trigger appropriate side effects + * if requested by the player. + */ + bool hasPermission(Being *being, unsigned int flags); + + bool hasPermission(const std::string &being, unsigned int flags); + + /** + * Updates the relationship with this player. + */ + void setRelation(const std::string &name, + PlayerRelation::Relation relation); + + /** + * Updates the relationship with this player. + */ + PlayerRelation::Relation getRelation(const std::string &name); + + /** + * Deletes the information recorded for a player. + */ + void removePlayer(const std::string &name); + + /** + * Retrieves the default permissions. + */ + unsigned int getDefault() const; + + /** + * Sets the default permissions. + */ + void setDefault(unsigned int permissions); + + /** + * Retrieves all known player ignore strategies. + * + * The player ignore strategies are allocated statically and must + * not be deleted. + */ + std::vector *getPlayerIgnoreStrategies(); + + /** + * Return the current player ignore strategy. + * + * \return A player ignore strategy, or NULL + */ + PlayerIgnoreStrategy *getPlayerIgnoreStrategy() const + { return mIgnoreStrategy; } + + /** + * Sets the strategy to call when ignoring players. + */ + void setPlayerIgnoreStrategy(PlayerIgnoreStrategy *strategy) + { mIgnoreStrategy = strategy; } + + /** + * For a given ignore strategy short name, find the appropriate index + * in the ignore strategies vector. + * + * \param The short name of the ignore strategy to look up + * \return The appropriate index, or -1 + */ + int getPlayerIgnoreStrategyIndex(const std::string &shortname); + + /** + * Retrieves a sorted vector of all players for which we have any + * relations recorded. + */ + std::vector *getPlayers(); + + /** + * Removes all recorded player info. + */ + void clear(); + + /** + * Do we persist our `ignore' setup? + */ + bool getPersistIgnores() const + { return mPersistIgnores; } + + void ignoreTrade(std::string name); + + /** + * Change the `ignore persist' flag. + * + * @param value Whether to persist ignores + */ + void setPersistIgnores(bool value) + { mPersistIgnores = value; } + + void addListener(PlayerRelationsListener *listener) + { mListeners.push_back(listener); } + + void removeListener(PlayerRelationsListener *listener) + { mListeners.remove(listener); } + + private: + void signalUpdate(const std::string &name); + + bool mPersistIgnores; // If NOT set, we delete the + // ignored data upon reloading + unsigned int mDefaultPermissions; + + PlayerIgnoreStrategy *mIgnoreStrategy; + std::map mRelations; + std::list mListeners; + std::vector mIgnoreStrategies; +}; + + +extern PlayerRelationsManager player_relations; // singleton representation + // of player relations + + +#endif // PLAYER_RELATIONS_H diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 000000000..6b215b22c --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,45 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "position.h" + +std::ostream& operator <<(std::ostream &os, const Position &p) +{ + os << "(" << p.x << ", " << p.y << ")"; + return os; +} + +std::ostream& operator <<(std::ostream &os, const Path &path) +{ + Path::const_iterator i = path.begin(), i_end = path.end(); + + os << "("; + while (i != i_end) + { + os << *i; + ++i; + if (i != i_end) + os << ", "; + } + os << ")"; + + return os; +} diff --git a/src/position.h b/src/position.h new file mode 100644 index 000000000..454709c08 --- /dev/null +++ b/src/position.h @@ -0,0 +1,55 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef POSITION_H +#define POSITION_H + +#include +#include + +/** + * A position along a being's path. + */ +struct Position +{ + Position(int x, int y): + x(x), y(y) + { } + + int x; + int y; +}; + +typedef std::list Path; +typedef Path::iterator PathIterator; + +/** + * Appends a string representation of a position to the output stream. + */ +std::ostream& operator <<(std::ostream &os, const Position &p); + +/** + * Appends a string representation of a path (sequence of positions) to the + * output stream. + */ +std::ostream& operator <<(std::ostream &os, const Path &path); + +#endif // POSITION_H diff --git a/src/properties.h b/src/properties.h new file mode 100644 index 000000000..adec31d70 --- /dev/null +++ b/src/properties.h @@ -0,0 +1,126 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include "log.h" +#include +#include +#include + +/** + * A class holding a set of properties. + */ +class Properties +{ + public: + /** + * Destructor. + */ + virtual ~Properties() + { } + + /** + * Get a map property. + * + * @param name The name of the property. + * @param def Default value, empty string by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + const std::string getProperty(const std::string &name, + const std::string &def = "") const + { + PropertyMap::const_iterator i = mProperties.find(name); + return (i != mProperties.end()) ? i->second : def; + } + + /** + * Gets a map property as a float. + * + * @param name The name of the property. + * @param def Default value, 0.0f by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + float getFloatProperty(const std::string &name, float def = 0.0f) const + { + PropertyMap::const_iterator i = mProperties.find(name); + float ret = def; + if (i != mProperties.end()) + { + std::stringstream ss; + ss.str(i->second); + ss >> ret; + } + return ret; + } + + /** + * Gets a map property as a boolean. + * + * @param name The name of the property. + * @param def Default value, false by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + bool getBoolProperty(const std::string &name, bool def = false) const + { + PropertyMap::const_iterator i = mProperties.find(name); + bool ret = def; + if (i != mProperties.end()) + { + if (i->second == "true") + ret = true; + if (i->second == "false") + ret = false; + } + return ret; + } + + /** + * Returns whether a certain property is available. + * + * @param name The name of the property. + * @return true when a property is defined, + * false otherwise. + */ + bool hasProperty(const std::string &name) const + { return (mProperties.find(name) != mProperties.end()); } + + /** + * Set a map property. + * + * @param name The name of the property. + * @param value The value of the property. + */ + void setProperty(const std::string &name, const std::string &value) + { mProperties[name] = value; } + + + private: + typedef std::map PropertyMap; + PropertyMap mProperties; + +}; + +#endif diff --git a/src/resources/action.cpp b/src/resources/action.cpp new file mode 100644 index 000000000..923aa72c9 --- /dev/null +++ b/src/resources/action.cpp @@ -0,0 +1,52 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/action.h" + +#include "resources/animation.h" + +#include "utils/dtor.h" + +Action::Action() +{ +} + +Action::~Action() +{ + delete_all(mAnimations); +} + +Animation *Action::getAnimation(int direction) const +{ + Animations::const_iterator i = mAnimations.find(direction); + + // When the given direction is not available, return the first one. + // (either DEFAULT, or more usually DOWN). + if (i == mAnimations.end()) + i = mAnimations.begin(); + + return (i == mAnimations.end()) ? NULL : i->second; +} + +void Action::setAnimation(int direction, Animation *animation) +{ + mAnimations[direction] = animation; +} diff --git a/src/resources/action.h b/src/resources/action.h new file mode 100644 index 000000000..3bd7b75aa --- /dev/null +++ b/src/resources/action.h @@ -0,0 +1,51 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ACTION_H +#define ACTION_H + +#include + +#include + +class Animation; + +/** + * An action consists of several animations, one for each direction. + */ +class Action +{ + public: + Action(); + + ~Action(); + + void setAnimation(int direction, Animation *animation); + + Animation *getAnimation(int direction) const; + + protected: + typedef std::map Animations; + typedef Animations::iterator AnimationIterator; + Animations mAnimations; +}; + +#endif diff --git a/src/resources/ambientlayer.cpp b/src/resources/ambientlayer.cpp new file mode 100644 index 000000000..d80f380dd --- /dev/null +++ b/src/resources/ambientlayer.cpp @@ -0,0 +1,126 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/ambientlayer.h" + +#include "graphics.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +AmbientLayer::AmbientLayer(Image *img, float parallax, + float speedX, float speedY, bool keepRatio): + mImage(img), mParallax(parallax), + mPosX(0), mPosY(0), + mSpeedX(speedX), mSpeedY(speedY), + mKeepRatio(keepRatio) +{ + if (!mImage) + return; + + if (keepRatio && !mImage->useOpenGL() + /*&& defaultScreenWidth != 0 + && defaultScreenHeight != 0*/ + && graphics->getWidth() != defaultScreenWidth + && graphics->getHeight() != defaultScreenHeight) + { + // Rescale the overlay to keep the ratio as if we were on + // the default resolution... + Image *rescaledOverlay = mImage->SDLgetScaledImage( + static_cast(mImage->getWidth()) / defaultScreenWidth + * graphics->getWidth(), static_cast(mImage->getHeight()) + / defaultScreenHeight * graphics->getHeight()); + + if (rescaledOverlay) + { + // Replace the resource with the new one... + std::string idPath = mImage->getIdPath() + "_rescaled"; + ResourceManager::getInstance()->addResource( + idPath, rescaledOverlay); + mImage = rescaledOverlay; + rescaledOverlay->incRef(); + } + else + { + mImage->incRef(); + } + } + else + { + mImage->incRef(); + } +} + +AmbientLayer::~AmbientLayer() +{ + if (mImage) + mImage->decRef(); +} + +void AmbientLayer::update(int timePassed, float dx, float dy) +{ + if (!mImage) + return; + + // Self scrolling of the overlay + mPosX -= mSpeedX * static_cast(timePassed) / 10; + mPosY -= mSpeedY * static_cast(timePassed) / 10; + + // Parallax scrolling + mPosX += dx * mParallax; + mPosY += dy * mParallax; + + int imgW = mImage->getWidth(); + int imgH = mImage->getHeight(); + + // Wrap values + while (mPosX > imgW) + mPosX -= static_cast(imgW); + while (mPosX < 0) + mPosX += static_cast(imgW); + + while (mPosY > imgH) + mPosY -= static_cast(imgH); + while (mPosY < 0) + mPosY += static_cast(imgH); +} + +void AmbientLayer::draw(Graphics *graphics, int x, int y) +{ + if (!mImage) + return; + + if (!mImage->useOpenGL() || !mKeepRatio) + { + graphics->drawImagePattern(mImage, static_cast(-mPosX), + static_cast(-mPosY), x + static_cast(mPosX), + y + static_cast(mPosY)); + } + else + { + graphics->drawRescaledImagePattern(mImage, static_cast(-mPosX), + static_cast(-mPosY), x + static_cast(mPosX), + y + static_cast(mPosY), + static_cast(mImage->getWidth()) + / defaultScreenWidth * graphics->getWidth(), + static_cast(mImage->getHeight()) / defaultScreenHeight + * graphics->getHeight()); + } +} diff --git a/src/resources/ambientlayer.h b/src/resources/ambientlayer.h new file mode 100644 index 000000000..928a568d2 --- /dev/null +++ b/src/resources/ambientlayer.h @@ -0,0 +1,59 @@ +/* + * The Mana Client + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RESOURCES_AMBIENTOVERLAY_H +#define RESOURCES_AMBIENTOVERLAY_H + +class Graphics; +class Image; + +class AmbientLayer +{ + public: + /** + * Constructor. + * + * @param img the image this overlay displays + * @param parallax scroll factor based on camera position + * @param speedX scrolling speed in x-direction + * @param speedY scrolling speed in y-direction + * @param keepRatio rescale the image to keep + * the same ratio than in 800x600 resolution mode. + */ + AmbientLayer(Image *img, float parallax, + float speedX, float speedY, bool keepRatio = false); + + ~AmbientLayer(); + + void update(int timePassed, float dx, float dy); + + void draw(Graphics *graphics, int x, int y); + + private: + Image *mImage; + float mParallax; + float mPosX; /**< Current layer X position. */ + float mPosY; /**< Current layer Y position. */ + float mSpeedX; /**< Scrolling speed in X direction. */ + float mSpeedY; /**< Scrolling speed in Y direction. */ + bool mKeepRatio; /**< Keep overlay ratio on every resolution */ +}; + +#endif diff --git a/src/resources/ambientoverlay.cpp b/src/resources/ambientoverlay.cpp new file mode 100644 index 000000000..8f579c8c7 --- /dev/null +++ b/src/resources/ambientoverlay.cpp @@ -0,0 +1,126 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/ambientoverlay.h" + +#include "graphics.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +AmbientOverlay::AmbientOverlay(Image *img, float parallax, + float speedX, float speedY, bool keepRatio): + mImage(img), mParallax(parallax), + mPosX(0), mPosY(0), + mSpeedX(speedX), mSpeedY(speedY), + mKeepRatio(keepRatio) +{ + if (!mImage) + return; + + if (mImage && keepRatio && !mImage->useOpenGL() + /*&& defaultScreenWidth != 0 + && defaultScreenHeight != 0*/ + && graphics->getWidth() != defaultScreenWidth + && graphics->getHeight() != defaultScreenHeight) + { + // Rescale the overlay to keep the ratio as if we were on + // the default resolution... + Image *rescaledOverlay = mImage->SDLgetScaledImage( + static_cast(mImage->getWidth()) / defaultScreenWidth + * graphics->getWidth(), static_cast(mImage->getHeight()) + / defaultScreenHeight * graphics->getHeight()); + + if (rescaledOverlay) + { + // Replace the resource with the new one... + std::string idPath = mImage->getIdPath() + "_rescaled"; + ResourceManager::getInstance()->addResource( + idPath, rescaledOverlay); + mImage = rescaledOverlay; + } + else + { + mImage->incRef(); + } + } + else + { + mImage->incRef(); + } +} + +AmbientOverlay::~AmbientOverlay() +{ + if (mImage) + mImage->decRef(); +} + +void AmbientOverlay::update(int timePassed, float dx, float dy) +{ + if (!mImage) + return; + + // Self scrolling of the overlay + mPosX -= mSpeedX * static_cast(timePassed) / 10; + mPosY -= mSpeedY * static_cast(timePassed) / 10; + + // Parallax scrolling + mPosX += dx * mParallax; + mPosY += dy * mParallax; + + int imgW = mImage->getWidth(); + int imgH = mImage->getHeight(); + + // Wrap values + while (mPosX > imgW) + mPosX -= static_cast(imgW); + while (mPosX < 0) + mPosX += static_cast(imgW); + + while (mPosY > imgH) + mPosY -= static_cast(imgH); + while (mPosY < 0) + mPosY += static_cast(imgH); +} + +void AmbientOverlay::draw(Graphics *graphics, int x, int y) +{ + if (!mImage) + return; + + if (!mImage->useOpenGL() || !mKeepRatio) + { + graphics->drawImagePattern(mImage, static_cast(-mPosX), + static_cast(-mPosY), x + static_cast(mPosX), + y + static_cast(mPosY)); + } + else + { + graphics->drawRescaledImagePattern(mImage, static_cast(-mPosX), + static_cast(-mPosY), x + static_cast(mPosX), + y + static_cast(mPosY), + static_cast(mImage->getWidth()) / defaultScreenWidth + * graphics->getWidth(), + static_cast(mImage->getHeight()) / defaultScreenHeight + * graphics->getHeight()); + } +} diff --git a/src/resources/ambientoverlay.h b/src/resources/ambientoverlay.h new file mode 100644 index 000000000..483ee98ed --- /dev/null +++ b/src/resources/ambientoverlay.h @@ -0,0 +1,60 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RESOURCES_AMBIENTOVERLAY_H +#define RESOURCES_AMBIENTOVERLAY_H + +class Graphics; +class Image; + +class AmbientOverlay +{ + public: + /** + * Constructor. + * + * @param img the image this overlay displays + * @param parallax scroll factor based on camera position + * @param speedX scrolling speed in x-direction + * @param speedY scrolling speed in y-direction + * @param keepRatio rescale the image to keep + * the same ratio than in 800x600 resolution mode. + */ + AmbientOverlay(Image *img, float parallax, + float speedX, float speedY, bool keepRatio = false); + + ~AmbientOverlay(); + + void update(int timePassed, float dx, float dy); + + void draw(Graphics *graphics, int x, int y); + + private: + Image *mImage; + float mParallax; + float mPosX; /**< Current layer X position. */ + float mPosY; /**< Current layer Y position. */ + float mSpeedX; /**< Scrolling speed in X direction. */ + float mSpeedY; /**< Scrolling speed in Y direction. */ + bool mKeepRatio; /**< Keep overlay ratio on every resolution */ +}; + +#endif diff --git a/src/resources/animation.cpp b/src/resources/animation.cpp new file mode 100644 index 000000000..f6ececba0 --- /dev/null +++ b/src/resources/animation.cpp @@ -0,0 +1,46 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/animation.h" + +#include "utils/dtor.h" + +Animation::Animation(): + mDuration(0) +{ +} + +void Animation::addFrame(Image *image, int delay, int offsetX, int offsetY) +{ + Frame frame = { image, delay, offsetX, offsetY }; + mFrames.push_back(frame); + mDuration += delay; +} + +void Animation::addTerminator() +{ + addFrame(NULL, 0, 0, 0); +} + +bool Animation::isTerminator(const Frame &candidate) +{ + return (candidate.image == NULL); +} diff --git a/src/resources/animation.h b/src/resources/animation.h new file mode 100644 index 000000000..e472adc40 --- /dev/null +++ b/src/resources/animation.h @@ -0,0 +1,90 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ANIMATION_H +#define ANIMATION_H + +#include + +#include + +class Image; + +/** + * A single frame in an animation, with a delay and an offset. + */ +struct Frame +{ + Image *image; + int delay; + int offsetX; + int offsetY; +}; + +/** + * An animation consists of several frames, each with their own delay and + * offset. + */ +class Animation +{ + public: + Animation(); + + /** + * Appends a new animation at the end of the sequence. + */ + void addFrame(Image *image, int delay, int offsetX, int offsetY); + + /** + * Appends an animation terminator that states that the animation + * should not loop. + */ + void addTerminator(); + + /** + * Returns the frame at the specified index. + */ + Frame *getFrame(int index) + { return &(mFrames[index]); } + + /** + * Returns the length of this animation in frames. + */ + unsigned int getLength() const + { return static_cast(mFrames.size()); } + + /** + * Returns the duration of this animation. + */ + int getDuration() const + { return mDuration; } + + /** + * Determines whether the given animation frame is a terminator. + */ + static bool isTerminator(const Frame &phase); + + protected: + std::vector mFrames; + int mDuration; +}; + +#endif diff --git a/src/resources/beinginfo.cpp b/src/resources/beinginfo.cpp new file mode 100644 index 000000000..1b2ed3be1 --- /dev/null +++ b/src/resources/beinginfo.cpp @@ -0,0 +1,115 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/beinginfo.h" + +#include "log.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +BeingInfo *BeingInfo::Unknown = new BeingInfo; + +BeingInfo::BeingInfo(): + mName(_("unnamed")), + mTargetCursorSize(ActorSprite::TC_MEDIUM), + mWalkMask(Map::BLOCKMASK_WALL | Map::BLOCKMASK_CHARACTER + | Map::BLOCKMASK_MONSTER), + mBlockType(Map::BLOCKTYPE_CHARACTER), + mTargetOffsetX(0), mTargetOffsetY(0), + mMaxHP(0), mStaticMaxHP(false) +{ + SpriteDisplay display; + display.sprites.push_back(SpriteReference::Empty); + + setDisplay(display); +} + +BeingInfo::~BeingInfo() +{ + delete_all(mSounds); + delete_all(mAttacks); + mSounds.clear(); +} + +void BeingInfo::setDisplay(SpriteDisplay display) +{ + mDisplay = display; +} + +void BeingInfo::setTargetCursorSize(const std::string &size) +{ + if (size == "small") + { + setTargetCursorSize(ActorSprite::TC_SMALL); + } + else if (size == "medium") + { + setTargetCursorSize(ActorSprite::TC_MEDIUM); + } + else if (size == "large") + { + setTargetCursorSize(ActorSprite::TC_LARGE); + } + else + { + logger->log("Unknown target cursor type \"%s\" for %s - using medium " + "sized one", size.c_str(), getName().c_str()); + setTargetCursorSize(ActorSprite::TC_MEDIUM); + } +} + +void BeingInfo::addSound(SoundEvent event, const std::string &filename) +{ + if (mSounds.find(event) == mSounds.end()) + mSounds[event] = new std::vector; + + if (mSounds[event]) + mSounds[event]->push_back("sfx/" + filename); +} + +const std::string &BeingInfo::getSound(SoundEvent event) const +{ + static std::string empty(""); + + SoundEvents::const_iterator i = mSounds.find(event); + return (i == mSounds.end()) ? empty : + i->second->at(rand() % i->second->size()); +} + +const Attack *BeingInfo::getAttack(int type) const +{ + // need remove in destructor? + static Attack *empty = new Attack(SpriteAction::ATTACK, "", ""); + + Attacks::const_iterator i = mAttacks.find(type); + return (i == mAttacks.end()) ? empty : (*i).second; +} + +void BeingInfo::addAttack(int id, std::string action, + const std::string &particleEffect, + const std::string &missileParticle) +{ + if (mAttacks[id]) + delete mAttacks[id]; + + mAttacks[id] = new Attack(action, particleEffect, missileParticle); +} diff --git a/src/resources/beinginfo.h b/src/resources/beinginfo.h new file mode 100644 index 000000000..08fa1443e --- /dev/null +++ b/src/resources/beinginfo.h @@ -0,0 +1,161 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BEINGINFO_H +#define BEINGINFO_H + +#include "actorsprite.h" + +#include "resources/spritedef.h" + +#include +#include +#include +#include + +struct Attack +{ + std::string action; + std::string particleEffect; + std::string missileParticle; + + Attack(std::string action, std::string particleEffect, + std::string missileParticle) + { + this->action = action; + this->particleEffect = particleEffect; + this->missileParticle = missileParticle; + } +}; + +typedef std::map Attacks; + +enum SoundEvent +{ + SOUND_EVENT_HIT = 0, + SOUND_EVENT_MISS, + SOUND_EVENT_HURT, + SOUND_EVENT_DIE +}; + +typedef std::map* > SoundEvents; + +/** + * Holds information about a certain type of monster. This includes the name + * of the monster, the sprite to display and the sounds the monster makes. + * + * @see MonsterDB + * @see NPCDB + */ +class BeingInfo +{ + public: + static BeingInfo *Unknown; + + BeingInfo(); + + ~BeingInfo(); + + void setName(const std::string &name) { mName = name; } + + const std::string &getName() const + { return mName; } + + void setDisplay(SpriteDisplay display); + + const SpriteDisplay &getDisplay() const + { return mDisplay; } + + void setTargetCursorSize(const std::string &size); + + void setTargetCursorSize(ActorSprite::TargetCursorSize targetSize) + { mTargetCursorSize = targetSize; } + + ActorSprite::TargetCursorSize getTargetCursorSize() const + { return mTargetCursorSize; } + + void addSound(SoundEvent event, const std::string &filename); + + const std::string &getSound(SoundEvent event) const; + + void addAttack(int id, std::string action, + const std::string &particleEffect, + const std::string &missileParticle); + + const Attack *getAttack(int type) const; + + void setWalkMask(unsigned char mask) + { mWalkMask = mask; } + + /** + * Gets the way the being is blocked by other objects + */ + unsigned char getWalkMask() const + { return mWalkMask; } + + void setBlockType(Map::BlockType blockType) + { mBlockType = blockType; } + + Map::BlockType getBlockType() const + { return mBlockType; } + + void setTargetOffsetX(int n) + { mTargetOffsetX = n; } + + int getTargetOffsetX() const + { return mTargetOffsetX; } + + void setTargetOffsetY(int n) + { mTargetOffsetY = n; } + + int getTargetOffsetY() const + { return mTargetOffsetY; } + + void setMaxHP(int n) + { mMaxHP = n; } + + int getMaxHP() const + { return mMaxHP; } + + bool isStaticMaxHP() const + { return mStaticMaxHP; } + + void setStaticMaxHP(bool n) + { mStaticMaxHP = n; } + + private: + SpriteDisplay mDisplay; + std::string mName; + ActorSprite::TargetCursorSize mTargetCursorSize; + SoundEvents mSounds; + Attacks mAttacks; + unsigned char mWalkMask; + Map::BlockType mBlockType; + int mTargetOffsetX; + int mTargetOffsetY; + int mMaxHP; + bool mStaticMaxHP; +}; + +typedef std::map BeingInfos; +typedef BeingInfos::iterator BeingInfoIterator; + +#endif // BEINGINFO_H diff --git a/src/resources/colordb.cpp b/src/resources/colordb.cpp new file mode 100644 index 000000000..97842b02d --- /dev/null +++ b/src/resources/colordb.cpp @@ -0,0 +1,115 @@ +/* + * Color database + * Copyright (C) 2008 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/colordb.h" + +#include "log.h" + +#include "utils/xml.h" + +#include + +namespace +{ + ColorDB::Colors mColors; + bool mLoaded = false; + std::string mFail = "#ffffff"; +} + +void ColorDB::load() +{ + if (mLoaded) + unload(); + + XML::Document *doc = new XML::Document("hair.xml"); + xmlNodePtr root = doc->rootNode(); + bool hairXml = true; + + if (!root || !xmlStrEqual(root->name, BAD_CAST "colors")) + { + logger->log1("Trying to fall back on colors.xml"); + + hairXml = false; + + delete doc; + doc = new XML::Document("colors.xml"); + root = doc->rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "colors")) + { + logger->log1("ColorDB: Failed to find any color files."); + mColors[0] = mFail; + mLoaded = true; + + delete doc; + + return; + } + } + for_each_xml_child_node(node, root) + { + if (xmlStrEqual(node->name, BAD_CAST "color")) + { + int id = XML::getProperty(node, "id", 0); + + if (mColors.find(id) != mColors.end()) + logger->log("ColorDB: Redefinition of dye ID %d", id); + + mColors[id] = hairXml ? + XML::getProperty(node, "value", "#FFFFFF") : + XML::getProperty(node, "dye", "#FFFFFF"); + } + } + + delete doc; + + mLoaded = true; +} + +void ColorDB::unload() +{ + logger->log1("Unloading color database..."); + + mColors.clear(); + mLoaded = false; +} + +std::string &ColorDB::get(int id) +{ + if (!mLoaded) + load(); + + ColorIterator i = mColors.find(id); + + if (i == mColors.end()) + { + logger->log("ColorDB: Error, unknown dye ID# %d", id); + return mFail; + } + else + { + return i->second; + } +} + +int ColorDB::size() +{ + return static_cast(mColors.size()); +} diff --git a/src/resources/colordb.h b/src/resources/colordb.h new file mode 100644 index 000000000..57b523882 --- /dev/null +++ b/src/resources/colordb.h @@ -0,0 +1,51 @@ +/* + * Color database + * Copyright (C) 2008 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COLOR_MANAGER_H +#define COLOR_MANAGER_H + +#include +#include + +/** + * Color information database. + */ +namespace ColorDB +{ + /** + * Loads the color data from colors.xml. + */ + void load(); + + /** + * Clear the color data + */ + void unload(); + + std::string &get(int id); + + int size(); + + // Color DB + typedef std::map Colors; + typedef Colors::iterator ColorIterator; +} + +#endif diff --git a/src/resources/dye.cpp b/src/resources/dye.cpp new file mode 100644 index 000000000..6f9609453 --- /dev/null +++ b/src/resources/dye.cpp @@ -0,0 +1,320 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/dye.h" + +#include "log.h" + +#include +#include + +DyePalette::DyePalette(const std::string &description) +{ + int size = static_cast(description.length()); + if (size == 0) + return; + if (description[0] != '#') + { + // TODO: load palette from file. + return; + } + + int pos = 1; + for ( ; ; ) + { + if (pos + 6 > size) + break; + + int v = 0; + for (int i = 0; i < 6; ++i) + { + char c = description[pos + i]; + int n; + + if ('0' <= c && c <= '9') + n = c - '0'; + else if ('A' <= c && c <= 'F') + n = c - 'A' + 10; + else if ('a' <= c && c <= 'f') + n = c - 'a' + 10; + else + goto error; + + v = (v << 4) | n; + } + Color c = + { + { + static_cast(v >> 16), + static_cast(v >> 8), + static_cast(v) + } + }; + mColors.push_back(c); + pos += 6; + + if (pos == size) + return; + if (description[pos] != ',') + break; + + ++pos; + } + + error: + logger->log("Error, invalid embedded palette: %s", description.c_str()); +} + +/* +void DyePalette::addFirstColor(const int color[3]) +{ + Color c = { {color[0], color[1], color[2]} }; + mColors.insert(mColors.begin(), c); +} + +void DyePalette::addLastColor(const int color[3]) +{ + Color c = { {color[0], color[1], color[2]} }; + mColors.push_back(c); +} +*/ + +void DyePalette::getColor(int intensity, int color[3]) const +{ + if (intensity == 0) + { + color[0] = 0; + color[1] = 0; + color[2] = 0; + return; + } + + int last = static_cast(mColors.size()); + if (last == 0) return; + + int i = intensity * last / 255; + int t = intensity * last % 255; + + int j = t != 0 ? i : i - 1; + + if (j >= last) + j = 0; + + // Get the exact color if any, the next color otherwise. + int r2 = mColors[j].value[0], + g2 = mColors[j].value[1], + b2 = mColors[j].value[2]; + + if (t == 0) + { + // Exact color. + color[0] = r2; + color[1] = g2; + color[2] = b2; + return; + } + + // Get the previous color. First color is implicitly black. + int r1 = 0, g1 = 0, b1 = 0; + if (i > 0) + { + r1 = mColors[i - 1].value[0]; + g1 = mColors[i - 1].value[1]; + b1 = mColors[i - 1].value[2]; + } + + // Perform a linear interpolation. + color[0] = ((255 - t) * r1 + t * r2) / 255; + color[1] = ((255 - t) * g1 + t * g2) / 255; + color[2] = ((255 - t) * b1 + t * b2) / 255; +} + +void DyePalette::getColor(double intensity, int color[3]) const +{ + // Nothing to do here + if (mColors.size() == 0) + return; + + // Force range + if (intensity > 1.0) + intensity = 1.0; + else if (intensity < 0.0) + intensity = 0.0; + + // Scale up + intensity = intensity * static_cast(mColors.size() - 1); + + // Color indices + int i = static_cast(floor(intensity)); + int j = static_cast(ceil(intensity)); + + if (i == j) + { + // Exact color. + color[0] = mColors[i].value[0]; + color[1] = mColors[i].value[1]; + color[2] = mColors[i].value[2]; + return; + } + + intensity -= i; + double rest = 1 - intensity; + + // Get the colors + int r1 = mColors[i].value[0], + g1 = mColors[i].value[1], + b1 = mColors[i].value[2], + r2 = mColors[j].value[0], + g2 = mColors[j].value[1], + b2 = mColors[j].value[2]; + + // Perform the interpolation. + color[0] = static_cast(rest * r1 + intensity * r2); + color[1] = static_cast(rest * g1 + intensity * g2); + color[2] = static_cast(rest * b1 + intensity * b2); +} + +Dye::Dye(const std::string &description) +{ + for (int i = 0; i < 7; ++i) + mDyePalettes[i] = 0; + + if (description.empty()) + return; + + std::string::size_type next_pos = 0, length = description.length(); + do + { + std::string::size_type pos = next_pos; + next_pos = description.find(';', pos); + + if (next_pos == std::string::npos) + next_pos = length; + + if (next_pos <= pos + 3 || description[pos + 1] != ':') + { + logger->log("Error, invalid dye: %s", description.c_str()); + return; + } + + int i = 0; + + switch (description[pos]) + { + case 'R': i = 0; break; + case 'G': i = 1; break; + case 'Y': i = 2; break; + case 'B': i = 3; break; + case 'M': i = 4; break; + case 'C': i = 5; break; + case 'W': i = 6; break; + default: + logger->log("Error, invalid dye: %s", description.c_str()); + return; + } + mDyePalettes[i] = new DyePalette(description.substr( + pos + 2, next_pos - pos - 2)); + ++next_pos; + } + while (next_pos < length); +} + +Dye::~Dye() +{ + for (int i = 0; i < 7; ++i) + { + delete mDyePalettes[i]; + mDyePalettes[i] = 0; + } +} + +void Dye::update(int color[3]) const +{ + int cmax = std::max(color[0], std::max(color[1], color[2])); + if (cmax == 0) + return; + + int cmin = std::min(color[0], std::min(color[1], color[2])); + int intensity = color[0] + color[1] + color[2]; + + if (cmin != cmax && + (cmin != 0 || (intensity != cmax && intensity != 2 * cmax))) + { + // not pure + return; + } + + int i = (color[0] != 0) | ((color[1] != 0) << 1) | ((color[2] != 0) << 2); + + if (mDyePalettes[i - 1]) + mDyePalettes[i - 1]->getColor(cmax, color); +} + +void Dye::instantiate(std::string &target, const std::string &palettes) +{ + std::string::size_type next_pos = target.find('|'); + + if (next_pos == std::string::npos || palettes.empty()) + return; + + ++next_pos; + + std::ostringstream s; + s << target.substr(0, next_pos); + std::string::size_type last_pos = target.length(), pal_pos = 0; + do + { + std::string::size_type pos = next_pos; + next_pos = target.find(';', pos); + + if (next_pos == std::string::npos) + next_pos = last_pos; + + if (next_pos == pos + 1 && pal_pos != std::string::npos) + { + std::string::size_type pal_next_pos = palettes.find(';', pal_pos); + s << target[pos] << ':'; + if (pal_next_pos == std::string::npos) + { + s << palettes.substr(pal_pos); + s << target.substr(next_pos); + pal_pos = std::string::npos; + break; + } + s << palettes.substr(pal_pos, pal_next_pos - pal_pos); + pal_pos = pal_next_pos + 1; + } + else if (next_pos > pos + 2) + { + s << target.substr(pos, next_pos - pos); + } + else + { + logger->log("Error, invalid dye placeholder: %s", target.c_str()); + return; + } + s << target[next_pos]; + ++next_pos; + } + while (next_pos < last_pos); + + target = s.str(); +} diff --git a/src/resources/dye.h b/src/resources/dye.h new file mode 100644 index 000000000..b2f62547a --- /dev/null +++ b/src/resources/dye.h @@ -0,0 +1,107 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DYE_H +#define DYE_H + +#include +#include + +/** + * Class for performing a linear interpolation between colors. + */ +class DyePalette +{ + public: + + /** + * Creates a palette based on the given string. + * The string is either a file name or a sequence of hexadecimal RGB + * values separated by ',' and starting with '#'. + */ + DyePalette(const std::string &pallete); + +/* + void addFirstColor(const int color[3]); + + void addLastColor(const int color[3]); +*/ + + /** + * Gets a pixel color depending on its intensity. First color is + * implicitly black (0, 0, 0). + */ + void getColor(int intensity, int color[3]) const; + + /** + * Gets a pixel color depending on its intensity. + */ + void getColor(double intensity, int color[3]) const; + + private: + + struct Color { unsigned char value[3]; }; + + std::vector< Color > mColors; +}; + +/** + * Class for dispatching pixel-recoloring amongst several palettes. + */ +class Dye +{ + public: + + /** + * Creates a set of palettes based on the given string. + * + * The parts of string are separated by semi-colons. Each part starts + * by an uppercase letter, followed by a colon and then a palette name. + */ + Dye(const std::string &dye); + + /** + * Destroys the associated palettes. + */ + ~Dye(); + + /** + * Modifies a pixel color. + */ + void update(int color[3]) const; + + /** + * Fills the blank in a dye placeholder with some palette names. + */ + static void instantiate(std::string &target, + const std::string &palettes); + + private: + + /** + * The order of the palettes, as well as their uppercase letter, is: + * + * Red, Green, Yellow, Blue, Magenta, White (or rather gray). + */ + DyePalette *mDyePalettes[7]; +}; + +#endif diff --git a/src/resources/emotedb.cpp b/src/resources/emotedb.cpp new file mode 100644 index 000000000..4d0ba34b4 --- /dev/null +++ b/src/resources/emotedb.cpp @@ -0,0 +1,222 @@ +/* + * Emote database + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/emotedb.h" + +#include "animatedsprite.h" +#include "log.h" + +#include "utils/xml.h" +#include "configuration.h" + +namespace +{ + EmoteInfos mEmoteInfos; + EmoteInfo mUnknown; + bool mLoaded = false; + int mLastEmote = 0; +} + +void EmoteDB::load() +{ + if (mLoaded) + unload(); + + mLastEmote = 0; + + EmoteSprite *unknownSprite = new EmoteSprite; + unknownSprite->sprite = AnimatedSprite::load( + paths.getStringValue("spriteErrorFile")); + unknownSprite->name = "unknown"; + mUnknown.sprites.push_back(unknownSprite); + + logger->log1("Initializing emote database..."); + + XML::Document doc("emotes.xml"); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "emotes")) + { + logger->log1("Emote Database: Error while loading emotes.xml!"); + return; + } + + //iterate s + for_each_xml_child_node(emoteNode, rootNode) + { + if (!xmlStrEqual(emoteNode->name, BAD_CAST "emote")) + continue; + + int id = XML::getProperty(emoteNode, "id", -1); + if (id == -1) + { + logger->log1("Emote Database: Emote with missing ID in " + "emotes.xml!"); + continue; + } + + EmoteInfo *currentInfo = new EmoteInfo; + + for_each_xml_child_node(spriteNode, emoteNode) + { + if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite")) + { + EmoteSprite *currentSprite = new EmoteSprite; + std::string file = paths.getStringValue("sprites") + + (std::string) (const char*) + spriteNode->xmlChildrenNode->content; + currentSprite->sprite = AnimatedSprite::load(file, + XML::getProperty(spriteNode, "variant", 0)); + currentSprite->name = XML::getProperty(spriteNode, "name", ""); + currentInfo->sprites.push_back(currentSprite); + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx")) + { + std::string particlefx = (const char*)( + spriteNode->xmlChildrenNode->content); + currentInfo->particles.push_back(particlefx); + } + } + mEmoteInfos[id] = currentInfo; + if (id > mLastEmote) + mLastEmote = id; + } + + + XML::Document doc2("graphics/sprites/manaplus_emotes.xml"); + rootNode = doc2.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "emotes")) + { + logger->log1("Emote Database: Error while loading" + " manaplus_emotes.xml!"); + return; + } + + //iterate s + for_each_xml_child_node(emoteNode, rootNode) + { + if (!xmlStrEqual(emoteNode->name, BAD_CAST "emote")) + continue; + + int id = XML::getProperty(emoteNode, "id", -1); + if (id == -1) + { + logger->log1("Emote Database: Emote with missing ID in " + "manaplus_emotes.xml!"); + continue; + } + + EmoteInfo *currentInfo = new EmoteInfo; + + for_each_xml_child_node(spriteNode, emoteNode) + { + if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite")) + { + EmoteSprite *currentSprite = new EmoteSprite; + std::string file = paths.getStringValue("sprites") + + (std::string) (const char*) + spriteNode->xmlChildrenNode->content; + currentSprite->sprite = AnimatedSprite::load(file, + XML::getProperty(spriteNode, "variant", 0)); + currentSprite->name = XML::getProperty(spriteNode, "name", ""); + currentInfo->sprites.push_back(currentSprite); + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx")) + { + std::string particlefx = (const char*)( + spriteNode->xmlChildrenNode->content); + currentInfo->particles.push_back(particlefx); + } + } + mEmoteInfos[id] = currentInfo; + if (id > mLastEmote) + mLastEmote = id; + } + + mLoaded = true; +} + +void EmoteDB::unload() +{ + for (EmoteInfos::const_iterator i = mEmoteInfos.begin(); + i != mEmoteInfos.end(); + i++) + { + while (!i->second->sprites.empty()) + { + delete i->second->sprites.front()->sprite; + delete i->second->sprites.front(); + i->second->sprites.pop_front(); + } + delete i->second; + } + + mEmoteInfos.clear(); + + while (!mUnknown.sprites.empty()) + { + delete mUnknown.sprites.front()->sprite; + delete mUnknown.sprites.front(); + mUnknown.sprites.pop_front(); + } + + mLoaded = false; +} + +const EmoteInfo *EmoteDB::get(int id, bool allowNull) +{ + EmoteInfos::const_iterator i = mEmoteInfos.find(id); + + if (i == mEmoteInfos.end()) + { + if (allowNull) + return NULL; + logger->log("EmoteDB: Warning, unknown emote ID %d requested", id); + return &mUnknown; + } + else + { + return i->second; + } +} + +const AnimatedSprite *EmoteDB::getAnimation(int id, bool allowNull) +{ + const EmoteInfo *info = get(id, allowNull); + if (!info) + return NULL; + + return info->sprites.front()->sprite; +} + +const EmoteSprite *EmoteDB::getSprite(int id, bool allowNull) +{ + const EmoteInfo *info = get(id, allowNull); + if (!info) + return NULL; + + return info->sprites.front(); +} + +const int &EmoteDB::getLast() +{ + return mLastEmote; +} diff --git a/src/resources/emotedb.h b/src/resources/emotedb.h new file mode 100644 index 000000000..f9a4b8f9f --- /dev/null +++ b/src/resources/emotedb.h @@ -0,0 +1,64 @@ +/* + * Emote database + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EMOTE_DB_H +#define EMOTE_DB_H + +#include +#include +#include + +class AnimatedSprite; + +struct EmoteSprite +{ + const AnimatedSprite *sprite; + std::string name; +}; + +struct EmoteInfo +{ + std::list sprites; + std::list particles; +}; + +typedef std::map EmoteInfos; + +/** + * Emote information database. + */ +namespace EmoteDB +{ + void load(); + + void unload(); + + const EmoteInfo *get(int id, bool allowNull = false); + + const AnimatedSprite *getAnimation(int id, bool allowNull = false); + + const EmoteSprite *getSprite(int id, bool allowNull = false); + + const int &getLast(); + + typedef EmoteInfos::iterator EmoteInfosIterator; +} + +#endif diff --git a/src/resources/image.cpp b/src/resources/image.cpp new file mode 100644 index 000000000..78cec909b --- /dev/null +++ b/src/resources/image.cpp @@ -0,0 +1,797 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/image.h" + +#include "resources/dye.h" +#include "resources/resourcemanager.h" + +#ifdef USE_OPENGL +#include "openglgraphics.h" +#include "opengl1graphics.h" +#endif + +#include "log.h" +#include "main.h" + +#include "utils/stringutils.h" + +#include +#include + +#ifdef USE_OPENGL +int Image::mUseOpenGL = 0; +int Image::mTextureType = 0; +int Image::mTextureSize = 0; +#endif +bool Image::mEnableAlphaCache = false; +bool Image::mEnableAlpha = true; + +Image::Image(SDL_Surface *image, bool hasAlphaChannel, Uint8 *alphaChannel): + mAlpha(1.0f), + mHasAlphaChannel(hasAlphaChannel), + mSDLSurface(image), + mAlphaChannel(alphaChannel) +{ +#ifdef USE_OPENGL + mGLImage = 0; +#endif + + mUseAlphaCache = Image::mEnableAlphaCache; + + mBounds.x = 0; + mBounds.y = 0; + + mLoaded = false; + + if (mSDLSurface) + { + mBounds.w = static_cast(mSDLSurface->w); + mBounds.h = static_cast(mSDLSurface->h); + + mLoaded = true; + } + else + { + logger->log( + "Image::Image(SDL_Surface*): Couldn't load invalid Surface!"); + } +} + +#ifdef USE_OPENGL +Image::Image(GLuint glimage, int width, int height, + int texWidth, int texHeight): + mAlpha(1.0f), + mHasAlphaChannel(true), + mSDLSurface(0), + mAlphaChannel(0), + mUseAlphaCache(false), + mGLImage(glimage), + mTexWidth(texWidth), + mTexHeight(texHeight) +{ + mBounds.x = 0; + mBounds.y = 0; + mBounds.w = static_cast(width); + mBounds.h = static_cast(height); + + if (mGLImage) + { + mLoaded = true; + } + else + { + logger->log( + "Image::Image(GLuint*, ...): Couldn't load invalid Surface!"); + mLoaded = false; + } +} +#endif + +Image::~Image() +{ + unload(); +} + +Resource *Image::load(void *buffer, unsigned bufferSize) +{ + // Load the raw file data from the buffer in an RWops structure + SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize); + SDL_Surface *tmpImage = IMG_Load_RW(rw, 1); + + if (!tmpImage) + { + logger->log("Error, image load failed: %s", IMG_GetError()); + return NULL; + } + + Image *image = load(tmpImage); + + SDL_FreeSurface(tmpImage); + return image; +} + +Resource *Image::load(void *buffer, unsigned bufferSize, Dye const &dye) +{ + SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize); + SDL_Surface *tmpImage = IMG_Load_RW(rw, 1); + + if (!tmpImage) + { + logger->log("Error, image load failed: %s", IMG_GetError()); + return NULL; + } + + SDL_PixelFormat rgba; + rgba.palette = NULL; + rgba.BitsPerPixel = 32; + rgba.BytesPerPixel = 4; + rgba.Rmask = 0xFF000000; rgba.Rloss = 0; rgba.Rshift = 24; + rgba.Gmask = 0x00FF0000; rgba.Gloss = 0; rgba.Gshift = 16; + rgba.Bmask = 0x0000FF00; rgba.Bloss = 0; rgba.Bshift = 8; + rgba.Amask = 0x000000FF; rgba.Aloss = 0; rgba.Ashift = 0; + rgba.colorkey = 0; + rgba.alpha = 255; + + SDL_Surface *surf = SDL_ConvertSurface(tmpImage, &rgba, SDL_SWSURFACE); + SDL_FreeSurface(tmpImage); + + Uint32 *pixels = static_cast< Uint32 * >(surf->pixels); + for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) + { + int alpha = *pixels & 255; + if (!alpha) continue; + int v[3]; + v[0] = (*pixels >> 24) & 255; + v[1] = (*pixels >> 16) & 255; + v[2] = (*pixels >> 8 ) & 255; + dye.update(v); + *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha; + } + + Image *image = load(surf); + SDL_FreeSurface(surf); + return image; +} + +Image *Image::load(SDL_Surface *tmpImage) +{ +#ifdef USE_OPENGL + if (mUseOpenGL) + return _GLload(tmpImage); +#endif + return _SDLload(tmpImage); +} + +void Image::SDLCleanCache() +{ + ResourceManager *resman = ResourceManager::getInstance(); + + for (std::map::iterator + i = mAlphaCache.begin(), i_end = mAlphaCache.end(); + i != i_end; ++i) + { + if (mSDLSurface != i->second) + resman->scheduleDelete(i->second); + i->second = 0; + } + mAlphaCache.clear(); +} + +void Image::unload() +{ + mLoaded = false; + + if (mSDLSurface) + { + SDLCleanCache(); + // Free the image surface. + SDL_FreeSurface(mSDLSurface); + mSDLSurface = NULL; + + delete[] mAlphaChannel; + mAlphaChannel = NULL; + } + +#ifdef USE_OPENGL + if (mGLImage) + { + glDeleteTextures(1, &mGLImage); + mGLImage = 0; + } +#endif +} + +int Image::useOpenGL() const +{ +#ifdef USE_OPENGL + return mUseOpenGL; +#else + return 0; +#endif +} + +bool Image::hasAlphaChannel() +{ + if (mLoaded) + return mHasAlphaChannel; + +#ifdef USE_OPENGL + if (mUseOpenGL) + return true; +#endif + + return false; +} + +SDL_Surface *Image::getByAlpha(float alpha) +{ + std::map::iterator it = mAlphaCache.find(alpha); + if (it != mAlphaCache.end()) + return (*it).second; + return 0; +} + +void Image::setAlpha(float alpha) +{ + if (mAlpha == alpha || !mEnableAlpha) + return; + + if (alpha < 0.0f || alpha > 1.0f) + return; + + if (mSDLSurface) + { + if (mUseAlphaCache) + { + SDL_Surface *surface = getByAlpha(mAlpha); + if (!surface) + { + if (mAlphaCache.size() > 100) + { +#ifdef DEBUG_ALPHA_CACHE + logger->log("cleanCache"); + for (std::map::iterator + i = mAlphaCache.begin(), i_end = mAlphaCache.end(); + i != i_end; ++i) + { + logger->log("alpha: " + toString(i->first)); + } +#endif + SDLCleanCache(); + } + surface = mSDLSurface; + if (surface) + mAlphaCache[mAlpha] = surface; + } + else + { + logger->log("cache bug"); + } + + surface = getByAlpha(alpha); + if (surface) + { +// logger->log("hit"); + if (mSDLSurface == surface) + logger->log("bug"); +// else +// SDL_FreeSurface(mSDLSurface); + mAlphaCache.erase(alpha); + mSDLSurface = surface; + mAlpha = alpha; + return; + } + else + { + mSDLSurface = Image::SDLDuplicateSurface(mSDLSurface); + } + // logger->log("miss"); + } + + mAlpha = alpha; + + if (!hasAlphaChannel()) + { + // Set the alpha value this image is drawn at + SDL_SetAlpha(mSDLSurface, SDL_SRCALPHA, + static_cast(255 * mAlpha)); + } + else + { + if (SDL_MUSTLOCK(mSDLSurface)) + SDL_LockSurface(mSDLSurface); + + // Precompute as much as possible + int maxHeight = std::min((mBounds.y + mBounds.h), mSDLSurface->h); + int maxWidth = std::min((mBounds.x + mBounds.w), mSDLSurface->w); + int i = 0; + + for (int y = mBounds.y; y < maxHeight; y++) + { + for (int x = mBounds.x; x < maxWidth; x++) + { + i = y * mSDLSurface->w + x; + // Only change the pixel if it was visible at load time... + Uint8 sourceAlpha = mAlphaChannel[i]; + if (sourceAlpha > 0) + { + Uint8 r, g, b, a; + SDL_GetRGBA((static_cast + (mSDLSurface->pixels))[i], + mSDLSurface->format, + &r, &g, &b, &a); + + a = (Uint8) (static_cast(sourceAlpha) * mAlpha); + + // Here is the pixel we want to set + (static_cast(mSDLSurface->pixels))[i] = + SDL_MapRGBA(mSDLSurface->format, r, g, b, a); + } + } + } + + if (SDL_MUSTLOCK(mSDLSurface)) + SDL_UnlockSurface(mSDLSurface); + } + } + else + { + mAlpha = alpha; + } +} + +Image* Image::SDLmerge(Image *image, int x, int y) +{ + if (!mSDLSurface || !image || !image->mSDLSurface) + return NULL; + + SDL_Surface* surface = new SDL_Surface(*(image->mSDLSurface)); + + Uint32 surface_pix, cur_pix; + Uint8 r, g, b, a, p_r, p_g, p_b, p_a; + double f_a, f_ca, f_pa; + SDL_PixelFormat *current_fmt = mSDLSurface->format; + SDL_PixelFormat *surface_fmt = surface->format; + int current_offset, surface_offset; + int offset_x, offset_y; + + SDL_LockSurface(surface); + SDL_LockSurface(mSDLSurface); + // for each pixel lines of a source image + for (offset_x = (x > 0 ? 0 : -x); offset_x < image->getWidth() && + x + offset_x < getWidth(); offset_x++) + { + for (offset_y = (y > 0 ? 0 : -y); offset_y < image->getHeight() + && y + offset_y < getHeight(); offset_y++) + { + // Computing offset on both images + current_offset = (y + offset_y) * getWidth() + x + offset_x; + surface_offset = offset_y * surface->w + offset_x; + + // Retrieving a pixel to merge + surface_pix = ((Uint32*) surface->pixels)[surface_offset]; + cur_pix = ((Uint32*) mSDLSurface->pixels)[current_offset]; + + // Retreiving each channel of the pixel using pixel format + r = (Uint8)(((surface_pix & surface_fmt->Rmask) >> + surface_fmt->Rshift) << surface_fmt->Rloss); + g = (Uint8)(((surface_pix & surface_fmt->Gmask) >> + surface_fmt->Gshift) << surface_fmt->Gloss); + b = (Uint8)(((surface_pix & surface_fmt->Bmask) >> + surface_fmt->Bshift) << surface_fmt->Bloss); + a = (Uint8)(((surface_pix & surface_fmt->Amask) >> + surface_fmt->Ashift) << surface_fmt->Aloss); + + // Retreiving previous alpha value + p_a = (Uint8)(((cur_pix & current_fmt->Amask) >> + current_fmt->Ashift) << current_fmt->Aloss); + + // new pixel with no alpha or nothing on previous pixel + if (a == SDL_ALPHA_OPAQUE || (p_a == 0 && a > 0)) + ((Uint32 *)(surface->pixels))[current_offset] = + SDL_MapRGBA(current_fmt, r, g, b, a); + else if (a > 0) + { // alpha is lower => merge color with previous value + f_a = static_cast(a) / 255.0; + f_ca = 1.0 - f_a; + f_pa = static_cast(p_a) / 255.0; + p_r = (Uint8)(((cur_pix & current_fmt->Rmask) >> + current_fmt->Rshift) << current_fmt->Rloss); + p_g = (Uint8)(((cur_pix & current_fmt->Gmask) >> + current_fmt->Gshift) << current_fmt->Gloss); + p_b = (Uint8)(((cur_pix & current_fmt->Bmask) >> + current_fmt->Bshift) << current_fmt->Bloss); + r = (Uint8)((double) p_r * f_ca * f_pa + (double)r * f_a); + g = (Uint8)((double) p_g * f_ca * f_pa + (double)g * f_a); + b = (Uint8)((double) p_b * f_ca * f_pa + (double)b * f_a); + a = (a > p_a ? a : p_a); + ((Uint32 *)(surface->pixels))[current_offset] = + SDL_MapRGBA(current_fmt, r, g, b, a); + } + } + } + SDL_UnlockSurface(surface); + SDL_UnlockSurface(mSDLSurface); + + Image *newImage = new Image(surface); + + return newImage; +} + +Image* Image::SDLgetScaledImage(int width, int height) +{ + // No scaling on incorrect new values. + if (width == 0 || height == 0) + return NULL; + + // No scaling when there is ... no different given size ... + if (width == getWidth() && height == getHeight()) + return NULL; + + Image* scaledImage = NULL; + SDL_Surface* scaledSurface = NULL; + + if (mSDLSurface) + { + scaledSurface = zoomSurface(mSDLSurface, + (double) width / getWidth(), + (double) height / getHeight(), + 1); + + // The load function takes care of the SDL<->OpenGL implementation + // and about freeing the given SDL_surface*. + if (scaledSurface) + scaledImage = load(scaledSurface); + } + return scaledImage; +} + +SDL_Surface* Image::convertTo32Bit(SDL_Surface* tmpImage) +{ + if (!tmpImage) + return NULL; + SDL_PixelFormat RGBAFormat; + RGBAFormat.palette = 0; + RGBAFormat.colorkey = 0; + RGBAFormat.alpha = 0; + RGBAFormat.BitsPerPixel = 32; + RGBAFormat.BytesPerPixel = 4; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + RGBAFormat.Rmask = 0xFF000000; + RGBAFormat.Rshift = 0; + RGBAFormat.Rloss = 0; + RGBAFormat.Gmask = 0x00FF0000; + RGBAFormat.Gshift = 8; + RGBAFormat.Gloss = 0; + RGBAFormat.Bmask = 0x0000FF00; + RGBAFormat.Bshift = 16; + RGBAFormat.Bloss = 0; + RGBAFormat.Amask = 0x000000FF; + RGBAFormat.Ashift = 24; + RGBAFormat.Aloss = 0; +#else + RGBAFormat.Rmask = 0x000000FF; + RGBAFormat.Rshift = 24; + RGBAFormat.Rloss = 0; + RGBAFormat.Gmask = 0x0000FF00; + RGBAFormat.Gshift = 16; + RGBAFormat.Gloss = 0; + RGBAFormat.Bmask = 0x00FF0000; + RGBAFormat.Bshift = 8; + RGBAFormat.Bloss = 0; + RGBAFormat.Amask = 0xFF000000; + RGBAFormat.Ashift = 0; + RGBAFormat.Aloss = 0; +#endif + return SDL_ConvertSurface(tmpImage, &RGBAFormat, SDL_SWSURFACE); +} + +SDL_Surface* Image::SDLDuplicateSurface(SDL_Surface* tmpImage) +{ + if (!tmpImage || !tmpImage->format) + return NULL; + + return SDL_ConvertSurface(tmpImage, tmpImage->format, SDL_SWSURFACE); +} + +Image *Image::_SDLload(SDL_Surface *tmpImage) +{ + if (!tmpImage) + return NULL; + + bool hasAlpha = false; + bool converted = false; + + // The alpha channel to be filled with alpha values + Uint8 *alphaChannel = new Uint8[tmpImage->w * tmpImage->h]; + + if (tmpImage->format->BitsPerPixel != 32) + { + tmpImage = convertTo32Bit(tmpImage); + + if (!tmpImage) + { + delete[] alphaChannel; + return NULL; + } + converted = true; + } + + // Figure out whether the image uses its alpha layer + for (int i = 0; i < tmpImage->w * tmpImage->h; ++i) + { + Uint8 r, g, b, a; + SDL_GetRGBA(((Uint32*) tmpImage->pixels)[i], + tmpImage->format, &r, &g, &b, &a); + + if (a != 255) + hasAlpha = true; + + alphaChannel[i] = a; + } + + SDL_Surface *image; + + // Convert the surface to the current display format + if (hasAlpha) + { + image = SDL_DisplayFormatAlpha(tmpImage); + } + else + { + image = SDL_DisplayFormat(tmpImage); + + // We also delete the alpha channel since + // it's not used. + delete[] alphaChannel; + alphaChannel = 0; + } + + if (!image) + { + logger->log1("Error: Image convert failed."); + delete[] alphaChannel; + return 0; + } + + if (converted) + SDL_FreeSurface(tmpImage); + + return new Image(image, hasAlpha, alphaChannel); +} + +#ifdef USE_OPENGL +Image *Image::_GLload(SDL_Surface *tmpImage) +{ + if (!tmpImage) + return NULL; + + // Flush current error flag. + glGetError(); + + int width = tmpImage->w; + int height = tmpImage->h; + int realWidth = powerOfTwo(width); + int realHeight = powerOfTwo(height); + + if (realWidth < width || realHeight < height) + { + logger->log("Warning: image too large, cropping to %dx%d texture!", + tmpImage->w, tmpImage->h); + } + + // Make sure the alpha channel is not used, but copied to destination + SDL_SetAlpha(tmpImage, 0, SDL_ALPHA_OPAQUE); + + // Determine 32-bit masks based on byte order + Uint32 rmask, gmask, bmask, amask; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + rmask = 0xff000000; + gmask = 0x00ff0000; + bmask = 0x0000ff00; + amask = 0x000000ff; +#else + rmask = 0x000000ff; + gmask = 0x0000ff00; + bmask = 0x00ff0000; + amask = 0xff000000; +#endif + + SDL_Surface *oldImage = tmpImage; + tmpImage = SDL_CreateRGBSurface(SDL_SWSURFACE, realWidth, realHeight, + 32, rmask, gmask, bmask, amask); + + if (!tmpImage) + { + logger->log("Error, image convert failed: out of memory"); + return NULL; + } + + SDL_BlitSurface(oldImage, NULL, tmpImage, NULL); + + GLuint texture; + glGenTextures(1, &texture); + if (mUseOpenGL == 1) + OpenGLGraphics::bindTexture(mTextureType, texture); + else if (mUseOpenGL == 2) +// glBindTexture(mTextureType, texture); + OpenGL1Graphics::bindTexture(mTextureType, texture); + + if (SDL_MUSTLOCK(tmpImage)) + SDL_LockSurface(tmpImage); + + glTexImage2D(mTextureType, 0, 4, tmpImage->w, tmpImage->h, + 0, GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->pixels); + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glTexParameteri(mTextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(mTextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + if (SDL_MUSTLOCK(tmpImage)) + SDL_UnlockSurface(tmpImage); + + SDL_FreeSurface(tmpImage); + + GLenum error = glGetError(); + if (error) + { + std::string errmsg = "Unknown error"; + switch (error) + { + case GL_INVALID_ENUM: + errmsg = "GL_INVALID_ENUM"; + break; + case GL_INVALID_VALUE: + errmsg = "GL_INVALID_VALUE"; + break; + case GL_INVALID_OPERATION: + errmsg = "GL_INVALID_OPERATION"; + break; + case GL_STACK_OVERFLOW: + errmsg = "GL_STACK_OVERFLOW"; + break; + case GL_STACK_UNDERFLOW: + errmsg = "GL_STACK_UNDERFLOW"; + break; + case GL_OUT_OF_MEMORY: + errmsg = "GL_OUT_OF_MEMORY"; + break; + default: + break; + } + logger->log("Error: Image GL import failed: %s", errmsg.c_str()); + return NULL; + } + + return new Image(texture, width, height, realWidth, realHeight); +} + +void Image::setLoadAsOpenGL(int useOpenGL) +{ + Image::mUseOpenGL = useOpenGL; +} + +int Image::powerOfTwo(int input) +{ + int value; + if (mTextureType == GL_TEXTURE_2D) + { + value = 1; + while (value < input && value < mTextureSize) + value <<= 1; + } + else + { + value = input; + } + return value >= mTextureSize ? mTextureSize : value; +} +#endif + +Image *Image::getSubImage(int x, int y, int width, int height) +{ + // Create a new clipped sub-image +#ifdef USE_OPENGL + if (mUseOpenGL) + { + return new SubImage(this, mGLImage, x, y, width, height, + mTexWidth, mTexHeight); + } +#endif + + return new SubImage(this, mSDLSurface, x, y, width, height); +} + +void Image::SDLTerminateAlphaCache() +{ + SDLCleanCache(); + mUseAlphaCache = false; +} + +//============================================================================ +// SubImage Class +//============================================================================ + +SubImage::SubImage(Image *parent, SDL_Surface *image, + int x, int y, int width, int height): + Image(image), + mParent(parent) +{ + if (mParent) + { + mParent->incRef(); + mParent->SDLTerminateAlphaCache(); + mHasAlphaChannel = mParent->hasAlphaChannel(); + mAlphaChannel = mParent->SDLgetAlphaChannel(); + } + else + { + mHasAlphaChannel = false; + mAlphaChannel = 0; + } + + // Set up the rectangle. + mBounds.x = static_cast(x); + mBounds.y = static_cast(y); + mBounds.w = static_cast(width); + mBounds.h = static_cast(height); + mUseAlphaCache = false; +} + +#ifdef USE_OPENGL +SubImage::SubImage(Image *parent, GLuint image, + int x, int y, int width, int height, + int texWidth, int texHeight): + Image(image, width, height, texWidth, texHeight), + mParent(parent) +{ + if (mParent) + mParent->incRef(); + + // Set up the rectangle. + mBounds.x = static_cast(x); + mBounds.y = static_cast(y); + mBounds.w = static_cast(width); + mBounds.h = static_cast(height); +} +#endif + +SubImage::~SubImage() +{ + // Avoid destruction of the image + mSDLSurface = 0; + // Avoid possible destruction of its alpha channel + mAlphaChannel = 0; +#ifdef USE_OPENGL + mGLImage = 0; +#endif + if (mParent) + mParent->decRef(); +} + +Image *SubImage::getSubImage(int x, int y, int w, int h) +{ + if (mParent) + return mParent->getSubImage(mBounds.x + x, mBounds.y + y, w, h); + else + return NULL; +} diff --git a/src/resources/image.h b/src/resources/image.h new file mode 100644 index 000000000..7145b9d48 --- /dev/null +++ b/src/resources/image.h @@ -0,0 +1,308 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGE_H +#define IMAGE_H + +#include "main.h" + +#include "resources/resource.h" + +#include + +#ifdef USE_OPENGL + +/* The definition of OpenGL extensions by SDL is giving problems with recent + * gl.h headers, since they also include these definitions. As we're not using + * extensions anyway it's safe to just disable the SDL version. + */ +#define NO_SDL_GLEXT + +#include +#endif + +#include + +class Dye; +class Position; + +/** + * Defines a class for loading and storing images. + */ +class Image : public Resource +{ + friend class CompoundSprite; + friend class Graphics; +#ifdef USE_OPENGL + friend class OpenGLGraphics; + friend class OpenGL1Graphics; +#endif + + public: + /** + * Destructor. + */ + virtual ~Image(); + + /** + * Loads an image from a buffer in memory. + * + * @param buffer The memory buffer containing the image data. + * @param bufferSize The size of the memory buffer in bytes. + * + * @return NULL if an error occurred, a valid pointer + * otherwise. + */ + static Resource *load(void *buffer, unsigned bufferSize); + + /** + * Loads an image from a buffer in memory and recolors it. + * + * @param buffer The memory buffer containing the image data. + * @param bufferSize The size of the memory buffer in bytes. + * @param dye The dye used to recolor the image. + * + * @return NULL if an error occurred, a valid pointer + * otherwise. + */ + static Resource *load(void *buffer, unsigned bufferSize, + Dye const &dye); + + /** + * Loads an image from an SDL surface. + */ + static Image *load(SDL_Surface *); + + static SDL_Surface* convertTo32Bit(SDL_Surface* tmpImage); + + /** + * Frees the resources created by SDL. + */ + virtual void unload(); + + /** + * Tells is the image is loaded + */ + bool isLoaded() + { return mLoaded; } + + /** + * Returns the width of the image. + */ + inline int getWidth() const // was virtual + { return mBounds.w; } + + /** + * Returns the height of the image. + */ + inline int getHeight() const // was virtual + { return mBounds.h; } + + /** + * Tells if the image was loaded using OpenGL or SDL + * @return true if OpenGL, false if SDL. + */ + int useOpenGL() const; + + /** + * Tells if the image has got an alpha channel + * @return true if it's true, false otherwise. + */ + bool hasAlphaChannel(); + + /** + * Sets the alpha value of this image. + */ + virtual void setAlpha(float alpha); + + /** + * Returns the alpha value of this image. + */ + float getAlpha() const + { return mAlpha; } + + /** + * Creates a new image with the desired clipping rectangle. + * + * @return NULL if creation failed and a valid + * object otherwise. + */ + virtual Image *getSubImage(int x, int y, int width, int height); + + + // SDL only public functions + + /** + * Gets an scaled instance of an image. + * + * @param width The desired width of the scaled image. + * @param height The desired height of the scaled image. + * + * @return A new Image* object. + */ + Image* SDLgetScaledImage(int width, int height); + + /** + * Merges two image SDL_Surfaces together. This is for SDL use only, as + * reducing the number of surfaces that SDL has to render can cut down + * on the number of blit operations necessary, which in turn can help + * improve overall framerates. Don't use unless you are using it to + * reduce the number of overall layers that need to be drawn through SDL. + */ + Image *SDLmerge(Image *image, int x, int y); + + /** + * Get the alpha Channel of a SDL surface. + */ + Uint8 *SDLgetAlphaChannel() const + { return mAlphaChannel; } + + SDL_Surface* SDLDuplicateSurface(SDL_Surface* tmpImage); + + void SDLCleanCache(); + + void SDLTerminateAlphaCache(); + + static void SDLSetEnableAlphaCache(bool n) + { mEnableAlphaCache = n; } + + static void setEnableAlpha(bool n) + { mEnableAlpha = n; } + +#ifdef USE_OPENGL + + // OpenGL only public functions + + /** + * Sets the target image format. Use false for SDL and + * true for OpenGL. + */ + static void setLoadAsOpenGL(int useOpenGL); + + static int getLoadAsOpenGL() + { return mUseOpenGL; } + + int getTextureWidth() const + { return mTexWidth; } + + int getTextureHeight() const + { return mTexHeight; } + + static int getTextureType() + { return mTextureType; } +#endif + + protected: + + // ----------------------- + // Generic protected members + // ----------------------- + + SDL_Rect mBounds; + bool mLoaded; + float mAlpha; + bool mHasAlphaChannel; + + // ----------------------- + // SDL protected members + // ----------------------- + + /** SDL Constructor */ + Image(SDL_Surface *image, bool hasAlphaChannel = false, + Uint8 *alphaChannel = NULL); + + /** SDL_Surface to SDL_Surface Image loader */ + static Image *_SDLload(SDL_Surface *tmpImage); + + SDL_Surface *getByAlpha(float alpha); + + SDL_Surface *mSDLSurface; + + /** Alpha Channel pointer used for 32bit based SDL surfaces */ + Uint8 *mAlphaChannel; + + std::map mAlphaCache; + + bool mUseAlphaCache; + + static bool mEnableAlphaCache; + static bool mEnableAlpha; + + // ----------------------- + // OpenGL protected members + // ----------------------- +#ifdef USE_OPENGL + /** + * OpenGL Constructor. + */ + Image(GLuint glimage, int width, int height, + int texWidth, int texHeight); + + /** + * Returns the first power of two equal or bigger than the input. + */ + static int powerOfTwo(int input); + + static Image *_GLload(SDL_Surface *tmpImage); + + GLuint mGLImage; + int mTexWidth, mTexHeight; + + static int mUseOpenGL; + static int mTextureType; + static int mTextureSize; +#endif +}; + +/** + * A clipped version of a larger image. + */ +class SubImage : public Image +{ + public: + /** + * Constructor. + */ + SubImage(Image *parent, SDL_Surface *image, + int x, int y, int width, int height); +#ifdef USE_OPENGL + SubImage(Image *parent, GLuint image, int x, int y, + int width, int height, int texWidth, int textHeight); +#endif + + /** + * Destructor. + */ + ~SubImage(); + + /** + * Creates a new image with the desired clipping rectangle. + * + * @return NULL if creation failed and a valid + * image otherwise. + */ + Image *getSubImage(int x, int y, int width, int height); + + private: + Image *mParent; +}; + +#endif diff --git a/src/resources/imageloader.cpp b/src/resources/imageloader.cpp new file mode 100644 index 000000000..97c7a0146 --- /dev/null +++ b/src/resources/imageloader.cpp @@ -0,0 +1,114 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/imageloader.h" + +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include + +#include + +#include + +ProxyImage::ProxyImage(SDL_Surface *s): + mImage(NULL), mSDLImage(s) +{ +} + +ProxyImage::~ProxyImage() +{ + free(); +} + +void ProxyImage::free() +{ + if (mSDLImage) + { + SDL_FreeSurface(mSDLImage); + mSDLImage = 0; + } + else + { + delete mImage; + mImage = 0; + } +} + +int ProxyImage::getWidth() const +{ + if (mSDLImage) + return mSDLImage->w; + else if (mImage) + return mImage->getWidth(); + else + return 0; +// return mSDLImage ? mSDLImage->w : mImage->getWidth(); +} + +int ProxyImage::getHeight() const +{ + if (mSDLImage) + return mSDLImage->h; + else if (mImage) + return mImage->getHeight(); + else + return 0; +// return mSDLImage ? mSDLImage->h : mImage->getHeight(); +} + +gcn::Color ProxyImage::getPixel(int x, int y) +{ + assert(mSDLImage); + return gcn::SDLgetPixel(mSDLImage, x, y); +} + +void ProxyImage::putPixel(int x, int y, gcn::Color const &color) +{ + if (!mSDLImage) + return; + gcn::SDLputPixel(mSDLImage, x, y, color); +} + +void ProxyImage::convertToDisplayFormat() +{ + if (!mSDLImage) + return; + + /* The picture is most probably full of the pink pixels Guichan uses for + transparency, as this function will only be called when setting an image + font. Get rid of them. */ + SDL_SetColorKey(mSDLImage, SDL_SRCCOLORKEY, + SDL_MapRGB(mSDLImage->format, 255, 0, 255)); + mImage = ::Image::load(mSDLImage); + SDL_FreeSurface(mSDLImage); + mSDLImage = NULL; +} + +gcn::Image *ImageLoader::load(const std::string &filename, bool convert) +{ + ResourceManager *resman = ResourceManager::getInstance(); + ProxyImage *i = new ProxyImage(resman->loadSDLSurface(filename)); + if (convert) + i->convertToDisplayFormat(); + return i; +} diff --git a/src/resources/imageloader.h b/src/resources/imageloader.h new file mode 100644 index 000000000..7f53ff240 --- /dev/null +++ b/src/resources/imageloader.h @@ -0,0 +1,69 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGELOADER_H +#define IMAGELOADER_H + +#include +#include + +#include + +class Image; +struct SDL_Surface; + +class ProxyImage : public gcn::Image +{ + public: + ProxyImage(SDL_Surface *); + ~ProxyImage(); + + void free(); + int getWidth() const; + int getHeight() const; + gcn::Color getPixel(int x, int y); + void putPixel(int x, int y, gcn::Color const &color); + void convertToDisplayFormat(); + + /** + * Gets the image handled by this proxy. + */ + ::Image *getImage() const + { return mImage; } + + private: + ::Image *mImage; /**< The real image. */ + + /** + * An SDL surface kept around until Guichan is done reading pixels from + * an OpenGL texture. + */ + SDL_Surface *mSDLImage; +}; + +class ImageLoader : public gcn::ImageLoader +{ + public: + gcn::Image *load(const std::string &filename, + bool convertToDisplayFormat); +}; + +#endif diff --git a/src/resources/imageset.cpp b/src/resources/imageset.cpp new file mode 100644 index 000000000..93d3138aa --- /dev/null +++ b/src/resources/imageset.cpp @@ -0,0 +1,64 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/imageset.h" + +#include "log.h" + +#include "resources/image.h" + +#include "utils/dtor.h" + +ImageSet::ImageSet(Image *img, int width, int height, int margin, int spacing) +{ + if (img) + { + for (int y = margin; y + height <= img->getHeight() - margin; + y += height + spacing) + { + for (int x = margin; x + width <= img->getWidth() - margin; + x += width + spacing) + { + mImages.push_back(img->getSubImage(x, y, width, height)); + } + } + } + mWidth = width; + mHeight = height; +} + +ImageSet::~ImageSet() +{ + delete_all(mImages); +} + +Image* ImageSet::get(size_type i) const +{ + if (i >= mImages.size()) + { + logger->log("Warning: No sprite %d in this image set", (int) i); + return NULL; + } + else + { + return mImages[i]; + } +} diff --git a/src/resources/imageset.h b/src/resources/imageset.h new file mode 100644 index 000000000..bfb834128 --- /dev/null +++ b/src/resources/imageset.h @@ -0,0 +1,72 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGESET_H +#define IMAGESET_H + +#include "resources/resource.h" + +#include + +class Image; + +/** + * Stores a set of subimages originating from a single image. + */ +class ImageSet : public Resource +{ + public: + /** + * Cuts the passed image in a grid of sub images. + */ + ImageSet(Image *img, int w, int h, int margin = 0, int spacing = 0); + + /** + * Destructor. + */ + ~ImageSet(); + + /** + * Returns the width of the images in the image set. + */ + int getWidth() const + { return mWidth; } + + /** + * Returns the height of the images in the image set. + */ + int getHeight() const + { return mHeight; } + + typedef std::vector::size_type size_type; + Image* get(size_type i) const; + + size_type size() const + { return mImages.size(); } + + private: + std::vector mImages; + + int mHeight; /**< Height of the images in the image set. */ + int mWidth; /**< Width of the images in the image set. */ +}; + +#endif diff --git a/src/resources/imagewriter.cpp b/src/resources/imagewriter.cpp new file mode 100644 index 000000000..f3f4be2b4 --- /dev/null +++ b/src/resources/imagewriter.cpp @@ -0,0 +1,111 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/imagewriter.h" + +#include "log.h" + +#include +#include +#include + +bool ImageWriter::writePNG(SDL_Surface *surface, const std::string &filename) +{ + // TODO Maybe someone can make this look nice? + + png_structp png_ptr; + png_infop info_ptr; + png_bytep *row_pointers; + int colortype; + + if (SDL_MUSTLOCK(surface)) + SDL_LockSurface(surface); + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png_ptr) + { + logger->log1("Had trouble creating png_structp"); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + logger->log1("Could not create png_info"); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + logger->log("problem writing to %s", filename.c_str()); + return false; + } + + FILE *fp = fopen(filename.c_str(), "wb"); + if (!fp) + { + logger->log("could not open file %s for writing", filename.c_str()); + return false; + } + + png_init_io(png_ptr, fp); + + colortype = (surface->format->BitsPerPixel == 24) ? + PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + + png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png_ptr, info_ptr); + + png_set_packing(png_ptr); + + row_pointers = new png_bytep[surface->h]; + if (!row_pointers) + { + logger->log1("Had trouble converting surface to row pointers"); + fclose(fp); + return false; + } + + for (int i = 0; i < surface->h; i++) + { + row_pointers[i] = (png_bytep)(Uint8 *)surface->pixels + + i * surface->pitch; + } + + png_write_image(png_ptr, row_pointers); + png_write_end(png_ptr, info_ptr); + + fclose(fp); + + delete [] row_pointers; + + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + + if (SDL_MUSTLOCK(surface)) + SDL_UnlockSurface(surface); + + return true; +} diff --git a/src/resources/imagewriter.h b/src/resources/imagewriter.h new file mode 100644 index 000000000..ac07320a6 --- /dev/null +++ b/src/resources/imagewriter.h @@ -0,0 +1,31 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +struct SDL_Surface; + +class ImageWriter +{ + public: + static bool writePNG(SDL_Surface *surface, + const std::string &filename); +}; diff --git a/src/resources/itemdb.cpp b/src/resources/itemdb.cpp new file mode 100644 index 000000000..cc70e33b7 --- /dev/null +++ b/src/resources/itemdb.cpp @@ -0,0 +1,463 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/itemdb.h" + +#include "log.h" + +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" +#include "configuration.h" + +#include + +#include + +namespace +{ + ItemDB::ItemInfos mItemInfos; + ItemDB::NamedItemInfos mNamedItemInfos; + ItemInfo *mUnknown; + bool mLoaded = false; +} + +// Forward declarations +static void loadSpriteRef(ItemInfo *itemInfo, xmlNodePtr node); +static void loadSoundRef(ItemInfo *itemInfo, xmlNodePtr node); +static void loadFloorSprite(SpriteDisplay *display, xmlNodePtr node); +static int parseSpriteName(std::string &name); + +static char const *const fields[][2] = +{ + { "attack", N_("Attack %+d") }, + { "defense", N_("Defense %+d") }, + { "hp", N_("HP %+d") }, + { "mp", N_("MP %+d") } +}; + +static std::list extraStats; + +void ItemDB::setStatsList(const std::list &stats) +{ + extraStats = stats; +} + +static ItemType itemTypeFromString(const std::string &name) +{ + if (name == "generic") + { + return ITEM_UNUSABLE; + } + else if (name == "usable") + { + return ITEM_USABLE; + } + else if (name == "equip-1hand") + { + return ITEM_EQUIPMENT_ONE_HAND_WEAPON; + } + else if (name == "equip-2hand") + { + return ITEM_EQUIPMENT_TWO_HANDS_WEAPON; + } + else if (name == "equip-torso") + { + return ITEM_EQUIPMENT_TORSO; + } + else if (name == "equip-arms") + { + return ITEM_EQUIPMENT_ARMS; + } + else if (name == "equip-head") + { + return ITEM_EQUIPMENT_HEAD; + } + else if (name == "equip-legs") + { + return ITEM_EQUIPMENT_LEGS; + } + else if (name == "equip-shield") + { + return ITEM_EQUIPMENT_SHIELD; + } + else if (name == "equip-ring") + { + return ITEM_EQUIPMENT_RING; + } + else if (name == "equip-charm") + { + return ITEM_EQUIPMENT_CHARM; + } + else if (name == "equip-necklace" || name == "equip-neck") + { + return ITEM_EQUIPMENT_NECKLACE; + } + else if (name == "equip-feet") + { + return ITEM_EQUIPMENT_FEET; + } + else if (name == "equip-ammo") + { + return ITEM_EQUIPMENT_AMMO; + } + else if (name == "racesprite") + { + return ITEM_SPRITE_RACE; + } + else if (name == "hairsprite") + { + return ITEM_SPRITE_HAIR; + } + else + { + logger->log("Unknown item type: " + name); + return ITEM_UNUSABLE; + } +} + +static std::string normalized(const std::string &name) +{ + std::string normalized = name; + return toLower(trim(normalized)); +} + +void ItemDB::load() +{ + if (mLoaded) + unload(); + + logger->log1("Initializing item database..."); + + mUnknown = new ItemInfo; + mUnknown->setName(_("Unknown item")); + mUnknown->setDisplay(SpriteDisplay()); + std::string errFile = paths.getStringValue("spriteErrorFile"); + mUnknown->setSprite(errFile, GENDER_MALE); + mUnknown->setSprite(errFile, GENDER_FEMALE); + + XML::Document doc("items.xml"); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items")) + { + logger->log("ItemDB: Error while loading items.xml!"); + mLoaded = true; + return; + } + + for_each_xml_child_node(node, rootNode) + { + if (!xmlStrEqual(node->name, BAD_CAST "item")) + continue; + + int id = XML::getProperty(node, "id", 0); + + if (id == 0) + { + logger->log1("ItemDB: Invalid or missing item ID in items.xml!"); + continue; + } + else if (mItemInfos.find(id) != mItemInfos.end()) + { + logger->log("ItemDB: Redefinition of item ID %d", id); + } + + std::string typeStr = XML::getProperty(node, "type", "other"); + int weight = XML::getProperty(node, "weight", 0); + int view = XML::getProperty(node, "view", 0); + + std::string name = XML::getProperty(node, "name", ""); + std::string image = XML::getProperty(node, "image", ""); + std::string description = XML::getProperty(node, "description", ""); + std::string attackAction = XML::getProperty(node, "attack-action", ""); + std::string drawBefore = XML::getProperty(node, "drawBefore", ""); + std::string drawAfter = XML::getProperty(node, "drawAfter", ""); + + int drawPriority = XML::getProperty(node, "drawPriority", 0); + + int attackRange = XML::getProperty(node, "attack-range", 0); + std::string missileParticle = XML::getProperty( + node, "missile-particle", ""); + + SpriteDisplay display; + display.image = image; + + ItemInfo *itemInfo = new ItemInfo; + itemInfo->setId(id); + itemInfo->setName(name.empty() ? _("unnamed") : name); + itemInfo->setDescription(description); + itemInfo->setType(itemTypeFromString(typeStr)); + itemInfo->setView(view); + itemInfo->setWeight(weight); + itemInfo->setAttackAction(attackAction); + itemInfo->setAttackRange(attackRange); + itemInfo->setMissileParticle(missileParticle); + itemInfo->setDrawBefore(parseSpriteName(drawBefore)); + itemInfo->setDrawAfter(parseSpriteName(drawAfter)); + itemInfo->setDrawPriority(drawPriority); + + std::string effect; + for (int i = 0; i < int(sizeof(fields) / sizeof(fields[0])); ++i) + { + int value = XML::getProperty(node, fields[i][0], 0); + if (!value) + continue; + if (!effect.empty()) + effect += " / "; + effect += strprintf(gettext(fields[i][1]), value); + } + for (std::list::iterator it = extraStats.begin(); + it != extraStats.end(); it++) + { + int value = XML::getProperty(node, it->tag.c_str(), 0); + if (!value) + continue; + if (!effect.empty()) + effect += " / "; + effect += strprintf(it->format.c_str(), value); + } + std::string temp = XML::getProperty(node, "effect", ""); + if (!effect.empty() && !temp.empty()) + effect += " / "; + effect += temp; + itemInfo->setEffect(effect); + + for_each_xml_child_node(itemChild, node) + { + if (xmlStrEqual(itemChild->name, BAD_CAST "sprite")) + { + std::string attackParticle = XML::getProperty( + itemChild, "particle-effect", ""); + itemInfo->setParticleEffect(attackParticle); + + loadSpriteRef(itemInfo, itemChild); + } + else if (xmlStrEqual(itemChild->name, BAD_CAST "sound")) + { + loadSoundRef(itemInfo, itemChild); + } + else if (xmlStrEqual(itemChild->name, BAD_CAST "floor")) + { + loadFloorSprite(&display, itemChild); + } + } + + itemInfo->setDisplay(display); + + mItemInfos[id] = itemInfo; + if (!name.empty()) + { + std::string temp = normalized(name); + + NamedItemInfos::const_iterator itr = mNamedItemInfos.find(temp); + if (itr == mNamedItemInfos.end()) + { + mNamedItemInfos[temp] = itemInfo; + } + else + { + logger->log("ItemDB: Duplicate name of item found item %d", + id); + } + } + + if (!attackAction.empty()) + { + if (attackRange == 0) + { + logger->log("ItemDB: Missing attack range from weapon %i!", + id); + } + } + +#define CHECK_PARAM(param, error_value) \ + if (param == error_value) \ + logger->log("ItemDB: Missing " #param " attribute for item %i!", \ + id) + + if (id >= 0) + { + CHECK_PARAM(name, ""); + CHECK_PARAM(description, ""); + CHECK_PARAM(image, ""); + } + // CHECK_PARAM(effect, ""); + // CHECK_PARAM(type, 0); + // CHECK_PARAM(weight, 0); + // CHECK_PARAM(slot, 0); + +#undef CHECK_PARAM + } + + mLoaded = true; +} + +void ItemDB::unload() +{ + logger->log1("Unloading item database..."); + + delete mUnknown; + mUnknown = 0; + + delete_all(mItemInfos); + mItemInfos.clear(); + mNamedItemInfos.clear(); + mLoaded = false; +} + +bool ItemDB::exists(int id) +{ + assert(mLoaded); + + ItemInfos::const_iterator i = mItemInfos.find(id); + + return i != mItemInfos.end(); +} + +const ItemInfo &ItemDB::get(int id) +{ + assert(mLoaded); + + ItemInfos::const_iterator i = mItemInfos.find(id); + + if (i == mItemInfos.end()) + { + logger->log("ItemDB: Warning, unknown item ID# %d", id); + return *mUnknown; + } + + return *(i->second); +} + +const ItemInfo &ItemDB::get(const std::string &name) +{ + assert(mLoaded); + + NamedItemInfos::const_iterator i = mNamedItemInfos.find(normalized(name)); + + if (i == mNamedItemInfos.end()) + { + if (!name.empty()) + { + logger->log("ItemDB: Warning, unknown item name \"%s\"", + name.c_str()); + } + return *mUnknown; + } + + return *(i->second); +} + +const std::map &ItemDB::getItemInfos() +{ + return mItemInfos; +} + +int parseSpriteName(std::string &name) +{ + int id = -1; + if (name == "shoes") + id = 1; + else if (name == "bottomclothes" || name == "bottom" || name == "pants") + id = 2; + else if (name == "topclothes" || name == "top" || name == "torso") + id = 3; + else if (name == "misc1") + id = 4; + else if (name == "misc2") + id = 5; + else if (name == "hair") + id = 6; + else if (name == "hat") + id = 7; + else if (name == "cap") + id = 8; + else if (name == "gloves") + id = 9; + else if (name == "weapon") + id = 10; + else if (name == "shield") + id = 11; + + return id; +} + +void loadSpriteRef(ItemInfo *itemInfo, xmlNodePtr node) +{ + std::string gender = XML::getProperty(node, "gender", "unisex"); + std::string filename = (const char*) node->xmlChildrenNode->content; + + if (gender == "male" || gender == "unisex") + { + itemInfo->setSprite(filename, GENDER_MALE); + } + if (gender == "female" || gender == "unisex") + { + itemInfo->setSprite(filename, GENDER_FEMALE); + } +} + +void loadSoundRef(ItemInfo *itemInfo, xmlNodePtr node) +{ + std::string event = XML::getProperty(node, "event", ""); + std::string filename = (const char*) node->xmlChildrenNode->content; + + if (event == "hit") + { + itemInfo->addSound(EQUIP_EVENT_HIT, filename); + } + else if (event == "strike") + { + itemInfo->addSound(EQUIP_EVENT_STRIKE, filename); + } + else + { + logger->log("ItemDB: Ignoring unknown sound event '%s'", + event.c_str()); + } +} + +void loadFloorSprite(SpriteDisplay *display, xmlNodePtr floorNode) +{ + for_each_xml_child_node(spriteNode, floorNode) + { + if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite")) + { + SpriteReference *currentSprite = new SpriteReference; + currentSprite->sprite + = (const char*)spriteNode->xmlChildrenNode->content; + currentSprite->variant + = XML::getProperty(spriteNode, "variant", 0); + display->sprites.push_back(currentSprite); + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx")) + { + std::string particlefx + = (const char*)spriteNode->xmlChildrenNode->content; + display->particles.push_back(particlefx); + } + } +} diff --git a/src/resources/itemdb.h b/src/resources/itemdb.h new file mode 100644 index 000000000..76e646c0f --- /dev/null +++ b/src/resources/itemdb.h @@ -0,0 +1,79 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEM_MANAGER_H +#define ITEM_MANAGER_H + +#include +#include +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class ItemInfo; + +/** + * Item information database. + */ +namespace ItemDB +{ + /** + * Loads the item data from items.xml. + */ + void load(); + + /** + * Frees item data. + */ + void unload(); + + bool exists(int id); + + const ItemInfo &get(int id); + const ItemInfo &get(const std::string &name); + + // Items database + typedef std::map ItemInfos; + typedef std::map NamedItemInfos; + + const std::map &getItemInfos(); + + struct Stat + { + Stat(const std::string &tag, + const std::string &format): + tag(tag), + format(format) + {} + + std::string tag; + std::string format; + }; + + void setStatsList(const std::list &stats); + +} + +#endif diff --git a/src/resources/iteminfo.cpp b/src/resources/iteminfo.cpp new file mode 100644 index 000000000..300c6bd26 --- /dev/null +++ b/src/resources/iteminfo.cpp @@ -0,0 +1,66 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/iteminfo.h" + +#include "resources/itemdb.h" +#include "configuration.h" + +const std::string &ItemInfo::getSprite(Gender gender) const +{ + if (mView) + { + // Forward the request to the item defining how to view this item + return ItemDB::get(mView).getSprite(gender); + } + else + { + static const std::string empty = ""; + std::map::const_iterator i = + mAnimationFiles.find(gender); + + return (i != mAnimationFiles.end()) ? i->second : empty; + } +} + +void ItemInfo::setAttackAction(std::string attackAction) +{ + if (attackAction.empty()) + mAttackAction = SpriteAction::ATTACK; // (Equal to unarmed animation) + else + mAttackAction = attackAction; +} + +void ItemInfo::addSound(EquipmentSoundEvent event, const std::string &filename) +{ + mSounds[event].push_back(paths.getStringValue("sfx") + filename); +} + +const std::string &ItemInfo::getSound(EquipmentSoundEvent event) const +{ + static const std::string empty; + std::map< EquipmentSoundEvent, + std::vector >::const_iterator i; + + i = mSounds.find(event); + + return i == mSounds.end() ? empty : i->second[rand() % i->second.size()]; +} diff --git a/src/resources/iteminfo.h b/src/resources/iteminfo.h new file mode 100644 index 000000000..bb84193bb --- /dev/null +++ b/src/resources/iteminfo.h @@ -0,0 +1,242 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ITEMINFO_H +#define ITEMINFO_H + +#include "being.h" + +#include "resources/spritedef.h" + +#include +#include +#include + +enum EquipmentSoundEvent +{ + EQUIP_EVENT_STRIKE = 0, + EQUIP_EVENT_HIT +}; + +enum EquipmentSlot +{ + // Equipment rules: + // 1 Brest equipment + EQUIP_TORSO_SLOT = 0, + // 1 arms equipment + EQUIP_ARMS_SLOT = 1, + // 1 head equipment + EQUIP_HEAD_SLOT = 2, + // 1 legs equipment + EQUIP_LEGS_SLOT = 3, + // 1 feet equipment + EQUIP_FEET_SLOT = 4, + // 2 rings + EQUIP_RING1_SLOT = 5, + EQUIP_RING2_SLOT = 6, + // 1 necklace + EQUIP_NECKLACE_SLOT = 7, + // Fight: + // 2 one-handed weapons + // or 1 two-handed weapon + // or 1 one-handed weapon + 1 shield. + EQUIP_FIGHT1_SLOT = 8, + EQUIP_FIGHT2_SLOT = 9, + // Projectile: + // this item does not amount to one, it only indicates the chosen projectile. + EQUIP_PROJECTILE_SLOT = 10 +}; + + +/** + * Enumeration of available Item types. + */ +enum ItemType +{ + ITEM_UNUSABLE = 0, + ITEM_USABLE, + ITEM_EQUIPMENT_ONE_HAND_WEAPON, + ITEM_EQUIPMENT_TWO_HANDS_WEAPON, + ITEM_EQUIPMENT_TORSO, + ITEM_EQUIPMENT_ARMS, // 5 + ITEM_EQUIPMENT_HEAD, + ITEM_EQUIPMENT_LEGS, + ITEM_EQUIPMENT_SHIELD, + ITEM_EQUIPMENT_RING, + ITEM_EQUIPMENT_NECKLACE, // 10 + ITEM_EQUIPMENT_FEET, + ITEM_EQUIPMENT_AMMO, + ITEM_EQUIPMENT_CHARM, + ITEM_SPRITE_RACE, + ITEM_SPRITE_HAIR // 15 +}; + +/** + * Defines a class for storing item infos. This includes information used when + * the item is equipped. + */ +class ItemInfo +{ + public: + /** + * Constructor. + */ + ItemInfo(): + mType(ITEM_UNUSABLE), + mWeight(0), + mView(0), + mId(0), + mDrawBefore(-1), + mDrawAfter(-1), + mDrawPriority(0), + mAttackAction(SpriteAction::INVALID), + mAttackRange(0) + { + } + + void setId(int id) + { mId = id; } + + int getId() const + { return mId; } + + void setName(const std::string &name) + { mName = name; } + + const std::string &getName() const + { return mName; } + + void setParticleEffect(const std::string &particleEffect) + { mParticle = particleEffect; } + + std::string getParticleEffect() const { return mParticle; } + + void setDisplay(SpriteDisplay display) + { mDisplay = display; } + + const SpriteDisplay &getDisplay() const + { return mDisplay; } + + void setDescription(const std::string &description) + { mDescription = description; } + + const std::string &getDescription() const + { return mDescription; } + + void setEffect(const std::string &effect) + { mEffect = effect; } + + const std::string &getEffect() const { return mEffect; } + + void setType(ItemType type) + { mType = type; } + + ItemType getType() const + { return mType; } + + void setWeight(int weight) + { mWeight = weight; } + + int getWeight() const + { return mWeight; } + + int getView() const + { return mView; } + + void setView(int view) + { mView = view; } + + void setSprite(const std::string &animationFile, Gender gender) + { mAnimationFiles[gender] = animationFile; } + + const std::string &getSprite(Gender gender) const; + + void setAttackAction(std::string attackAction); + + // Handlers for seting and getting the string used for particles when attacking + void setMissileParticle(std::string s) { mMissileParticle = s; } + + std::string getMissileParticle() const { return mMissileParticle; } + + std::string getAttackAction() const + { return mAttackAction; } + + int getAttackRange() const + { return mAttackRange; } + + void setAttackRange(int r) + { mAttackRange = r; } + + void addSound(EquipmentSoundEvent event, const std::string &filename); + + const std::string &getSound(EquipmentSoundEvent event) const; + + int getDrawBefore() const + { return mDrawBefore; } + + void setDrawBefore(int n) + { mDrawBefore = n; } + + int getDrawAfter() const + { return mDrawAfter; } + + void setDrawAfter(int n) + { mDrawAfter = n; } + + int getDrawPriority() const + { return mDrawPriority; } + + void setDrawPriority(int n) + { mDrawPriority = n; } + + protected: + SpriteDisplay mDisplay; /**< Display info (like icon) */ + std::string mName; + std::string mDescription; /**< Short description. */ + std::string mEffect; /**< Description of effects. */ + ItemType mType; /**< Item type. */ + std::string mParticle; /**< Particle effect used with this item */ + int mWeight; /**< Weight in grams. */ + int mView; /**< Item ID of how this item looks. */ + int mId; /**< Item ID */ + int mDrawBefore; + int mDrawAfter; + int mDrawPriority; + + // Equipment related members. + /** Attack type, in case of weapon. + * See SpriteAction in spritedef.h for more info. + * Attack action sub-types (bow, sword, ...) are defined in items.xml. + */ + std::string mAttackAction; + int mAttackRange; /**< Attack range, will be zero if non weapon. */ + + // Particle to be shown when weapon attacks + std::string mMissileParticle; + + /** Maps gender to sprite filenames. */ + std::map mAnimationFiles; + + /** Stores the names of sounds to be played at certain event. */ + std::map< EquipmentSoundEvent, std::vector > mSounds; +}; + +#endif diff --git a/src/resources/mapreader.cpp b/src/resources/mapreader.cpp new file mode 100644 index 000000000..cf26830f2 --- /dev/null +++ b/src/resources/mapreader.cpp @@ -0,0 +1,723 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/mapreader.h" + +#include "configuration.h" +#include "log.h" +#include "map.h" +#include "tileset.h" + +#include "resources/animation.h" +#include "resources/image.h" +#include "resources/resourcemanager.h" + +#include "utils/base64.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include +#include +#include + +int inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out, unsigned int &outLength); + +int inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out); + +static std::string resolveRelativePath(std::string base, std::string relative) +{ + // Remove trailing "/", if present + size_t i = base.length(); + if (base.at(i - 1) == '/') + base.erase(i - 1, i); + + while (relative.substr(0, 3) == "../") + { + relative.erase(0, 3); // Remove "../" + if (!base.empty()) // If base is already empty, we can't trim anymore + { + i = base.find_last_of('/'); + if (i == std::string::npos) + i = 0; + base.erase(i, base.length()); // Remove deepest folder in base + } + } + + // Re-add trailing slash, if needed + if (!base.empty() && base[base.length() - 1] != '/') + base += '/'; + + return base + relative; +} + +/** + * Inflates either zlib or gzip deflated memory. The inflated memory is + * expected to be freed by the caller. + */ +int inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out, unsigned int &outLength) +{ + int bufferSize = 256 * 1024; + int ret; + z_stream strm; + + out = (unsigned char*) malloc(bufferSize); + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = in; + strm.avail_in = inLength; + strm.next_out = out; + strm.avail_out = bufferSize; + + ret = inflateInit2(&strm, 15 + 32); + + if (ret != Z_OK) + return ret; + + do + { + if (strm.next_out == NULL) + { + inflateEnd(&strm); + return Z_MEM_ERROR; + } + + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); + + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void) inflateEnd(&strm); + return ret; + default: + break; + } + + if (ret != Z_STREAM_END) + { + out = (unsigned char*) realloc(out, bufferSize * 2); + + if (out == NULL) + { + inflateEnd(&strm); + return Z_MEM_ERROR; + } + + strm.next_out = out + bufferSize; + strm.avail_out = bufferSize; + bufferSize *= 2; + } + } + while (ret != Z_STREAM_END); + assert(strm.avail_in == 0); + + outLength = bufferSize - strm.avail_out; + (void) inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; +} + +int inflateMemory(unsigned char *in, unsigned int inLength, + unsigned char *&out) +{ + unsigned int outLength = 0; + int ret = inflateMemory(in, inLength, out, outLength); + + if (ret != Z_OK || out == NULL) + { + if (ret == Z_MEM_ERROR) + { + logger->log1("Error: Out of memory while decompressing map data!"); + } + else if (ret == Z_VERSION_ERROR) + { + logger->log1("Error: Incompatible zlib version!"); + } + else if (ret == Z_DATA_ERROR) + { + logger->log1("Error: Incorrect zlib compressed data!"); + } + else + { + logger->log1("Error: Unknown error while decompressing map data!"); + } + + free(out); + out = NULL; + outLength = 0; + } + + return outLength; +} + +Map *MapReader::readMap(const std::string &filename) +{ + logger->log("Attempting to read map %s", filename.c_str()); + // Load the file through resource manager + ResourceManager *resman = ResourceManager::getInstance(); + int fileSize; + void *buffer = resman->loadFile(filename, fileSize); + Map *map = NULL; + + if (buffer == NULL) + { + logger->log("Map file not found (%s)", filename.c_str()); + return NULL; + } + + unsigned char *inflated; + unsigned int inflatedSize; + + if (filename.find(".gz", filename.length() - 3) != std::string::npos) + { + // Inflate the gzipped map data + inflatedSize = + inflateMemory((unsigned char*) buffer, fileSize, inflated); + free(buffer); + + if (inflated == NULL) + { + logger->log("Could not decompress map file (%s)", + filename.c_str()); + return NULL; + } + } + else + { + inflated = (unsigned char*) buffer; + inflatedSize = fileSize; + } + + XML::Document doc((char*) inflated, inflatedSize); + free(inflated); + + xmlNodePtr node = doc.rootNode(); + + // Parse the inflated map data + if (node) + { + if (!xmlStrEqual(node->name, BAD_CAST "map")) + { + logger->log("Error: Not a map file (%s)!", filename.c_str()); + } + else + { + map = readMap(node, filename); + } + } + else + { + logger->log("Error while parsing map file (%s)!", filename.c_str()); + } + + if (map) map->setProperty("_filename", filename); + + return map; +} + +Map *MapReader::readMap(xmlNodePtr node, const std::string &path) +{ + // Take the filename off the path + const std::string pathDir = path.substr(0, path.rfind("/") + 1); + + const int w = XML::getProperty(node, "width", 0); + const int h = XML::getProperty(node, "height", 0); + const int tilew = XML::getProperty(node, "tilewidth", -1); + const int tileh = XML::getProperty(node, "tileheight", -1); + + bool showWarps = config.getBoolValue("warpParticle"); + const std::string warpPath = paths.getStringValue("particles") + + paths.getStringValue("portalEffectFile"); + + if (tilew < 0 || tileh < 0) + { + logger->log("MapReader: Warning: " + "Unitialized tile width or height value for map: %s", + path.c_str()); + return 0; + } + + Map *map = new Map(w, h, tilew, tileh); + + for_each_xml_child_node(childNode, node) + { + if (xmlStrEqual(childNode->name, BAD_CAST "tileset")) + { + Tileset *tileset = readTileset(childNode, pathDir, map); + if (tileset) + { + map->addTileset(tileset); + } + } + else if (xmlStrEqual(childNode->name, BAD_CAST "layer")) + { + readLayer(childNode, map); + } + else if (xmlStrEqual(childNode->name, BAD_CAST "properties")) + { + readProperties(childNode, map); + } + else if (xmlStrEqual(childNode->name, BAD_CAST "objectgroup")) + { + // The object group offset is applied to each object individually + const int tileOffsetX = XML::getProperty(childNode, "x", 0); + const int tileOffsetY = XML::getProperty(childNode, "y", 0); + const int offsetX = tileOffsetX * tilew; + const int offsetY = tileOffsetY * tileh; + + for_each_xml_child_node(objectNode, childNode) + { + if (xmlStrEqual(objectNode->name, BAD_CAST "object")) + { + std::string objType = XML::getProperty( + objectNode, "type", ""); + + objType = toUpper(objType); + +/* + if (objType == "NPC" || + objType == "SCRIPT") + { + logger->log("hidden obj: " + objType); + // Silently skip server-side objects. + continue; + } +*/ + + const std::string objName = XML::getProperty( + objectNode, "name", ""); + const int objX = XML::getProperty(objectNode, "x", 0); + const int objY = XML::getProperty(objectNode, "y", 0); + const int objW = XML::getProperty(objectNode, "width", 0); + const int objH = XML::getProperty(objectNode, "height", 0); + + logger->log("- Loading object name: %s type: %s at %d:%d" + " (%dx%d)", objName.c_str(), objType.c_str(), + objX, objY, objW, objH); + + if (objType == "PARTICLE_EFFECT") + { + if (objName.empty()) + { + logger->log1(" Warning: No particle file given"); + continue; + } + + map->addParticleEffect(objName, + objX + offsetX, + objY + offsetY, + objW, objH); + } + else if (objType == "WARP") + { + if (showWarps) + { + map->addParticleEffect(warpPath, + objX, objY, objW, objH); + } + map->addPortal(objName, MapItem::PORTAL, + objX, objY, objW, objH); + } + else if (objType == "SPAWN") + { +// map->addPortal(_("Spawn: ") + objName, MapItem::PORTAL, +// objX, objY, objW, objH); + } + else + { + logger->log1(" Warning: Unknown object type"); + } + } + } + } + } + + map->initializeAmbientLayers(); + + return map; +} + +void MapReader::readProperties(xmlNodePtr node, Properties *props) +{ + if (!props) + return; + + for_each_xml_child_node(childNode, node) + { + if (!xmlStrEqual(childNode->name, BAD_CAST "property")) + continue; + + // Example: + const std::string name = XML::getProperty(childNode, "name", ""); + const std::string value = XML::getProperty(childNode, "value", ""); + + if (!name.empty() && !value.empty()) + props->setProperty(name, value); + } +} + +static void setTile(Map *map, MapLayer *layer, int x, int y, int gid) +{ + const Tileset * const set = map->getTilesetWithGid(gid); + if (layer) + { + // Set regular tile on a layer + Image * const img = set ? set->get(gid - set->getFirstGid()) : 0; + layer->setTile(x, y, img); + } + else + { + // Set collision tile +// if (set && (gid - set->getFirstGid() == 1)) buggy update + if (set && (gid - set->getFirstGid() != 0)) + map->blockTile(x, y, Map::BLOCKTYPE_WALL); + } +} + +void MapReader::readLayer(xmlNodePtr node, Map *map) +{ + // Layers are not necessarily the same size as the map + const int w = XML::getProperty(node, "width", map->getWidth()); + const int h = XML::getProperty(node, "height", map->getHeight()); + const int offsetX = XML::getProperty(node, "x", 0); + const int offsetY = XML::getProperty(node, "y", 0); + std::string name = XML::getProperty(node, "name", ""); + name = toLower(name); + + const bool isFringeLayer = (name.substr(0, 6) == "fringe"); + const bool isCollisionLayer = (name.substr(0, 9) == "collision"); + + MapLayer *layer = 0; + + if (!isCollisionLayer) + { + layer = new MapLayer(offsetX, offsetY, w, h, isFringeLayer); + map->addLayer(layer); + } + + logger->log("- Loading layer \"%s\"", name.c_str()); + int x = 0; + int y = 0; + + // Load the tile data + for_each_xml_child_node(childNode, node) + { + if (!xmlStrEqual(childNode->name, BAD_CAST "data")) + continue; + + const std::string encoding = + XML::getProperty(childNode, "encoding", ""); + const std::string compression = + XML::getProperty(childNode, "compression", ""); + + if (encoding == "base64") + { + if (!compression.empty() && compression != "gzip") + { + logger->log1("Warning: only gzip layer" + " compression supported!"); + return; + } + + // Read base64 encoded map file + xmlNodePtr dataChild = childNode->xmlChildrenNode; + if (!dataChild) + continue; + + int len = static_cast(strlen( + (const char*)dataChild->content) + 1); + unsigned char *charData = new unsigned char[len + 1]; + const char *charStart = (const char*)dataChild->content; + unsigned char *charIndex = charData; + + while (*charStart) + { + if (*charStart != ' ' && *charStart != '\t' && + *charStart != '\n') + { + *charIndex = *charStart; + charIndex++; + } + charStart++; + } + *charIndex = '\0'; + + int binLen; + unsigned char *binData = php3_base64_decode(charData, + static_cast(strlen((char*)charData)), &binLen); + + delete[] charData; + + if (binData) + { + if (compression == "gzip") + { + // Inflate the gzipped layer data + unsigned char *inflated; + unsigned int inflatedSize = + inflateMemory(binData, binLen, inflated); + + free(binData); + binData = inflated; + binLen = inflatedSize; + + if (!inflated) + { + logger->log1("Error: Could not decompress layer!"); + return; + } + } + + for (int i = 0; i < binLen - 3; i += 4) + { + const int gid = binData[i] | + binData[i + 1] << 8 | + binData[i + 2] << 16 | + binData[i + 3] << 24; + + setTile(map, layer, x, y, gid); + + TileAnimation* ani = map->getAnimationForGid(gid); + if (ani) + ani->addAffectedTile(layer, x + y * w); + + x++; + if (x == w) + { + x = 0; y++; + + // When we're done, don't crash on too much data + if (y == h) + break; + } + } + free(binData); + } + } + else + { + // Read plain XML map file + for_each_xml_child_node(childNode2, childNode) + { + if (!xmlStrEqual(childNode2->name, BAD_CAST "tile")) + continue; + + const int gid = XML::getProperty(childNode2, "gid", -1); + setTile(map, layer, x, y, gid); + + x++; + if (x == w) + { + x = 0; y++; + if (y >= h) + break; + } + } + } + + if (y < h) + std::cerr << "TOO SMALL!\n"; + if (x) + std::cerr << "TOO SMALL!\n"; + + // There can be only one data element + break; + } + +/* + if (!layer) + return; + + for (int y = 0; y < layer->getHeight(); y ++) + { + for (int x = 0 ; x < layer->getWidth() ; x ++) + { + int width; + int c = layer->getTileDrawWidth(x, y, layer->getWidth(), width); + layer->setTileInfo(x, y, width, c); + } + } +*/ + +/* + Image *img1 = 0; + for (int y = 0; y < layer->getHeight(); y ++) + { + int skipWidth = 0; + int skipCount = 0; + img1 = layer->getTile(0, y); + layer->setTileInfo(layer->getWidth() - 1, y, skipWidth, skipCount); + for (int x = layer->getWidth() - 1 ; x > 0 ; x --) + { + Image *img = layer->getTile(x, y); + if (img) + { + if (img != img1) + { // different tile + skipWidth = 0; + skipCount = 0; + } + else + { // same tile + skipWidth += img1->getWidth(); + skipCount ++; + } + } + else + { + skipWidth = 0; + skipCount = 0; + } + layer->setTileInfo(x, y, skipWidth, skipCount); + img1 = img; + } + } +*/ +} + +Tileset *MapReader::readTileset(xmlNodePtr node, const std::string &path, + Map *map) +{ + if (!map) + return NULL; + + int firstGid = XML::getProperty(node, "firstgid", 0); + int margin = XML::getProperty(node, "margin", 0); + int spacing = XML::getProperty(node, "spacing", 0); + XML::Document* doc = NULL; + Tileset *set = NULL; + std::string pathDir(path); + + if (xmlHasProp(node, BAD_CAST "source")) + { + std::string filename = XML::getProperty(node, "source", ""); + filename = resolveRelativePath(path, filename); + + doc = new XML::Document(filename); + node = doc->rootNode(); + + // Reset path to be realtive to the tsx file + pathDir = filename.substr(0, filename.rfind("/") + 1); + } + + const int tw = XML::getProperty(node, "tilewidth", map->getTileWidth()); + const int th = XML::getProperty(node, "tileheight", map->getTileHeight()); + + for_each_xml_child_node(childNode, node) + { + if (xmlStrEqual(childNode->name, BAD_CAST "image")) + { + const std::string source = XML::getProperty( + childNode, "source", ""); + + if (!source.empty()) + { + std::string sourceStr = resolveRelativePath(pathDir, source); + + ResourceManager *resman = ResourceManager::getInstance(); + Image* tilebmp = resman->getImage(sourceStr); + + if (tilebmp) + { + set = new Tileset(tilebmp, tw, th, firstGid, margin, + spacing); + tilebmp->decRef(); + } + else + { + logger->log("Warning: Failed to load tileset (%s)", + source.c_str()); + } + } + } + else if (xmlStrEqual(childNode->name, BAD_CAST "tile")) + { + for_each_xml_child_node(tileNode, childNode) + { + if (!xmlStrEqual(tileNode->name, BAD_CAST "properties")) + continue; + + int tileGID = firstGid + XML::getProperty(childNode, "id", 0); + + // read tile properties to a map for simpler handling + std::map tileProperties; + for_each_xml_child_node(propertyNode, tileNode) + { + if (!xmlStrEqual(propertyNode->name, BAD_CAST "property")) + continue; + std::string name = XML::getProperty( + propertyNode, "name", ""); + int value = XML::getProperty(propertyNode, "value", 0); + tileProperties[name] = value; + logger->log("Tile Prop of %d \"%s\" = \"%d\"", + tileGID, name.c_str(), value); + } + + // create animation + if (!set) + continue; + + Animation *ani = new Animation; + for (int i = 0; ; i++) + { + std::map::iterator iFrame, iDelay; + iFrame = tileProperties.find( + "animation-frame" + toString(i)); + iDelay = tileProperties.find( + "animation-delay" + toString(i)); + if (iFrame != tileProperties.end() + && iDelay != tileProperties.end()) + { + ani->addFrame(set->get(iFrame->second), + iDelay->second, 0, 0); + } + else + { + break; + } + } + + if (ani->getLength() > 0) + { + map->addAnimation(tileGID, new TileAnimation(ani)); + logger->log("Animation length: %d", ani->getLength()); + } + else + { + delete ani; + ani = 0; + } + } + } + } + + delete doc; + + return set; +} diff --git a/src/resources/mapreader.h b/src/resources/mapreader.h new file mode 100644 index 000000000..ffa69d838 --- /dev/null +++ b/src/resources/mapreader.h @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MAPREADER_H +#define MAPREADER_H + +#include + +#include + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Map; +class Properties; +class Tileset; + +/** + * Reader for XML map files (*.tmx) + */ +class MapReader +{ + public: + /** + * Read an XML map from a file. + */ + static Map *readMap(const std::string &filename); + + /** + * Read an XML map from a parsed XML tree. The path is used to find the + * location of referenced tileset images. + */ + static Map *readMap(xmlNodePtr node, const std::string &path); + + private: + /** + * Reads the properties element. + * + * @param node The properties element. + * @param props The Properties instance to which the properties will + * be assigned. + */ + static void readProperties(xmlNodePtr node, Properties* props); + + /** + * Reads a map layer and adds it to the given map. + */ + static void readLayer(xmlNodePtr node, Map *map); + + /** + * Reads a tile set. + */ + static Tileset *readTileset(xmlNodePtr node, const std::string &path, + Map *map); +}; + +#endif diff --git a/src/resources/monsterdb.cpp b/src/resources/monsterdb.cpp new file mode 100644 index 000000000..badd123c7 --- /dev/null +++ b/src/resources/monsterdb.cpp @@ -0,0 +1,199 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/monsterdb.h" + +#include "log.h" + +#include "net/net.h" + +#include "resources/beinginfo.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" +#include "utils/xml.h" + +#include "configuration.h" + +#define OLD_TMWATHENA_OFFSET 1002 + +namespace +{ + BeingInfos mMonsterInfos; + bool mLoaded = false; +} + +void MonsterDB::load() +{ + if (mLoaded) + unload(); + + logger->log1("Initializing monster database..."); + + XML::Document doc("monsters.xml"); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "monsters")) + { + logger->log("Monster Database: Error while loading monster.xml!"); + mLoaded = true; + return; + } + + int offset = XML::getProperty(rootNode, "offset", Net::getNetworkType() == + ServerInfo::TMWATHENA ? OLD_TMWATHENA_OFFSET : 0); + + //iterate s + for_each_xml_child_node(monsterNode, rootNode) + { + if (!xmlStrEqual(monsterNode->name, BAD_CAST "monster")) + continue; + + BeingInfo *currentInfo = new BeingInfo; + + currentInfo->setWalkMask(Map::BLOCKMASK_WALL + | Map::BLOCKMASK_CHARACTER + | Map::BLOCKMASK_MONSTER); + currentInfo->setBlockType(Map::BLOCKTYPE_MONSTER); + + currentInfo->setName(XML::getProperty( + monsterNode, "name", _("unnamed"))); + + currentInfo->setTargetCursorSize(XML::getProperty(monsterNode, + "targetCursor", "medium")); + + currentInfo->setTargetOffsetX(XML::getProperty(monsterNode, + "targetOffsetX", 0)); + + currentInfo->setTargetOffsetY(XML::getProperty(monsterNode, + "targetOffsetY", 0)); + + currentInfo->setMaxHP(XML::getProperty(monsterNode, + "maxHP", 0)); + + if (currentInfo->getMaxHP()) + currentInfo->setStaticMaxHP(true); + + SpriteDisplay display; + + //iterate s and s + for_each_xml_child_node(spriteNode, monsterNode) + { + if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite")) + { + SpriteReference *currentSprite = new SpriteReference; + currentSprite->sprite + = (const char*)spriteNode->xmlChildrenNode->content; + + currentSprite->variant = XML::getProperty( + spriteNode, "variant", 0); + display.sprites.push_back(currentSprite); + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "sound")) + { + std::string event = XML::getProperty(spriteNode, "event", ""); + const char *filename; + filename = (const char*) spriteNode->xmlChildrenNode->content; + + if (event == "hit") + { + currentInfo->addSound(SOUND_EVENT_HIT, filename); + } + else if (event == "miss") + { + currentInfo->addSound(SOUND_EVENT_MISS, filename); + } + else if (event == "hurt") + { + currentInfo->addSound(SOUND_EVENT_HURT, filename); + } + else if (event == "die") + { + currentInfo->addSound(SOUND_EVENT_DIE, filename); + } + else + { + logger->log("MonsterDB: Warning, sound effect %s for " + "unknown event %s of monster %s", + filename, event.c_str(), + currentInfo->getName().c_str()); + } + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "attack")) + { + const int id = XML::getProperty(spriteNode, "id", 0); + const std::string particleEffect = XML::getProperty( + spriteNode, "particle-effect", ""); + const std::string spriteAction = XML::getProperty(spriteNode, + "action", + "attack"); + const std::string missileParticle = XML::getProperty( + spriteNode, "missile-particle", ""); + currentInfo->addAttack(id, spriteAction, + particleEffect, missileParticle); + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx")) + { + display.particles.push_back( + (const char*) spriteNode->xmlChildrenNode->content); + } + } + currentInfo->setDisplay(display); + + mMonsterInfos[XML::getProperty( + monsterNode, "id", 0) + offset] = currentInfo; + } + + mLoaded = true; +} + +void MonsterDB::unload() +{ + delete_all(mMonsterInfos); + mMonsterInfos.clear(); + + mLoaded = false; +} + + +BeingInfo *MonsterDB::get(int id) +{ + BeingInfoIterator i = mMonsterInfos.find(id); + + if (i == mMonsterInfos.end()) + { + i = mMonsterInfos.find(id + /*1002*/ OLD_TMWATHENA_OFFSET); + if (i == mMonsterInfos.end()) + { + logger->log("MonsterDB: Warning, unknown monster ID %d requested", + id); + return BeingInfo::Unknown; + } + else + { + return i->second; + } + } + else + { + return i->second; + } +} diff --git a/src/resources/monsterdb.h b/src/resources/monsterdb.h new file mode 100644 index 000000000..50f704388 --- /dev/null +++ b/src/resources/monsterdb.h @@ -0,0 +1,39 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MONSTER_DB_H +#define MONSTER_DB_H + +class BeingInfo; + +/** + * Monster information database. + */ +namespace MonsterDB +{ + void load(); + + void unload(); + + BeingInfo *get(int id); +} + +#endif diff --git a/src/resources/music.cpp b/src/resources/music.cpp new file mode 100644 index 000000000..c939d4f79 --- /dev/null +++ b/src/resources/music.cpp @@ -0,0 +1,84 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/music.h" + +#include "log.h" + +Music::Music(Mix_Chunk *music): + mChunk(music), + mChannel(-1) +{ +} + +Music::~Music() +{ + //Mix_FreeMusic(music); + Mix_FreeChunk(mChunk); +} + +Resource *Music::load(void *buffer, unsigned bufferSize) +{ + // Load the raw file data from the buffer in an RWops structure + SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize); + + // Use Mix_LoadMUS to load the raw music data + //Mix_Music* music = Mix_LoadMUS_RW(rw); Need to be implemeted + Mix_Chunk *tmpMusic = Mix_LoadWAV_RW(rw, 1); + + if (tmpMusic) + { + return new Music(tmpMusic); + } + else + { + logger->log("Error, failed to load music: %s", Mix_GetError()); + return NULL; + } +} + +bool Music::play(int loops) +{ + /* + * Warning: loops should be always set to -1 (infinite) with current + * implementation to avoid halting the playback of other samples + */ + + /*if (Mix_PlayMusic(music, loops)) + return true;*/ + Mix_VolumeChunk(mChunk, 120); + mChannel = Mix_PlayChannel(-1, mChunk, loops); + + return mChannel != -1; +} + +void Music::stop() +{ + /* + * Warning: very dungerous trick, it could try to stop channels occupied + * by samples rather than the current music file + */ + + //Mix_HaltMusic(); + + if (mChannel != -1) + Mix_HaltChannel(mChannel); +} diff --git a/src/resources/music.h b/src/resources/music.h new file mode 100644 index 000000000..c0cf5abe9 --- /dev/null +++ b/src/resources/music.h @@ -0,0 +1,81 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MUSIC_H +#define MUSIC_H + +#include "resources/resource.h" + +#ifdef __APPLE__ +#include +#else +#include +#endif + +/** + * Defines a class for loading and storing music. + */ +class Music : public Resource +{ + public: + /** + * Destructor. + */ + virtual ~Music(); + + /** + * Loads a music from a buffer in memory. + * + * @param buffer The memory buffer containing the music data. + * @param bufferSize The size of the memory buffer in bytes. + * + * @return NULL if the an error occurred, a valid pointer + * otherwise. + */ + static Resource *load(void *buffer, unsigned bufferSize); + + /** + * Plays the music. + * + * @param loops Number of times to repeat the playback. + * + * @return true if the playback started properly + * false otherwise. + */ + virtual bool play(int loops); + + /** + * Stops the music. + */ + virtual void stop(); + + protected: + /** + * Constructor. + */ + Music(Mix_Chunk *music); + + //Mix_Music *music; + Mix_Chunk *mChunk; + int mChannel; +}; + +#endif diff --git a/src/resources/npcdb.cpp b/src/resources/npcdb.cpp new file mode 100644 index 000000000..a5b9298b0 --- /dev/null +++ b/src/resources/npcdb.cpp @@ -0,0 +1,127 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/npcdb.h" + +#include "log.h" + +#include "resources/beinginfo.h" + +#include "utils/dtor.h" +#include "utils/xml.h" +#include "configuration.h" + +namespace +{ + BeingInfos mNPCInfos; + bool mLoaded = false; +} + +void NPCDB::load() +{ + if (mLoaded) + unload(); + + logger->log1("Initializing NPC database..."); + + XML::Document doc("npcs.xml"); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "npcs")) + { + logger->log("NPC Database: Error while loading npcs.xml!"); + mLoaded = true; + return; + } + + //iterate s + for_each_xml_child_node(npcNode, rootNode) + { + if (!xmlStrEqual(npcNode->name, BAD_CAST "npc")) + continue; + + int id = XML::getProperty(npcNode, "id", 0); + if (id == 0) + { + logger->log1("NPC Database: NPC with missing ID in npcs.xml!"); + continue; + } + + BeingInfo *currentInfo = new BeingInfo; + + currentInfo->setTargetCursorSize(XML::getProperty(npcNode, + "targetCursor", "medium")); + + currentInfo->setTargetOffsetX(XML::getProperty(npcNode, + "targetOffsetX", 0)); + + currentInfo->setTargetOffsetY(XML::getProperty(npcNode, + "targetOffsetY", 0)); + SpriteDisplay display; + for_each_xml_child_node(spriteNode, npcNode) + { + if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite")) + { + SpriteReference *currentSprite = new SpriteReference; + currentSprite->sprite = + (const char*)spriteNode->xmlChildrenNode->content; + currentSprite->variant = + XML::getProperty(spriteNode, "variant", 0); + display.sprites.push_back(currentSprite); + } + else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx")) + { + std::string particlefx = + (const char*)spriteNode->xmlChildrenNode->content; + display.particles.push_back(particlefx); + } + } + + currentInfo->setDisplay(display); + + mNPCInfos[id] = currentInfo; + } + + mLoaded = true; +} + +void NPCDB::unload() +{ + delete_all(mNPCInfos); + mNPCInfos.clear(); + + mLoaded = false; +} + +BeingInfo *NPCDB::get(int id) +{ + BeingInfoIterator i = mNPCInfos.find(id); + + if (i == mNPCInfos.end()) + { + logger->log("NPCDB: Warning, unknown NPC ID %d requested", id); + return BeingInfo::Unknown; + } + else + { + return i->second; + } +} diff --git a/src/resources/npcdb.h b/src/resources/npcdb.h new file mode 100644 index 000000000..b0c89c804 --- /dev/null +++ b/src/resources/npcdb.h @@ -0,0 +1,39 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NPC_DB_H +#define NPC_DB_H + +class BeingInfo; + +/** + * NPC information database. + */ +namespace NPCDB +{ + void load(); + + void unload(); + + BeingInfo *get(int id); +} + +#endif diff --git a/src/resources/resource.cpp b/src/resources/resource.cpp new file mode 100644 index 000000000..5a8dd3176 --- /dev/null +++ b/src/resources/resource.cpp @@ -0,0 +1,58 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/resource.h" + +#include "client.h" +#include "log.h" + +#include "resources/resourcemanager.h" + +#include + +Resource::~Resource() +{ +} + +void Resource::incRef() +{ + mRefCount++; +} + +void Resource::decRef() +{ + // Reference may not already have reached zero + if (mRefCount == 0) + { + logger->log("Warning: mRefCount already zero for %s", mIdPath.c_str()); + return; +// assert(false); + } + + mRefCount--; + + if (mRefCount == 0) + { + // Warn the manager that this resource is no longer used. + ResourceManager *resman = ResourceManager::getInstance(); + resman->release(this); + } +} diff --git a/src/resources/resource.h b/src/resources/resource.h new file mode 100644 index 000000000..89fcc9de7 --- /dev/null +++ b/src/resources/resource.h @@ -0,0 +1,81 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RESOURCE_H +#define RESOURCE_H + +#include +#include + +/** + * A generic reference counted resource object. + */ +class Resource +{ + friend class ResourceManager; + + public: + /** + * Constructor + */ + Resource(): mRefCount(0) + { } + + /** + * Increments the internal reference count. + */ + void incRef(); + + /** + * Decrements the reference count and deletes the object + * if no references are left. + * + * @return true if the object was deleted + * false otherwise. + */ + void decRef(); + + /** + * Return the path identifying this resource. + */ + const std::string &getIdPath() const + { return mIdPath; } + + /** + * Return refCount for this resource. + */ + unsigned getRefCount() + { return mRefCount; } + + protected: + /** + * Destructor. + */ + virtual ~Resource(); + + private: + std::string mIdPath; /**< Path identifying this resource. */ + time_t mTimeStamp; /**< Time at which the resource was orphaned. */ + unsigned mRefCount; /**< Reference count. */ + std::string mName; +}; + +#endif diff --git a/src/resources/resourcemanager.cpp b/src/resources/resourcemanager.cpp new file mode 100644 index 000000000..7bdcce386 --- /dev/null +++ b/src/resources/resourcemanager.cpp @@ -0,0 +1,658 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/resourcemanager.h" + +#include "client.h" +#include "configuration.h" +#include "log.h" +#include "main.h" + +#include "resources/dye.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/music.h" +#include "resources/soundeffect.h" +#include "resources/spritedef.h" + +#include +#include +#include +#include + +#include + +#define THEMES_FOLDER "themes" + +ResourceManager *ResourceManager::instance = NULL; + +ResourceManager::ResourceManager() + : mOldestOrphan(0), + mSelectedSkin(""), + mSkinName("") +{ + logger->log1("Initializing resource manager..."); +} + +ResourceManager::~ResourceManager() +{ + mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end()); + + // Release any remaining spritedefs first because they depend on image sets + ResourceIterator iter = mResources.begin(); + +#ifdef DEBUG_LEAKS + while (iter != mResources.end()) + { + if (iter->second) + { + if (iter->second->getRefCount()) + { + logger->log("ResourceLeak: " + iter->second->getIdPath() + + " (" + toString(iter->second->getRefCount()) + + ")"); + } + } + ++iter; + } + + iter = mResources.begin(); +#endif + + while (iter != mResources.end()) + { +#ifdef DEBUG_LEAKS + if (iter->second && iter->second->getRefCount()) + { + ++iter; + continue; + } +#endif + if (dynamic_cast(iter->second) != 0) + { + cleanUp(iter->second); + ResourceIterator toErase = iter; + ++iter; + mResources.erase(toErase); + } + else + { + ++iter; + } + } + + // Release any remaining image sets first because they depend on images + iter = mResources.begin(); + while (iter != mResources.end()) + { +#ifdef DEBUG_LEAKS + if (iter->second && iter->second->getRefCount()) + { + ++iter; + continue; + } +#endif + if (dynamic_cast(iter->second) != 0) + { + cleanUp(iter->second); + ResourceIterator toErase = iter; + ++iter; + mResources.erase(toErase); + } + else + { + ++iter; + } + } + + // Release remaining resources, logging the number of dangling references. + iter = mResources.begin(); + while (iter != mResources.end()) + { +#ifdef DEBUG_LEAKS + if (iter->second && iter->second->getRefCount()) + { + ++iter; + continue; + } +#endif + if (iter->second) + cleanUp(iter->second); + ++iter; + } +} + +void ResourceManager::cleanUp(Resource *res) +{ + if (!res) + return; + + if (res->mRefCount > 0) + { + logger->log("ResourceManager::~ResourceManager() cleaning up %d " + "reference%s to %s", + res->mRefCount, + (res->mRefCount == 1) ? "" : "s", + res->mIdPath.c_str()); + } + + delete res; +} + +void ResourceManager::cleanOrphans() +{ + timeval tv; + gettimeofday(&tv, NULL); + // Delete orphaned resources after 30 seconds. + time_t oldest = tv.tv_sec, threshold = oldest - 30; + + if (mOrphanedResources.empty() || mOldestOrphan >= threshold) + return; + + ResourceIterator iter = mOrphanedResources.begin(); + while (iter != mOrphanedResources.end()) + { + Resource *res = iter->second; + if (!res) + { + ++iter; + continue; + } + time_t t = res->mTimeStamp; + if (t >= threshold) + { + if (t < oldest) oldest = t; + ++iter; + } + else + { + logger->log("ResourceManager::release(%s)", res->mIdPath.c_str()); + ResourceIterator toErase = iter; + ++iter; + mOrphanedResources.erase(toErase); + delete res; // delete only after removal from list, + // to avoid issues in recursion + } + } + + mOldestOrphan = oldest; +} + +bool ResourceManager::setWriteDir(const std::string &path) +{ + return (bool) PHYSFS_setWriteDir(path.c_str()); +} + +bool ResourceManager::addToSearchPath(const std::string &path, bool append) +{ + logger->log("Adding to PhysicsFS: %s (%s)", path.c_str(), + append ? "append" : "prepend"); + if (!PHYSFS_addToSearchPath(path.c_str(), append ? 1 : 0)) + { + logger->log("Error: %s", PHYSFS_getLastError()); + return false; + } + return true; +} + +bool ResourceManager::removeFromSearchPath(const std::string &path) +{ + logger->log("Removing from PhysicsFS: %s", path.c_str()); + if (!PHYSFS_removeFromSearchPath(path.c_str())) + { + logger->log("Error: %s", PHYSFS_getLastError()); + return false; + } + return true; +} + +void ResourceManager::searchAndAddArchives(const std::string &path, + const std::string &ext, + bool append) +{ + const char *dirSep = PHYSFS_getDirSeparator(); + char **list = PHYSFS_enumerateFiles(path.c_str()); + + for (char **i = list; *i; i++) + { + size_t len = strlen(*i); + + if (len > ext.length() && !ext.compare((*i) + (len - ext.length()))) + { + std::string file, realPath, archive, realFixPath; + + file = path + (*i); + realPath = std::string(PHYSFS_getRealDir(file.c_str())); + archive = realPath + dirSep + file; + + addToSearchPath(archive, append); + } + } + + PHYSFS_freeList(list); +} + +void ResourceManager::searchAndRemoveArchives(const std::string &path, + const std::string &ext) +{ + const char *dirSep = PHYSFS_getDirSeparator(); + char **list = PHYSFS_enumerateFiles(path.c_str()); + + for (char **i = list; *i; i++) + { + size_t len = strlen(*i); + + if (len > ext.length() && !ext.compare((*i) + (len - ext.length()))) + { + std::string file, realPath, archive, realFixPath; + + file = path + (*i); + realPath = std::string(PHYSFS_getRealDir(file.c_str())); + archive = realPath + dirSep + file; + + removeFromSearchPath(archive); + } + } + + PHYSFS_freeList(list); +} + +bool ResourceManager::mkdir(const std::string &path) +{ + return (bool) PHYSFS_mkdir(path.c_str()); +} + +bool ResourceManager::exists(const std::string &path) +{ + return PHYSFS_exists(path.c_str()); +} + +bool ResourceManager::isDirectory(const std::string &path) +{ + return PHYSFS_isDirectory(path.c_str()); +} + +std::string ResourceManager::getPath(const std::string &file) +{ + // get the real path to the file + const char* tmp = PHYSFS_getRealDir(file.c_str()); + std::string path; + + // if the file is not in the search path, then its NULL + if (tmp) + { + path = std::string(tmp) + "/" + file; + } + else + { + // if not found in search path return the default path + path = Client::getPackageDirectory() + "/" + file; + } + + return path; +} + +bool ResourceManager::addResource(const std::string &idPath, + Resource* resource) +{ + if (resource) + { + resource->incRef(); + resource->mIdPath = idPath; + mResources[idPath] = resource; + return true; + } + return false; +} + +Resource *ResourceManager::get(const std::string &idPath, generator fun, + void *data) +{ + // Check if the id exists, and return the value if it does. + ResourceIterator resIter = mResources.find(idPath); + if (resIter != mResources.end()) + { + if (resIter->second) + resIter->second->incRef(); + return resIter->second; + } + + resIter = mOrphanedResources.find(idPath); + if (resIter != mOrphanedResources.end()) + { + Resource *res = resIter->second; + mResources.insert(*resIter); + mOrphanedResources.erase(resIter); + if (res) + res->incRef(); + return res; + } + + Resource *resource = fun(data); + + if (resource) + { + resource->incRef(); + resource->mIdPath = idPath; + mResources[idPath] = resource; + cleanOrphans(); + } + else + { + logger->log("Error loaging image: " + idPath); + } + + // Returns NULL if the object could not be created. + return resource; +} + +struct ResourceLoader +{ + ResourceManager *manager; + std::string path; + ResourceManager::loader fun; + static Resource *load(void *v) + { + ResourceLoader *l = static_cast< ResourceLoader * >(v); + int fileSize; + if (!l->manager) + return NULL; + void *buffer = l->manager->loadFile(l->path, fileSize); + if (!buffer) + return NULL; + Resource *res = l->fun(buffer, fileSize); + free(buffer); + return res; + } +}; + +Resource *ResourceManager::load(const std::string &path, loader fun) +{ + ResourceLoader l = { this, path, fun }; + return get(path, ResourceLoader::load, &l); +} + +Music *ResourceManager::getMusic(const std::string &idPath) +{ + return static_cast(load(idPath, Music::load)); +} + +SoundEffect *ResourceManager::getSoundEffect(const std::string &idPath) +{ + return static_cast(load(idPath, SoundEffect::load)); +} + +struct DyedImageLoader +{ + ResourceManager *manager; + std::string path; + static Resource *load(void *v) + { + if (!v) + return NULL; + + DyedImageLoader *l = static_cast< DyedImageLoader * >(v); + if (!l->manager) + return NULL; + + std::string path = l->path; + std::string::size_type p = path.find('|'); + Dye *d = NULL; + if (p != std::string::npos) + { + d = new Dye(path.substr(p + 1)); + path = path.substr(0, p); + } + int fileSize; + void *buffer = l->manager->loadFile(path, fileSize); + if (!buffer) + { + delete d; + return 0; + } + Resource *res = d ? Image::load(buffer, fileSize, *d) + : Image::load(buffer, fileSize); + free(buffer); + delete d; + return res; + } +}; + +Image *ResourceManager::getImage(const std::string &idPath) +{ + DyedImageLoader l = { this, idPath }; + return static_cast(get(idPath, DyedImageLoader::load, &l)); +} + +/* +Image *ResourceManager::getSkinImage(const std::string &idPath) +{ + if (mSelectedSkin.empty()) + return getImage(idPath); + + DyedImageLoader l = { this, mSelectedSkin + idPath }; + void *ptr = get(idPath, DyedImageLoader::load, &l); + if (ptr) + return static_cast(ptr); + else + return getImage(idPath); +} +*/ + +struct ImageSetLoader +{ + ResourceManager *manager; + std::string path; + int w, h; + static Resource *load(void *v) + { + if (!v) + return NULL; + + ImageSetLoader *l = static_cast< ImageSetLoader * >(v); + if (!l->manager) + return NULL; + + Image *img = l->manager->getImage(l->path); + if (!img) + return NULL; + ImageSet *res = new ImageSet(img, l->w, l->h); + img->decRef(); + return res; + } +}; + +ImageSet *ResourceManager::getImageSet(const std::string &imagePath, + int w, int h) +{ + ImageSetLoader l = { this, imagePath, w, h }; + std::stringstream ss; + ss << imagePath << "[" << w << "x" << h << "]"; + return static_cast(get(ss.str(), ImageSetLoader::load, &l)); +} + +struct SpriteDefLoader +{ + std::string path; + int variant; + static Resource *load(void *v) + { + if (!v) + return NULL; + + SpriteDefLoader *l = static_cast< SpriteDefLoader * >(v); + return SpriteDef::load(l->path, l->variant); + } +}; + +SpriteDef *ResourceManager::getSprite(const std::string &path, int variant) +{ + SpriteDefLoader l = { path, variant }; + std::stringstream ss; + ss << path << "[" << variant << "]"; + return static_cast(get(ss.str(), SpriteDefLoader::load, &l)); +} + +void ResourceManager::release(Resource *res) +{ + if (!res) + return; + + ResourceIterator resIter = mResources.find(res->mIdPath); + + // The resource has to exist + assert(resIter != mResources.end() && resIter->second == res); + + timeval tv; + gettimeofday(&tv, NULL); + time_t timestamp = tv.tv_sec; + + res->mTimeStamp = timestamp; + if (mOrphanedResources.empty()) mOldestOrphan = timestamp; + + mOrphanedResources.insert(*resIter); + mResources.erase(resIter); +} + +ResourceManager *ResourceManager::getInstance() +{ + // Create a new instance if necessary. + if (!instance) + instance = new ResourceManager; + return instance; +} + +void ResourceManager::deleteInstance() +{ + delete instance; + instance = 0; +} + +void *ResourceManager::loadFile(const std::string &fileName, int &fileSize) +{ + // Attempt to open the specified file using PhysicsFS + PHYSFS_file *file = PHYSFS_openRead(fileName.c_str()); + + // If the handler is an invalid pointer indicate failure + if (file == NULL) + { + logger->log("Warning: Failed to load %s: %s", + fileName.c_str(), PHYSFS_getLastError()); + return NULL; + } + + // Log the real dir of the file + logger->log("Loaded %s/%s", PHYSFS_getRealDir(fileName.c_str()), + fileName.c_str()); + + // Get the size of the file + fileSize = static_cast(PHYSFS_fileLength(file)); + + // Allocate memory and load the file + void *buffer = malloc(fileSize); + PHYSFS_read(file, buffer, 1, fileSize); + + // Close the file and let the user deallocate the memory + PHYSFS_close(file); + + return buffer; +} + +bool ResourceManager::copyFile(const std::string &src, const std::string &dst) +{ + PHYSFS_file *srcFile = PHYSFS_openRead(src.c_str()); + if (!srcFile) + { + logger->log("Read error: %s", PHYSFS_getLastError()); + return false; + } + PHYSFS_file *dstFile = PHYSFS_openWrite(dst.c_str()); + if (!dstFile) + { + logger->log("Write error: %s", PHYSFS_getLastError()); + PHYSFS_close(srcFile); + return false; + } + + int fileSize = static_cast(PHYSFS_fileLength(srcFile)); + void *buf = malloc(fileSize); + PHYSFS_read(srcFile, buf, 1, fileSize); + PHYSFS_write(dstFile, buf, 1, fileSize); + + PHYSFS_close(srcFile); + PHYSFS_close(dstFile); + free(buf); + return true; +} + +std::vector ResourceManager::loadTextFile( + const std::string &fileName) +{ + int contentsLength; + char *fileContents = (char*)loadFile(fileName, contentsLength); + std::vector lines; + + if (!fileContents) + { + logger->log("Couldn't load text file: %s", fileName.c_str()); + return lines; + } + + std::istringstream iss(std::string(fileContents, contentsLength)); + std::string line; + + while (getline(iss, line)) + lines.push_back(line); + + free(fileContents); + return lines; +} + +SDL_Surface *ResourceManager::loadSDLSurface(const std::string &filename) +{ + int fileSize; + void *buffer = loadFile(filename, fileSize); + SDL_Surface *tmp = NULL; + + if (buffer) + { + SDL_RWops *rw = SDL_RWFromMem(buffer, fileSize); + tmp = IMG_Load_RW(rw, 1); + ::free(buffer); + } + + return tmp; +} + +void ResourceManager::scheduleDelete(SDL_Surface* surface) +{ + deletedSurfaces.insert(surface); +} + +void ResourceManager::clearScheduled() +{ + for (std::set::iterator i = deletedSurfaces.begin(), + i_end = deletedSurfaces.end(); i != i_end; ++i) + { + SDL_FreeSurface(*i); + } + deletedSurfaces.clear(); +} diff --git a/src/resources/resourcemanager.h b/src/resources/resourcemanager.h new file mode 100644 index 000000000..163369a64 --- /dev/null +++ b/src/resources/resourcemanager.h @@ -0,0 +1,265 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RESOURCE_MANAGER_H +#define RESOURCE_MANAGER_H + +#include +#include +#include +#include +#include + +class Image; +class ImageSet; +class Music; +class Resource; +class SoundEffect; +class SpriteDef; +struct SDL_Surface; + +/** + * A class for loading and managing resources. + */ +class ResourceManager +{ + friend class Resource; + + public: + + typedef Resource *(*loader)(void *, unsigned); + typedef Resource *(*generator)(void *); + + ResourceManager(); + + /** + * Destructor. Cleans up remaining resources, warning about resources + * that were still referenced. + */ + ~ResourceManager(); + + /** + * Sets the write directory. + * + * @param path The path of the directory to be added. + * @return true on success, false otherwise. + */ + bool setWriteDir(const std::string &path); + + /** + * Adds a directory or archive to the search path. If append is true + * then the directory is added to the end of the search path, otherwise + * it is added at the front. + * + * @return true on success, false otherwise. + */ + bool addToSearchPath(const std::string &path, bool append); + + /** + * Remove a directory or archive from the search path. + * + * @return true on success, false otherwise. + */ + bool removeFromSearchPath(const std::string &path); + + /** + * Searches for zip files and adds them to the search path. + */ + void searchAndAddArchives(const std::string &path, + const std::string &ext, + bool append); + + /** + * Searches for zip files and remove them from the search path. + */ + void searchAndRemoveArchives(const std::string &path, + const std::string &ext); + + /** + * Creates a directory in the write path + */ + bool mkdir(const std::string &path); + + /** + * Checks whether the given file or directory exists in the search path + */ + bool exists(const std::string &path); + + /** + * Checks whether the given path is a directory. + */ + bool isDirectory(const std::string &path); + + /** + * Returns the real path to a file. Note that this method will always + * return a path, it does not check whether the file exists. + * + * @param file The file to get the real path to. + * @return The real path. + */ + std::string getPath(const std::string &file); + + /** + * Creates a resource and adds it to the resource map. + * + * @param idPath The resource identifier path. + * @param fun A function for generating the resource. + * @param data Extra parameters for the generator. + * @return A valid resource or NULL if the resource could + * not be generated. + */ + Resource *get(const std::string &idPath, generator fun, void *data); + + /** + * Loads a resource from a file and adds it to the resource map. + * + * @param path The file name. + * @param fun A function for parsing the file. + * @return A valid resource or NULL if the resource could + * not be loaded. + */ + Resource *load(const std::string &path, loader fun); + + /** + * Adds a preformatted resource to the resource map. + * + * @param path The file name. + * @param Resource The Resource to add. + * @return true if successfull, false otherwise. + */ + bool addResource(const std::string &idPath, Resource* resource); + + /** + * Copies a file from one place to another (useful for extracting + * raw files from a zip archive, for example) + * + * @param src Source file name + * @param dst Destination file name + * @return true on success, false on failure. An error message should be + * in the log file. + */ + bool copyFile(const std::string &src, const std::string &dst); + + /** + * Convenience wrapper around ResourceManager::get for loading + * images. + */ + Image *getImage(const std::string &idPath); + + /** + * Convenience wrapper around ResourceManager::get for loading + * songs. + */ + Music *getMusic(const std::string &idPath); + + /** + * Convenience wrapper around ResourceManager::get for loading + * samples. + */ + SoundEffect *getSoundEffect(const std::string &idPath); + + /** + * Creates a image set based on the image referenced by the given + * path and the supplied sprite sizes + */ + ImageSet *getImageSet(const std::string &imagePath, int w, int h); + + /** + * Creates a sprite definition based on a given path and the supplied + * variant. + */ + SpriteDef *getSprite(const std::string &path, int variant = 0); + + /** + * Releases a resource, placing it in the set of orphaned resources. + */ + void release(Resource *); + + /** + * Allocates data into a buffer pointer for raw data loading. The + * returned data is expected to be freed using free(). + * + * @param fileName The name of the file to be loaded. + * @param fileSize The size of the file that was loaded. + * + * @return An allocated byte array containing the data that was loaded, + * or NULL on fail. + */ + void *loadFile(const std::string &fileName, int &fileSize); + + /** + * Retrieves the contents of a text file. + */ + std::vector loadTextFile(const std::string &fileName); + + /** + * Loads the given filename as an SDL surface. The returned surface is + * expected to be freed by the caller using SDL_FreeSurface. + */ + SDL_Surface *loadSDLSurface(const std::string &filename); + + void scheduleDelete(SDL_Surface* surface); + + void clearScheduled(); + + /** + * Returns an instance of the class, creating one if it does not + * already exist. + */ + static ResourceManager *getInstance(); + + /** + * Deletes the class instance if it exists. + */ + static void deleteInstance(); + +/* + void selectSkin(); + + Image *getSkinImage(const std::string &idPath); + + std::string mapPathToSkin(const std::string &file); + + void fillSkinsList(std::vector &list) const; + + std::string getSkinName() const { return mSkinName; } +*/ + + private: + /** + * Deletes the resource after logging a cleanup message. + */ + static void cleanUp(Resource *resource); + + void cleanOrphans(); + + static ResourceManager *instance; + typedef std::map Resources; + typedef Resources::iterator ResourceIterator; + std::set deletedSurfaces; + Resources mResources; + Resources mOrphanedResources; + time_t mOldestOrphan; + std::string mSelectedSkin; + std::string mSkinName; +}; + +#endif diff --git a/src/resources/soundeffect.cpp b/src/resources/soundeffect.cpp new file mode 100644 index 000000000..823529c63 --- /dev/null +++ b/src/resources/soundeffect.cpp @@ -0,0 +1,58 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/soundeffect.h" + +#include "log.h" + +SoundEffect::~SoundEffect() +{ + Mix_FreeChunk(mChunk); +} + +Resource *SoundEffect::load(void *buffer, unsigned bufferSize) +{ + if (!buffer) + return NULL; + + // Load the raw file data from the buffer in an RWops structure + SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize); + + // Load the music data and free the RWops structure + Mix_Chunk *tmpSoundEffect = Mix_LoadWAV_RW(rw, 1); + + if (tmpSoundEffect) + { + return new SoundEffect(tmpSoundEffect); + } + else + { + logger->log("Error, failed to load sound effect: %s", Mix_GetError()); + return NULL; + } +} + +bool SoundEffect::play(int loops, int volume) +{ + Mix_VolumeChunk(mChunk, volume); + + return Mix_PlayChannel(-1, mChunk, loops) != -1; +} diff --git a/src/resources/soundeffect.h b/src/resources/soundeffect.h new file mode 100644 index 000000000..e7c832f42 --- /dev/null +++ b/src/resources/soundeffect.h @@ -0,0 +1,75 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SOUND_EFFECT_H +#define SOUND_EFFECT_H + +#include "resources/resource.h" + +#ifdef __APPLE__ +#include +#else +#include +#endif + +/** + * Defines a class for loading and storing sound effects. + */ +class SoundEffect : public Resource +{ + public: + /** + * Destructor. + */ + virtual ~SoundEffect(); + + /** + * Loads a sample from a buffer in memory. + * + * @param buffer The memory buffer containing the sample data. + * @param bufferSize The size of the memory buffer in bytes. + * + * @return NULL if the an error occurred, a valid pointer + * otherwise. + */ + static Resource *load(void *buffer, unsigned bufferSize); + + /** + * Plays the sample. + * + * @param loops Number of times to repeat the playback. + * @param volume Sample playback volume. + * + * @return true if the playback started properly + * false otherwise. + */ + virtual bool play(int loops, int volume); + + protected: + /** + * Constructor. + */ + SoundEffect(Mix_Chunk *soundEffect): mChunk(soundEffect) {} + + Mix_Chunk *mChunk; +}; + +#endif // SOUND_EFFECT_H diff --git a/src/resources/specialdb.cpp b/src/resources/specialdb.cpp new file mode 100644 index 000000000..93edfb683 --- /dev/null +++ b/src/resources/specialdb.cpp @@ -0,0 +1,132 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/specialdb.h" + +#include "log.h" + +#include "utils/dtor.h" +#include "utils/xml.h" + + +namespace +{ + SpecialInfos mSpecialInfos; + bool mLoaded = false; +} + +SpecialInfo::TargetMode SpecialDB::targetModeFromString(const std::string& str) +{ + if (str == "self") return SpecialInfo::TARGET_SELF; + else if (str == "friend") return SpecialInfo::TARGET_FRIEND; + else if (str == "enemy") return SpecialInfo::TARGET_ENEMY; + else if (str == "being") return SpecialInfo::TARGET_BEING; + else if (str == "point") return SpecialInfo::TARGET_POINT; + + logger->log("SpecialDB: Warning, unknown target mode \"%s\"", + str.c_str() ); + return SpecialInfo::TARGET_SELF; +} + +void SpecialDB::load() +{ + if (mLoaded) + unload(); + + logger->log("Initializing special database..."); + + XML::Document doc("specials.xml"); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "specials")) + { + logger->log("Error loading specials file specials.xml"); + return; + } + + std::string setName; + + for_each_xml_child_node(set, root) + { + if (xmlStrEqual(set->name, BAD_CAST "set")) + { + setName = XML::getProperty(set, "name", "Actions"); + + for_each_xml_child_node(special, set) + { + if (xmlStrEqual(special->name, BAD_CAST "special")) + { + SpecialInfo *info = new SpecialInfo(); + int id = XML::getProperty(special, "id", 0); + info->id = id; + info->set = setName; + info->name = XML::getProperty(special, "name", ""); + info->icon = XML::getProperty(special, "icon", ""); + + info->isActive = XML::getBoolProperty( + special, "active", false); + info->targetMode = targetModeFromString(XML::getProperty( + special, "target", "self")); + + info->level = XML::getProperty(special, "level", -1); + info->hasLevel = info->level > -1; + + info->hasRechargeBar = XML::getBoolProperty(special, + "recharge", false); + info->rechargeNeeded = 0; + info->rechargeCurrent = 0; + + if (mSpecialInfos.find(id) != mSpecialInfos.end()) + { + logger->log("SpecialDB: Duplicate special ID" + " %d (ignoring)", id); + } + else + { + mSpecialInfos[id] = info; + } + } + } + } + } + + mLoaded = true; +} + +void SpecialDB::unload() +{ + delete_all(mSpecialInfos); + mSpecialInfos.clear(); + + mLoaded = false; +} + + +SpecialInfo *SpecialDB::get(int id) +{ + SpecialInfos::iterator i = mSpecialInfos.find(id); + + if (i == mSpecialInfos.end()) + return NULL; + else + return i->second; + return NULL; +} + diff --git a/src/resources/specialdb.h b/src/resources/specialdb.h new file mode 100644 index 000000000..988651723 --- /dev/null +++ b/src/resources/specialdb.h @@ -0,0 +1,72 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SPECIAL_DB_H +#define SPECIAL_DB_H + +#include +#include + +struct SpecialInfo +{ + enum TargetMode + { + TARGET_SELF = 0, // no target selection + TARGET_FRIEND, // target friendly being + TARGET_ENEMY, // target hostile being + TARGET_BEING, // target any being + TARGET_POINT // target map location + }; + int id; + std::string set; // tab on which the special is shown + std::string name; // displayed name of special + std::string icon; // filename of graphical icon + + bool isActive; // true when the special can be used + TargetMode targetMode; // target mode + + bool hasLevel; // true when the special has levels + int level; // level of special when applicable + + bool hasRechargeBar; // true when the special has a recharge bar + int rechargeNeeded; // maximum recharge when applicable + int rechargeCurrent; // current recharge when applicable +}; + +/** + * Special information database. + */ +namespace SpecialDB +{ + void load(); + + void unload(); + + /** gets the special info for ID. Will return 0 when it is + * a server-specific special. + */ + SpecialInfo *get(int id); + + SpecialInfo::TargetMode targetModeFromString(const std::string& str); +} + +typedef std::map SpecialInfos; + +#endif diff --git a/src/resources/spritedef.cpp b/src/resources/spritedef.cpp new file mode 100644 index 000000000..dddee575f --- /dev/null +++ b/src/resources/spritedef.cpp @@ -0,0 +1,339 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/spritedef.h" + +#include "log.h" + +#include "resources/action.h" +#include "resources/animation.h" +#include "resources/dye.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include "configuration.h" + +#include "utils/xml.h" + +#include + +SpriteReference *SpriteReference::Empty = new SpriteReference( + paths.getStringValue("spriteErrorFile"), 0); + +Action *SpriteDef::getAction(std::string action) const +{ + Actions::const_iterator i = mActions.find(action); + + if (i == mActions.end()) + { + logger->log("Warning: no action \"%s\" defined!", action.c_str()); + return NULL; + } + + return i->second; +} + +SpriteDef *SpriteDef::load(const std::string &animationFile, int variant) +{ + std::string::size_type pos = animationFile.find('|'); + std::string palettes; + if (pos != std::string::npos) + palettes = animationFile.substr(pos + 1); + + XML::Document doc(animationFile.substr(0, pos)); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "sprite")) + { + logger->log("Error, failed to parse %s", animationFile.c_str()); + + std::string errorFile = paths.getStringValue("sprites") + + paths.getStringValue("spriteErrorFile"); + if (animationFile != errorFile) + return load(errorFile, 0); + else + return NULL; + } + + SpriteDef *def = new SpriteDef; + def->loadSprite(rootNode, variant, palettes); + def->substituteActions(); + return def; +} + +void SpriteDef::substituteAction(std::string complete, std::string with) +{ + if (mActions.find(complete) == mActions.end()) + { + Actions::iterator i = mActions.find(with); + if (i != mActions.end()) + mActions[complete] = i->second; + } +} + +void SpriteDef::substituteActions() +{ + substituteAction(SpriteAction::STAND, SpriteAction::DEFAULT); + substituteAction(SpriteAction::MOVE, SpriteAction::STAND); + substituteAction(SpriteAction::ATTACK, SpriteAction::STAND); + substituteAction(SpriteAction::CAST_MAGIC, SpriteAction::ATTACK); + substituteAction(SpriteAction::USE_ITEM, SpriteAction::CAST_MAGIC); + substituteAction(SpriteAction::SIT, SpriteAction::STAND); + substituteAction(SpriteAction::SLEEP, SpriteAction::SIT); + substituteAction(SpriteAction::HURT, SpriteAction::STAND); + substituteAction(SpriteAction::DEAD, SpriteAction::HURT); +} + +void SpriteDef::loadSprite(xmlNodePtr spriteNode, int variant, + const std::string &palettes) +{ + // Get the variant + const int variantCount = XML::getProperty(spriteNode, "variants", 0); + int variant_offset = 0; + + if (variantCount > 0 && variant < variantCount) + { + variant_offset = + variant * XML::getProperty(spriteNode, "variant_offset", 0); + } + + for_each_xml_child_node(node, spriteNode) + { + if (xmlStrEqual(node->name, BAD_CAST "imageset")) + loadImageSet(node, palettes); + else if (xmlStrEqual(node->name, BAD_CAST "action")) + loadAction(node, variant_offset); + else if (xmlStrEqual(node->name, BAD_CAST "include")) + includeSprite(node); + } +} + +void SpriteDef::loadImageSet(xmlNodePtr node, const std::string &palettes) +{ + const std::string name = XML::getProperty(node, "name", ""); + + // We don't allow redefining image sets. This way, an included sprite + // definition will use the already loaded image set with the same name. + if (mImageSets.find(name) != mImageSets.end()) + return; + + const int width = XML::getProperty(node, "width", 0); + const int height = XML::getProperty(node, "height", 0); + std::string imageSrc = XML::getProperty(node, "src", ""); + Dye::instantiate(imageSrc, palettes); + + ResourceManager *resman = ResourceManager::getInstance(); + ImageSet *imageSet = resman->getImageSet(imageSrc, width, height); + + if (!imageSet) + { + logger->log1("Couldn't load imageset!"); + return; + } + + mImageSets[name] = imageSet; +} + +void SpriteDef::loadAction(xmlNodePtr node, int variant_offset) +{ + const std::string actionName = XML::getProperty(node, "name", ""); + const std::string imageSetName = XML::getProperty(node, "imageset", ""); + + ImageSetIterator si = mImageSets.find(imageSetName); + if (si == mImageSets.end()) + { + logger->log("Warning: imageset \"%s\" not defined in %s", + imageSetName.c_str(), getIdPath().c_str()); + return; + } + ImageSet *imageSet = si->second; + + if (actionName == SpriteAction::INVALID) + { + logger->log("Warning: Unknown action \"%s\" defined in %s", + actionName.c_str(), getIdPath().c_str()); + return; + } + Action *action = new Action; + mActions[actionName] = action; + + // dirty hack to fix bad resources in tmw server + if (actionName == "attack_stab") + mActions["attack"] = action; + + // When first action set it as default direction + if (mActions.size() == 1) + mActions[SpriteAction::DEFAULT] = action; + + // Load animations + for_each_xml_child_node(animationNode, node) + { + if (xmlStrEqual(animationNode->name, BAD_CAST "animation")) + loadAnimation(animationNode, action, imageSet, variant_offset); + } +} + +void SpriteDef::loadAnimation(xmlNodePtr animationNode, + Action *action, ImageSet *imageSet, + int variant_offset) +{ + if (!action || !imageSet) + return; + + const std::string directionName = + XML::getProperty(animationNode, "direction", ""); + const SpriteDirection directionType = makeSpriteDirection(directionName); + + if (directionType == DIRECTION_INVALID) + { + logger->log("Warning: Unknown direction \"%s\" used in %s", + directionName.c_str(), getIdPath().c_str()); + return; + } + + Animation *animation = new Animation; + action->setAnimation(directionType, animation); + + // Get animation frames + for_each_xml_child_node(frameNode, animationNode) + { + const int delay = XML::getProperty(frameNode, "delay", 0); + int offsetX = XML::getProperty(frameNode, "offsetX", 0); + int offsetY = XML::getProperty(frameNode, "offsetY", 0); + offsetY -= imageSet->getHeight() - 32; + offsetX -= imageSet->getWidth() / 2 - 16; + + if (xmlStrEqual(frameNode->name, BAD_CAST "frame")) + { + const int index = XML::getProperty(frameNode, "index", -1); + + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *img = imageSet->get(index + variant_offset); + + if (!img) + { + logger->log("No image at index %d", index + variant_offset); + continue; + } + + animation->addFrame(img, delay, offsetX, offsetY); + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence")) + { + int start = XML::getProperty(frameNode, "start", -1); + const int end = XML::getProperty(frameNode, "end", -1); + + if (start < 0 || end < 0) + { + logger->log1("No valid value for 'start' or 'end'"); + continue; + } + + while (end >= start) + { + Image *img = imageSet->get(start + variant_offset); + + if (!img) + { + logger->log("No image at index %d", + start + variant_offset); + start++; + continue; + } + + animation->addFrame(img, delay, offsetX, offsetY); + start++; + } + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "end")) + { + animation->addTerminator(); + } + } // for frameNode +} + +void SpriteDef::includeSprite(xmlNodePtr includeNode) +{ + // TODO: Perform circular dependency check, since it's easy to crash the + // client this way. + const std::string filename = XML::getProperty(includeNode, "file", ""); + + if (filename.empty()) + return; + + XML::Document doc(paths.getStringValue("sprites") + filename); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "sprite")) + { + logger->log("Error, no sprite root node in %s", filename.c_str()); + return; + } + + loadSprite(rootNode, 0); +} + +SpriteDef::~SpriteDef() +{ + // Actions are shared, so ensure they are deleted only once. + std::set< Action * > actions; + for (Actions::const_iterator i = mActions.begin(), + i_end = mActions.end(); i != i_end; ++i) + { + actions.insert(i->second); + } + + for (std::set< Action * >::const_iterator i = actions.begin(), + i_end = actions.end(); i != i_end; ++i) + { + delete *i; + } + +//need actions.clear? + + for (ImageSetIterator i = mImageSets.begin(); + i != mImageSets.end(); ++i) + { + i->second->decRef(); + } +} + +SpriteDirection SpriteDef::makeSpriteDirection(const std::string &direction) +{ + if (direction.empty() || direction == "default") + return DIRECTION_DEFAULT; + else if (direction == "up") + return DIRECTION_UP; + else if (direction == "left") + return DIRECTION_LEFT; + else if (direction == "right") + return DIRECTION_RIGHT; + else if (direction == "down") + return DIRECTION_DOWN; + else + return DIRECTION_INVALID; +} diff --git a/src/resources/spritedef.h b/src/resources/spritedef.h new file mode 100644 index 000000000..84582c779 --- /dev/null +++ b/src/resources/spritedef.h @@ -0,0 +1,176 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SPRITEDEF_H +#define SPRITEDEF_H + +#include "resources/resource.h" + +#include + +#include +#include +#include + +class Action; +class ImageSet; + +struct SpriteReference +{ + static SpriteReference *Empty; + + SpriteReference(): + sprite(""), variant(0) + {} + + SpriteReference(std::string sprite, int variant) + { this->sprite = sprite; this->variant = variant; } + + std::string sprite; + int variant; +}; + +struct SpriteDisplay +{ + std::string image; + std::list sprites; + std::list particles; +}; + +typedef std::list::const_iterator SpriteRefs; + +/* + * Remember those are the main action. + * Action subtypes, e.g.: "attack_bow" are to be passed by items.xml after + * an ACTION_ATTACK call. + * Which special to be use to to be passed with the USE_SPECIAL call. + * Running, walking, ... is a sub-type of moving. + * ... + * Please don't add hard-coded subtypes here! + */ +namespace SpriteAction +{ + static const std::string DEFAULT = "stand"; + static const std::string STAND = "stand"; + static const std::string SIT = "sit"; + static const std::string SLEEP = "sleep"; + static const std::string DEAD = "dead"; + static const std::string MOVE = "walk"; + static const std::string ATTACK = "attack"; + static const std::string HURT = "hurt"; + static const std::string USE_SPECIAL = "special"; + static const std::string CAST_MAGIC = "magic"; + static const std::string USE_ITEM = "item"; + static const std::string INVALID = ""; +} + +enum SpriteDirection +{ + DIRECTION_DEFAULT = 0, + DIRECTION_UP, + DIRECTION_DOWN, + DIRECTION_LEFT, + DIRECTION_RIGHT, + DIRECTION_INVALID +}; + +/** + * Defines a class to load an animation. + */ +class SpriteDef : public Resource +{ + public: + /** + * Loads a sprite definition file. + */ + static SpriteDef *load(const std::string &file, int variant); + + /** + * Returns the specified action. + */ + Action *getAction(std::string action) const; + + /** + * Converts a string into a SpriteDirection enum. + */ + static SpriteDirection + makeSpriteDirection(const std::string &direction); + + private: + /** + * Constructor. + */ + SpriteDef() {} + + /** + * Destructor. + */ + ~SpriteDef(); + + /** + * Loads a sprite element. + */ + void loadSprite(xmlNodePtr spriteNode, int variant, + const std::string &palettes = ""); + + /** + * Loads an imageset element. + */ + void loadImageSet(xmlNodePtr node, const std::string &palettes); + + /** + * Loads an action element. + */ + void loadAction(xmlNodePtr node, int variant_offset); + + /** + * Loads an animation element. + */ + void loadAnimation(xmlNodePtr animationNode, + Action *action, ImageSet *imageSet, + int variant_offset); + + /** + * Include another sprite into this one. + */ + void includeSprite(xmlNodePtr includeNode); + + /** + * Complete missing actions by copying existing ones. + */ + void substituteActions(); + + /** + * When there are no animations defined for the action "complete", its + * animations become a copy of those of the action "with". + */ + void substituteAction(std::string complete, std::string with); + + typedef std::map ImageSets; + typedef ImageSets::iterator ImageSetIterator; + + typedef std::map Actions; + + ImageSets mImageSets; + Actions mActions; +}; + +#endif // SPRITEDEF_H diff --git a/src/resources/wallpaper.cpp b/src/resources/wallpaper.cpp new file mode 100644 index 000000000..d3ccef38b --- /dev/null +++ b/src/resources/wallpaper.cpp @@ -0,0 +1,172 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "resources/wallpaper.h" + +#include "resources/resourcemanager.h" +#include "log.h" + +#include "utils/stringutils.h" +#include "configuration.h" + +#include + +#include +#include +#include +#include + +//define WALLPAPER_FOLDER "graphics/images/" +//define WALLPAPER_BASE "login_wallpaper.png" + +struct WallpaperData +{ + std::string filename; + int width; + int height; +}; + +bool wallpaperCompare(WallpaperData a, WallpaperData b); + +static std::vector wallpaperData; +static bool haveBackup; // Is the backup (no size given) version available? + +static std::string wallpaperPath; +static std::string wallpaperFile; + +// Search for the wallpaper path values sequentially.. +static void initDefaultWallpaperPaths() +{ + ResourceManager *resman = ResourceManager::getInstance(); + + // Init the path + wallpaperPath = branding.getStringValue("wallpapersPath"); + + if (!wallpaperPath.empty() && resman->isDirectory(wallpaperPath)) + return; + else + wallpaperPath = paths.getValue("wallpapers", "graphics/images/"); + + // Init the default file + wallpaperFile = branding.getStringValue("wallpaperFile"); + + if (!wallpaperFile.empty()) + return; + else + wallpaperFile = paths.getValue("wallpaperFile", "login_wallpaper.png"); +} + +bool wallpaperCompare(WallpaperData a, WallpaperData b) +{ + int aa = a.width * a.height; + int ab = b.width * b.height; + + return (aa > ab || (aa == ab && a.width > b.width)); +} +#include +void Wallpaper::loadWallpapers() +{ + wallpaperData.clear(); + + initDefaultWallpaperPaths(); + + char **imgs = PHYSFS_enumerateFiles(wallpaperPath.c_str()); + + for (char **i = imgs; *i != NULL; i++) + { + int width; + int height; + + // If the backup file is found, we tell it. + if (strncmp (*i, wallpaperFile.c_str(), strlen(*i)) == 0) + haveBackup = true; + + // If the image format is terminated by: "_x.png" + // It is taken as a potential wallpaper. + + // First, get the base filename of the image: + std::string filename = *i; + unsigned long separator = filename.rfind("_"); + filename = filename.substr(0, separator); + + // Check that the base filename doesn't have any '%' markers. + separator = filename.find("%"); + if (separator == std::string::npos) + { + // Then, append the width and height search mask. + filename.append("_%dx%d.png"); + + if (sscanf(*i, filename.c_str(), &width, &height) == 2) + { + WallpaperData wp; + wp.filename = wallpaperPath; + wp.filename.append(*i); + wp.width = width; + wp.height = height; + wallpaperData.push_back(wp); + } + } + } + + PHYSFS_freeList(imgs); + + std::sort(wallpaperData.begin(), wallpaperData.end(), wallpaperCompare); +} + +std::string Wallpaper::getWallpaper(int width, int height) +{ + std::vector::iterator iter; + WallpaperData wp; + + // Wallpaper filename container + std::vector wallPaperVector; + + for (iter = wallpaperData.begin(); iter != wallpaperData.end(); iter++) + { + wp = *iter; + if (wp.width <= width && wp.height <= height) + wallPaperVector.push_back(wp.filename); + } + + if (!wallPaperVector.empty()) + { + // If we've got more than one occurence of a valid wallpaper... + if (wallPaperVector.size() > 0) + { + // Return randomly a wallpaper between vector[0] and + // vector[vector.size() - 1] + srand((unsigned) time(0)); + return wallPaperVector[int(static_cast( + wallPaperVector.size()) * rand() / (RAND_MAX + 1.0))]; + } + else // If there at least one, we return it + { + return wallPaperVector[0]; + } + } + + // Return the backup file if everything else failed... + if (haveBackup) + return std::string(wallpaperPath + wallpaperFile); + + // Return an empty string if everything else failed + return std::string(); +} diff --git a/src/resources/wallpaper.h b/src/resources/wallpaper.h new file mode 100644 index 000000000..bb640d1ef --- /dev/null +++ b/src/resources/wallpaper.h @@ -0,0 +1,50 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WALLPAPER_H +#define WALLPAPER_H + +#include + +/** + * Handles organizing and choosing of wallpapers. + */ +class Wallpaper +{ + public: + /** + * Reads the folder that contains wallpapers and organizes the + * wallpapers found by area, width, and height. + */ + static void loadWallpapers(); + + /** + * Returns the largest wallpaper for the given resolution, or the + * default wallpaper if none are found. + * + * @param width the desired width + * @param height the desired height + * @return the file to use, or empty if no wallpapers are useable + */ + static std::string getWallpaper(int width, int height); +}; + +#endif // WALLPAPER_H diff --git a/src/rotationalparticle.cpp b/src/rotationalparticle.cpp new file mode 100644 index 000000000..1e0ae9618 --- /dev/null +++ b/src/rotationalparticle.cpp @@ -0,0 +1,82 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "rotationalparticle.h" +#include "graphics.h" +#include "simpleanimation.h" + +#define PI 3.14159265 + +RotationalParticle::RotationalParticle(Map *map, Animation *animation): + ImageParticle(map, NULL), + mAnimation(new SimpleAnimation(animation)) +{ +} + +RotationalParticle::RotationalParticle(Map *map, xmlNodePtr animationNode): + ImageParticle(map, 0), + mAnimation(new SimpleAnimation(animationNode)) +{ +} + +RotationalParticle::~RotationalParticle() +{ + delete mAnimation; + mAnimation = 0; + mImage = 0; +} + +bool RotationalParticle::update() +{ + if (!mAnimation) + return false; + + // TODO: cache velocities to avoid spamming atan2() + + float rad = static_cast(atan2(mVelocity.x, mVelocity.y)); + if (rad < 0) + rad = static_cast(PI + (PI + rad)); + int size = mAnimation->getLength(); + + float range = static_cast(PI / size); + + // Determines which frame the particle should play + if (rad < range || rad > ((PI*2) - range)) + { + mAnimation->setFrame(0); + } + else + { + for (int c = 1; c < size; c++) + { + if (((static_cast(c) * (2 * range)) - range) < rad + && rad < ((static_cast(c) * (2 * range)) + range)) + { + mAnimation->setFrame(c); + break; + } + } + } + + mImage = mAnimation->getCurrentImage(); + + return Particle::update(); +} diff --git a/src/rotationalparticle.h b/src/rotationalparticle.h new file mode 100644 index 000000000..535cfb58a --- /dev/null +++ b/src/rotationalparticle.h @@ -0,0 +1,48 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ROTATIONAL_PARTICLE_H +#define ROTATIONAL_PARTICLE_H + +#include "imageparticle.h" + +#include + +class Animation; +class Map; +class SimpleAnimation; + +class RotationalParticle : public ImageParticle +{ + public: + RotationalParticle(Map *map, Animation *animation); + + RotationalParticle(Map *map, xmlNodePtr animationNode); + + ~RotationalParticle(); + + virtual bool update(); + + private: + SimpleAnimation *mAnimation; /**< Used animation for this particle */ +}; + +#endif diff --git a/src/shopitem.cpp b/src/shopitem.cpp new file mode 100644 index 000000000..56fec1fac --- /dev/null +++ b/src/shopitem.cpp @@ -0,0 +1,95 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "shopitem.h" + +#include "units.h" + +#include "utils/stringutils.h" + +#include "resources/iteminfo.h" + +ShopItem::ShopItem(int inventoryIndex, int id, + int quantity, int price) : + Item(id, 0), + mPrice(price) +{ + mDisplayName = getInfo().getName() + " (" + + Units::formatCurrency(mPrice).c_str() + ") "; + if (quantity > 0) + mDisplayName += "[" + toString(quantity) + "]"; + + setInvIndex(inventoryIndex); + addDuplicate(inventoryIndex, quantity); +} + +ShopItem::ShopItem (int id, int price) : Item (id, 0), mPrice(price) +{ + mDisplayName = getInfo().getName() + + " (" + Units::formatCurrency(mPrice).c_str() + ")"; + setInvIndex(-1); + addDuplicate(-1, 0); +} + +ShopItem::~ShopItem() +{ + /** Clear all remaining duplicates on Object destruction. */ + while (!mDuplicates.empty()) + { + delete mDuplicates.top(); + mDuplicates.pop(); + } +} + +void ShopItem::addDuplicate(int inventoryIndex, int quantity) +{ + DuplicateItem* di = new DuplicateItem; + di->inventoryIndex = inventoryIndex; + di->quantity = quantity; + mDuplicates.push(di); + mQuantity += quantity; +} + +void ShopItem::addDuplicate() +{ + DuplicateItem* di = new DuplicateItem; + di->inventoryIndex = -1; + di->quantity = 0; + mDuplicates.push(di); +} + +int ShopItem::sellCurrentDuplicate(int quantity) +{ + DuplicateItem* dupl = mDuplicates.top(); + if (!dupl) + return 0; + + int sellCount = quantity <= dupl->quantity ? quantity : dupl->quantity; + dupl->quantity -= sellCount; + mQuantity -= sellCount; + if (dupl->quantity == 0) + { + delete dupl; + dupl = 0; + mDuplicates.pop(); + } + return sellCount; +} diff --git a/src/shopitem.h b/src/shopitem.h new file mode 100644 index 000000000..04d26df8f --- /dev/null +++ b/src/shopitem.h @@ -0,0 +1,140 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _SHOPITEM_H +#define _SHOPITEM_H + +#include "item.h" + +#include + +/** + * Represents an item in a shop inventory. It can store quantity and inventory + * indices of duplicate entries in the shop as well. + */ +class ShopItem : public Item +{ + public: + /** + * Constructor. Creates a new ShopItem. + * + * @param inventoryIndex the inventory index of the item + * @param id the id of the item + * @param quantity number of available copies of the item + * @param price price of the item + */ + ShopItem(int inventoryIndex, int id, int quantity, int price); + + /** + * Constructor. Creates a new ShopItem. Inventory index will be set to + * -1 and quantity to 0. + * + * @param id the id of the item + * @param price price of the item + */ + ShopItem(int id, int price); + + /** + * Destructor. + */ + ~ShopItem(); + + /** + * Add a duplicate. Id and price will be taken from this item. + * + * @param inventoryIndex the inventory index of the item + * @param quantity number of available copies of the item + */ + void addDuplicate(int inventoryIndex, int quantity); + + /** + * Add a duplicate. Id and price will be taken from this item. + * Needed for compatibility with ShopDuplicateItems (see) class + * documentation). + */ + void addDuplicate(); + + /** + * Gets the quantity of the currently topmost duplicate. + * + * @return the quantity of the currently topmost duplicate + */ + int getCurrentQuantity() const + { + return mDuplicates.empty() ? 0 : mDuplicates.top()->quantity; + } + + /** + * Gets the inventory index of the currently topmost duplicate. + * + * @return the inventory index of the currently topmost duplicate + */ + int getCurrentInvIndex() const + { + return mDuplicates.empty() ? mInvIndex : + mDuplicates.top()->inventoryIndex; + } + + /** + * Reduces the quantity of the topmost duplicate by the specified + * amount. Also reduces the total quantity of this DuplicateItem. + * Empty duplicates are automatically removed. + * + * If the amount is bigger than the quantity of the current topmost, + * only sell as much as possible. Returns the amount actually sold (do + * not ignore the return value!) + * + * @return the amount, that actually was sold. + */ + int sellCurrentDuplicate(int quantity); + + /** + * Gets the price of the item. + * + * @return the price of the item + */ + int getPrice() const + { return mPrice; } + + /** + * Gets the display name for the item in the shop list. + * + * @return the display name for the item in the shop list + */ + const std::string &getDisplayName() const + { return mDisplayName; } + + protected: + int mPrice; + std::string mDisplayName; + + /** + * Struct to keep track of duplicates. + */ + typedef struct + { + int inventoryIndex; + int quantity; + } DuplicateItem; + std::stack mDuplicates; /** <-- Stores duplicates */ +}; + +#endif diff --git a/src/simpleanimation.cpp b/src/simpleanimation.cpp new file mode 100644 index 000000000..154da1e40 --- /dev/null +++ b/src/simpleanimation.cpp @@ -0,0 +1,212 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "simpleanimation.h" + +#include "graphics.h" +#include "log.h" + +#include "utils/stringutils.h" + +#include "resources/animation.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +SimpleAnimation::SimpleAnimation(Animation *animation): + mAnimation(animation), + mAnimationTime(0), + mAnimationPhase(0), + mCurrentFrame(mAnimation->getFrame(0)), + mInitialized(true) +{ +} + +SimpleAnimation::SimpleAnimation(xmlNodePtr animationNode): + mAnimation(new Animation), + mAnimationTime(0), + mAnimationPhase(0), + mInitialized(false) +{ + initializeAnimation(animationNode); + if (mAnimation) + mCurrentFrame = mAnimation->getFrame(0); + else + mCurrentFrame = 0; +} + +SimpleAnimation::~SimpleAnimation() +{ + delete mAnimation; + mAnimation = 0; +} + +bool SimpleAnimation::draw(Graphics *graphics, int posX, int posY) const +{ + if (!mCurrentFrame || !mCurrentFrame->image) + return false; + + return graphics->drawImage(mCurrentFrame->image, + posX + mCurrentFrame->offsetX, + posY + mCurrentFrame->offsetY); +} + +void SimpleAnimation::reset() +{ + mAnimationTime = 0; + mAnimationPhase = 0; +} + +void SimpleAnimation::setFrame(int frame) +{ + if (!mAnimation) + return; + + if (frame < 0) + frame = 0; + if ((unsigned)frame >= mAnimation->getLength()) + frame = mAnimation->getLength() - 1; + mAnimationPhase = frame; + mCurrentFrame = mAnimation->getFrame(mAnimationPhase); +} + +void SimpleAnimation::update(int timePassed) +{ + if (!mCurrentFrame || !mAnimation) + return; + + if (mInitialized && mAnimation) + { + mAnimationTime += timePassed; + + while (mAnimationTime > mCurrentFrame->delay + && mCurrentFrame->delay > 0) + { + mAnimationTime -= mCurrentFrame->delay; + mAnimationPhase++; + + if ((unsigned)mAnimationPhase >= mAnimation->getLength()) + mAnimationPhase = 0; + + mCurrentFrame = mAnimation->getFrame(mAnimationPhase); + } + } + +} + +int SimpleAnimation::getLength() const +{ + if (!mAnimation) + return 0; + + return mAnimation->getLength(); +} + +Image *SimpleAnimation::getCurrentImage() const +{ + if (mCurrentFrame) + return mCurrentFrame->image; + else + return NULL; +} + +void SimpleAnimation::initializeAnimation(xmlNodePtr animationNode) +{ + mInitialized = false; + + if (!animationNode) + return; + + ImageSet *imageset = ResourceManager::getInstance()->getImageSet( + XML::getProperty(animationNode, "imageset", ""), + XML::getProperty(animationNode, "width", 0), + XML::getProperty(animationNode, "height", 0) + ); + + if (!imageset) + return; + + // Get animation frames + for (xmlNodePtr frameNode = animationNode->xmlChildrenNode; + frameNode; frameNode = frameNode->next) + { + int delay = XML::getProperty(frameNode, "delay", 0); + int offsetX = XML::getProperty(frameNode, "offsetX", 0); + int offsetY = XML::getProperty(frameNode, "offsetY", 0); + offsetY -= imageset->getHeight() - 32; + offsetX -= imageset->getWidth() / 2 - 16; + + if (xmlStrEqual(frameNode->name, BAD_CAST "frame")) + { + int index = XML::getProperty(frameNode, "index", -1); + + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + if (mAnimation) + mAnimation->addFrame(img, delay, offsetX, offsetY); + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence")) + { + int start = XML::getProperty(frameNode, "start", -1); + int end = XML::getProperty(frameNode, "end", -1); + + if (start < 0 || end < 0) + { + logger->log1("No valid value for 'start' or 'end'"); + continue; + } + + while (end >= start) + { + Image *img = imageset->get(start); + + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + if (mAnimation) + mAnimation->addFrame(img, delay, offsetX, offsetY); + start++; + } + } + else if (xmlStrEqual(frameNode->name, BAD_CAST "end")) + { + if (mAnimation) + mAnimation->addTerminator(); + } + } + + mInitialized = true; +} diff --git a/src/simpleanimation.h b/src/simpleanimation.h new file mode 100644 index 000000000..e679442e4 --- /dev/null +++ b/src/simpleanimation.h @@ -0,0 +1,86 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SIMPLEANIMAION_H +#define SIMPLEANIMAION_H + +#include "utils/xml.h" + +class Animation; +class Frame; +class Graphics; +class Image; + +/** + * This class is a leightweight alternative to the AnimatedSprite class. + * It hosts a looping animation without actions and directions. + */ +class SimpleAnimation +{ + public: + /** + * Creates a simple animation with an already created \a animation. + * Takes ownership over the given animation. + */ + SimpleAnimation(Animation *animation); + + /** + * Creates a simple animation that creates its animation from XML Data. + */ + SimpleAnimation(xmlNodePtr animationNode); + + ~SimpleAnimation(); + + void setFrame(int frame); + + int getLength() const; + + void update(int timePassed); + + bool draw(Graphics *graphics, int posX, int posY) const; + + /** + * Resets the animation. + */ + void reset(); + + Image *getCurrentImage() const; + + private: + void initializeAnimation(xmlNodePtr animationNode); + + /** The hosted animation. */ + Animation *mAnimation; + + /** Time in game ticks the current frame is shown. */ + int mAnimationTime; + + /** Index of current animation phase. */ + int mAnimationPhase; + + /** Current animation phase. */ + Frame *mCurrentFrame; + + /** Tell whether the animation is ready */ + bool mInitialized; +}; + +#endif diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 000000000..8742ce361 --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,344 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "configuration.h" +#include "log.h" +#include "sound.h" + +#include "resources/resourcemanager.h" +#include "resources/soundeffect.h" + +#include "configuration.h" + +Sound::Sound(): + mInstalled(false), + mSfxVolume(100), + mMusicVolume(60), + mMusic(0), + mPlayBattle(false), + mPlayGui(false), + mPlayMusic(false) +{ +} + +Sound::~Sound() +{ + config.removeListener("playBattleSound", this); + config.removeListener("playGuiSound", this); + config.removeListener("playMusic", this); +} + +void Sound::optionChanged(const std::string &value) +{ + if (value == "playBattleSound") + mPlayBattle = config.getBoolValue("playBattleSound"); + else if (value == "playGuiSound") + mPlayGui = config.getBoolValue("playGuiSound"); + else if (value == "playMusic") + mPlayMusic = config.getBoolValue("playMusic"); +} + +void Sound::init() +{ + // Don't initialize sound engine twice + if (mInstalled) + return; + + logger->log1("Sound::init() Initializing sound..."); + + mPlayBattle = config.getBoolValue("playBattleSound"); + mPlayGui = config.getBoolValue("playGuiSound"); + mPlayMusic = config.getBoolValue("playMusic"); + config.addListener("playBattleSound", this); + config.addListener("playGuiSound", this); + config.addListener("playMusic", this); + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) + { + logger->log1("Sound::init() Failed to initialize audio subsystem"); + return; + } + + const size_t audioBuffer = 4096; + + const int res = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, + MIX_DEFAULT_CHANNELS, audioBuffer); + if (res < 0) + { + logger->log("Sound::init Could not initialize audio: %s", + Mix_GetError()); + return; + } + + Mix_AllocateChannels(16); + Mix_VolumeMusic(mMusicVolume); + Mix_Volume(-1, mSfxVolume); + + info(); + + mInstalled = true; + + if (!mCurrentMusicFile.empty() && mPlayMusic) + playMusic(mCurrentMusicFile); +} + +void Sound::info() +{ + SDL_version compiledVersion; + const SDL_version *linkedVersion; + char driver[40] = "Unknown"; + const char *format = "Unknown"; + int rate = 0; + Uint16 audioFormat = 0; + int channels = 0; + + MIX_VERSION(&compiledVersion); + linkedVersion = Mix_Linked_Version(); + + SDL_AudioDriverName(driver, 40); + + Mix_QuerySpec(&rate, &audioFormat, &channels); + switch (audioFormat) + { + case AUDIO_U8: format = "U8"; break; + case AUDIO_S8: format = "S8"; break; + case AUDIO_U16LSB: format = "U16LSB"; break; + case AUDIO_S16LSB: format = "S16LSB"; break; + case AUDIO_U16MSB: format = "U16MSB"; break; + case AUDIO_S16MSB: format = "S16MSB"; break; + default: break; + } + + logger->log("Sound::info() SDL_mixer: %i.%i.%i (compiled)", + compiledVersion.major, + compiledVersion.minor, + compiledVersion.patch); + logger->log("Sound::info() SDL_mixer: %i.%i.%i (linked)", + linkedVersion->major, + linkedVersion->minor, + linkedVersion->patch); + logger->log("Sound::info() Driver: %s", driver); + logger->log("Sound::info() Format: %s", format); + logger->log("Sound::info() Rate: %i", rate); + logger->log("Sound::info() Channels: %i", channels); +} + +int Sound::getMaxVolume() const +{ + return MIX_MAX_VOLUME; +} + +void Sound::setMusicVolume(int volume) +{ + mMusicVolume = volume; + + if (mInstalled) + Mix_VolumeMusic(mMusicVolume); +} + +void Sound::setSfxVolume(int volume) +{ + mSfxVolume = volume; + + if (mInstalled) + Mix_Volume(-1, mSfxVolume); +} + +static Mix_Music *loadMusic(const std::string &filename) +{ + ResourceManager *resman = ResourceManager::getInstance(); + std::string path = resman->getPath( + paths.getStringValue("music") + filename); + + if (path.find(".zip/") != std::string::npos || + path.find(".zip\\") != std::string::npos) + { + // Music file is a virtual file inside a zip archive - we have to copy + // it to a temporary physical file so that SDL_mixer can stream it. + logger->log("Loading music \"%s\" from temporary file tempMusic.ogg", + path.c_str()); + bool success = resman->copyFile(paths.getStringValue("music") + + filename, "tempMusic.ogg"); + if (success) + path = resman->getPath("tempMusic.ogg"); + else + return NULL; + } + else + { + logger->log("Loading music \"%s\"", path.c_str()); + } + + if (path.empty()) + return 0; + + Mix_Music *music = Mix_LoadMUS(path.c_str()); + + if (!music) + { + logger->log("Mix_LoadMUS() Error loading '%s': %s", path.c_str(), + Mix_GetError()); + } + + return music; +} + +void Sound::playMusic(const std::string &filename) +{ + mCurrentMusicFile = filename; + + if (!mInstalled || !mPlayMusic) + return; + + haltMusic(); + + if ((mMusic = loadMusic(filename))) + Mix_PlayMusic(mMusic, -1); // Loop forever +} + +void Sound::stopMusic() +{ + if (!mInstalled) + return; + + logger->log1("Sound::stopMusic()"); + + if (mMusic) + { + Mix_HaltMusic(); + Mix_FreeMusic(mMusic); + mMusic = NULL; + } +} + +void Sound::fadeInMusic(const std::string &path, int ms) +{ + mCurrentMusicFile = path; + + if (!mInstalled || !mPlayMusic) + return; + + haltMusic(); + + if ((mMusic = loadMusic(path.c_str()))) + Mix_FadeInMusic(mMusic, -1, ms); // Loop forever +} + +void Sound::fadeOutMusic(int ms) +{ + mCurrentMusicFile.clear(); + + if (!mInstalled) + return; + + logger->log("Sound::fadeOutMusic() Fading-out (%i ms)", ms); + + if (mMusic) + { + Mix_FadeOutMusic(ms); + Mix_FreeMusic(mMusic); + mMusic = NULL; + } +} + +void Sound::playSfx(const std::string &path) +{ + if (!mInstalled || path.empty() || !mPlayBattle) + return; + + std::string tmpPath; + if (!path.find("sfx/")) + tmpPath = path; + else + tmpPath = paths.getValue("sfx", "sfx/") + path; + ResourceManager *resman = ResourceManager::getInstance(); + SoundEffect *sample = resman->getSoundEffect(tmpPath); + if (sample) + { + logger->log("Sound::playSfx() Playing: %s", path.c_str()); + sample->play(0, 120); + } +} + +void Sound::playGuiSfx(const std::string &path) +{ + if (!mInstalled || path.empty() || !mPlayGui) + return; + + ResourceManager *resman = ResourceManager::getInstance(); + SoundEffect *sample = resman->getSoundEffect( + paths.getStringValue("sfx") + path); + if (sample) + { + logger->log("Sound::playSfx() Playing: %s", path.c_str()); + sample->play(0, 120); + } +} + +void Sound::close() +{ + if (!mInstalled) + return; + + haltMusic(); + logger->log1("Sound::close() Shutting down sound..."); + Mix_CloseAudio(); + + mInstalled = false; +} + +void Sound::haltMusic() +{ + if (!mMusic) + return; + + Mix_HaltMusic(); + Mix_FreeMusic(mMusic); + mMusic = NULL; +} + +void Sound::changeAudio() +{ + if (mInstalled) + close(); + else + init(); +} + +void Sound::volumeOff() +{ + if (mInstalled) + { + Mix_VolumeMusic(0); + Mix_Volume(-1, 0); + } +} + +void Sound::volumeRestore() +{ + if (mInstalled) + { + Mix_VolumeMusic(mMusicVolume); + Mix_Volume(-1, mSfxVolume); + } +} diff --git a/src/sound.h b/src/sound.h new file mode 100644 index 000000000..100c228c1 --- /dev/null +++ b/src/sound.h @@ -0,0 +1,130 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SOUND_H +#define SOUND_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "configlistener.h" + +#include + +/** Sound engine + * + * \ingroup CORE + */ +class Sound : public ConfigListener +{ + public: + Sound(); + ~Sound(); + + void optionChanged(const std::string &value); + + /** + * Installs the sound engine. + */ + void init(); + + /** + * Removes all sound functionalities. + */ + void close(); + + /** + * Starts background music. + * + * @param path The full path to the music file. + */ + void playMusic(const std::string &path); + + /** + * Stops currently running background music track. + */ + void stopMusic(); + + /** + * Fades in background music. + * + * @param path The full path to the music file. + * @param ms Duration of fade-in effect (ms) + */ + void fadeInMusic(const std::string &path, int ms = 2000); + + /** + * Fades out currently running background music track. + * + * @param ms Duration of fade-out effect (ms) + */ + void fadeOutMusic(int ms); + + int getMaxVolume() const; + + void setMusicVolume(int volume); + void setSfxVolume(int volume); + + /** + * Plays an item. + * + * @param path The resource path to the sound file. + */ + void playSfx(const std::string &path); + + /** + * Plays an item for gui. + * + * @param path The resource path to the sound file. + */ + void playGuiSfx(const std::string &path); + + void changeAudio(); + + void volumeOff(); + + void volumeRestore(); + + private: + /** Logs various info about sound device. */ + void info(); + + /** Halts and frees currently playing music. */ + void haltMusic(); + + bool mInstalled; + + int mSfxVolume; + int mMusicVolume; + + std::string mCurrentMusicFile; + Mix_Music *mMusic; + bool mPlayBattle; + bool mPlayGui; + bool mPlayMusic; +}; + +extern Sound sound; + +#endif diff --git a/src/spellmanager.cpp b/src/spellmanager.cpp new file mode 100644 index 000000000..2f4e7bd26 --- /dev/null +++ b/src/spellmanager.cpp @@ -0,0 +1,346 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "spellmanager.h" + +#include "being.h" +#include "configuration.h" +#include "log.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "gui/chat.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "utils/dtor.h" +#include "utils/stringutils.h" + +SpellManager::SpellManager() +{ + //fillSpells(); + load(); +} + +SpellManager::~SpellManager() +{ + delete_all(mSpells); + mSpells.clear(); + mSpellsVector.clear(); +// save(); +} + +TextCommand* SpellManager::getSpell(int spellId) +{ + if (spellId < 0 || (unsigned int)spellId >= mSpells.size()) + return NULL; + + return mSpells[spellId]; +} + +TextCommand* SpellManager::getSpellByItem(int itemId) +{ + return getSpell(itemId - SPELL_MIN_ID); +} + +void SpellManager::fillSpells() +{ +//id, std::string name, std::string symbol, ST type, int basicLvl, MagicSchool school, int schoolLvl, int mana) + + addSpell(new TextCommand(0, "lum", "#lum", ALLOWTARGET, + "", 1, SKILL_MAGIC_LIFE, 0, 6)); + addSpell(new TextCommand(1, "inm", "#inma", NEEDTARGET, + "", 2, SKILL_MAGIC_LIFE, 2, 10)); + addSpell(new TextCommand(2, "fla", "#flar", NOTARGET, + "", 1, SKILL_MAGIC_WAR, 0, 10)); + addSpell(new TextCommand(3, "chi", "#chiza", NOTARGET, + "", 1, SKILL_MAGIC_WAR, 0, 9)); + addSpell(new TextCommand(4, "ing", "#ingrav", NOTARGET, + "", 2, SKILL_MAGIC_WAR, 2, 20)); + addSpell(new TextCommand(5, "fri", "#frillyar", NOTARGET, + "", 2, SKILL_MAGIC_WAR, 2, 25)); + addSpell(new TextCommand(6, "upm", "#upmarmu", NOTARGET, + "", 2, SKILL_MAGIC_WAR, 2, 20)); + addSpell(new TextCommand(7, "ite", "#itenplz", NOTARGET, + "", 1, SKILL_MAGIC_NATURE, 0, 3)); + addSpell(new TextCommand(8, "bet", "#betsanc", ALLOWTARGET, + "", 2, SKILL_MAGIC_NATURE, 2, 14)); + addSpell(new TextCommand(9, "abi", "#abizit", NOTARGET, + "", 1, SKILL_MAGIC, 0, 1)); + addSpell(new TextCommand(10, "inw", "#inwilt", NOTARGET, + "", 2, SKILL_MAGIC, 2, 7)); + addSpell(new TextCommand(11, "hi", "hi", NOTARGET, "")); + addSpell(new TextCommand(12, "hea", "heal", NOTARGET, "")); + addSpell(new TextCommand(13, "@sp", "@spawn maggot 10", NOTARGET, "")); + for (int f = 12; f < SPELL_SHORTCUT_ITEMS; f++) + addSpell(new TextCommand(f)); +} + +bool SpellManager::addSpell(TextCommand *spell) +{ + if (!spell) + return false; + + std::map::iterator + i = mSpells.find(spell->getId()); + if (i == mSpells.end()) + { + mSpells[spell->getId()] = spell; + mSpellsVector.push_back(spell); + return true; + } + return false; +} + +std::vector SpellManager::getAll() +{ + //logger->log(("mSpellsVector = " + toString(mSpellsVector.size())).c_str()); + return mSpellsVector; +} + +void SpellManager::useItem(int itemId) +{ + invoke(itemId - SPELL_MIN_ID); +} + +void SpellManager::invoke(int spellId) +{ + if (!player_node) + return; + + TextCommand* spell = getSpell(spellId); + if (!spell) + return; + + if (spell->getCommand() == "") + return; + + if (spell->getCommandType() == TEXT_COMMAND_TEXT + || (Net::getPlayerHandler()->canUseMagic() + && PlayerInfo::getStatEffective(SKILL_MAGIC) >= spell->getBaseLvl() + && PlayerInfo::getStatEffective( + spell->getSchool()) >= spell->getSchoolLvl() + && PlayerInfo::getAttribute(MP) >= spell->getMana())) + { + Being* target = player_node->getTarget(); + if (spell->getTargetType() == NOTARGET) + { + invokeSpell(spell); + } + if ((target && (target->getType() != Being::MONSTER + || spell->getCommandType() == TEXT_COMMAND_TEXT)) + && (spell->getTargetType() == ALLOWTARGET + || spell->getTargetType() == NEEDTARGET)) + { + invokeSpell(spell, target); + } + else if (spell->getTargetType() == ALLOWTARGET) + { + invokeSpell(spell); + } + } +} + +void SpellManager::invokeSpell(TextCommand* spell) const +{ + if (!chatWindow || !spell) + return; + chatWindow->localChatInput(parseCommand(spell->getCommand(), 0)); +} + +void SpellManager::invokeSpell(TextCommand* spell, Being* target) const +{ + if (!chatWindow || !spell || !target) + return; + chatWindow->localChatInput(parseCommand(spell->getCommand(), target)); +} + +std::string SpellManager::parseCommand(std::string command, + Being *target) const +{ + if (!player_node) + return command; + + std::string name = ""; + std::string id = ""; + std::string name2; + + if (target) + { + name = target->getName(); + name2 = target->getName(); + id = toString(target->getId()); + } + else + { + name2 = player_node->getName(); + } + + bool found = false; + + int idx = command.find(""); + if (idx >= 0) + { + found = true; + command = replaceAll(command, "", name); + } + idx = command.find(""); + if (idx >= 0) + { + found = true; + command = replaceAll(command, "", id); + } + idx = command.find(""); + if (idx >= 0) + { + found = true; + command = replaceAll(command, "", name2); + } + + if (!found && !name.empty()) + command += " " + name; + + return command; +} + +TextCommand *SpellManager::createNewSpell() +{ + return new TextCommand(static_cast(mSpellsVector.size())); +} + +void SpellManager::load(bool oldConfig) +{ + Configuration *cfg; + if (oldConfig) + cfg = &config; + else + cfg = &serverConfig; + + delete_all(mSpells); + mSpells.clear(); + mSpellsVector.clear(); + + if (cfg->getValue("commandShortcutFlags0", "") == "") + { + fillSpells(); + save(); + return; + } + + unsigned int targetType; + unsigned int basicLvl; + unsigned int school; + unsigned int schoolLvl; + unsigned int mana; + unsigned int commandType; + + for (int i = 0; i < SPELL_SHORTCUT_ITEMS; i++) + { + std::string flags = + cfg->getValue("commandShortcutFlags" + toString(i), ""); + std::stringstream ss(flags); + ss >> commandType; + ss >> targetType; + ss >> basicLvl; + ss >> school; + ss >> schoolLvl; + ss >> mana; + + std::string cmd = cfg->getValue("commandShortcutCmd" + + toString(i), ""); + std::string symbol = cfg->getValue("commandShortcutSymbol" + + toString(i), ""); + std::string icon = cfg->getValue("commandShortcutIcon" + + toString(i), ""); + + if ((TextCommandType)commandType == TEXT_COMMAND_MAGIC) + { + addSpell(new TextCommand(i, symbol, cmd, (SpellTarget)targetType, + icon, basicLvl, (MagicSchool)school, schoolLvl, mana)); + } + else + { + addSpell(new TextCommand(i, symbol, cmd, (SpellTarget)targetType, + icon)); + } + } +} + +void SpellManager::save() +{ + for (int i = 0; i < SPELL_SHORTCUT_ITEMS; i++) + { + TextCommand *spell = mSpellsVector[i]; + if (spell) + { + serverConfig.setValue("commandShortcutCmd" + toString(i), + spell->getCommand()); + serverConfig.setValue("commandShortcutSymbol" + toString(i), + spell->getSymbol()); + serverConfig.setValue("commandShortcutIcon" + toString(i), + spell->getIcon()); + serverConfig.setValue("commandShortcutFlags" + toString(i), + strprintf("%u %u %u %u %u %u", spell->getCommandType(), + spell->getTargetType(), spell->getBaseLvl(), + spell->getSchool(), spell->getSchoolLvl(), spell->getMana())); + } + } +} + +std::string SpellManager::autoComplete(std::string partName) +{ + std::vector::iterator i = mSpellsVector.begin(); + std::string newName = ""; + TextCommand *newCommand = NULL; + + while (i != mSpellsVector.end()) + { + TextCommand *cmd = *i; + std::string line = cmd->getCommand(); + + if (line != "") + { + std::string::size_type pos = line.find(partName, 0); + if (pos == 0) + { + if (newName != "") + { + newName = findSameSubstring(line, newName); + newCommand = NULL; + } + else + { + newName = line; + newCommand = cmd; + } + } + } + ++i; + } + if (newName != "" && newCommand + && newCommand->getTargetType() == NEEDTARGET) + { + return newName + " "; + } + return newName; +} diff --git a/src/spellmanager.h b/src/spellmanager.h new file mode 100644 index 000000000..b156fb74b --- /dev/null +++ b/src/spellmanager.h @@ -0,0 +1,76 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPELLMANAGER_H +#define SPELLMANAGER_H + +#include +#include +#include "textcommand.h" + +#include "being.h" + +#define SPELL_MIN_ID 100000 +#define SPELL_SHORTCUT_ITEMS 49 + +class SpellManager +{ + public: + SpellManager(); + ~SpellManager(); + + TextCommand *getSpell(int spellId); + + TextCommand* getSpellByItem(int itemId); + + bool addSpell(TextCommand *spell); + + TextCommand *createNewSpell(); + + std::vector getAll(); + + void useItem(int itemId); + + void invoke(int spellId); + + void load(bool oldConfig = false); + + void save(); + + std::string autoComplete(std::string partName); + + private: + void fillSpells(); + + void invokeSpell(TextCommand* spell, Being* target) const; + + void invokeSpell(TextCommand* spell) const; + + std::string parseCommand(std::string command, Being* target) const; + + std::map mSpells; + std::vector mSpellsVector; +}; + +extern SpellManager *spellManager; + +#endif // SPELLMANAGER_H diff --git a/src/spellshortcut.cpp b/src/spellshortcut.cpp new file mode 100644 index 000000000..5f3da7c4d --- /dev/null +++ b/src/spellshortcut.cpp @@ -0,0 +1,71 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "configuration.h" +#include "inventory.h" +#include "item.h" +#include "spellshortcut.h" +#include "localplayer.h" + +#include "gui/chat.h" +#include "gui/widgets/chattab.h" + +#include "net/inventoryhandler.h" +#include "net/net.h" + +#include "utils/stringutils.h" + +SpellShortcut *spellShortcut; + +SpellShortcut::SpellShortcut() +{ + mItemSelected = -1; + load(); +} + +SpellShortcut::~SpellShortcut() +{ +} + +void SpellShortcut::load() +{ + for (int f = 0; f < SPELL_SHORTCUT_ITEMS; f ++) + mItems[f] = -1; + + if (!spellManager) + return; + + std::vector spells = spellManager->getAll(); + int k = 0; + + for (std::vector::const_iterator i = spells.begin(), + i_end = spells.end(); i != i_end && k < SPELL_SHORTCUT_ITEMS; ++i) + { + mItems[k++] = (*i)->getId(); + } + +} + +unsigned int SpellShortcut::getSpellsCount() const +{ + return SPELL_SHORTCUT_ITEMS; +} \ No newline at end of file diff --git a/src/spellshortcut.h b/src/spellshortcut.h new file mode 100644 index 000000000..5210fc4dc --- /dev/null +++ b/src/spellshortcut.h @@ -0,0 +1,91 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPELLSHORTCUT_H +#define SPELLSHORTCUT_H + +#include "spellmanager.h" +#include "log.h" +#include "utils/stringutils.h" + +class TextCommand; + +/** + * The class which keeps track of the item shortcuts. + */ +class SpellShortcut +{ + public: + /** + * Constructor. + */ + SpellShortcut(); + + /** + * Destructor. + */ + ~SpellShortcut(); + + /** + * Load the configuration information. + */ + void load(); + + unsigned int getSpellsCount() const; + + /** + * Set the item that is selected. + * + * @param itemId The ID of the item that is to be assigned. + */ + void setItemSelected(int itemId) + { mItemSelected = itemId; } + + /** + * A flag to check if the item is selected. + */ + bool isItemSelected() const + { return mItemSelected > -1; } + + /** + * Returns selected shortcut item ID. + */ + int getSelectedItem() const + { return mItemSelected; } + + /** + * Returns the shortcut item ID specified by the index. + * + * @param index Index of the shortcut item. + */ + int getItem(int index) const + { return mItems[index]; } + + private: + int mItems[SPELL_SHORTCUT_ITEMS]; /**< The items stored. */ + int mItemSelected; /**< The item held by cursor. */ +}; + +extern SpellShortcut *spellShortcut; +extern SpellManager *spellManager; + +#endif diff --git a/src/sprite.h b/src/sprite.h new file mode 100644 index 000000000..4374a1e14 --- /dev/null +++ b/src/sprite.h @@ -0,0 +1,110 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SPRITE_H +#define SPRITE_H + +#include "resources/spritedef.h" + +class Graphics; +class Image; + +class Sprite +{ + public: + virtual ~Sprite() {} + + /** + * Resets the sprite. + * + * @returns true if the sprite changed, false otherwise + */ + virtual bool reset() = 0; + + /** + * Plays an action using the current direction. + * + * @returns true if the sprite changed, false otherwise + */ + virtual bool play(std::string action) = 0; + + /** + * Inform the animation of the passed time so that it can output the + * correct animation frame. + * + * @returns true if the sprite changed, false otherwise + */ + virtual bool update(int time) = 0; + + /** + * Draw the current animation frame at the coordinates given in screen + * pixels. + */ + virtual bool draw(Graphics* graphics, int posX, int posY) const = 0; + + /** + * Gets the width in pixels of the image of the current frame + */ + virtual int getWidth() const = 0; + + /** + * Gets the height in pixels of the image of the current frame + */ + virtual int getHeight() const = 0; + + /** + * Returns a reference to the current image being drawn. + */ + virtual const Image* getImage() const = 0; + + /** + * Sets the direction. + * + * @returns true if the sprite changed, false otherwise + */ + virtual bool setDirection(SpriteDirection direction) = 0; + + /** + * Sets the alpha value of the animated sprite + */ + virtual void setAlpha(float alpha) + { mAlpha = alpha; } + + /** + * Returns the current alpha opacity of the animated sprite. + */ + virtual float getAlpha() const + { return mAlpha; } + + /** + * Returns the current frame number for the sprite. + */ + virtual unsigned int getCurrentFrame() const = 0; + + /** + * Returns the frame count for the sprite. + */ + virtual unsigned int getFrameCount() const = 0; + + protected: + float mAlpha; /**< The alpha opacity used to draw */ +}; + +#endif // SPRITE_H diff --git a/src/statuseffect.cpp b/src/statuseffect.cpp new file mode 100644 index 000000000..1a399410b --- /dev/null +++ b/src/statuseffect.cpp @@ -0,0 +1,205 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "statuseffect.h" + +#include "log.h" +#include "sound.h" + +#include "gui/widgets/chattab.h" + +#include "utils/xml.h" + +#include "configuration.h" + +#include + +#define STATUS_EFFECTS_FILE "status-effects.xml" + +void unloadMap(std::map map); + +bool StatusEffect::mLoaded = false; + +StatusEffect::StatusEffect() : + mPersistentParticleEffect(false) +{} + +StatusEffect::~StatusEffect() +{} + +void StatusEffect::playSFX() +{ + if (!mSFXEffect.empty()) + sound.playSfx(mSFXEffect); +} + +void StatusEffect::deliverMessage() +{ + if (!mMessage.empty() && localChatTab) + localChatTab->chatLog(mMessage, BY_SERVER); +} + +Particle *StatusEffect::getParticle() +{ + if (!particleEngine || mParticleEffect.empty()) + return NULL; + else + return particleEngine->addEffect(mParticleEffect, 0, 0); +} + +AnimatedSprite *StatusEffect::getIcon() +{ + if (mIcon.empty()) + { + return NULL; + } + else + { + AnimatedSprite *sprite = AnimatedSprite::load( + paths.getStringValue("sprites") + mIcon); + if (false && sprite) + { + sprite->play(SpriteAction::DEFAULT); + sprite->reset(); + } + return sprite; + } +} + +std::string StatusEffect::getAction() +{ + if (mAction.empty()) + return SpriteAction::INVALID; + else + return mAction; +} + + +// -- initialisation and static parts -- + + +typedef std::map status_effect_map[2]; + +static status_effect_map statusEffects; +static status_effect_map stunEffects; +static std::map blockEffectIndexMap; + +int StatusEffect::blockEffectIndexToEffectIndex(int blockIndex) +{ + if (blockEffectIndexMap.find(blockIndex) == blockEffectIndexMap.end()) + return -1; + return blockEffectIndexMap[blockIndex]; +} + +StatusEffect *StatusEffect::getStatusEffect(int index, bool enabling) +{ + return statusEffects[enabling][index]; +} + +StatusEffect *StatusEffect::getStunEffect(int index, bool enabling) +{ + return stunEffects[enabling][index]; +} + +void StatusEffect::load() +{ + if (mLoaded) + unload(); + + XML::Document doc(STATUS_EFFECTS_FILE); + xmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "status-effects")) + { + logger->log1("Error loading status effects file: " + STATUS_EFFECTS_FILE); + return; + } + + for_each_xml_child_node(node, rootNode) + { + status_effect_map *the_map = NULL; + + int index = atoi(XML::getProperty(node, "id", "-1").c_str()); + + if (xmlStrEqual(node->name, BAD_CAST "status-effect")) + { + the_map = &statusEffects; + int block_index = atoi(XML::getProperty( + node, "block-id", "-1").c_str()); + + if (index >= 0 && block_index >= 0) + blockEffectIndexMap[block_index] = index; + + } + else if (xmlStrEqual(node->name, BAD_CAST "stun-effect")) + the_map = &stunEffects; + + if (the_map) + { + StatusEffect *startEffect = new StatusEffect; + StatusEffect *endEffect = new StatusEffect; + + startEffect->mMessage = XML::getProperty( + node, "start-message", ""); + startEffect->mSFXEffect = XML::getProperty( + node, "start-audio", ""); + startEffect->mParticleEffect = XML::getProperty( + node, "start-particle", ""); + + startEffect->mIcon = XML::getProperty(node, "icon", ""); + startEffect->mAction = XML::getProperty(node, "action", ""); + startEffect->mPersistentParticleEffect = (XML::getProperty( + node, "persistent-particle-effect", "no")) != "no"; + + endEffect->mMessage = XML::getProperty(node, "end-message", ""); + endEffect->mSFXEffect = XML::getProperty(node, "end-audio", ""); + endEffect->mParticleEffect = XML::getProperty( + node, "end-particle", ""); + + (*the_map)[1][index] = startEffect; + (*the_map)[0][index] = endEffect; + } + } +} + +void unloadMap(std::map map) +{ + std::map::iterator it; + + for (it = map.begin(); it != map.end(); it++) + delete (*it).second; + + map.clear(); +} + +void StatusEffect::unload() +{ + if (!mLoaded) + return; + + unloadMap(statusEffects[0]); + unloadMap(statusEffects[1]); + unloadMap(stunEffects[0]); + unloadMap(stunEffects[1]); + + mLoaded = false; +} diff --git a/src/statuseffect.h b/src/statuseffect.h new file mode 100644 index 000000000..cb0439120 --- /dev/null +++ b/src/statuseffect.h @@ -0,0 +1,112 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef STATUS_EFFECT_H +#define STATUS_EFFECT_H + +#include "particle.h" +#include "animatedsprite.h" + +#include "resources/animation.h" + +class StatusEffect +{ +public: + StatusEffect(); + ~StatusEffect(); + + /** + * Plays the sound effect associated with this status effect, if possible. + */ + void playSFX(); + + /** + * Delivers the chat message associated with this status effect, if + * possible. + */ + void deliverMessage(); + + /** + * Creates the particle effect associated with this status effect, if + * possible. + */ + Particle *getParticle(); + + /** + * Retrieves the status icon for this effect, if applicable + */ + AnimatedSprite *getIcon(); + + /** + * Retrieves an action to perform, or SpriteAction::INVALID + */ + std::string getAction(); + + /** + * Determines whether the particle effect should be restarted when the + * being changes maps + */ + bool particleEffectIsPersistent() const + { return mPersistentParticleEffect; } + + + /** + * Retrieves a status effect. + * + * \param index Index of the status effect. + * \param enabling Whether to retrieve the activating effect (true) or + * the deactivating effect (false). + */ + static StatusEffect *getStatusEffect(int index, bool enabling); + + /** + * Retrieves a stun effect. + * + * \param index Index of the stun effect. + * \param enabling Whether to retrieve the activating effect (true) or + * the deactivating effect (false). + */ + static StatusEffect *getStunEffect(int index, bool enabling); + + /** + * Maps a block effect index to its corresponding effect index. Block + * effect indices are used for opt2/opt3/status.option blocks; their + * mapping to regular effect indices is handled in the config file. + * + * Returns -1 on failure. + */ + static int blockEffectIndexToEffectIndex(int blocKIndex); + + static void load(); + + static void unload(); +private: + static bool mLoaded; + + std::string mMessage; + std::string mSFXEffect; + std::string mParticleEffect; + std::string mIcon; + std::string mAction; + bool mPersistentParticleEffect; +}; + +#endif // !defined(STATUS_EFFECT_H) diff --git a/src/text.cpp b/src/text.cpp new file mode 100644 index 000000000..e878d96b8 --- /dev/null +++ b/src/text.cpp @@ -0,0 +1,194 @@ +/* + * Support for non-overlapping floating text + * Copyright (C) 2008 Douglas Boffey + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "text.h" + +#include "configuration.h" +#include "textmanager.h" +#include "textrenderer.h" + +#include "gui/gui.h" +#include "gui/palette.h" +#include "gui/theme.h" + +#include "resources/resourcemanager.h" +#include "resources/image.h" + +#include + +int Text::mInstances = 0; +ImageRect Text::mBubble; +Image *Text::mBubbleArrow; + +Text::Text(const std::string &text, int x, int y, + gcn::Graphics::Alignment alignment, + const gcn::Color* color, bool isSpeech, + gcn::Font *font) : + mText(text), + mColor(color), + mIsSpeech(isSpeech) +{ + if (!font) + mFont = gui->getFont(); + else + mFont = font; + + if (textManager == 0) + { + textManager = new TextManager; + Image *sbImage = Theme::getImageFromTheme("bubble.png|W:#" + + config.getStringValue("speechBubblecolor")); + if (sbImage) + { + mBubble.grid[0] = sbImage->getSubImage(0, 0, 5, 5); + mBubble.grid[1] = sbImage->getSubImage(5, 0, 5, 5); + mBubble.grid[2] = sbImage->getSubImage(10, 0, 5, 5); + mBubble.grid[3] = sbImage->getSubImage(0, 5, 5, 5); + mBubble.grid[4] = sbImage->getSubImage(5, 5, 5, 5); + mBubble.grid[5] = sbImage->getSubImage(10, 5, 5, 5); + mBubble.grid[6] = sbImage->getSubImage(0, 10, 5, 5); + mBubble.grid[7] = sbImage->getSubImage(5, 10, 5, 5); + mBubble.grid[8] = sbImage->getSubImage(10, 10, 5, 5); + mBubbleArrow = sbImage->getSubImage(0, 15, 15, 10); + } + else + { + for (int f = 0; f < 9; f ++) + mBubble.grid[f] = 0; + mBubbleArrow = 0; + } + const float bubbleAlpha = config.getFloatValue("speechBubbleAlpha"); + for (int i = 0; i < 9; i++) + { + if (mBubble.grid[i]) + mBubble.grid[i]->setAlpha(bubbleAlpha); + } + if (mBubbleArrow) + mBubbleArrow->setAlpha(bubbleAlpha); + if (sbImage) + sbImage->decRef(); + } + ++mInstances; + mHeight = mFont->getHeight(); + mWidth = mFont->getWidth(text); + + switch (alignment) + { + case gcn::Graphics::LEFT: + mXOffset = 0; + break; + case gcn::Graphics::CENTER: + mXOffset = mWidth / 2; + break; + case gcn::Graphics::RIGHT: + mXOffset = mWidth; + break; + default: + break; + } + mX = x - mXOffset; + mY = y; + if (textManager) + textManager->addText(this); +} + +Text::~Text() +{ + if (textManager) + textManager->removeText(this); + if (--mInstances == 0) + { + delete textManager; + textManager = 0; + delete mBubble.grid[0]; + mBubble.grid[0] = 0; + delete mBubble.grid[1]; + mBubble.grid[1] = 0; + delete mBubble.grid[2]; + mBubble.grid[2] = 0; + delete mBubble.grid[3]; + mBubble.grid[3] = 0; + delete mBubble.grid[4]; + mBubble.grid[4] = 0; + delete mBubble.grid[5]; + mBubble.grid[5] = 0; + delete mBubble.grid[6]; + mBubble.grid[6] = 0; + delete mBubble.grid[7]; + mBubble.grid[7] = 0; + delete mBubble.grid[8]; + mBubble.grid[8] = 0; + delete mBubbleArrow; + mBubbleArrow = 0; + } +} + +void Text::setColor(const gcn::Color *color) +{ + mColor = color; +} + +void Text::adviseXY(int x, int y) +{ + if (textManager) + textManager->moveText(this, x - mXOffset, y); +} + +void Text::draw(gcn::Graphics *graphics, int xOff, int yOff) +{ + if (mIsSpeech) + { + static_cast(graphics)->drawImageRect( + mX - xOff - 5, mY - yOff - 5, mWidth + 10, mHeight + 10, + mBubble); + /* + if (mWidth >= 15) + { + static_cast(graphics)->drawImage( + mBubbleArrow, mX - xOff - 7 + mWidth / 2, + mY - yOff + mHeight + 4); + } + */ + } + + TextRenderer::renderText(graphics, mText, + mX - xOff, mY - yOff, gcn::Graphics::LEFT, + *mColor, mFont, !mIsSpeech, true); +} + +FlashText::FlashText(const std::string &text, int x, int y, + gcn::Graphics::Alignment alignment, + const gcn::Color *color, gcn::Font *font) : + Text(text, x, y, alignment, color, false, font), + mTime(0) +{ +} + +void FlashText::draw(gcn::Graphics *graphics, int xOff, int yOff) +{ + if (mTime) + { + if ((--mTime & 4) == 0) + return; + } + Text::draw(graphics, xOff, yOff); +} diff --git a/src/text.h b/src/text.h new file mode 100644 index 000000000..7c8063ccf --- /dev/null +++ b/src/text.h @@ -0,0 +1,113 @@ +/* + * Support for non-overlapping floating text + * Copyright (C) 2008 Douglas Boffey + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "graphics.h" +#include "guichanfwd.h" + +#include + +class TextManager; + +class Text +{ + friend class TextManager; + + public: + /** + * Constructor creates a text object to display on the screen. + */ + Text(const std::string &text, int x, int y, + gcn::Graphics::Alignment alignment, + const gcn::Color *color, bool isSpeech = false, + gcn::Font *font = 0); + + /** + * Destructor. The text is removed from the screen. + */ + virtual ~Text(); + + void setColor(const gcn::Color *color); + + int getWidth() const + { return mWidth; } + + int getHeight() const + { return mHeight; } + + /** + * Allows the originator of the text to specify the ideal coordinates. + */ + void adviseXY(int x, int y); + + /** + * Draws the text. + */ + virtual void draw(gcn::Graphics *graphics, int xOff, int yOff); + + private: + int mX; /**< Actual x-value of left of text written. */ + int mY; /**< Actual y-value of top of text written. */ + int mWidth; /**< The width of the text. */ + int mHeight; /**< The height of the text. */ + int mXOffset; /**< The offset of mX from the desired x. */ + static int mInstances; /**< Instances of text. */ + std::string mText; /**< The text to display. */ + const gcn::Color *mColor; /**< The color of the text. */ + gcn::Font *mFont; /**< The font of the text */ + bool mIsSpeech; /**< Is this text a speech bubble? */ + + protected: + static ImageRect mBubble; /**< Speech bubble graphic */ + static Image *mBubbleArrow; /**< Speech bubble arrow graphic */ +}; + +class FlashText : public Text +{ + public: + FlashText(const std::string &text, int x, int y, + gcn::Graphics::Alignment alignment, + const gcn::Color* color, + gcn::Font *font = 0); + + /** + * Remove the text from the screen + */ + virtual ~FlashText() {} + + /** + * Flash the text for so many refreshes. + */ + void flash(int time) {mTime = time; } + + /** + * Draws the text. + */ + virtual void draw(gcn::Graphics *graphics, int xOff, int yOff); + + private: + int mTime; /**< Time left for flashing */ +}; + +#endif // TEXT_H diff --git a/src/textcommand.cpp b/src/textcommand.cpp new file mode 100644 index 000000000..402aef5c5 --- /dev/null +++ b/src/textcommand.cpp @@ -0,0 +1,115 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "textcommand.h" + +#include "gui/theme.h" + +#include "resources/image.h" +#include "resources/iteminfo.h" +#include "resources/resourcemanager.h" + +TextCommand::TextCommand(unsigned int id, std::string symbol, + std::string command, SpellTarget type, + std::string icon, unsigned int basicLvl, + MagicSchool school, unsigned int schoolLvl, + int mana) : + mImage(0) +{ + mId = id; + mCommand = command; + mSymbol = symbol; + mTargetType = type; + mIcon = icon; + mBaseLvl = basicLvl; + mSchool = school; + mSchoolLvl = schoolLvl; + mMana = mana; + mCommandType = TEXT_COMMAND_MAGIC; + loadImage(); +} + + +TextCommand::TextCommand(unsigned int id, std::string symbol, + std::string command, SpellTarget type, + std::string icon) : + mImage(0) +{ + mId = id; + mCommand = command; + mSymbol = symbol; + mTargetType = type; + mIcon = icon; + mCommandType = TEXT_COMMAND_TEXT; + mBaseLvl = 0; + mSchool = SKILL_MAGIC; + mSchoolLvl = 0; + mMana = 0; + mCommandType = TEXT_COMMAND_TEXT; + loadImage(); +} + +TextCommand::TextCommand(unsigned int id) : + mImage(0) +{ + mId = id; + mCommand = ""; + mSymbol = ""; + mTargetType = NOTARGET; + mIcon = ""; + mCommandType = TEXT_COMMAND_TEXT; + mBaseLvl = 0; + mSchool = SKILL_MAGIC; + mSchoolLvl = 0; + mMana = 0; + mCommandType = TEXT_COMMAND_TEXT; + loadImage(); +} + + +TextCommand::~TextCommand() +{ + if (mImage) + { + mImage->decRef(); + mImage = 0; + } +} + +void TextCommand::loadImage() +{ + if (mImage) + { + mImage->decRef(); + mImage = 0; + } + + if (getIcon().empty()) + return; + + ResourceManager *resman = ResourceManager::getInstance(); + SpriteDisplay display = ItemDB::get(getIcon()).getDisplay(); + mImage = resman->getImage("graphics/items/" + display.image); + + if (!mImage) + mImage = Theme::getImageFromTheme("unknown-item.png"); +} \ No newline at end of file diff --git a/src/textcommand.h b/src/textcommand.h new file mode 100644 index 000000000..dbd621901 --- /dev/null +++ b/src/textcommand.h @@ -0,0 +1,173 @@ +/* + * The Mana World + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * + * This file is part of The Mana World. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TEXTCOMMAND_H +#define TEXTCOMMAND_H + +#include "resources/itemdb.h" + +#define MAGIC_START_ID 340 + +class Image; + +enum SpellTarget +{ + NOTARGET = 0, + ALLOWTARGET = 1, + NEEDTARGET = 2 +}; + +enum MagicSchool +{ + SKILL_MAGIC = 340, + SKILL_MAGIC_LIFE = 341, + SKILL_MAGIC_WAR = 342, + SKILL_MAGIC_TRANSMUTE = 343, + SKILL_MAGIC_NATURE = 344, + SKILL_MAGIC_ASTRAL = 345 +}; + +enum TextCommandType +{ + TEXT_COMMAND_MAGIC = 0, + TEXT_COMMAND_TEXT = 1 +}; + +/** + * Represents one or more instances of a certain item type. + */ +class TextCommand +{ + public: + /** + * Constructor. + */ + TextCommand(unsigned int id, std::string symbol, + std::string command, SpellTarget type, + std::string icon, unsigned int basicLvl, + MagicSchool school = SKILL_MAGIC, + unsigned int schoolLvl = 0, int mana = 0); + + /** + * Constructor. + */ + TextCommand(unsigned int id, std::string symbol, + std::string command, SpellTarget type, + std::string icon); + + /** + * Constructor. + */ + TextCommand(unsigned int id); + + /** + * Destructor. + */ + ~TextCommand(); + + std::string getName() const + { return mCommand; } + + std::string getCommand() const + { return mCommand; } + + std::string getSymbol() const + { return mSymbol; } + + unsigned int getId() const + { return mId; } + + SpellTarget getTargetType() const + { return mTargetType; } + + std::string getIcon() const + { return mIcon; } + + int getMana() const + { return mMana; } + + MagicSchool getSchool() const + { return mSchool; } + + int getBaseLvl() const + { return mBaseLvl; } + + int getSchoolLvl() const + { return mSchoolLvl; } + + TextCommandType getCommandType() const + { return mCommandType; } + + void setCommand(std::string command) + { mCommand = command; } + + void setSymbol(std::string symbol) + { mSymbol = symbol; } + + void setId(unsigned int id) + { mId = id; } + + void setTargetType(SpellTarget targetType) + { mTargetType = targetType; } + + void setIcon(std::string icon) + { mIcon = icon; loadImage(); } + + void setMana(unsigned int mana) + { mMana = mana; } + + void setSchool(MagicSchool school) + { mSchool = school; } + + void setBaseLvl(unsigned int baseLvl) + { mBaseLvl = baseLvl; } + + void setSchoolLvl(unsigned int schoolLvl) + { mSchoolLvl = schoolLvl; } + + void setCommandType(TextCommandType commandType) + { mCommandType = commandType; } + + bool isEmpty() const + { return mCommand == "" && mSymbol == "" ; } + + Image *getImage() + { return mImage; } + + private: + void loadImage(); + + protected: + std::string mCommand; + std::string mSymbol; + SpellTarget mTargetType; + std::string mIcon; + unsigned int mId; + int mMana; + MagicSchool mSchool; + int mBaseLvl; + int mSchoolLvl; + TextCommandType mCommandType; + Image *mImage; +}; + +#endif diff --git a/src/textmanager.cpp b/src/textmanager.cpp new file mode 100644 index 000000000..348ab829b --- /dev/null +++ b/src/textmanager.cpp @@ -0,0 +1,170 @@ +/* + * Support for non-overlapping floating text + * Copyright (C) 2008 Douglas Boffey + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "textmanager.h" + +#include "text.h" + +#include + +TextManager *textManager = 0; + +TextManager::TextManager() +{ +} + +void TextManager::addText(Text *text) +{ + place(text, 0, text->mX, text->mY, text->mHeight); + mTextList.push_back(text); +} + +void TextManager::moveText(Text *text, int x, int y) +{ + text->mX = x; + text->mY = y; + place(text, text, text->mX, text->mY, text->mHeight); +} + +void TextManager::removeText(const Text *text) +{ + for (TextList::iterator ptr = mTextList.begin(), + pEnd = mTextList.end(); ptr != pEnd; ++ptr) + { + if (*ptr == text) + { + mTextList.erase(ptr); + return; + } + } +} + +TextManager::~TextManager() +{ +} + +void TextManager::draw(gcn::Graphics *graphics, int xOff, int yOff) +{ + for (TextList::iterator bPtr = mTextList.begin(), ePtr = mTextList.end(); + bPtr != ePtr; ++bPtr) + { + (*bPtr)->draw(graphics, xOff, yOff); + } +} + +void TextManager::place(const Text *textObj, const Text *omit, + int &x _UNUSED_, int &y, int h) +{ + int xLeft = textObj->mX; + int xRight1 = xLeft + textObj->mWidth; + const int TEST = 50; // Number of lines to test for text + const int nBeings = 50; + bool occupied[TEST]; // is some other text obscuring this line? + std::memset(&occupied, 0, sizeof(occupied)); // set all to false + int wantedTop = (TEST - h) / 2; // Entry in occupied at top of text + int occupiedTop = y - wantedTop; // Line in map representing to of occupied + int cnt = 0; + + for (TextList::const_iterator ptr = mTextList.begin(), + pEnd = mTextList.end(); ptr != pEnd && cnt < nBeings; ++ptr, cnt ++) + { + if (*ptr != omit && + (*ptr)->mX + 1 <= xRight1 && + (*ptr)->mX + (*ptr)->mWidth > xLeft) + { + int from = (*ptr)->mY - occupiedTop; + int to = from + (*ptr)->mHeight - 1; + if (to < 0 || from >= TEST) // out of range considered + continue; + if (from < 0) + from = 0; + if (to >= TEST) + to = TEST - 1; + for (int i = from; i <= to; ++i) + occupied[i] = true; + } + } + bool ok = true; + for (int i = wantedTop; i < wantedTop + h; ++i) + { + ok = ok && !occupied[i]; + } + + if (ok) + return; + + // Have to move it up or down, so find nearest spaces either side + int consec = 0; + int upSlot = -1; // means not found + for (int seek = wantedTop + h - 2; seek >= 0; --seek) + { + if (occupied[seek]) + { + consec = 0; + } + else + { + if (++consec == h) + { + upSlot = seek; + break; + } + } + } + int downSlot = -1; + consec = 0; + for (int seek = wantedTop + 1; seek < TEST; ++seek) + { + if (occupied[seek]) + { + consec = 0; + } + else + { + if (++consec == h) + { + downSlot = seek - h + 1; + break; + } + } + } + if (upSlot == -1 && downSlot == -1) // no good solution, so leave as is + { + return; + } + if (upSlot == -1) // must go down + { + y += downSlot - wantedTop; + return; + } + if (downSlot == -1) // must go up + { + y -= wantedTop - upSlot; + return; + } + if (wantedTop - upSlot > downSlot - wantedTop) // down is better + { + y += downSlot - wantedTop; + } + else + { + y -= wantedTop - upSlot; + } +} diff --git a/src/textmanager.h b/src/textmanager.h new file mode 100644 index 000000000..a75623708 --- /dev/null +++ b/src/textmanager.h @@ -0,0 +1,82 @@ +/* + * Support for non-overlapping floating text + * Copyright (C) 2008 Douglas Boffey + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXTMANAGER_H +#define TEXTMANAGER_H + +#include + +#include "guichanfwd.h" + +#ifdef __GNUC__ +#define _UNUSED_ __attribute__ ((unused)) +#else +#define _UNUSED_ +#endif + +class Text; + +class TextManager +{ + public: + /** + * Constructor + */ + TextManager(); + + /** + * Add text to the manager + */ + void addText(Text *text); + + /** + * Move the text around the screen + */ + void moveText(Text *text, int x, int y); + + /** + * Remove the text from the manager + */ + void removeText(const Text *text); + + /** + * Destroy the manager + */ + ~TextManager(); + + /** + * Draw the text + */ + void draw(gcn::Graphics *graphics, int xOff, int yOff); + + private: + /** + * Position the text so as to avoid conflict + */ + void place(const Text *textObj, const Text *omit, + int &x, int &y, int h); + + typedef std::list TextList; /**< The container type */ + TextList mTextList; /**< The container */ +}; + +extern TextManager *textManager; + +#endif // TEXTMANAGER_H diff --git a/src/textparticle.cpp b/src/textparticle.cpp new file mode 100644 index 000000000..b70155347 --- /dev/null +++ b/src/textparticle.cpp @@ -0,0 +1,69 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "textparticle.h" + +#include "textrenderer.h" + +#include + +TextParticle::TextParticle(Map *map, const std::string &text, + const gcn::Color *color, + gcn::Font *font, bool outline): + Particle(map), + mText(text), + mTextFont(font), + mColor(color), + mOutline(outline) +{ +} + +bool TextParticle::draw(Graphics *graphics, int offsetX, int offsetY) const +{ + if (!isAlive()) + return false; + + int screenX = (int) mPos.x + offsetX; + int screenY = (int) mPos.y - (int) mPos.z + offsetY; + + float alpha = mAlpha * 255.0f; + + if (mFadeOut && mLifetimeLeft > -1 && mLifetimeLeft < mFadeOut) + { + alpha = alpha * static_cast(mLifetimeLeft) + / static_cast(mFadeOut); + } + + if (mFadeIn && mLifetimePast < mFadeIn) + { + alpha = alpha * static_cast(mLifetimePast) + / static_cast(mFadeIn); + } + + gcn::Color color = *mColor; +// color.a = (int)alpha; + + TextRenderer::renderText(graphics, mText, + screenX, screenY, gcn::Graphics::CENTER, + color, mTextFont, mOutline, false, static_cast(alpha)); + + return true; +} diff --git a/src/textparticle.h b/src/textparticle.h new file mode 100644 index 000000000..7d99a057b --- /dev/null +++ b/src/textparticle.h @@ -0,0 +1,54 @@ +/* + * The Mana Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXTPARTICLE_H +#define TEXTPARTICLE_H + +#include "guichanfwd.h" +#include "particle.h" + +class TextParticle : public Particle +{ + public: + /** + * Constructor. + */ + TextParticle(Map *map, const std::string &text, + const gcn::Color* color, + gcn::Font *font, bool outline = false); + + /** + * Draws the particle image. + */ + virtual bool draw(Graphics *graphics, int offsetX, int offsetY) const; + + // hack to improve text visibility + virtual int getPixelY() const + { return (int) (mPos.y + mPos.z); } + + private: + std::string mText; /**< Text of the particle. */ + gcn::Font *mTextFont; /**< Font used for drawing the text. */ + const gcn::Color *mColor; /**< Color used for drawing the text. */ + bool mOutline; /**< Make the text better readable */ +}; + +#endif diff --git a/src/textrenderer.h b/src/textrenderer.h new file mode 100644 index 000000000..9b3c0292e --- /dev/null +++ b/src/textrenderer.h @@ -0,0 +1,78 @@ +/* + * Text Renderer + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEXT_RENDERER_H +#define TEXT_RENDERER_H + +#include "graphics.h" + +#include "gui/theme.h" + +/** + * Class for text rendering. Used by the TextParticle, the Text and FlashText + * objects and the Preview in the color dialog. + */ +class TextRenderer +{ + public: + /** + * Renders a specified text. + */ + static inline void renderText(gcn::Graphics *graphics, + const std::string &text, + int x, int y, + gcn::Graphics::Alignment align, + const gcn::Color &color, + gcn::Font *font, + bool outline = false, + bool shadow = false, + int alpha = 255) + { + graphics->setFont(font); + + // Text shadow + if (shadow) + { + graphics->setColor(Theme::getThemeColor(Theme::SHADOW, + color.a / 2)); + if (outline) + graphics->drawText(text, x + 2, y + 2, align); + else + graphics->drawText(text, x + 1, y + 1, align); + } + + if (outline) + { + // Text outline + graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, alpha)); +// graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, color.a)); + graphics->drawText(text, x + 1, y, align); + graphics->drawText(text, x - 1, y, align); + graphics->drawText(text, x, y + 1, align); + graphics->drawText(text, x, y - 1, align); + } + + graphics->setColor(color); + graphics->drawText(text, x, y, align); + } +}; + +#endif diff --git a/src/tileset.h b/src/tileset.h new file mode 100644 index 000000000..9a45667c5 --- /dev/null +++ b/src/tileset.h @@ -0,0 +1,53 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TILESET_H +#define TILESET_H + +#include "resources/imageset.h" + +/** + * A tileset, which is basically just an image set but it stores a firstgid. + */ +class Tileset : public ImageSet +{ + public: + /** + * Constructor. + */ + Tileset(Image *img, int w, int h, int firstGid, + int margin, int spacing): + ImageSet(img, w, h, margin, spacing), + mFirstGid(firstGid) + { + } + + /** + * Returns the first gid. + */ + int getFirstGid() const + { return mFirstGid; } + + private: + int mFirstGid; +}; + +#endif diff --git a/src/units.cpp b/src/units.cpp new file mode 100644 index 000000000..68272c24a --- /dev/null +++ b/src/units.cpp @@ -0,0 +1,248 @@ +/* + * Support for custom units + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "units.h" + +#include "log.h" + +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include +#include +#include + +struct UnitLevel +{ + std::string symbol; + int count; + int round; +}; + +struct UnitDescription +{ + std::vector levels; + double conversion; + bool mix; +}; + +enum UnitType +{ + UNIT_WEIGHT = 0, + UNIT_CURRENCY = 1, + UNIT_END +}; + +std::string formatUnit(int value, int type); + +struct UnitDescription units[UNIT_END]; + +void Units::loadUnits() +{ + { // Setup default weight + struct UnitDescription ud; + + ud.conversion = 1.0; + ud.mix = false; + + struct UnitLevel bu; + bu.symbol = "g"; + bu.count = 1; + bu.round = 0; + + ud.levels.push_back(bu); + + struct UnitLevel ul; + ul.symbol = "kg"; + ul.count = 1000; + ul.round = 2; + + ud.levels.push_back(ul); + + units[UNIT_WEIGHT] = ud; + } + + { // Setup default currency + struct UnitDescription ud; + + ud.conversion = 1.0; + ud.mix = false; + + struct UnitLevel bu; + bu.symbol = "¤"; + bu.count = 1; + bu.round = 0; + + ud.levels.push_back(bu); + + units[UNIT_CURRENCY] = ud; + } + + XML::Document doc("units.xml"); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "units")) + { + logger->log1("Error loading unit definition file: units.xml"); + return; + } + + for_each_xml_child_node(node, root) + { + if (xmlStrEqual(node->name, BAD_CAST "unit")) + { + struct UnitDescription ud; + int level = 1; + const std::string type = XML::getProperty(node, "type", ""); + ud.conversion = XML::getProperty(node, "conversion", 1); + ud.mix = XML::getProperty(node, "mix", "no") == "yes"; + + struct UnitLevel bu; + bu.symbol = XML::getProperty(node, "base", "¤"); + bu.count = 1; + bu.round = XML::getProperty(node, "round", 2); + + ud.levels.push_back(bu); + + for_each_xml_child_node(uLevel, node) + { + if (xmlStrEqual(uLevel->name, BAD_CAST "level")) + { + struct UnitLevel ul; + ul.symbol = XML::getProperty(uLevel, "symbol", + strprintf("¤%d", level)); + ul.count = XML::getProperty(uLevel, "count", -1); + ul.round = XML::getProperty(uLevel, "round", bu.round); + + if (ul.count > 0) + { + ud.levels.push_back(ul); + level++; + } + else + { + logger->log("Error bad unit count: %d for %s in %s", + ul.count, ul.symbol.c_str(), + bu.symbol.c_str()); + } + } + } + + // Add one more level for saftey + struct UnitLevel ll; + ll.symbol = ""; + ll.count = INT_MAX; + ll.round = 0; + + ud.levels.push_back(ll); + + if (type == "weight") + units[UNIT_WEIGHT] = ud; + else if (type == "currency") + units[UNIT_CURRENCY] = ud; + else + logger->log("Error unknown unit type: %s", type.c_str()); + } + } +} + +std::string formatUnit(int value, int type) +{ + struct UnitDescription ud = units[type]; + struct UnitLevel ul; + + // Shortcut for 0; do the same for values less than 0 (for now) + if (value <= 0) + { + ul = ud.levels[0]; + return strprintf("0%s", ul.symbol.c_str()); + } + else + { + double amount = ud.conversion * value; + + // If only the first level is needed, act like mix if false + if (ud.mix && ud.levels.size() > 0 && ud.levels[1].count < amount) + { + std::string output; + struct UnitLevel pl = ud.levels[0]; + ul = ud.levels[1]; + int levelAmount = (int) amount; + int nextAmount = 0; + + if (ul.count) + levelAmount /= ul.count; + + amount -= levelAmount * ul.count; + + if (amount > 0) + { + output = strprintf("%.*f%s", pl.round, amount, + pl.symbol.c_str()); + } + + for (unsigned int i = 2; i < ud.levels.size(); i++) + { + pl = ul; + ul = ud.levels[i]; + + if (ul.count) + nextAmount = levelAmount / ul.count; + levelAmount %= ul.count; + + if (levelAmount > 0) output = strprintf("%d%s", + levelAmount, pl.symbol.c_str()) + output; + + if (!nextAmount) + break; + levelAmount = nextAmount; + } + + return output; + } + else + { + for (unsigned int i = 0; i < ud.levels.size(); i++) + { + ul = ud.levels[i]; + if (amount < ul.count && ul.count > 0) + { + ul = ud.levels[i - 1]; + break; + } + if (ul.count) + amount /= ul.count; + } + + return strprintf("%.*f%s", ul.round, amount, ul.symbol.c_str()); + } + } +} + +std::string Units::formatCurrency(int value) +{ + return formatUnit(value, UNIT_CURRENCY); +} + +std::string Units::formatWeight(int value) +{ + return formatUnit(value, UNIT_WEIGHT); +} diff --git a/src/units.h b/src/units.h new file mode 100644 index 000000000..d779efe70 --- /dev/null +++ b/src/units.h @@ -0,0 +1,46 @@ +/* + * Support for custom units + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UNITS_H +#define UNITS_H + +#include + +class Units +{ + public: + /** + * Loads and parses the units.xml file (if found). + */ + static void loadUnits(); + + /** + * Formats the given number in the correct currency format. + */ + static std::string formatCurrency(int value); + + /** + * Formats the given number in the correct weight/mass format. + */ + static std::string formatWeight(int value); +}; + +#endif // UNITS_H diff --git a/src/utils/base64.cpp b/src/utils/base64.cpp new file mode 100644 index 000000000..1221a7c7f --- /dev/null +++ b/src/utils/base64.cpp @@ -0,0 +1,170 @@ +/* + +----------------------------------------------------------------------+ + | PHP HTML Embedded Scripting Language Version 3.0 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2000 PHP Development Team (See Credits file) | + +----------------------------------------------------------------------+ + | This program is free software; you can redistribute it and/or modify | + | it under the terms of one of the following licenses: | + | | + | A) the GNU General Public License as published by the Free Software | + | Foundation; either version 2 of the License, or (at your option) | + | any later version. | + | | + | B) the PHP License as published by the PHP Development Team and | + | included in the distribution in the file: LICENSE | + | | + | This program 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 General Public License for more details. | + | | + | You should have received a copy of both licenses referred to here. | + | If you did not, or have any questions about PHP licensing, please | + | contact core@php.net. | + +----------------------------------------------------------------------+ + | Author: Jim Winstead (jimw@php.net) | + +----------------------------------------------------------------------+ + */ + +#include "utils/base64.h" + +#include +#include + +static char base64_table[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0' +}; +static char base64_pad = '='; + +unsigned char *php3_base64_encode(const unsigned char *string, + int length, int *ret_length) +{ + const unsigned char *current = string; + int i = 0; + unsigned char *result = (unsigned char *)malloc( + ((length + 3 - length % 3) * 4 / 3 + 1) * sizeof(char)); + + while (length > 2) + { /* keep going until we have less than 24 bits */ + result[i++] = base64_table[current[0] >> 2]; + result[i++] = base64_table[((current[0] & 0x03) + << 4) + (current[1] >> 4)]; + result[i++] = base64_table[((current[1] & 0x0f) + << 2) + (current[2] >> 6)]; + result[i++] = base64_table[current[2] & 0x3f]; + + current += 3; + length -= 3; /* we just handle 3 octets of data */ + } + + /* now deal with the tail end of things */ + if (length != 0) + { + result[i++] = base64_table[current[0] >> 2]; + if (length > 1) + { + result[i++] = base64_table[((current[0] & 0x03) << 4) + + (current[1] >> 4)]; + result[i++] = base64_table[(current[1] & 0x0f) << 2]; + result[i++] = base64_pad; + } + else + { + result[i++] = base64_table[(current[0] & 0x03) << 4]; + result[i++] = base64_pad; + result[i++] = base64_pad; + } + } + if (ret_length) + { + *ret_length = i; + } + result[i] = '\0'; + return result; +} + +/* as above, but backwards. :) */ +unsigned char *php3_base64_decode(const unsigned char *string, + int length, int *ret_length) +{ + const unsigned char *current = string; + int ch, i = 0, j = 0, k; + char *chp; + + unsigned char *result = (unsigned char *)malloc(length + 1); + + if (result == NULL) + return NULL; + + /* run through the whole string, converting as we go */ + while ((ch = *current++) != '\0') + { + if (ch == base64_pad) break; + + /* When Base64 gets POSTed, all pluses are interpreted as spaces. + This line changes them back. It's not exactly the Base64 spec, + but it is completely compatible with it (the spec says that + spaces are invalid). This will also save many people considerable + headache. - Turadg Aleahmad + */ + + if (ch == ' ') ch = '+'; + + chp = strchr(base64_table, ch); + if (!chp) + continue; + ch = chp - base64_table; + + switch(i % 4) + { + case 0: + result[j] = ch << 2; + break; + case 1: + result[j++] |= ch >> 4; + result[j] = (ch & 0x0f) << 4; + break; + case 2: + result[j++] |= ch >>2; + result[j] = (ch & 0x03) << 6; + break; + case 3: + result[j++] |= ch; + break; + default: + break; + } + i++; + } + + k = j; + /* mop things up if we ended on a boundary */ + if (ch == base64_pad) + { + switch(i % 4) + { + case 0: + case 1: + free(result); + return 0; + case 2: + k++; + case 3: + result[k++] = 0; + default: + break; + } + } + if (ret_length) + { + *ret_length = j; + } + result[k] = '\0'; + return result; +} diff --git a/src/utils/base64.h b/src/utils/base64.h new file mode 100644 index 000000000..92c230169 --- /dev/null +++ b/src/utils/base64.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | PHP HTML Embedded Scripting Language Version 3.0 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997,1998 PHP Development Team (See Credits file) | + +----------------------------------------------------------------------+ + | This program is free software; you can redistribute it and/or modify | + | it under the terms of one of the following licenses: | + | | + | A) the GNU General Public License as published by the Free Software | + | Foundation; either version 2 of the License, or (at your option) | + | any later version. | + | | + | B) the PHP License as published by the PHP Development Team and | + | included in the distribution in the file: LICENSE | + | | + | This program 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 General Public License for more details. | + | | + | You should have received a copy of both licenses referred to here. | + | If you did not, or have any questions about PHP licensing, please | + | contact core@php.net. | + +----------------------------------------------------------------------+ + | Author: Jim Winstead (jimw@php.net) | + +----------------------------------------------------------------------+ + */ + +#ifndef BASE64_H +#define BASE64_H + +extern unsigned char *php3_base64_encode(const unsigned char *, int, int *); +extern unsigned char *php3_base64_decode(const unsigned char *, int, int *); + +#endif /* BASE64_H */ diff --git a/src/utils/copynpaste.cpp b/src/utils/copynpaste.cpp new file mode 100644 index 000000000..498c012b5 --- /dev/null +++ b/src/utils/copynpaste.cpp @@ -0,0 +1,324 @@ +/* + * Retrieve string pasted depending on OS mechanisms. + * Copyright (C) 2001-2010 Wormux Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * IMPORTANT! + * + * This code was taken from Wormux svn trunk at Feb 25 2010. Please don't + * make any unnecessary modifications, and try to sync up modifications + * when possible. + */ + +#ifdef _MSC_VER +# include "msvc/config.h" +#elif defined(HAVE_CONFIG_H) +# include "config.h" +#endif + +#include +#include "copynpaste.h" + +#ifdef WIN32 +bool RetrieveBuffer(std::string& text, std::string::size_type& pos) +{ + bool ret = false; + + if (!OpenClipboard(NULL)) + return false; + + HANDLE h = GetClipboardData(CF_UNICODETEXT); + if (h) + { + LPCWSTR data = (LPCWSTR)GlobalLock(h); + + if (data) + { + int len = WideCharToMultiByte(CP_UTF8, 0, data, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + // Convert from UTF-16 to UTF-8 + void *temp = malloc(len); + if (WideCharToMultiByte(CP_UTF8, 0, data, -1, (LPSTR)temp, len, NULL, NULL)) + { + text.insert(pos, (char*)temp); + pos += len-1; + } + free(temp); + ret = true; + } + } + GlobalUnlock(h); + } + else + { + h = GetClipboardData(CF_TEXT); + + if (h) + { + const char *data = (char*)GlobalLock(h); + if (data) + { + text.insert(pos, data); + pos += strlen(data); + ret = true; + } + GlobalUnlock(h); + } + } + + CloseClipboard(); + return ret; +} +#elif defined(__APPLE__) + +#ifdef Status +#undef Status +#endif + +#include + +// Sorry for the very long code, all nicer OS X APIs are coded in Objective C and not C! +// Also it does very thorough error handling +bool GetDataFromPasteboard( PasteboardRef inPasteboard, char* flavorText /* out */, const int bufSize ) +{ + OSStatus err = noErr; + PasteboardSyncFlags syncFlags; + ItemCount itemCount; + + syncFlags = PasteboardSynchronize( inPasteboard ); + + //require_action( syncFlags & kPasteboardModified, PasteboardOutOfSync, + // err = badPasteboardSyncErr ); + + err = PasteboardGetItemCount( inPasteboard, &itemCount ); + require_noerr( err, CantGetPasteboardItemCount ); + + for (UInt32 itemIndex = 1; itemIndex <= itemCount; itemIndex++) + { + PasteboardItemID itemID; + CFArrayRef flavorTypeArray; + CFIndex flavorCount; + + err = PasteboardGetItemIdentifier( inPasteboard, itemIndex, &itemID ); + require_noerr( err, CantGetPasteboardItemIdentifier ); + + err = PasteboardCopyItemFlavors( inPasteboard, itemID, &flavorTypeArray ); + require_noerr( err, CantCopyPasteboardItemFlavors ); + + flavorCount = CFArrayGetCount( flavorTypeArray ); + + for (CFIndex flavorIndex = 0; flavorIndex < flavorCount; flavorIndex++) + { + CFStringRef flavorType; + CFDataRef flavorData; + CFIndex flavorDataSize; + flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex); + + // we're only interested by text... + if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) + { + err = PasteboardCopyItemFlavorData( inPasteboard, itemID, + flavorType, &flavorData ); + require_noerr( err, CantCopyFlavorData ); + flavorDataSize = CFDataGetLength( flavorData ); + flavorDataSize = (flavorDataSize<254) ? flavorDataSize : 254; + + if (flavorDataSize+2 > bufSize) + { + fprintf(stderr, "Cannot copy clipboard, contents is too big!\n"); + return false; + } + + for (short dataIndex = 0; dataIndex <= flavorDataSize; dataIndex++) + { + char byte = *(CFDataGetBytePtr( flavorData ) + dataIndex); + flavorText[dataIndex] = byte; + } + + flavorText[flavorDataSize] = '\0'; + flavorText[flavorDataSize+1] = '\n'; + + CFRelease (flavorData); + return true; + } + + continue; + CantCopyFlavorData: fprintf(stderr, "Cannot copy clipboard, CantCopyFlavorData!\n"); + } + + CFRelease (flavorTypeArray); + continue; + + CantCopyPasteboardItemFlavors: fprintf(stderr, "Cannot copy clipboard, CantCopyPasteboardItemFlavors!\n"); continue; + CantGetPasteboardItemIdentifier: fprintf(stderr, "Cannot copy clipboard, CantGetPasteboardItemIdentifier!\n"); continue; + } + fprintf(stderr, "Cannot copy clipboard, found no acceptable flavour!\n"); + return false; + + CantGetPasteboardItemCount: fprintf(stderr, "Cannot copy clipboard, CantGetPasteboardItemCount!\n"); return false; + //PasteboardOutOfSync: fprintf(stderr, "Cannot copy clipboard, PasteboardOutOfSync!\n"); return false; +} + +bool getClipBoard(char* text /* out */, const int bufSize ) +{ + OSStatus err = noErr; + + PasteboardRef theClipboard; + err = PasteboardCreate( kPasteboardClipboard, &theClipboard ); + require_noerr( err, PasteboardCreateFailed ); + + if (!GetDataFromPasteboard(theClipboard, text, bufSize)) + { + fprintf(stderr, "Cannot copy clipboard, GetDataFromPasteboardFailed!\n"); + return false; + } + + CFRelease(theClipboard); + + return true; + + // ---- error handling + PasteboardCreateFailed: fprintf(stderr, "Cannot copy clipboard, PasteboardCreateFailed!\n"); + CFRelease(theClipboard); + return false; +} + +bool RetrieveBuffer(std::string& text, std::string::size_type& pos) +{ + const int bufSize = 512; + char buffer[bufSize+1]; + + if (getClipBoard(buffer, bufSize)) + { + text = buffer; + pos += strlen(buffer); + return true; + } + else + { + return false; + } +} + +#elif USE_X11 +static char* getSelection(Display *dpy, Window us, Atom selection) +{ + int max_events = 50; + Window owner = XGetSelectionOwner (dpy, selection); + int ret; + + //printf("XConvertSelection on %s\n", XGetAtomName(dpy, selection)); + if (owner == None) + { + //printf("No owner\n"); + return NULL; + } + XConvertSelection(dpy, selection, XA_STRING, XA_PRIMARY, us, CurrentTime); + XFlush(dpy); + + while (max_events--) + { + XEvent e; + + XNextEvent(dpy, &e); + if(e.type == SelectionNotify) + { + //printf("Received %s\n", XGetAtomName(dpy, e.xselection.selection)); + if(e.xselection.property == None) + { + //printf("Couldn't convert\n"); + return NULL; + } + + long unsigned len, left, dummy; + int format; + Atom type; + unsigned char *data = NULL; + + ret = XGetWindowProperty(dpy, us, e.xselection.property, 0, 0, False, + AnyPropertyType, &type, &format, &len, &left, + &data); + if (left < 1) + { + if (ret == Success) + XFree(data); + return NULL; + } + + ret = XGetWindowProperty(dpy, us, e.xselection.property, 0, left, False, + AnyPropertyType, &type, &format, &len, &dummy, + &data); + if (ret != Success) + { + //printf("Failed to get property: %p on %lu\n", data, len); + return NULL; + } + + //printf(">>> Got %s: len=%lu left=%lu (event %i)\n", data, len, left, 50-max_events); + return (char*)data; + } + } + return NULL; +} + +bool RetrieveBuffer(std::string& text, std::string::size_type& pos) +{ + SDL_SysWMinfo info; + + //printf("Retrieving buffer...\n"); + SDL_VERSION(&info.version); + if ( SDL_GetWMInfo(&info) ) + { + Display *dpy = info.info.x11.display; + Window us = info.info.x11.window; + char *data = NULL; + + if (!data) + { + data = getSelection(dpy, us, XA_PRIMARY); + } + if (!data) + { + data = getSelection(dpy, us, XA_SECONDARY); + } + if (!data) + { + Atom XA_CLIPBOARD = XInternAtom(dpy, "CLIPBOARD", 0); + data = getSelection(dpy, us, XA_CLIPBOARD); + } + if (data) + { + // check cursor position + if (pos > text.size()) { + pos = text.size(); + } + + text.insert(pos, data); + pos += strlen(data); + XFree(data); + + return true; + } + } + return false; +} +#else +bool RetrieveBuffer(std::string&, std::string::size_type&) { return false; } +#endif diff --git a/src/utils/copynpaste.h b/src/utils/copynpaste.h new file mode 100644 index 000000000..1a7c81d05 --- /dev/null +++ b/src/utils/copynpaste.h @@ -0,0 +1,33 @@ +/* + * Retrieve string pasted depending on OS mechanisms. + * Copyright (C) 2001-2010 Wormux Team + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +/** + * Attempts to retrieve text from the clipboard buffer and inserts it in + * \a text at position \pos. The characters are encoded in utf-8. + * + * Implemented for Windows, X11 and Mac OS X. + * + * @return true when successful or false when there + * was a problem retrieving the clipboard buffer. + */ +bool RetrieveBuffer(std::string& text, std::string::size_type& pos); + diff --git a/src/utils/dtor.h b/src/utils/dtor.h new file mode 100644 index 000000000..a50189c4e --- /dev/null +++ b/src/utils/dtor.h @@ -0,0 +1,54 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTILS_DTOR_H +#define UTILS_DTOR_H + +#include +#include +#include + +template +struct dtor : public std::unary_function +{ + void operator()(T &ptr) { delete ptr; } +}; + +template +struct dtor > : +public std::unary_function , void> +{ + void operator()(std::pair &pair) { delete pair.second; } +}; + +template +inline dtor make_dtor(Cont const&) +{ + return dtor(); +} + +template +inline void delete_all(Container &c) +{ + std::for_each(c.begin(), c.end(), make_dtor(c)); +} + +#endif diff --git a/src/utils/gettext.h b/src/utils/gettext.h new file mode 100644 index 000000000..feeb6595b --- /dev/null +++ b/src/utils/gettext.h @@ -0,0 +1,44 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTILS_GETTEXT_H +#define UTILS_GETTEXT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#if ENABLE_NLS + +#define _(s) (const_cast (gettext(s))) +#define N_(s) (const_cast (s)) + +#else + +#define gettext(s) (const_cast (s)) +#define _(s) (const_cast (s)) +#define N_(s) (const_cast (s)) + +#endif + +#endif // UTILS_GETTEXT_H diff --git a/src/utils/mathutils.h b/src/utils/mathutils.h new file mode 100644 index 000000000..4917b5566 --- /dev/null +++ b/src/utils/mathutils.h @@ -0,0 +1,114 @@ +/* + * The Mana Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTILS_MATHUTILS_H +#define UTILS_MATHUTILS_H + +#include + +static uint16_t crc_table[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +inline uint16_t getCrc16(const std::string &str) +{ + size_t f = str.size(); + uint16_t crc16 = 0xffff; + + while (f != 0) + { + f --; + crc16 = static_cast( + crc_table[(crc16 ^ (str[f])) & 0xff] ^ (crc16 >> 8)); + } + + return crc16; +} + +/* A very fast function to calculate the approximate inverse square root of a + * floating point value and a helper function that uses it for getting the + * normal squareroot. For an explanation of the inverse squareroot function + * read: + * http://www.math.purdue.edu/~clomont/Math/Papers/2003/InvSqrt.pdf + * + * Unfortunately the original creator of this function seems to be unknown. + */ + +inline float fastInvSqrt(float x) +{ + union { int i; float x; } tmp; + float xhalf = 0.5f * x; + tmp.x = x; + tmp.i = 0x5f375a86 - (tmp.i >> 1); + x = tmp.x; + x = x * (1.5f - xhalf * x * x); + return x; +} + +inline float fastSqrt(float x) +{ + return 1.0f / fastInvSqrt(x); +} + +inline float weightedAverage(float n1, float n2, float w) +{ + if (w < 0.0f) + return n1; + + if (w > 1.0f) + return n2; + + return w * n2 + (1.0f - w) * n1; +} + +#endif // UTILS_MATHUTILS_H diff --git a/src/utils/mkdir.cpp b/src/utils/mkdir.cpp new file mode 100644 index 000000000..e7b126ae7 --- /dev/null +++ b/src/utils/mkdir.cpp @@ -0,0 +1,118 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#if defined WIN32 +#include +#endif + +#include + +#ifdef _MKDIR_TEST_ +// compile with -D_MKDIR_TEST_ to get a standalone binary +#include +#include +#endif + +#include "mkdir.h" + +int mkdir_r(const char *pathname) +{ + char tmp[PATH_MAX]; + char *p; + + if (strlen(pathname) >= PATH_MAX - 2) + return -1; + + strncpy(tmp, pathname, sizeof(tmp) - 1); + tmp[PATH_MAX - 1] = '\0'; + + int len = strlen(tmp); + + // terminate the pathname with '/' + if (tmp[len - 1] != '/') + { + tmp[len] = '/'; + tmp[len + 1] = '\0'; + } + + for (p = tmp; *p; p++) + { +#if defined WIN32 + if (*p == '/' || *p == '\\') +#else + if (*p == '/') +#endif + { + *p = '\0'; + // ignore a slash at the beginning of a path + if (strlen(tmp) == 0) + { + *p = '/'; + continue; + } + + // check if the name already exists, but not as directory + struct stat statbuf; + if (!stat(tmp, &statbuf)) + { + if (S_ISDIR(statbuf.st_mode)) + { + *p = '/'; + continue; + } + else + return -1; + } + +#if defined WIN32 + if (!CreateDirectory(tmp, 0)) +#else + if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) +#endif + { +#if defined WIN32 + // hack, hack. just assume that x: might be a drive + // letter, and try again + if (!(strlen(tmp) == 2 && + !strcmp(tmp + 1, ":"))) +#endif + return -1; + } + +#ifdef _MKDIR_TEST_ + printf("%s\n", tmp); +#endif + *p = '/'; + } + } + return 0; +} + +#ifdef _MKDIR_TEST_ +int main(int argc, char** argv) +{ + if (argc < 2) exit(1); + mkdir_r(argv[1]); +} +#endif diff --git a/src/utils/mkdir.h b/src/utils/mkdir.h new file mode 100644 index 000000000..9369b4e7b --- /dev/null +++ b/src/utils/mkdir.h @@ -0,0 +1,26 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _MKDIR_H +#define _MKDIR_H + +int mkdir_r(const char *pathname); + +#endif diff --git a/src/utils/mutex.h b/src/utils/mutex.h new file mode 100644 index 000000000..f98bcdfec --- /dev/null +++ b/src/utils/mutex.h @@ -0,0 +1,97 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MUTEX_H +#define MUTEX_H + +#include "log.h" + +#include + +/** + * A mutex provides mutual exclusion of access to certain data that is + * accessed by multiple threads. + */ +class Mutex +{ +public: + Mutex(); + ~Mutex(); + + void lock(); + void unlock(); + +private: + Mutex(const Mutex&); // prevent copying + Mutex& operator=(const Mutex&); + + SDL_mutex *mMutex; +}; + +/** + * A convenience class for locking a mutex. + */ +class MutexLocker +{ +public: + MutexLocker(Mutex *mutex); + ~MutexLocker(); + +private: + Mutex *mMutex; +}; + + +inline Mutex::Mutex() +{ + mMutex = SDL_CreateMutex(); +} + +inline Mutex::~Mutex() +{ + SDL_DestroyMutex(mMutex); +} + +inline void Mutex::lock() +{ + if (SDL_mutexP(mMutex) == -1) + logger->log("Mutex locking failed: %s", SDL_GetError()); +} + +inline void Mutex::unlock() +{ + if (SDL_mutexV(mMutex) == -1) + logger->log("Mutex unlocking failed: %s", SDL_GetError()); +} + + +inline MutexLocker::MutexLocker(Mutex *mutex): + mMutex(mutex) +{ + mMutex->lock(); +} + +inline MutexLocker::~MutexLocker() +{ + mMutex->unlock(); +} + +#endif // MUTEX_H diff --git a/src/utils/sha256.cpp b/src/utils/sha256.cpp new file mode 100644 index 000000000..29bbed29f --- /dev/null +++ b/src/utils/sha256.cpp @@ -0,0 +1,294 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file has been slighly modified as part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* +------------------------------------+ + * | Inspire Internet Relay Chat Daemon | + * +------------------------------------+ + * + * InspIRCd: (C) 2002-2008 InspIRCd Development Team + * See: http://www.inspircd.org/wiki/index.php/Credits + * + * This program is free but copyrighted software; see + * the file COPYING for details. + * + * --------------------------------------------------- + */ + +/* m_sha256 - Based on m_opersha256 written by Special + * Modified and improved by Craig Edwards, December 2006. + * + * + * FIPS 180-2 SHA-224/256/384/512 implementation + * Last update: 05/23/2005 + * Issue date: 04/30/2005 + * + * Copyright (C) 2005 Olivier Gay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "utils/sha256.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifdef HAVE_STDINT_H +#include +#else +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +#endif + +#define SHA256_BLOCK_SIZE (512 / 8) + +/** An sha 256 context, used by original m_opersha256 */ +class SHA256Context +{ + public: + unsigned int tot_len; + unsigned int len; + unsigned char block[2 * SHA256_BLOCK_SIZE]; + uint32_t h[8]; +}; + +#define SHA256_DIGEST_SIZE (256 / 8) + +#define SHFR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) +#define CH(x, y, z) ((x & y) ^ (~x & z)) +#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) + +#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3)) +#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10)) + +#define UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8_t) ((x) ); \ + *((str) + 2) = (uint8_t) ((x) >> 8); \ + *((str) + 1) = (uint8_t) ((x) >> 16); \ + *((str) + 0) = (uint8_t) ((x) >> 24); \ +} + +#define PACK32(str, x) \ +{ \ + *(x) = ((uint32_t) *((str) + 3) ) \ + | ((uint32_t) *((str) + 2) << 8) \ + | ((uint32_t) *((str) + 1) << 16) \ + | ((uint32_t) *((str) + 0) << 24); \ +} + +/* Macros used for loops unrolling */ + +#define SHA256_SCR(i) \ +{ \ + w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \ + + SHA256_F3(w[i - 15]) + w[i - 16]; \ +} + +const unsigned int sha256_h0[8] = +{ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +uint32_t sha256_k[64] = +{ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +void SHA256Init(SHA256Context *ctx); + +void SHA256Transform(SHA256Context *ctx, + unsigned char *message, + unsigned int block_nb); + +void SHA256Update(SHA256Context *ctx, + unsigned char *message, + unsigned int len); + +void SHA256Final(SHA256Context *ctx, unsigned char *digest); + +std::string SHA256Hash(const char *src, int len); + +void SHA256Init(SHA256Context *ctx) +{ + for (int i = 0; i < 8; i++) + ctx->h[i] = sha256_h0[i]; + ctx->len = 0; + ctx->tot_len = 0; +} + +void SHA256Transform(SHA256Context *ctx, + unsigned char *message, + unsigned int block_nb) +{ + uint32_t w[64]; + uint32_t wv[8]; + unsigned char *sub_block; + for (unsigned int i = 1; i <= block_nb; i++) + { + int j; + sub_block = message + ((i - 1) << 6); + + for (j = 0; j < 16; j++) + PACK32(&sub_block[j << 2], &w[j]); + for (j = 16; j < 64; j++) + SHA256_SCR(j); + for (j = 0; j < 8; j++) + wv[j] = ctx->h[j]; + for (j = 0; j < 64; j++) + { + uint32_t t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j]; + uint32_t t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + for (j = 0; j < 8; j++) + ctx->h[j] += wv[j]; + } +} + +void SHA256Update(SHA256Context *ctx, + unsigned char *message, + unsigned int len) +{ + /* + * XXX here be dragons! + * After many hours of pouring over this, I think I've found the problem. + * When Special created our module from the reference one, he used: + * + * unsigned int rem_len = SHA256_BLOCK_SIZE - ctx->len; + * + * instead of the reference's version of: + * + * unsigned int tmp_len = SHA256_BLOCK_SIZE - ctx->len; + * unsigned int rem_len = len < tmp_len ? len : tmp_len; + * + * I've changed back to the reference version of this code, and it seems to work with no errors. + * So I'm inclined to believe this was the problem.. + * -- w00t (January 06, 2008) + */ + unsigned int tmp_len = SHA256_BLOCK_SIZE - ctx->len; + unsigned int rem_len = len < tmp_len ? len : tmp_len; + + memcpy(&ctx->block[ctx->len], message, rem_len); + if (ctx->len + len < SHA256_BLOCK_SIZE) + { + ctx->len += len; + return; + } + unsigned int new_len = len - rem_len; + unsigned int block_nb = new_len / SHA256_BLOCK_SIZE; + unsigned char *shifted_message = message + rem_len; + SHA256Transform(ctx, ctx->block, 1); + SHA256Transform(ctx, shifted_message, block_nb); + rem_len = new_len % SHA256_BLOCK_SIZE; + memcpy(ctx->block, &shifted_message[block_nb << 6],rem_len); + ctx->len = rem_len; + ctx->tot_len += (block_nb + 1) << 6; +} + +void SHA256Final(SHA256Context *ctx, unsigned char *digest) +{ + unsigned int block_nb = (1 + ((SHA256_BLOCK_SIZE - 9) < (ctx->len % SHA256_BLOCK_SIZE))); + unsigned int len_b = (ctx->tot_len + ctx->len) << 3; + unsigned int pm_len = block_nb << 6; + memset(ctx->block + ctx->len, 0, pm_len - ctx->len); + ctx->block[ctx->len] = 0x80; + UNPACK32(len_b, ctx->block + pm_len - 4); + SHA256Transform(ctx, ctx->block, block_nb); + for (int i = 0 ; i < 8; i++) + UNPACK32(ctx->h[i], &digest[i << 2]); +} + +std::string SHA256Hash(const char *src, int len) +{ + // Generate the hash + unsigned char bytehash[SHA256_DIGEST_SIZE]; + SHA256Context ctx; + SHA256Init(&ctx); + SHA256Update(&ctx, (unsigned char *)src, (unsigned int)len); + SHA256Final(&ctx, bytehash); + // Convert it to hex + const char* hxc = "0123456789abcdef"; + std::string hash = ""; + for (int i = 0; i < SHA256_DIGEST_SIZE; i++) + { + hash += hxc[bytehash[i] / 16]; + hash += hxc[bytehash[i] % 16]; + } + return hash; +} + +std::string sha256(const std::string &string) +{ + return SHA256Hash(string.c_str(), string.length()); +} diff --git a/src/utils/sha256.h b/src/utils/sha256.h new file mode 100644 index 000000000..1248e8ddd --- /dev/null +++ b/src/utils/sha256.h @@ -0,0 +1,35 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTILS_SHA256_H +#define UTILS_SHA256_H + +#include + +/** + * Returns the SHA-256 hash for the given string. + * + * @param string the string to create the SHA-256 hash for + * @return the SHA-256 hash for the given string. + */ +std::string sha256(const std::string &string); + +#endif // UTILS_SHA256_H diff --git a/src/utils/specialfolder.cpp b/src/utils/specialfolder.cpp new file mode 100644 index 000000000..646077161 --- /dev/null +++ b/src/utils/specialfolder.cpp @@ -0,0 +1,78 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef WIN32 +#include "specialfolder.h" +#include + +#ifdef _SPECIALFOLDERLOCATION_TEST_ +// compile with -D_SPECIALFOLDERLOCATION_TEST_ to get a standalone +// binary for testing +#include +#endif + +/* + * Retrieve the pathname of special folders on win32, or an empty string + * on error / if the folder does not exist. + * See http://msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx for + * a list of folder ids + */ +std::string getSpecialFolderLocation(int folderId) +{ + std::string ret; + LPITEMIDLIST pItemIdList; + LPMALLOC pMalloc; + char szPath[_MAX_PATH]; + + // get the item ID list for folderId + HRESULT hr = SHGetSpecialFolderLocation(NULL, folderId, &pItemIdList); + if (hr != S_OK) + return ret; + + // convert the ID list into a file system path + if (SHGetPathFromIDList(pItemIdList, szPath) == FALSE) + return ret; + + // get the IMalloc pointer and free all resources we used + hr = SHGetMalloc(&pMalloc); + pMalloc->Free(pItemIdList); + pMalloc->Release(); + + ret = szPath; + return ret; +} + +#ifdef _SPECIALFOLDERLOCATION_TEST_ +int main() +{ + std::cout << "APPDATA " << getSpecialFolderLocation(CSIDL_APPDATA) + << std::endl; + std::cout << "DESKTOP " << getSpecialFolderLocation(CSIDL_DESKTOP) + << std::endl; + std::cout << "LOCAL_APPDATA " + << getSpecialFolderLocation(CSIDL_LOCAL_APPDATA) + << std::endl; + std::cout << "MYPICTURES " << getSpecialFolderLocation(CSIDL_MYPICTURES) + << std::endl; + std::cout << "PERSONAL " << getSpecialFolderLocation(CSIDL_PERSONAL) + << std::endl; +} +#endif +#endif diff --git a/src/utils/specialfolder.h b/src/utils/specialfolder.h new file mode 100644 index 000000000..c2c2e0bea --- /dev/null +++ b/src/utils/specialfolder.h @@ -0,0 +1,30 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _SPECIALFOLDER_H +#define _SPECIALFOLDER_H + +#ifdef WIN32 +#include +#include +std::string getSpecialFolderLocation(int folderId); +#endif + +#endif diff --git a/src/utils/stringutils.cpp b/src/utils/stringutils.cpp new file mode 100644 index 000000000..e69b03937 --- /dev/null +++ b/src/utils/stringutils.cpp @@ -0,0 +1,360 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "utils/stringutils.h" + +#include +#include +#include +#include +#include + +static int UTF8_MAX_SIZE = 10; + +std::string &trim(std::string &str) +{ + std::string::size_type pos = str.find_last_not_of(' '); + if (pos != std::string::npos) + { + str.erase(pos + 1); + pos = str.find_first_not_of(' '); + + if (pos != std::string::npos) + str.erase(0, pos); + } + else + { + // There is nothing else but whitespace in the string + str.clear(); + } + return str; +} + +std::string &toLower(std::string &str) +{ + std::transform(str.begin(), str.end(), str.begin(), tolower); + return str; +} + +std::string &toUpper(std::string &str) +{ + std::transform(str.begin(), str.end(), str.begin(), toupper); + return str; +} + +unsigned int atox(const std::string &str) +{ + unsigned int value; + sscanf(str.c_str(), "0x%06x", &value); + + return value; +} + +const char *ipToString(int address) +{ + static char asciiIP[18]; + + sprintf(asciiIP, "%i.%i.%i.%i", + (unsigned char)(address), + (unsigned char)(address >> 8), + (unsigned char)(address >> 16), + (unsigned char)(address >> 24)); + + return asciiIP; +} + +std::string strprintf(char const *format, ...) +{ + char buf[257]; + va_list(args); + va_start(args, format); + int nb = vsnprintf(buf, 256, format, args); + va_end(args); + if (nb < 256) + return buf; + + // The static size was not big enough, try again with a dynamic allocation. + ++nb; + char *buf2 = new char[nb]; + va_start(args, format); + vsnprintf(buf2, nb, format, args); + va_end(args); + std::string res(buf2); + delete [] buf2; + return res; +} + +std::string &removeBadChars(std::string &str) +{ + std::string::size_type pos; + do + { + pos = str.find_first_of("@#[]"); + if (pos != std::string::npos) + str.erase(pos, 1); + } + while (pos != std::string::npos); + + return str; +} + +std::string removeColors(std::string msg) +{ + for (unsigned int f = 0; f < msg.length() - 2 && msg.length() > 2; f++) + { + while (msg.length() > f + 2 && msg.at(f) == '#' + && msg.at(f + 1) == '#') + { + msg = msg.erase(f, 3); + } + } + return msg; +} + +int compareStrI(const std::string &a, const std::string &b) +{ + std::string::const_iterator itA = a.begin(); + std::string::const_iterator endA = a.end(); + std::string::const_iterator itB = b.begin(); + std::string::const_iterator endB = b.end(); + + for (; itA < endA, itB < endB; ++itA, ++itB) + { + int comp = tolower(*itA) - tolower(*itB); + if (comp) + return comp; + } + + // Check string lengths + if (itA == endA && itB == endB) + return 0; + else if (itA == endA) + return -1; + else + return 1; +} + + +bool isWordSeparator(char chr) +{ + return (chr == ' ' || chr == ',' || chr == '.' || chr == '"'); +} + +const std::string findSameSubstring(const std::string &str1, + const std::string &str2) +{ + int minLength = str1.length() > str2.length() + ? static_cast(str2.length()) : static_cast(str1.length()); + for (int f = 0; f < minLength; f ++) + { + if (str1.at(f) != str2.at(f)) + return str1.substr(0, f); + } + return str1.substr(0, minLength); +} + +unsigned long findI(std::string str, std::string subStr) +{ + str = toLower(str); + subStr = toLower(subStr); + return str.find(subStr); +} + +unsigned long findI(std::string str, std::list &list) +{ + str = toLower(str); + unsigned long idx; + for (std::list::iterator i = list.begin(); + i != list.end(); i++) + { + std::string subStr = toLower(*i); + idx = str.find(subStr); + if (idx != std::string::npos) + return idx; + } + return std::string::npos; +} + +int base = 94; +int start = 33; + +const std::string encodeStr(unsigned int value, unsigned int size) +{ + std::string buf; + + do + { + buf += static_cast(value % base + start); + value /= base; + } + while (value); + + while (buf.length() < size) + buf += (char)start; + return buf; +} + + +unsigned int decodeStr(const std::string &str) +{ + if (str.empty()) + return 0; + + int res = str[0] - start; + int mult = 1; + for (unsigned int f = 1; f < str.length(); f ++) + { + mult *= base; + res = res + (str[f] - start) * mult; + } + return res; +} + +std::string extractNameFromSprite(std::string str) +{ + size_t pos1 = str.rfind("."); + if (pos1 != std::string::npos) + { + size_t pos2 = str.rfind("/"); + size_t pos3 = str.rfind("\\"); + if (pos3 != std::string::npos) + { + if (pos2 == std::string::npos || pos3 > pos2) + pos2 = pos3; + } + if (pos2 == std::string::npos) + pos2 = -1; + + int size = static_cast(pos1) - static_cast(pos2) - 1; + if (size > 0) + str = str.substr(pos2 + 1, size); + } + return str; +} + +std::string removeSpriteIndex(std::string str) +{ + size_t pos1 = str.rfind("["); + + if (pos1 != std::string::npos) + { + size_t pos2 = str.rfind("/"); + size_t pos3 = str.rfind("\\"); + if (pos3 != std::string::npos) + { + if (pos2 == std::string::npos || pos3 > pos2) + pos2 = pos3; + } + if (pos2 == std::string::npos) + pos2 = -1; + + int size = static_cast(pos1) - static_cast(pos2) - 1; + if (size > 0) + str = str.substr(pos2 + 1, size); + } + return str; +} + +const char* getSafeUtf8String(std::string text) +{ + int size = static_cast(text.size()) + UTF8_MAX_SIZE; + char* buf = new char[size]; + memcpy(buf, text.c_str(), text.size()); + memset(buf + text.size(), 0, UTF8_MAX_SIZE); + return buf; +} + +void getSafeUtf8String(std::string text, char *buf) +{ + int size = static_cast(text.size()) + UTF8_MAX_SIZE; + if (size > 65500) + text = text.substr(0, 65500); + memcpy(buf, text.c_str(), text.size()); + memset(buf + text.size(), 0, UTF8_MAX_SIZE); + return; +} + +std::string getFileName(std::string path) +{ + size_t pos = path.rfind("/"); + if (pos == std::string::npos) + pos = path.rfind("\\"); + if (pos == std::string::npos) + return path; + return path.substr(pos + 1); +} + +std::string& replaceAll(std::string& context, const std::string& from, + const std::string& to) +{ + size_t lookHere = 0; + size_t foundHere; + while ((foundHere = context.find(from, lookHere)) != std::string::npos) + { + context.replace(foundHere, from.size(), to); + lookHere = foundHere + to.size(); + } + return context; +} + +bool getBoolFromString(const std::string &text) +{ + std::string txt = text; + toLower(trim(txt)); + if (txt == "true" || txt == "yes" || txt == "on" || txt == "1") + return true; + else if (txt == "false" || txt == "no" || txt == "off" || txt == "0") + return false; + else + return (bool) atoi(txt.c_str()); +} + +void replaceSpecialChars(std::string &text) +{ + size_t idx = 0; + size_t pos1 = text.find("&"); + while (pos1 != std::string::npos) + { + idx = pos1 + 1; + if (idx >= text.size()) + break; + + unsigned f; + for (f = idx; f < text.size(); f ++) + { + if (text[f] < '0' || text[f] > '9') + break; + } + if (idx + 1 < f && text[f] == ';') + { + std::string str = " "; + str[0] = (char)atoi(text.substr(idx, f - idx).c_str()); + text = text.substr(0, pos1) + str + text.substr(f + 1); + pos1 += 1; + } + else + { + pos1 = f + 1; + } + + pos1 = text.find("&", pos1); + } +} \ No newline at end of file diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h new file mode 100644 index 000000000..4532a5b00 --- /dev/null +++ b/src/utils/stringutils.h @@ -0,0 +1,159 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTILS_STRINGUTILS_H +#define UTILS_STRINGUTILS_H + +#include +#include +#include + +/** + * Trims spaces off the end and the beginning of the given string. + * + * @param str the string to trim spaces off + * @return a reference to the trimmed string + */ +std::string &trim(std::string &str); + +/** + * Converts the given string to lower case. + * + * @param str the string to convert to lower case + * @return a reference to the given string converted to lower case + */ +std::string &toLower(std::string &str); + +/** + * Converts the given string to upper case. + * + * @param str the string to convert to upper case + * @return a reference to the given string converted to upper case + */ +std::string &toUpper(std::string &str); + + +/** + * Converts an ascii hexidecimal string to an integer + * + * @param str the hex string to convert to an int + * @return the integer representation of the hex string + */ +unsigned int atox(const std::string &str); + +/** + * Converts the given value to a string using std::stringstream. + * + * @param arg the value to convert to a string + * @return the string representation of arg + */ +template std::string toString(const T &arg) +{ + std::stringstream ss; + ss << arg; + return ss.str(); +} + +/** + * Converts the given IP address to a string. + * + * The returned string is statically allocated, and shouldn't be freed. It is + * changed upon the next use of this method. + * + * @param address the address to convert to a string + * @return the string representation of the address + */ +const char *ipToString(int address); + +/** + * A safe version of sprintf that returns a std::string of the result. + */ +std::string strprintf(char const *, ...) +#ifdef __GNUC__ + /* This attribute is nice: it even works through gettext invokation. For + example, gcc will complain that strprintf(_("%s"), 42) is ill-formed. */ + __attribute__((__format__(__printf__, 1, 2))) +#endif +; + +/** + * Removes bad characters from a string + * + * @param str the string to remove the bad chars from + * @return a reference to the string without bad chars + */ +std::string &removeBadChars(std::string &str); + +/** + * Removes colors from a string + * + * @param msg the string to remove the colors from + * @return string without colors + */ +std::string removeColors(std::string msg); + +bool isWordSeparator(char chr); + +const std::string findSameSubstring(const std::string &str1, + const std::string &str2); + +/** + * Compares the two strings case-insensitively. + * + * @param a the first string in the comparison + * @param b the second string in the comparison + * @return 0 if the strings are equal, positive if the first is greater, + * negative if the second is greater + */ +int compareStrI(const std::string &a, const std::string &b); + +unsigned long findI(std::string str, std::string subStr); + +unsigned long findI(std::string str, std::list &list); + +const std::string encodeStr(unsigned int value, unsigned int size = 0); + +unsigned int decodeStr(const std::string &str); + +std::string extractNameFromSprite(std::string str); + +std::string removeSpriteIndex(std::string str); + +const char* getSafeUtf8String(std::string text); + +void getSafeUtf8String(std::string text, char *buf); + +std::string getFileName(std::string path); + +std::string& replaceAll(std::string& context, const std::string& from, + const std::string& to); + +/** + * Returns a bool value depending on the given string value. + * + * @param text the string used to get the bool value + * @return a boolean value.. + */ +bool getBoolFromString(const std::string &text); + +void replaceSpecialChars(std::string &text); + +#endif // UTILS_STRINGUTILS_H diff --git a/src/utils/xml.cpp b/src/utils/xml.cpp new file mode 100644 index 000000000..9521f377d --- /dev/null +++ b/src/utils/xml.cpp @@ -0,0 +1,164 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "utils/xml.h" + +#include "log.h" + +#include "resources/resourcemanager.h" + +#include +#include +#include + +namespace XML +{ + Document::Document(const std::string &filename, bool useResman): + mDoc(0) + { + int size; + char *data = NULL; + if (useResman) + { + ResourceManager *resman = ResourceManager::getInstance(); + data = (char*) resman->loadFile(filename.c_str(), size); + } + else + { + std::ifstream file; + file.open(filename.c_str(), std::ios::in); + + if (file.is_open()) + { + // Get length of file + file.seekg (0, std::ios::end); + size = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + data = (char*) malloc(size); + + file.read(data, size); + file.close(); + } + else + { + logger->log("Error loading XML file %s", filename.c_str()); + } + } + + if (data) + { + mDoc = xmlParseMemory(data, size); + free(data); + + if (!mDoc) + logger->log("Error parsing XML file %s", filename.c_str()); + } + else + { + logger->log("Error loading %s", filename.c_str()); + } + } + + Document::Document(const char *data, int size) + { + if (data) + mDoc = xmlParseMemory(data, size); + else + mDoc = 0; + } + + Document::~Document() + { + if (mDoc) + xmlFreeDoc(mDoc); + } + + xmlNodePtr Document::rootNode() + { + return mDoc ? xmlDocGetRootElement(mDoc) : 0; + } + + int getProperty(xmlNodePtr node, const char* name, int def) + { + int &ret = def; + + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + if (prop) + { + ret = atoi((char*)prop); + xmlFree(prop); + } + + return ret; + } + + double getFloatProperty(xmlNodePtr node, const char* name, double def) + { + double &ret = def; + + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + if (prop) + { + ret = atof((char*)prop); + xmlFree(prop); + } + + return ret; + } + + std::string getProperty(xmlNodePtr node, const char *name, + const std::string &def) + { + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + if (prop) + { + std::string val = (char*)prop; + xmlFree(prop); + return val; + } + + return def; + } + + bool getBoolProperty(xmlNodePtr node, const char* name, bool def) + { + xmlChar *prop = xmlGetProp(node, BAD_CAST name); + + if (xmlStrEqual(prop, BAD_CAST "true" )) + return true; + if (xmlStrEqual(prop, BAD_CAST "false")) + return false; + return def; + } + + xmlNodePtr findFirstChildByName(xmlNodePtr parent, const char *name) + { + for_each_xml_child_node(child, parent) + { + if (xmlStrEqual(child->name, BAD_CAST name)) + return child; + } + + return NULL; + } + +} // namespace XML diff --git a/src/utils/xml.h b/src/utils/xml.h new file mode 100644 index 000000000..253c4e094 --- /dev/null +++ b/src/utils/xml.h @@ -0,0 +1,101 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef XML_H +#define XML_H + +#include + +#include + +/** + * XML helper functions. + */ +namespace XML +{ + /** + * A helper class for parsing an XML document, which also cleans it up + * again (RAII). + */ + class Document + { + public: + /** + * Constructor that attempts to load the given file through the + * resource manager. Logs errors. + */ + Document(const std::string &filename, bool useResman = true); + + /** + * Constructor that attempts to load an XML document from memory. + * Does not log errors. + * + * @param data the string to parse as XML + * @param size the length of the string in bytes + */ + Document(const char *data, int size); + + /** + * Destructor. Frees the loaded XML file. + */ + ~Document(); + + /** + * Returns the root node of the document (or NULL if there was a + * load error). + */ + xmlNodePtr rootNode(); + + private: + xmlDocPtr mDoc; + }; + + /** + * Gets an floating point property from an xmlNodePtr. + */ + double getFloatProperty(xmlNodePtr node, const char *name, double def); + + /** + * Gets an integer property from an xmlNodePtr. + */ + int getProperty(xmlNodePtr node, const char *name, int def); + + /** + * Gets a string property from an xmlNodePtr. + */ + std::string getProperty(xmlNodePtr node, const char *name, + const std::string &def); + + /** + * Gets a boolean property from an xmlNodePtr. + */ + bool getBoolProperty(xmlNodePtr node, const char *name, bool def); + + /** + * Finds the first child node with the given name + */ + xmlNodePtr findFirstChildByName(xmlNodePtr parent, const char *name); +} + +#define for_each_xml_child_node(var, parent) \ + for (xmlNodePtr var = parent->xmlChildrenNode; var; var = var->next) + +#endif // XML_H diff --git a/src/variabledata.h b/src/variabledata.h new file mode 100644 index 000000000..517324d39 --- /dev/null +++ b/src/variabledata.h @@ -0,0 +1,113 @@ +/* + * The Mana Client + * Copyright (C) 2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VARIABLEDATA_H +#define VARIABLEDATA_H + +#include + +namespace Mana +{ + +class VariableData +{ + public: + enum + { + DATA_NONE = 0, + DATA_INT, + DATA_STRING, + DATA_FLOAT, + DATA_BOOL + }; + + virtual ~VariableData() + {}; + + virtual int getType() const = 0; +}; + +class IntData : public VariableData +{ + public: + IntData(int value) + { mData = value; } + + int getData() const + { return mData; } + + int getType() const + { return DATA_INT; } + + private: + int mData; +}; + +class StringData : public VariableData +{ + public: + StringData(const std::string &value) + { mData = value; } + + const std::string &getData() const + { return mData; } + + int getType() const + { return DATA_STRING; } + + private: + std::string mData; +}; + +class FloatData : public VariableData +{ + public: + FloatData(double value) + { mData = value; } + + double getData() const + { return mData; } + + int getType() const + { return DATA_FLOAT; } + + private: + double mData; +}; + +class BoolData : public VariableData +{ + public: + BoolData(bool value) + { mData = value; } + + bool getData() const + { return mData; } + + int getType() const + { return DATA_BOOL; } + + private: + bool mData; +}; + +} // namespace Mana + +#endif diff --git a/src/vector.cpp b/src/vector.cpp new file mode 100644 index 000000000..e6f4e5bbf --- /dev/null +++ b/src/vector.cpp @@ -0,0 +1,28 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "vector.h" + +std::ostream& operator <<(std::ostream &os, const Vector &v) +{ + os << "Vector(" << v.x << ", " << v.y << ", " << v.z << ")"; + return os; +} diff --git a/src/vector.h b/src/vector.h new file mode 100644 index 000000000..163dcdbda --- /dev/null +++ b/src/vector.h @@ -0,0 +1,199 @@ +/* + * The Mana Client + * Copyright (C) 2007-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#include + +#include + +/** + * Vector class. Represents either a 3D point in space, a velocity or a force. + * Provides several convenient operator overloads. + */ +class Vector +{ + public: + /** + * Constructor. + */ + Vector(): + x(0.0f), + y(0.0f), + z(0.0f) + {} + + /** + * Constructor. + */ + Vector(float x, float y, float z = 0.0f): + x(x), + y(y), + z(z) + {} + + /** + * Copy constructor. + */ + Vector(const Vector &v): + x(v.x), + y(v.y), + z(v.z) + {} + + /** + * Returns true if all coordinates are set to 0, otherwise returns + * false. + */ + bool isNull() const + { + return x == 0.0f && y == 0.0f && z == 0.0f; + } + + /** + * Scale vector operator. + */ + Vector operator*(float c) const + { + return Vector(x * c, + y * c, + z * c); + } + + /** + * In-place scale vector operator. + */ + Vector &operator*=(float c) + { + x *= c; + y *= c; + z *= c; + return *this; + } + + /** + * Scale vector operator. + */ + Vector operator/(float c) const + { + return Vector(x / c, + y / c, + z / c); + } + + /** + * In-place scale vector operator. + */ + Vector &operator/=(float c) + { + x /= c; + y /= c; + z /= c; + return *this; + } + + /** + * Add vector operator. + */ + Vector operator+(const Vector &v) const + { + return Vector(x + v.x, + y + v.y, + z + v.z); + } + + /** + * In-place add vector operator. + */ + Vector &operator+=(const Vector &v) + { + x += v.x; + y += v.y; + z += v.z; + return *this; + } + + /** + * Subtract vector operator. + */ + Vector operator-(const Vector &v) const + { + return Vector(x - v.x, + y - v.y, + z - v.z); + } + + /** + * In-place subtract vector operator. + */ + Vector &operator-=(const Vector &v) + { + x -= v.x; + y -= v.y; + z -= v.z; + return *this; + } + + /** + * Returns the length of this vector. This method does a relatively + * slow square root. + */ + float length() const + { + return sqrtf(x * x + y * y + z * z); + } + + /** + * Returns the squared length of this vector. Avoids the square root. + */ + float squaredLength() const + { + return x * x + y * y + z * z; + } + + /** + * Returns the manhattan length of this vector. + */ + float manhattanLength() const + { + return fabsf(x) + fabsf(y) + fabsf(z); + } + + /** + * Returns a normalized version of this vector. This is a unit vector + * running parallel to it. + */ + Vector normalized() const + { + const float l = length(); + return Vector(x / l, y / l, z / l); + } + + float x, y, z; +}; + +/** + * Appends a string representation of a vector to the output stream. + */ +std::ostream& operator <<(std::ostream &os, const Vector &v); + +#endif // VECTOR_H diff --git a/src/winver.h b/src/winver.h new file mode 100644 index 000000000..4ff552db1 --- /dev/null +++ b/src/winver.h @@ -0,0 +1,6 @@ +/* VERSION DEFINITIONS */ +#define VER_MAJOR 1 +#define VER_MINOR 0 +#define VER_RELEASE 0 +#define VER_BUILD 0 +#define PACKAGE_VERSION "1.0.0" diff --git a/src/winver.h.in b/src/winver.h.in new file mode 100644 index 000000000..fb0aac2c0 --- /dev/null +++ b/src/winver.h.in @@ -0,0 +1,6 @@ +/* VERSION DEFINITIONS */ +#define VER_MAJOR ${VER_MAJOR} +#define VER_MINOR ${VER_MINOR} +#define VER_RELEASE ${VER_RELEASE} +#define VER_BUILD ${VER_BUILD} +#define PACKAGE_VERSION "${VERSION}" -- cgit v1.2.3-70-g09d2