Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
188 KB
Referenced Files
None
Subscribers
None
diff --git a/data/locale/zh_CN.po b/data/locale/zh_CN.po
index 5fc3f57..c1914ac 100644
--- a/data/locale/zh_CN.po
+++ b/data/locale/zh_CN.po
@@ -1,1085 +1,1191 @@
# Simplified Chinese translation for Me and My Shadow
# Copyright (C) 2012 Me and My Shadow
# This file is distributed under the same license (GNU GPLv3) as the meandmyshadow package.
# acme_pjz <acme_pjz@hotmail.com>, 2012
#
msgid ""
msgstr ""
"Project-Id-Version: Me and my shadow\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-07-29 17:24+0800\n"
-"PO-Revision-Date: 2012-07-29 17:25+0800\n"
+"POT-Creation-Date: 2012-07-31 15:41+0800\n"
+"PO-Revision-Date: 2012-07-31 15:42+0800\n"
"Last-Translator: acme_pjz <acme_pjz@hotmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"X-Poedit-Language: Chinese\n"
"X-Poedit-Country: CHINA\n"
"X-Poedit-SourceCharset: utf-8\n"
"X-Poedit-Basepath: .\n"
"X-Poedit-KeywordsList: _;__\n"
"X-Poedit-SearchPath-0: F:\\Projects\\meandmyshadow\\src\n"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:41
+#: F:\Projects\meandmyshadow\src/Addons.cpp:40
msgid "Addons"
msgstr "附加组件"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:59
+#: F:\Projects\meandmyshadow\src/Addons.cpp:58
msgid "Unable to initialize addon menu:"
msgstr "不能初始化附加组件菜单:"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:67
-#: F:\Projects\meandmyshadow\src/Addons.cpp:116
+#: F:\Projects\meandmyshadow\src/Addons.cpp:66
+#: F:\Projects\meandmyshadow\src/Addons.cpp:115
msgid "Back"
msgstr "后退"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:98
+#: F:\Projects\meandmyshadow\src/Addons.cpp:97
msgid "Levels"
msgstr "关卡列表"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:99
+#: F:\Projects\meandmyshadow\src/Addons.cpp:98
msgid "Level Packs"
msgstr "关卡包"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:100
+#: F:\Projects\meandmyshadow\src/Addons.cpp:99
msgid "Themes"
msgstr "主题"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:120
-#: F:\Projects\meandmyshadow\src/Addons.cpp:601
+#: F:\Projects\meandmyshadow\src/Addons.cpp:119
+#: F:\Projects\meandmyshadow\src/Addons.cpp:600
msgid "Install"
msgstr "安装"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:124
+#: F:\Projects\meandmyshadow\src/Addons.cpp:123
msgid "Update"
msgstr "升级"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:135
+#: F:\Projects\meandmyshadow\src/Addons.cpp:134
msgid "ERROR: unable to download addons file!"
msgstr "错误:无法下载附加组件!"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:147
+#: F:\Projects\meandmyshadow\src/Addons.cpp:146
msgid "ERROR: unable to load addon_list file!"
msgstr "错误:无法加载文件addon_list!"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:158
+#: F:\Projects\meandmyshadow\src/Addons.cpp:157
msgid "ERROR: Invalid file format of addons file!"
msgstr "错误:文件addons的格式无效!"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:179
+#: F:\Projects\meandmyshadow\src/Addons.cpp:178
msgid "ERROR: Unable to create the installed_addons file."
msgstr "错误:无法创建文件installed_addons。"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:191
+#: F:\Projects\meandmyshadow\src/Addons.cpp:190
msgid "ERROR: Invalid file format of the installed_addons!"
msgstr "错误:文件installed_addons的格式无效!"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:406
-#: F:\Projects\meandmyshadow\src/Addons.cpp:423
-#: F:\Projects\meandmyshadow\src/Addons.cpp:440
-#: F:\Projects\meandmyshadow\src/Addons.cpp:467
-#: F:\Projects\meandmyshadow\src/Addons.cpp:484
-#: F:\Projects\meandmyshadow\src/Addons.cpp:498
+#: F:\Projects\meandmyshadow\src/Addons.cpp:405
+#: F:\Projects\meandmyshadow\src/Addons.cpp:422
+#: F:\Projects\meandmyshadow\src/Addons.cpp:439
+#: F:\Projects\meandmyshadow\src/Addons.cpp:466
+#: F:\Projects\meandmyshadow\src/Addons.cpp:483
+#: F:\Projects\meandmyshadow\src/Addons.cpp:497
msgid "ERROR: Unable to download addon!"
msgstr "错误:无法下载附加组件"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:406
-#: F:\Projects\meandmyshadow\src/Addons.cpp:423
-#: F:\Projects\meandmyshadow\src/Addons.cpp:440
-#: F:\Projects\meandmyshadow\src/Addons.cpp:467
-#: F:\Projects\meandmyshadow\src/Addons.cpp:484
-#: F:\Projects\meandmyshadow\src/Addons.cpp:498
+#: F:\Projects\meandmyshadow\src/Addons.cpp:405
+#: F:\Projects\meandmyshadow\src/Addons.cpp:422
+#: F:\Projects\meandmyshadow\src/Addons.cpp:439
+#: F:\Projects\meandmyshadow\src/Addons.cpp:466
+#: F:\Projects\meandmyshadow\src/Addons.cpp:483
+#: F:\Projects\meandmyshadow\src/Addons.cpp:497
msgid "ERROR:"
msgstr "错误:"
-#: F:\Projects\meandmyshadow\src/Addons.cpp:596
+#: F:\Projects\meandmyshadow\src/Addons.cpp:595
msgid "Uninstall"
msgstr "卸载"
#: F:\Projects\meandmyshadow\src/Block.cpp:547
msgid "On"
msgstr "开启"
#: F:\Projects\meandmyshadow\src/Block.cpp:548
msgid "Off"
msgstr "关闭"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:598
#: F:\Projects\meandmyshadow\src/Functions.cpp:599
#: F:\Projects\meandmyshadow\src/Functions.cpp:600
+#: F:\Projects\meandmyshadow\src/Functions.cpp:601
+#: F:\Projects\meandmyshadow\src/Functions.cpp:617
msgid "knewave"
msgstr "DroidSansFallback"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:604
+#: F:\Projects\meandmyshadow\src/Functions.cpp:605
msgid "Blokletters-Viltstift"
msgstr "DroidSansFallback"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:656
+#: F:\Projects\meandmyshadow\src/Functions.cpp:678
msgid "Loading..."
msgstr "正在读取..."
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1181
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1208
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1546
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1209
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1236
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1574
#: F:\Projects\meandmyshadow\src/InputManager.cpp:233
msgid "OK"
msgstr "确定"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1182
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1194
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1204
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1550
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1210
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1222
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1232
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1578
msgid "Cancel"
msgstr "取消"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1186
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1214
msgid "Abort"
msgstr "终止"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1187
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1203
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1215
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1231
msgid "Retry"
msgstr "重试"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1188
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1216
msgid "Ignore"
msgstr "忽略"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1192
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1198
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1220
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1226
msgid "Yes"
msgstr "是"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1193
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1199
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1221
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1227
msgid "No"
msgstr "否"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1317
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1345
#, c-format
msgid ""
"%s already exists.\n"
"Do you want to overwrite it?"
msgstr ""
"%s 已经存在。\n"
"你是否想要覆盖它?"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1317
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1345
msgid "Overwrite Prompt"
msgstr "文件覆盖提示"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1338
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1356
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1366
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1384
#, c-format
msgid "Can't open file %s."
msgstr "不能打开文件 %s。"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1338
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1356
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1366
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1384
msgid "Error"
msgstr "错误"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1487
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1515
msgid "Save File"
msgstr "保存文件"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1487
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1515
msgid "Load File"
msgstr "打开文件"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1491
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1519
msgid "Search In"
msgstr "查找范围"
-#: F:\Projects\meandmyshadow\src/Functions.cpp:1501
+#: F:\Projects\meandmyshadow\src/Functions.cpp:1529
msgid "File Name"
msgstr "文件名"
#: F:\Projects\meandmyshadow\src/Game.cpp:245
#: F:\Projects\meandmyshadow\src/Game.cpp:851
#, c-format
msgid "Level %d %s"
msgstr "第 %d 关 %s"
#: F:\Projects\meandmyshadow\src/Game.cpp:669
#, c-format
msgid "Press %s key to save the game."
msgstr "按 %s 键来保存游戏。"
#: F:\Projects\meandmyshadow\src/Game.cpp:674
#, c-format
msgid "Press %s key to swap the position of player and shadow."
msgstr "按 %s 键交换你和阴影的位置。"
#: F:\Projects\meandmyshadow\src/Game.cpp:679
#, c-format
msgid "Press %s key to activate the switch."
msgstr "按 %s 键来激活开关。"
#: F:\Projects\meandmyshadow\src/Game.cpp:684
#, c-format
msgid "Press %s key to teleport."
msgstr "按 %s 键传送。"
#: F:\Projects\meandmyshadow\src/Game.cpp:725
#, c-format
msgid "Press %s to restart current level or press %s to load the game."
msgstr "按 %s 键重新开始游戏,或者按 %s 键读取进度。"
#: F:\Projects\meandmyshadow\src/Game.cpp:737
#, c-format
msgid "Press %s to restart current level."
msgstr "按 %s 键重新开始游戏。"
#: F:\Projects\meandmyshadow\src/Game.cpp:751
msgid "Your shadow has died."
msgstr "你的阴影死掉了。"
#: F:\Projects\meandmyshadow\src/Game.cpp:803
#, c-format
msgid "%d recordings"
msgstr "记录数 %d"
#: F:\Projects\meandmyshadow\src/Game.cpp:843
msgid "You've finished:"
msgstr "恭喜你完成了:"
#: F:\Projects\meandmyshadow\src/Game.cpp:1028
#, c-format
msgid "Time: %-.2fs"
msgstr "时间: %-.2f秒"
#: F:\Projects\meandmyshadow\src/Game.cpp:1037
#, c-format
msgid "Best time: %-.2fs"
msgstr "最佳时间: %-.2f秒"
#: F:\Projects\meandmyshadow\src/Game.cpp:1048
#, c-format
msgid "Target time: %-.2fs"
msgstr "目标时间: %-.2f秒"
#: F:\Projects\meandmyshadow\src/Game.cpp:1069
#, c-format
msgid "Recordings: %d"
msgstr "记录次数: %d"
#: F:\Projects\meandmyshadow\src/Game.cpp:1077
#, c-format
msgid "Best recordings: %d"
msgstr "最佳记录次数: %d"
#: F:\Projects\meandmyshadow\src/Game.cpp:1087
#, c-format
msgid "Target recordings: %d"
msgstr "目标记录次数: %d"
#: F:\Projects\meandmyshadow\src/Game.cpp:1100
#, c-format
msgid "You earned the %s medal"
msgstr "你拿到了%s"
#: F:\Projects\meandmyshadow\src/Game.cpp:1100
msgid "GOLD"
msgstr "金牌"
#: F:\Projects\meandmyshadow\src/Game.cpp:1100
msgid "SILVER"
msgstr "银牌"
#: F:\Projects\meandmyshadow\src/Game.cpp:1100
msgid "BRONZE"
msgstr "铜牌"
#: F:\Projects\meandmyshadow\src/Game.cpp:1115
msgid "Menu"
msgstr "菜单"
#: F:\Projects\meandmyshadow\src/Game.cpp:1122
#: F:\Projects\meandmyshadow\src/InputManager.cpp:44
msgid "Restart"
msgstr "重新开始"
#: F:\Projects\meandmyshadow\src/Game.cpp:1129
msgid "Next"
msgstr "下一关"
#: F:\Projects\meandmyshadow\src/Game.cpp:1184
msgid "Game replay is done."
msgstr "游戏重放已经完成。"
#: F:\Projects\meandmyshadow\src/Game.cpp:1184
msgid "Game Replay"
msgstr "游戏重放"
#: F:\Projects\meandmyshadow\src/Game.cpp:1355
#: F:\Projects\meandmyshadow\src/Game.cpp:1357
msgid "Congratulations"
msgstr "恭喜你"
#: F:\Projects\meandmyshadow\src/Game.cpp:1357
msgid "You have finished the levelpack!"
msgstr "你已经完成了整个关卡包!"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Up (in menu)"
msgstr "上(在菜单中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Down (in menu)"
msgstr "下(在菜单中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Left"
msgstr "左"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Right"
msgstr "右"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Jump"
msgstr "跳跃"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Action"
msgstr "动作键"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
msgid "Space (Record)"
msgstr "空格(记录键)"
+#: F:\Projects\meandmyshadow\src/InputManager.cpp:43
+msgid "Cancel recording"
+msgstr "取消记录"
+
#: F:\Projects\meandmyshadow\src/InputManager.cpp:44
msgid "Escape"
msgstr "退出"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:44
msgid "Tab (View shadow/Level prop.)"
msgstr "Tab (切换视角/关卡属性)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:44
msgid "Save game (in editor)"
msgstr "保存游戏(在地图编辑器中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:44
msgid "Load game"
msgstr "读取游戏"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:44
msgid "Swap (in editor)"
msgstr "交换玩家和阴影的位置(在地图编辑器中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:45
msgid "Teleport (in editor)"
msgstr "传送(在地图编辑器中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:45
msgid "Suicide (in editor)"
msgstr "自杀(在地图编辑器中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:45
msgid "Shift (in editor)"
msgstr "Shift (地图编辑器辅助按键)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:45
msgid "Next block type (in Editor)"
msgstr "下一个砖块类型(在地图编辑器中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:46
msgid "Previous block type (in editor)"
msgstr "上一个砖块类型(在地图编辑器中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:46
msgid "Select (in menu)"
msgstr "选择键(在菜单中)"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:204
msgid "Config Keys"
msgstr "设置按键"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:207
msgid "Select an item and press a key to config it."
msgstr "选择一个项目,然后按键进行设置。"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:221
msgid "Primary key"
msgstr "主按键"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:222
msgid "Alternative key"
msgstr "辅助按键"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:228
msgid "Unset the key"
msgstr "取消设置"
#: F:\Projects\meandmyshadow\src/InputManager.cpp:298
msgid "(Not set)"
msgstr "(未设置)"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:62
msgid "Block"
msgstr "砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:62
msgid "Player Start"
msgstr "玩家起点"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:62
msgid "Shadow Start"
msgstr "阴影起点"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:63
msgid "Exit"
msgstr "终点"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:63
msgid "Shadow Block"
msgstr "阴影砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:63
msgid "Spikes"
msgstr "带刺砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:64
msgid "Checkpoint"
msgstr "记录点"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:64
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:290
msgid "Swap"
msgstr "交换点"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:64
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2461
msgid "Fragile"
msgstr "易碎砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:65
msgid "Moving Block"
msgstr "移动砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:65
msgid "Moving Shadow Block"
msgstr "移动阴影砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:65
msgid "Moving Spikes"
msgstr "移动的刺"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:66
msgid "Teleporter"
msgstr "传送点"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:66
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2380
msgid "Button"
msgstr "按钮"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:66
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2382
msgid "Switch"
msgstr "开关"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:67
msgid "Conveyor Belt"
msgstr "传送带"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:67
msgid "Shadow Conveyor Belt"
msgstr "阴影传送带"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:67
msgid "Notification Block"
msgstr "消息方块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:67
msgid "Collectable"
msgstr "可收集的物品"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:153
msgid "Toolbox"
msgstr "工具箱"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:505
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3117
msgid "Select"
msgstr "选择"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:520
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3126
msgid "Configure"
msgstr "设置"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:535
msgid "Link"
msgstr "链接"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:550
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3123
msgid "Delete"
msgstr "删除"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:939
msgid "Are you sure you want to quit?"
msgstr "你确定要退出吗?"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:939
msgid "Quit prompt"
msgstr "退出提示"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1038
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1040
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1498
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1500
#, c-format
msgid "Level \"%s\" saved"
msgstr "关卡“%s”已保存"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1038
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1040
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1498
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1500
msgid "Saved"
msgstr "已保存"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1513
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3132
msgid "Level settings"
msgstr "关卡设置"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1517
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:202
msgid "Name:"
msgstr "名称:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1523
msgid "Theme:"
msgstr "主题:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1538
msgid "Target time (s):"
msgstr "目标时间(秒):"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:1549
msgid "Target recordings:"
msgstr "目标记录数:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2110
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2300
msgid "Defined"
msgstr "已设置"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2113
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2303
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2374
msgid "None"
msgstr "无"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2120
msgid "Moving block"
msgstr "移动砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2123
msgid "Moving shadow block"
msgstr "移动阴影砖块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2126
msgid "Moving spikes"
msgstr "移动的刺"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2133
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2252
msgid "Enabled"
msgstr "启用"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2139
msgid "Loop"
msgstr "循环"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2145
msgid "Path"
msgstr "路径"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2196
msgid "Notification block"
msgstr "消息方块"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2199
msgid "Enter message here:"
msgstr "输入消息:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2244
msgid "Shadow Conveyor belt"
msgstr "阴影传送带"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2246
msgid "Conveyor belt"
msgstr "传送带"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2258
msgid "Enter speed here:"
msgstr "输入速度:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2307
msgid "Portal"
msgstr "传送门"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2310
msgid "Activate on touch"
msgstr "接触时传送"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2316
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2410
msgid "Targets:"
msgstr "目标:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2371
#, c-format
msgid "%d Defined"
msgstr "已设置 %d 个"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2387
msgid "Behaviour:"
msgstr "行为:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2395
msgid "Toggle"
msgstr "切换"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2464
msgid "State:"
msgstr "状态:"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2470
msgid "Complete"
msgstr "完整的"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2471
msgid "One step"
msgstr "踩了一次"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2472
msgid "Two steps"
msgstr "踩了两次"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:2473
msgid "Gone"
msgstr "已经破碎"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3120
msgid "Add"
msgstr "添加"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3129
#: F:\Projects\meandmyshadow\src/LevelPlaySelect.cpp:66
msgid "Play"
msgstr "开始游戏"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3135
msgid "Save level"
msgstr "保存关卡"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3138
msgid "Back to menu"
msgstr "回主菜单"
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3187
#: F:\Projects\meandmyshadow\src/LevelEditor.cpp:3193
#, c-format
msgid "Movespeed: %s"
msgstr "移动速度: %s"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:43
msgid "Map Editor"
msgstr "地图编辑器"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:80
msgid "New Levelpack"
msgstr "新建关卡包"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:85
msgid "Pack Properties"
msgstr "关卡包属性"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:90
msgid "Remove Pack"
msgstr "删除关卡包"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:95
msgid "Move Map"
msgstr "移动地图"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:103
msgid "Remove Map"
msgstr "删除地图"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:108
msgid "Edit Map"
msgstr "编辑地图"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:199
msgid "Properties"
msgstr "属性"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:211
msgid "Description:"
msgstr "描述:"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:220
msgid "Congratulation text:"
msgstr "完成提示:"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:248
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:401
msgid "Add level"
msgstr "增加关卡"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:251
msgid "File name:"
msgstr "文件名:"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:275
msgid "Move level"
msgstr "移动关卡"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:278
msgid "Level: "
msgstr "关卡:"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:288
msgid "Before"
msgstr "之前"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:289
msgid "After"
msgstr "之后"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:464
msgid "Are you sure?"
msgstr "你确定吗?"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:464
msgid "Remove prompt"
msgstr "删除提示"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:609
msgid "No file name given for the new level."
msgstr "没有给新关卡指定文件名。"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:609
msgid "Missing file name"
msgstr "文件名未指定"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:686
msgid "The entered level number isn't valid!"
msgstr "输入的关卡编号无效!"
#: F:\Projects\meandmyshadow\src/LevelEditSelect.cpp:686
msgid "Illegal number"
msgstr "无效编号"
#: F:\Projects\meandmyshadow\src/LevelPlaySelect.cpp:44
msgid "Select Level"
msgstr "选择关卡"
#: F:\Projects\meandmyshadow\src/LevelPlaySelect.cpp:93
msgid "Choose a level"
msgstr "选择一个关卡"
#: F:\Projects\meandmyshadow\src/LevelPlaySelect.cpp:94
msgid "Time:"
msgstr "时间:"
#: F:\Projects\meandmyshadow\src/LevelPlaySelect.cpp:95
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:168
msgid "Recordings:"
msgstr "记录次数:"
-#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:269
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:57
+msgid "Newbie"
+msgstr "新手"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:57
+msgid "Congratulations, you completed one level!"
+msgstr "恭喜你成功完成了一个关卡!"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:58
+msgid "Experienced player"
+msgstr "有经验的玩家"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:58
+msgid "Completed 50 levels."
+msgstr "完成50个关卡。"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:59
+msgid "Expert"
+msgstr "专家"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:59
+msgid "Earned 50 gold medal."
+msgstr "拿到50个金牌"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:60
+msgid "Graduate"
+msgstr "毕业生"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:60
+msgid "Complete the tutorial level pack."
+msgstr "完成教程关卡包。"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:61
+msgid "Outstanding graduate"
+msgstr "优秀毕业生"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:61
+msgid "Complete the tutorial level pack with all levels gold medal."
+msgstr "以全部金牌的成绩完成教学关卡包。"
+
+#: F:\Projects\meandmyshadow\src/StatisticsManager.cpp:276
msgid "New achievement:"
msgstr "新成就:"
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:126
+msgid "Achievements and Statistics"
+msgstr "成就与统计信息"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:150
+msgid "Total"
+msgstr "总计"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:159
+msgid "Traveling distance (m)"
+msgstr "旅行距离(米)"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:160
+msgid "Jump times"
+msgstr "跳跃次数"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:161
+msgid "Die times"
+msgstr "死亡次数"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:168
+msgid "Switch pulled times:"
+msgstr "使用开关次数:"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:169
+msgid "Swap times:"
+msgstr "交换次数:"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:176
+msgid "Completed levels:"
+msgstr "完成关卡数:"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:218
+msgid "Playing time:"
+msgstr "玩游戏用时:"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:231
+msgid "Level editing time:"
+msgstr "地图编辑用时:"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:243
+msgid "Created levels:"
+msgstr "创建的关卡:"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:289
+msgid "You don't have any achievements now. Play the game and try to earn some!"
+msgstr "你目前没有获得任何成就。试着通过玩游戏获取一些!"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:318
+msgid "Achievements"
+msgstr "成就"
+
+#: F:\Projects\meandmyshadow\src/StatisticsScreen.cpp:319
+msgid "Statistics"
+msgstr "统计信息"
+
#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:49
msgid "Options"
msgstr "选项"
#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:52
msgid "Quit"
msgstr "退出"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:120
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:128
msgid "Enable internet in order to install addons."
msgstr "启用网络访问,才能下载附加组件。"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:120
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:128
msgid "Internet disabled"
msgstr "网络已禁用"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:203
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:205
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:707
+msgid "Credits"
+msgstr "作者"
+
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:236
msgid "Settings"
msgstr "选项"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:247
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:280
msgid "Music"
msgstr "音乐"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:255
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:288
msgid "Sound"
msgstr "音效"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:263
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:296
msgid "Fullscreen"
msgstr "全屏幕"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:268
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:301
msgid "Resolution"
msgstr "分辨率"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:350
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:383
msgid "Language"
msgstr "语言"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:359
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:392
msgid "Auto-Detect"
msgstr "自动检测"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:387
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:420
msgid "Theme"
msgstr "主题"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:420
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:453
msgid "Level themes"
msgstr "默认主题"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:425
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:458
msgid "Internet"
msgstr "网络"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:431
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:464
msgid "Internet proxy"
msgstr "网络代理"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:448
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:481
msgid "Clear Progress"
msgstr "清除进度"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:467
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:500
msgid "Save Changes"
msgstr "保存变更"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:577
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:610
msgid "Do you really want to reset level progress?"
msgstr "你确定要清除游戏进度吗?"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:577
+#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:610
msgid "Warning"
msgstr "警告"
-#: F:\Projects\meandmyshadow\src/TitleMenu.cpp:674
-msgid "Credits"
-msgstr "作者"
-
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "backspace"
msgstr "backspace"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "tab"
msgstr "tab"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "clear"
msgstr "clear"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "return"
msgstr "回车"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "pause"
msgstr "pause"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "escape"
msgstr "escape"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "space"
msgstr "空格"
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "delete"
msgstr "delete"
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "enter"
msgstr "enter"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:1
msgid "equals"
msgstr "equals"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "up"
msgstr "上"
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "down"
msgstr "下"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "right"
msgstr "右"
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "left"
msgstr "左"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "insert"
msgstr "insert"
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "home"
msgstr "home"
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "end"
msgstr "end"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "page up"
msgstr "page up"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "page down"
msgstr "page down"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "numlock"
msgstr "numlock"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:2
msgid "caps lock"
msgstr "caps lock"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "scroll lock"
msgstr "scroll lock"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "right shift"
msgstr "右 shift"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "left shift"
msgstr "左 shift"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "right ctrl"
msgstr "右 ctrl"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "left ctrl"
msgstr "左 ctrl"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "right alt"
msgstr "右 alt"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:3
msgid "left alt"
msgstr "左 alt"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "right meta"
msgstr "右 meta"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "left meta"
msgstr "左 meta"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "left super"
msgstr "左 super"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "right super"
msgstr "右 super"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "alt gr"
msgstr "alt gr"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "compose"
msgstr "compose"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "help"
msgstr "help"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:4
msgid "print screen"
msgstr "print screen"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:5
msgid "sys req"
msgstr "sys req"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:5
msgid "break"
msgstr "break"
#: F:\Projects\meandmyshadow\src/dummy.h:5
msgid "menu"
msgstr "menu"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:5
msgid "power"
msgstr "power"
# TRANSLATORS: name of a key
#: F:\Projects\meandmyshadow\src/dummy.h:5
msgid "euro"
msgstr "euro"
#: F:\Projects\meandmyshadow\src/dummy.h:5
msgid "undo"
msgstr "undo"
+#, fuzzy
+#~ msgid "Record times:"
+#~ msgstr "记录次数:"
diff --git a/src/Functions.cpp b/src/Functions.cpp
index aa82d5e..f4c6943 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1591 +1,1591 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_gfxPrimitives.h>
#include <SDL/SDL_rotozoom.h>
#include <SDL/SDL_syswm.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "Objects.h"
#include "Player.h"
#include "GameObjects.h"
#include "LevelPack.h"
#include "TitleMenu.h"
#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
#include "LevelPlaySelect.h"
#include "Addons.h"
#include "InputManager.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
extern "C" {
#include "libs/findlocale/findlocale.h"
}
#ifdef HARDWARE_ACCELERATION
#include <GL/gl.h>
#include <GL/glu.h>
//fix some Windows header bug
#ifndef GL_BGR
#define GL_BGR GL_BGR_EXT
#endif
#ifndef GL_BGRA
#define GL_BGRA GL_BGRA_EXT
#endif
#endif
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Workaround for the resizing below 800x600 for X systems.
#ifdef __linux__
#include<X11/Xlib.h>
#include<X11/Xutil.h>
#endif
//Initialise the imagemanager.
//The ImageManager is used to prevent loading images multiple times.
ImageManager imageManager;
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Initialise the levelPackManager.
//The LevelPackManager is used to prevent loading levelpacks multiple times and for the game to know which levelpacks there are.
LevelPackManager levelPackManager;
//Map containing changed settings using command line arguments.
map<string,string> tmpSettings;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=0;
#ifdef HARDWARE_ACCELERATION
GLuint screenTexture;
#endif
SDL_Surface* loadImage(string file){
//We use the imageManager to load the file.
return imageManager.loadImage(file);
}
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
rectangleRGBA(dest,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//Fill content's background color from function parameter
boxRGBA(dest,x+1,y+1,x+w-2,y+h-2,color >> 24,color >> 16,color >> 8,color >> 0);
//Draw first black borders around content and leave 1 pixel in every corner
lineRGBA(dest,x+1,y,x+w-2,y,0,0,0,255);
lineRGBA(dest,x+1,y+h-1,x+w-2,y+h-1,0,0,0,255);
lineRGBA(dest,x,y+1,x,y+h-2,0,0,0,255);
lineRGBA(dest,x+w-1,y+1,x+w-1,y+h-2,0,0,0,255);
//Fill the corners with transperent color to create anti-aliased borders
pixelRGBA(dest,x,y,0,0,0,160);
pixelRGBA(dest,x,y+h-1,0,0,0,160);
pixelRGBA(dest,x+w-1,y,0,0,0,160);
pixelRGBA(dest,x+w-1,y+h-1,0,0,0,160);
//Draw second lighter border around content
rectangleRGBA(dest,x+1,y+1,x+w-2,y+h-2,0,0,0,64);
//Create anti-aliasing in corners of second border
pixelRGBA(dest,x+1,y+1,0,0,0,50);
pixelRGBA(dest,x+1,y+h-2,0,0,0,50);
pixelRGBA(dest,x+w-2,y+1,0,0,0,50);
pixelRGBA(dest,x+w-2,y+h-2,0,0,0,50);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
lineRGBA(dest,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,dest,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),dest,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),dest,color);
}
}
bool createScreen(){
//Check if we are going fullscreen.
if(settings->getBoolValue("fullscreen"))
pickFullscreenResolution();
//Set the screen_width and height.
SCREEN_WIDTH=atoi(settings->getValue("width").c_str());
SCREEN_HEIGHT=atoi(settings->getValue("height").c_str());
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Boolean if this is the first screen creation.
bool initial=true;
//Check if we should use gl or software rendering.
if(settings->getBoolValue("gl")){
#ifdef HARDWARE_ACCELERATION
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,32);
SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
//Set the video mode.
Uint32 flags=SDL_HWSURFACE | SDL_OPENGL;
if(settings->getBoolValue("fullscreen"))
flags|=SDL_FULLSCREEN;
else if(settings->getBoolValue("resizable"))
flags|=SDL_RESIZABLE;
if(SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,flags)==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
//Delete the old screen.
//Warning: only if previous mode is OpenGL mode.
//NOTE: The previous mode can't switch during runtime.
if(screen){
SDL_FreeSurface(screen);
screen=NULL;
//There was a screen so this isn't the initial screen creation.
initial=false;
}
//Create a screen
screen=SDL_CreateRGBSurface(SDL_HWSURFACE,SCREEN_WIDTH,SCREEN_HEIGHT,32,0x00FF0000,0x0000FF00,0x000000FF,0);
//Create a texture.
glDeleteTextures(1,&screenTexture);
glGenTextures(1,&screenTexture);
//And set up gl correctly.
glClearColor(0, 0, 0, 0);
glClearDepth(1.0f);
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
glLoadIdentity();
#else
//NOTE: Hardware accelerated rendering requested but compiled without.
cerr<<"FATAL ERROR: Unable to use hardware acceleration (compiled without)."<<endl;
return false;
#endif
}else{
//Set the flags.
Uint32 flags=SDL_HWSURFACE | SDL_DOUBLEBUF;
if(settings->getBoolValue("fullscreen"))
flags|=SDL_FULLSCREEN;
else if(settings->getBoolValue("resizable"))
flags|=SDL_RESIZABLE;
//Check if there already was a screen.
if(screen)
initial=false;
//Create the screen and check if there weren't any errors.
screen=SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,flags);
if(screen==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow(initial);
//Create the temp surface, just a replica of the screen surface, free the previous one if any.
if(tempSurface)
SDL_FreeSurface(tempSurface);
tempSurface=SDL_CreateRGBSurface(SDL_HWSURFACE|SDL_SRCALPHA,
screen->w,screen->h,screen->format->BitsPerPixel,
screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);
//Set the the window caption.
SDL_WM_SetCaption(("Me and My Shadow "+version).c_str(),NULL);
SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return true;
}
#ifdef WIN32
static WNDPROC m_OldWindowProc=NULL;
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
if(msg==WM_GETMINMAXINFO){
if(m_OldWindowProc){
CallWindowProc(m_OldWindowProc,hwnd,msg,wParam,lParam);
}else{
DefWindowProc(hwnd,msg,wParam,lParam);
}
RECT r={0,0,800,600};
AdjustWindowRect(&r,GetWindowLong(hwnd,GWL_STYLE),FALSE);
MINMAXINFO *info=(MINMAXINFO*)lParam;
info->ptMinTrackSize.x=r.right-r.left;
info->ptMinTrackSize.y=r.bottom-r.top;
return 0;
}else{
if(m_OldWindowProc){
return CallWindowProc(m_OldWindowProc,hwnd,msg,wParam,lParam);
}else{
return DefWindowProc(hwnd,msg,wParam,lParam);
}
}
}
#endif
void pickFullscreenResolution(){
//Vector that will hold the resolutions to choose from.
vector<_res> resolutionList;
//Enumerate avaliable resolutions using SDL_ListModes()
//Note: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SDL_HWSURFACE);
if(modes==NULL || ((intptr_t)modes) == -1){
cout<<"Error: Can't enumerate avaliable screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const _res predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
//Fill the resolutionList.
for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
//Fill the resolutionList.
for(unsigned int i=0;modes[i]!=NULL;i++){
//Check if the resolution is higher than the minimum (800x600).
if(modes[i]->w>=800 && modes[i]->h>=600){
_res res={modes[i]->w, modes[i]->h};
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//The resolution that will hold the final result, we start with the minimum (800x600).
_res closestMatch={800,600};
int width=atoi(getSettings()->getValue("width").c_str());
//int height=atoi(getSettings()->getValue("height").c_str());
//Now loop through the resolutionList.
for(int i=0;i<(int)resolutionList.size();i++){
//The delta between the closestMatch and the resolution from the list.
int dM=(closestMatch.w-resolutionList[i].w);
//The delta between the target width and the resolution from the list.
int dT=(width-resolutionList[i].w);
//Since the resolutions are getting higher the lower (more negative) the further away it is.
//That's why we check if the deltaMatch is lower than the the deltaTarget.
if((dM)<(dT)){
closestMatch.w=resolutionList[i].w;
closestMatch.h=resolutionList[i].h;
}
}
//Now set the resolution to the closest match.
char s[64];
sprintf(s,"%d",closestMatch.w);
getSettings()->setValue("width",s);
sprintf(s,"%d",closestMatch.h);
getSettings()->setValue("height",s);
}
void configureWindow(bool initial){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//Retrieve the WM info from SDL containing the window handle.
struct SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
SDL_GetWMInfo(&wmInfo);
#ifdef __linux__
//We assume that a linux system running meandmyshadow is also running an Xorg server.
if(wmInfo.subsystem==SDL_SYSWM_X11){
//Create the size hints to give to the window.
XSizeHints* sizeHints;
if(!(sizeHints=XAllocSizeHints())){
cerr<<"ERROR: Unable to allocate memory for XSizeHings."<<endl;
return;
}
//Configure the size hint.
sizeHints->flags=PMinSize;
sizeHints->min_width=800;
sizeHints->min_height=600;
//Set the normal hints of the window.
(void)wmInfo.info.x11.lock_func;
XSetNormalHints(wmInfo.info.x11.display,wmInfo.info.x11.wmwindow,sizeHints);
(void)wmInfo.info.x11.unlock_func;
//Free size hint structure
XFree(sizeHints);
}else{
//No X11 so an unsupported window manager.
cerr<<"WARNING: Unsupported window manager."<<endl;
}
#elif defined(WIN32)
//We overwrite the window proc of SDL
WNDPROC wndproc=(WNDPROC)GetWindowLong(wmInfo.window,GWL_WNDPROC);
if(wndproc!=NULL && wndproc!=(WNDPROC)WindowProc){
m_OldWindowProc=wndproc;
SetWindowLong(wmInfo.window,GWL_WNDPROC,(LONG)(WNDPROC)WindowProc);
}
#endif
}
void onVideoResize(){
//Check if the resize event isn't malformed.
if(event.resize.w<=0 || event.resize.h<=0)
return;
//Check the size limit.
if(event.resize.w<800)
event.resize.w=800;
if(event.resize.h<600)
event.resize.h=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.resize.w && SCREEN_HEIGHT==event.resize.h)
return;
char s[32];
//Set the new width and height.
sprintf(s,"%d",event.resize.w);
getSettings()->setValue("width",s);
sprintf(s,"%d",event.resize.h);
getSettings()->setValue("height",s);
//Do resizing.
if(!createScreen())
return;
//Tell the theme to resize.
if(!loadTheme(""))
return;
//The new resolution is valid.
//Now we can save the settings. (TODO: should we save?)
//saveSettings();
//And let the currentState update it's GUI to the new resolution.
currentState->resize();
}
bool init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
fprintf(stderr,"FATAL ERROR: SDL_Init failed\n");
return false;
}
//Initialze SDL_mixer (audio).
if(Mix_OpenAudio(22050,MIX_DEFAULT_FORMAT,2,512)==-1){
fprintf(stderr,"FATAL ERROR: Mix_OpenAudio failed\n");
return false;
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
fprintf(stderr,"FATAL ERROR: TTF_Init failed\n");
return false;
}
//Create the screen.
if(!createScreen())
return false;
//Load key config. Then initalize joystick support.
inputMgr.loadConfig();
inputMgr.openAllJoysitcks();
//Init tinygettext for translations for the right language
dictionaryManager = new tinygettext::DictionaryManager();
dictionaryManager->add_directory(getDataPath()+"locale");
dictionaryManager->set_charset("UTF-8");
//Check if user have defined own language. If not, find it out for the player using findlocale
string lang=getSettings()->getValue("lang");
if(lang.length()>0){
printf("Locale set by user to %s\n",lang.c_str());
language=lang;
}else{
FL_Locale *locale;
FL_FindLocale(&locale,FL_MESSAGES);
printf("Locale isn't set by user: %s\n",locale->lang);
language=locale->lang;
if(locale->country!=NULL){
language+=string("_")+string(locale->country);
}
if(locale->variant!=NULL){
language+=string("@")+string(locale->variant);
}
FL_FreeLocale(&locale);
}
//Now set the language in the dictionaryManager.
dictionaryManager->set_language(tinygettext::Language::from_name(language));
//Disable annoying 'Couldn't translate: blah blah blah'
tinygettext::Log::set_log_info_callback(NULL);
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Nothing went wrong so we return true.
return true;
}
static TTF_Font* loadFont(const char* name,int size){
TTF_Font* tmpFont=TTF_OpenFont((getDataPath()+"font/"+name+".ttf").c_str(),size);
if(tmpFont){
return tmpFont;
}else{
return TTF_OpenFont((getDataPath()+"font/DroidSansFallback.ttf").c_str(),size);
}
}
bool loadFonts(){
//Load the fonts.
//NOTE: This is a separate method because it will be called separately when re-initing in case of language change.
//First close the fonts if needed.
if(fontTitle)
TTF_CloseFont(fontTitle);
if(fontGUI)
TTF_CloseFont(fontGUI);
if(fontGUISmall)
TTF_CloseFont(fontGUISmall);
if(fontText)
TTF_CloseFont(fontText);
/// TRANSLATORS: Font used in GUI:
/// - Use "knewave" for languages using Latin and Latin-derived alphabets
/// - "DroidSansFallback" can be used for non-Latin writing systems
fontTitle=loadFont(_("knewave"),55);
fontGUI=loadFont(_("knewave"),32);
fontGUISmall=loadFont(_("knewave"),24);
/// TRANSLATORS: Font used for normal text:
/// - Use "Blokletters-Viltstift" for languages using Latin and Latin-derived alphabets
/// - "DroidSansFallback" can be used for non-Latin writing systems
fontText=loadFont(_("Blokletters-Viltstift"),16);
if(fontTitle==NULL || fontGUI==NULL || fontGUISmall==NULL || fontText==NULL){
printf("ERROR: Unable to load fonts! \n");
return false;
}
//Nothing went wrong so return true.
return true;
}
//Generate small arrows used for some GUI widgets.
static void generateArrows(){
TTF_Font* fontArrow=loadFont(_("knewave"),18);
if(arrowLeft1){
SDL_FreeSurface(arrowLeft1);
SDL_FreeSurface(arrowRight1);
SDL_FreeSurface(arrowLeft2);
SDL_FreeSurface(arrowRight2);
}
arrowLeft1=TTF_RenderUTF8_Blended(fontArrow,"<",themeTextColor);
arrowRight1=TTF_RenderUTF8_Blended(fontArrow,">",themeTextColor);
arrowLeft2=TTF_RenderUTF8_Blended(fontArrow,"<",themeTextColorDialog);
arrowRight2=TTF_RenderUTF8_Blended(fontArrow,">",themeTextColorDialog);
TTF_CloseFont(fontArrow);
}
bool loadTheme(string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme")==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
}
//Resize background or load specific theme
if(name==""||name.empty()){
objThemes.scaleToScreen();
}else{
string theme=processFileName(name);
if(objThemes.appendThemeFromFile(theme+"/theme.mnmstheme")==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
return false;
}
}
generateArrows();
//Everything went fine so return true.
return true;
}
static Mix_Chunk* loadWAV(const char* s){
Mix_Chunk* c=Mix_LoadWAV(s);
if(c!=NULL) return c;
printf("ERROR: Can't load sound file %s: %s\n",s,SDL_GetError());
return NULL;
}
bool loadFiles(){
//Load the fonts.
if(!loadFonts())
return false;
//Show a loading screen
{
SDL_Rect r={0,0,screen->w,screen->h};
SDL_FillRect(screen,&r,0);
SDL_Color fg={255,255,255};
SDL_Surface *surface=TTF_RenderUTF8_Blended(fontTitle,_("Loading..."),fg);
if(surface!=NULL){
r.x=(screen->w-surface->w)/2;
r.y=(screen->h-surface->h)/2;
SDL_BlitSurface(surface,NULL,screen,&r);
SDL_FreeSurface(surface);
}
SDL_Flip(screen);
}
musicManager.destroy();
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Always load the default music list for fallback.
musicManager.loadMusicList((getDataPath()+"music/default.list"));
//Load the configured music list.
getMusicManager()->loadMusicList((getDataPath()+"music/"+getSettings()->getValue("musiclist")+".list"));
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
jumpSound=loadWAV((getDataPath()+"sfx/jump.wav").c_str());
hitSound=loadWAV((getDataPath()+"sfx/hit.wav").c_str());
saveSound=loadWAV((getDataPath()+"sfx/checkpoint.wav").c_str());
swapSound=loadWAV((getDataPath()+"sfx/swap.wav").c_str());
toggleSound=loadWAV((getDataPath()+"sfx/toggle.wav").c_str());
errorSound=loadWAV((getDataPath()+"sfx/error.wav").c_str());
collectSound=loadWAV((getDataPath()+"sfx/collect.wav").c_str());
achievementSound=loadWAV((getDataPath()+"sfx/achievement.ogg").c_str());
levelPackManager.destroy();
//Now sum up all the levelpacks.
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getDataPath()+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"custom/levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"custom/levelpacks/"+*i);
}
//Now we add a special levelpack that will contain the levels not in a levelpack.
LevelPack* levelsPack=new LevelPack;
levelsPack->levelpackName="Levels";
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}
//List the custom levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
customLevelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
customLevelsPack->setLocked(customLevelsPack->getLevelCount()-1);
}
//Add them to the manager.
levelPackManager.addLevelPack(levelsPack);
levelPackManager.addLevelPack(customLevelsPack);
//Load statistics
statsMgr.loadPicture();
statsMgr.registerAchievements();
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
- statsMgr.updateCompletedLevelsAndAchievements();
+ statsMgr.reloadCompletedLevelsAndAchievements();
//Load the theme, both menu and default.
if(!loadTheme(getSettings()->getValue("theme")))
return false;
//Nothing failed so return true.
return true;
}
bool loadSettings(){
settings=new Settings(getUserPath(USER_CONFIG)+"meandmyshadow.cfg");
settings->parseFile();
//Now apply settings changed through command line arguments, if any.
map<string,string>::iterator it;
for(it=tmpSettings.begin();it!=tmpSettings.end();++it){
settings->setValue(it->first,it->second);
}
tmpSettings.clear();
//Always return true?
return true;
}
bool saveSettings(){
settings->save();
//Always return true?
return true;
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
void flipScreen(){
if(settings->getBoolValue("gl")){
#ifdef HARDWARE_ACCELERATION
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//Create a texture from the screen surface.
glBindTexture(GL_TEXTURE_2D,screenTexture);
//Set the texture's stretching properties
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,0,screen->format->BytesPerPixel,screen->w,screen->h,0,GL_BGRA,GL_UNSIGNED_BYTE,screen->pixels);
glBegin(GL_QUADS);
glTexCoord2i(0,0); glVertex3f(0,0,0);
glTexCoord2i(1,0); glVertex3f(SCREEN_WIDTH,0,0);
glTexCoord2i(1,1); glVertex3f(SCREEN_WIDTH,SCREEN_HEIGHT,0);
glTexCoord2i(0,1); glVertex3f(0,SCREEN_HEIGHT,0);
glEnd();
SDL_GL_SwapBuffers();
#else
//NOTE: Trying to flip the screen using gl while compiled without.
cerr<<"FATAL ERROR: Unable to draw to screen using OpenGL (compiled without)."<<endl;
#endif
}else{
SDL_Flip(screen);
}
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Get rid of the currentstate.
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Destroy the imageManager.
imageManager.destroy();
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
Mix_FreeChunk(jumpSound);
Mix_FreeChunk(hitSound);
Mix_FreeChunk(saveSound);
Mix_FreeChunk(swapSound);
Mix_FreeChunk(toggleSound);
Mix_FreeChunk(errorSound);
Mix_FreeChunk(collectSound);
Mix_FreeChunk(achievementSound);
//Destroy the levelPackManager.
levelPackManager.destroy();
levels=NULL;
//Close all joysticks.
inputMgr.closeAllJoysticks();
//Close the fonts and quit SDL_ttf.
TTF_CloseFont(fontTitle);
TTF_CloseFont(fontGUI);
TTF_CloseFont(fontGUISmall);
TTF_CloseFont(fontText);
TTF_Quit();
//Remove the temp surface.
SDL_FreeSurface(tempSurface);
//Stop audio.and quit
Mix_CloseAudio();
Mix_Quit();
//And finally quit SDL.
SDL_Quit();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
currentState=new Game();
break;
case STATE_MENU:
currentState=new Menu();
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect();
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect();
break;
case STATE_LEVEL_EDITOR:
currentState=new LevelEditor();
break;
case STATE_OPTIONS:
currentState=new Options();
break;
case STATE_ADDONS:
currentState=new Addons();
break;
case STATE_CREDITS:
currentState=new Credits();
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen();
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
//Fade out.
int fade=255;
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
while(fade>0){
fade-=17;
if(fade<0)
fade=0;
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA, fade);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
flipScreen();
SDL_Delay(25);
}
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
void setCamera(const SDL_Rect* r,int count){
//SetCamera only works in the Level editor and mouse is inside window.
if(stateID==STATE_LEVEL_EDITOR&&(SDL_GetAppState()&SDL_APPMOUSEFOCUS)){
//Get the mouse coordinates.
int x,y;
SDL_GetMouseState(&x,&y);
//Make sure we avoid the toolbar.
SDL_Rect mouse={x,y,0,0};
//SDL_Rect toolbar={(SCREEN_WIDTH-460)/2,SCREEN_HEIGHT-50,460,50};
for(int i=0;i<count;i++){
if(checkCollision(mouse,r[i]))
return;
}
//Check if the mouse is near the left edge of the screen.
//Else check if the mouse is near the right edge.
if(x<50){
//We're near the left edge so move the camera.
camera.x-=5;
}else if(x>SCREEN_WIDTH-50){
//We're near the right edge so move the camera.
camera.x+=5;
}
//Check if the mouse is near the top edge of the screen.
//Else check if the mouse is near the bottom edge.
if(y<50){
//We're near the top edge so move the camera.
camera.y-=5;
}else if(y>SCREEN_HEIGHT-50){
//We're near the bottom edge so move the camera.
camera.y+=5;
}
}
}
int parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-f" || argument=="-fullscreen" || argument=="--fullscreen"){
tmpSettings["fullscreen"]="1";
}else if(argument=="-w" || argument=="-windowed" || argument=="--windowed"){
tmpSettings["fullscreen"]="0";
}else if(argument=="-mv" || argument=="-music" || argument=="--music"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set the music volume.
tmpSettings["music"]=argv[i];
}else if(argument=="-sv" || argument=="-sound" || argument=="--sound"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set sound volume.
tmpSettings["sound"]=argv[i];
}else if(argument=="-set" || argument=="--set"){
//We need a second and a third argument so we increase i.
i+=2;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//And set the setting.
tmpSettings[argv[i-1]]=argv[i];
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("%s\n",version.c_str());
return 0;
}else if(argument=="-h" || argument=="-help" || argument=="--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return -1;
}else{
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return -1;
}
}
//If everything went well we can return true.
return 1;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(string prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//The GUI objects.
GUIObject* obj;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
GUIObject* root=new GUIObject((SCREEN_WIDTH-600)/2,200,600,200,GUIObjectFrame,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//Pointer to the string.
char* lps=(char*)prompt.c_str();
//Pointer to a character.
char* lp=NULL;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Add a GUIObjectLabel with the sentence.
root->addChild(new GUIObject(0,y,root->width,25,GUIObjectLabel,lps,0,true,true,GUIGravityCenter));
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
root->top=(SCREEN_HEIGHT-y)/2;
root->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]=_("OK");value[0]=MsgBoxOK;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]=_("Abort");value[0]=MsgBoxAbort;
button[1]=_("Retry");value[1]=MsgBoxRetry;
button[2]=_("Ignore");value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
button[2]=_("Cancel");value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]=_("Retry");value[0]=MsgBoxRetry;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]=_("OK");value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Reduce y so that the buttons fit inside the frame.
y-=40;
double places[3]={0.0};
if(count==1){
places[0]=0.5;
}else if(count==2){
places[0]=0.4;
places[1]=0.6;
}else if(count==3){
places[0]=0.3;
places[1]=0.5;
places[2]=0.7;
}
//Loop to add the buttons.
for(int i=0;i<count;i++){
obj=new GUIObject(root->width*places[i],y,-1,36,GUIObjectButton,button[i].c_str(),value[i],true,true,GUIGravityCenter);
obj->eventCallback=&objHandler;
root->addChild(obj);
}
}
//Now we dim the screen and keep the GUI rendering/updating.
GUIOverlay* overlay=new GUIOverlay(root);
overlay->enterLoop(true);
//And return the result.
return (msgBoxResult)objHandler.ret;
}
struct fileDialogHandler:public GUIEventCallback{
public:
//The ret(urn) value, true=ok and false=cancel
bool ret;
//Boolean if it's a save dialog.
bool isSave;
//Boolean if the file should be verified.
bool verifyFile;
//Boolean if files should be listed instead of directories.
bool files;
//Pointer to the textfield containing the filename.
GUIObject* txtName;
//Pointer to the listbox containing the different files.
GUIListBox* lstFile;
//The extension the files listed should have.
const char* extension;
//The current filename.
string fileName;
//The current search path.
string path;
//Vector containing the search paths.
vector<string> searchPath;
public:
//Constructor.
fileDialogHandler(bool isSave=false,bool verifyFile=false, bool files=true):ret(false),
isSave(isSave),verifyFile(verifyFile),
files(files),txtName(NULL),lstFile(NULL),extension(NULL){}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check for the ok event.
if(name=="cmdOK"){
//Get the entered fileName from the text field.
std::string s=txtName->caption;
//If it doesn't contain a slash we need to add the path to the fileName.
if(s.find_first_of("/")==string::npos)
s=path+s;
//If the string empty we return.
if(s.empty() || s.find_first_of("*?")!=string::npos)
return;
//We only need to check for extensions if it isn't a folder dialog.
if(files){
//If there isn't right extension add it.
size_t found=s.find_first_of(".");
if(found!=string::npos)
s.replace(s.begin()+found+1,s.end(),extension);
else if (s.substr(found+1)!=extension)
s.append(string(".")+extension);
}
//Check if we should save or load the file.
//
if(isSave){
//Open the file with read permission to check if it already exists.
FILE* f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it exists.
if(f){
//Close the file.
fclose(f);
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//Prompt the user with a Yes or No question.
/// TRANSLATORS: Filename is coming before this text
if(msgBox(tfm::format(_("%s already exists.\nDo you want to overwrite it?"),s),MsgBoxYesNo,_("Overwrite Prompt"))!=MsgBoxYes){
//He answered no, so we return.
return;
}
}
//Check if we should verify the file.
//Verifying only applies to files not to directories.
if(verifyFile && files){
//Open the file with write permission.
f=fopen(processFileName(s).c_str(),"wb");
//Check if their aren't problems.
if(f){
//Close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//The file can't be opened so tell the user.
msgBox(tfm::format(_("Can't open file %s."),s),MsgBoxOKOnly,_("Error"));
return;
}
}
}else if(verifyFile && files){
//We need to verify a file for opening.
FILE *f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it didn't fail.
if(f){
//Succes, so close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//Unable to open file so tell the user.
msgBox(tfm::format(_("Can't open file %s."),s),MsgBoxOKOnly,_("Error"));
return;
}
}
//If we haven't returned then it's fine.
//Set the fileName to the chosen file.
fileName=s;
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Set return to true.
ret=true;
}else if(name=="cmdCancel"){
//Cancel means we can kill the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="lstFile"){
//Get a pointer to the listbox.
GUIListBox* obj1=lstFile;
//Make sure the option exist and change textfield to it.
if(obj1!=NULL && txtName!=NULL && obj1->value>=0 && obj1->value<(int)obj1->item.size()){
txtName->caption=obj1->item[obj1->value];
}
}else if(name=="lstSearchIn"){
//Get the searchpath listbox.
GUISingleLineListBox *obj1=dynamic_cast<GUISingleLineListBox*>(obj);
//Check if the entry exists.
if(obj1!=NULL && lstFile!=NULL && obj1->value>=0 && obj1->value<(int)searchPath.size()){
//Temp string.
string s;
//Get the new search path.
path=searchPath[obj1->value];
//Make sure it isn't empty.
if(!path.empty()){
//Process the filename.
s=processFileName(path);
}else{
//It's empty so we give the userpath.
s=getUserPath();
}
//Fill the list with files or directories.
if(files) {
lstFile->item=enumAllFiles(s,extension);
}else
lstFile->item=enumAllDirs(s);
//Remove any selection from the list.
lstFile->value=-1;
}
}
}
};
bool fileDialog(string& fileName,const char* title,const char* extension,const char* path,bool isSave,bool verifyFile,bool files){
//Pointer to GUIObject to make the GUI with.
GUIObject* obj;
//Create the fileDialogHandler, used for event handling.
fileDialogHandler objHandler(isSave,verifyFile,files);
//Vector containing the pathNames.
vector<string> pathNames;
//Set the extension of the objHandler.
objHandler.extension=extension;
//We now need to splits the given path into multiple path names.
if(path && path[0]){
//The string isn't empty.
//Pointer to the paths string.
char* lp=(char*)path;
//Pointer to the first newline.
char* lps=strchr(lp,'\n');
//Pointer used for checking if their's another newline.
//It will indicate if it's the last set or not.
char* lpe;
//Check for a newline.
if(lps){
//We have newline(s) so loop forever.
//We can only break out of the loop when the string ends.
for(;;){
//Add the first searchpath.
//This is the beginning of the string (lp) to the first newline. (lps)
objHandler.searchPath.push_back(string(lp,lps-lp));
//We should have another newline so search for it.
lpe=strchr(lps+1,'\n');
if(lpe){
//We found it so we add that to the pathname.
pathNames.push_back(string(lps+1,lpe-lps-1));
//And start over again by setting lp to the start of a new set of searchPath/pathName.
lp=lpe+1;
}else{
//There is no newline anymore, meaning the last entry, the rest of the string must be the pathName.
pathNames.push_back(string(lps+1));
//And break out of the loop.
break;
}
//We haven't broken out so search for a newline.
lps=strchr(lp,'\n');
//If there isn't a newline break.
if(!lps)
break;
}
}else{
//There is no newline thus the whole string is the searchPath.
objHandler.searchPath.push_back(path);
}
}else{
//Empty so put an empty string as searchPath.
objHandler.searchPath.push_back(string());
}
//It's time to create the GUI.
//If there are more than one pathNames we need to add a GUISingleLineListBox.
int base_y=pathNames.empty()?20:60;
//Create the frame.
GUIObject* root=new GUIObject(100,100-base_y/2,600,400+base_y,GUIObjectFrame,title?title:(isSave?_("Save File"):_("Load File")));
//Create the search path list box if needed.
if(!pathNames.empty()){
root->addChild(new GUIObject(8,40,184,36,GUIObjectLabel,_("Search In")));
GUISingleLineListBox* obj1=new GUISingleLineListBox(160,40,432,36);
obj1->item=pathNames;
obj1->value=0;
obj1->name="lstSearchIn";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
}
//Add the FileName label and textfield.
root->addChild(new GUIObject(8,20+base_y,184,36,GUIObjectLabel,_("File Name")));
{
//Fill the textbox with the given fileName.
string s=fileName;
if(!isSave){
//But only if it isn't empty.
if(s.empty() && extension && extension[0])
s=string("*.")+string(extension);
}
//Create the textbox and add it to the GUI.
objHandler.txtName=new GUIObject(160,20+base_y,432,36,GUIObjectTextBox,s.c_str());
root->addChild(objHandler.txtName);
}
//Now we add the ListBox containing the files or directories.
{
GUIListBox* obj1=new GUIListBox(8,60+base_y,584,292);
//Get the searchPath.
string s=objHandler.searchPath[0];
//Make sure it isn't empty.
if(!s.empty()){
objHandler.path=s;
s=processFileName(s);
}else{
s=getUserPath();
}
//Check if we should list files or directories.
if(files){
//Fill the list with files.
obj1->item=enumAllFiles(s,extension);
}else{
//Fill the list with directories.
obj1->item=enumAllDirs(s);
}
obj1->name="lstFile";
obj1->eventCallback=&objHandler;
root->addChild(obj1);
objHandler.lstFile=obj1;
}
//Now create the OK and Cancel buttons.
obj=new GUIObject(200,360+base_y,192,36,GUIObjectButton,_("OK"));
obj->name="cmdOK";
obj->eventCallback=&objHandler;
root->addChild(obj);
obj=new GUIObject(400,360+base_y,192,36,GUIObjectButton,_("Cancel"));
obj->name="cmdCancel";
obj->eventCallback=&objHandler;
root->addChild(obj);
//Create the gui overlay.
GUIOverlay* overlay=new GUIOverlay(root);
overlay->enterLoop();
//Now determine what the return value is (and if there is one).
if(objHandler.ret)
fileName=objHandler.fileName;
return objHandler.ret;
}
diff --git a/src/Game.cpp b/src/Game.cpp
index 3b14a83..284778a 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1394 +1,1460 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GameState.h"
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Objects.h"
#include "Game.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "InputManager.h"
+#include "StatisticsManager.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <locale>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
const char* Game::blockName[TYPE_MAX]={"Block","PlayerStart","ShadowStart",
"Exit","ShadowBlock","Spikes",
"Checkpoint","Swap","Fragile",
"MovingBlock","MovingShadowBlock","MovingSpikes",
"Teleporter","Button","Switch",
"ConveyorBelt","ShadowConveyorBelt","NotificationBlock", "Collectable"
};
map<string,int> Game::blockNameMap;
string Game::recordFile;
Game::Game(bool loadLevel):isReset(false)
,currentLevelNode(NULL)
,customTheme(NULL)
,background(NULL)
,won(false)
,interlevel(false)
,gameTipIndex(0)
,time(0),timeSaved(0)
,recordings(0),recordingsSaved(0)
,shadowCam(false)
,player(this),shadow(this),objLastCheckPoint(NULL){
saveStateNextTime=false;
loadStateNextTime=false;
//Reserve the memory for the GameObject tips.
memset(bmTips,0,sizeof(bmTips));
action=loadImage(getDataPath()+"gfx/actions.png");
medals=loadImage(getDataPath()+"gfx/medals.png");
//Get the collectable image from the theme.
//NOTE: Isn't there a better way to retrieve the image?
ThemeBlockInstance appearance;
objThemes.getBlock(TYPE_COLLECTABLE)->createInstance(&appearance);
collectable=appearance.currentState->parent->themeObjects[0]->picture.picture;
//Hide the cursor if not in the leveleditor.
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
//Check if we should load record file.
if(!recordFile.empty()){
loadRecord(recordFile.c_str());
recordFile.clear();
return;
}
//If we should load the level then load it.
if(loadLevel){
this->loadLevel(levels->getLevelpackPath()+levels->getLevelFile());
levels->saveLevelProgress();
}
}
Game::~Game(){
//Simply call our destroy method.
destroy();
//Before we leave make sure the cursor is visible.
SDL_ShowCursor(SDL_ENABLE);
}
void Game::destroy(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
//Done now clear the levelObjects vector.
levelObjects.clear();
//Clear the name and the editor data.
levelName.clear();
levelFile.clear();
editorData.clear();
//Loop through the tips.
for(int i=0;i<TYPE_MAX;i++){
//If it exist free the SDL_Surface.
if(bmTips[i])
SDL_FreeSurface(bmTips[i]);
}
memset(bmTips,0,sizeof(bmTips));
//Remove everything from the themeManager.
background=NULL;
if(customTheme)
objThemes.removeTheme();
customTheme=NULL;
//delete current level (if any)
if(currentLevelNode){
delete currentLevelNode;
currentLevelNode=NULL;
}
//Reset the time.
time=timeSaved=0;
recordings=recordingsSaved=0;
}
void Game::loadLevelFromNode(TreeStorageNode* obj,const string& fileName){
//Make sure there's nothing left from any previous levels.
destroy();
//set current level to loaded one.
currentLevelNode=obj;
//Temp var used for block locations.
SDL_Rect box;
//Set the level dimensions to the default, it will probably be changed by the editorData,
//but 800x600 is a fallback.
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
currentCollectables=0;
totalCollectables=0;
currentCollectablesSaved=0;
//Load the additional data.
for(map<string,vector<string> >::iterator i=obj->attributes.begin();i!=obj->attributes.end();++i){
if(i->first=="size"){
//We found the size attribute.
if(i->second.size()>=2){
//Set the dimensions of the level.
LEVEL_WIDTH=atoi(i->second[0].c_str());
LEVEL_HEIGHT=atoi(i->second[1].c_str());
}
}else if(i->second.size()>0){
//Any other data will be put into the editorData.
editorData[i->first]=i->second[0];
}
}
//Get the theme.
{
//If a theme is configured then load it.
string theme=processFileName(getSettings()->getValue("theme"));
//Check if level themes are enabled.
if(getSettings()->getBoolValue("leveltheme")) {
string &s=editorData["theme"];
if(!s.empty()){
customTheme=objThemes.appendThemeFromFile(processFileName(theme)+"/theme.mnmstheme");
}
}
//Set the Appearance of the player and the shadow.
objThemes.getCharacter(false)->createInstance(&player.appearance);
objThemes.getCharacter(true)->createInstance(&shadow.appearance);
}
for(unsigned int i=0;i<obj->subNodes.size();i++){
TreeStorageNode* obj1=obj->subNodes[i];
if(obj1==NULL) continue;
if(obj1->name=="tile" && obj1->value.size()>=3){
int objectType=blockNameMap[obj1->value[0]];
box.x=atoi(obj1->value[1].c_str());
box.y=atoi(obj1->value[2].c_str());
map<string,string> obj;
for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
if(i->second.size()>0) obj[i->first]=i->second[0];
}
//If the type is collectable, increase the number of totalCollectables
if (objectType==TYPE_COLLECTABLE)
totalCollectables++;
levelObjects.push_back( new Block ( box.x, box.y, objectType, this) );
levelObjects.back()->setEditorData(obj);
}
}
//Close exits if there are any collectables
if (totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
}
//Set the levelName to the name of the current level.
levelName=editorData["name"];
levelFile=fileName;
//Some extra stuff only needed when not in the levelEditor.
if(stateID!=STATE_LEVEL_EDITOR){
//We create a text with the text "Level <levelno> <levelName>".
//It will be shown in the left bottom corner of the screen.
string s;
if (levels->getLevelCount()>1){
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_C(levels->getDictionaryManager(),editorData["name"]));
}
SDL_Color fg={0,0,0,0};
bmTips[0]=TTF_RenderUTF8_Blended(fontText,s.c_str(),fg);
}
//Get the background
background=objThemes.getBackground(false);
if(background)
background->resetAnimation(true);
}
void Game::loadLevel(string fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode *obj=new TreeStorageNode();
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),obj,true)){
cout<<"Can't load level file "<<s<<endl;
delete obj;
return;
}
}
//Now call another function.
loadLevelFromNode(obj,fileName);
}
void Game::saveRecord(const char* fileName){
//check if current level is NULL (which should be impossible)
if(currentLevelNode==NULL) return;
TreeStorageNode obj;
POASerializer objSerializer;
//put current level to the node.
currentLevelNode->name="map";
obj.subNodes.push_back(currentLevelNode);
//serialize the game record using RLE compression.
#define PUSH_BACK \
if(j>0){ \
if(j>1){ \
sprintf(c,"%d*%d",last,j); \
}else{ \
sprintf(c,"%d",last); \
} \
v.push_back(c); \
}
vector<string> &v=obj.attributes["record"];
vector<int> *record=player.getRecord();
char c[64];
int i,j=0,last;
for(i=0;i<(int)record->size();i++){
int currentKey=(*record)[i];
if(j==0 || currentKey!=last){
PUSH_BACK;
last=currentKey;
j=1;
}else{
j++;
}
}
PUSH_BACK;
#undef PUSH_BACK
#ifdef RECORD_FILE_DEBUG
//add record file debug data.
{
obj.attributes["recordKeyPressLog"].push_back(player.keyPressLog());
vector<SDL_Rect> &playerPosition=player.playerPosition();
string s;
char c[32];
sprintf(c,"%d\n",int(playerPosition.size()));
s=c;
for(unsigned int i=0;i<playerPosition.size();i++){
SDL_Rect& r=playerPosition[i];
sprintf(c,"%d %d\n",r.x,r.y);
s+=c;
}
obj.attributes["recordPlayerPosition"].push_back(s);
}
#endif
//save it
objSerializer.saveNodeToFile(fileName,&obj,true,true);
//remove current level from node to prevent delete it.
obj.subNodes.clear();
}
void Game::loadRecord(const char* fileName){
//Create a TreeStorageNode that will hold the loaded data.
TreeStorageNode obj;
{
POASerializer objSerializer;
string s=fileName;
//Parse the file.
if(!objSerializer.loadNodeFromFile(s.c_str(),&obj,true)){
cout<<"Can't load record file "<<s<<endl;
return;
}
}
//find the node named 'map'.
bool loaded=false;
for(unsigned int i=0;i<obj.subNodes.size();i++){
if(obj.subNodes[i]->name=="map"){
//load the level. (fileName=???)
loadLevelFromNode(obj.subNodes[i],"???");
//remove this node to prevent delete it.
obj.subNodes[i]=NULL;
//over
loaded=true;
break;
}
}
if(!loaded){
cout<<"ERROR: Can't find subnode named 'map' from record file"<<endl;
return;
}
//load the record.
{
vector<int> *record=player.getRecord();
record->clear();
vector<string> &v=obj.attributes["record"];
for(unsigned int i=0;i<v.size();i++){
string &s=v[i];
string::size_type pos=s.find_first_of('*');
if(pos==string::npos){
//1 item only.
int i=atoi(s.c_str());
record->push_back(i);
}else{
//contains many items.
int i=atoi(s.substr(0,pos).c_str());
int j=atoi(s.substr(pos+1).c_str());
for(;j>0;j--){
record->push_back(i);
}
}
}
}
#ifdef RECORD_FILE_DEBUG
//load the debug data
{
vector<string> &v=obj.attributes["recordPlayerPosition"];
vector<SDL_Rect> &playerPosition=player.playerPosition();
playerPosition.clear();
if(!v.empty()){
if(!v[0].empty()){
stringstream st(v[0]);
int m;
st>>m;
for(int i=0;i<m;i++){
SDL_Rect r;
st>>r.x>>r.y;
r.w=0;
r.h=0;
playerPosition.push_back(r);
}
}
}
}
#endif
//play the record.
//TODO: tell the level manager don't save the level progress.
player.playRecord();
shadow.playRecord(); //???
}
/////////////EVENT///////////////
void Game::handleEvents(){
//First of all let the player handle input.
player.handleInput(&shadow);
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
//We need to quit so enter STATE_EXIT.
setNextState(STATE_EXIT);
}
//Check for the escape key.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
//Escape means we go one level up, to the level select state.
setNextState(STATE_LEVEL_SELECT);
//Save the progress.
levels->saveLevelProgress();
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}
//Check if 'r' is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_RESTART)){
//Only set isReset true if this isn't a replay.
if(!(player.isPlayFromRecord() && !interlevel))
isReset=true;
}
//Check for the next level buttons when in the interlevel popup.
if(inputMgr.isKeyDownEvent(INPUTMGR_SPACE) || (event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_RETURN || event.key.keysym.sym==SDLK_RCTRL))){
if(interlevel){
//The interlevel popup is shown so we need to delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now goto the next level.
gotoNextLevel();
}
}
//Check if tab is pressed.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
shadowCam=!shadowCam;
}
}
/////////////////LOGIC///////////////////
void Game::logic(){
//Check if we should save/load state.
if(saveStateNextTime){
saveState();
}else if(loadStateNextTime){
loadState();
}
saveStateNextTime=false;
loadStateNextTime=false;
//Add one tick to the time.
time++;
//Let the player store his move, if recording.
player.shadowSetState();
//Let the player give his recording to the shadow, if configured.
player.shadowGiveState(&shadow);
//Let the player jump.
player.jump();
//Let him move.
player.move(levelObjects);
//And let the camera follow him.
if(!shadowCam){
player.setMyCamera();
}else{
shadow.setMyCamera();
}
//Now let the shadow decide his move, if he's playing a recording.
shadow.moveLogic();
//Let the shadow jump.
shadow.jump();
//Let the shadow move.
shadow.move(levelObjects);
//Some levelObjects can move so update them.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->move();
}
//Process any event in the queue.
for(unsigned int idx=0;idx<eventQueue.size();idx++){
//Get the event from the queue.
typeGameObjectEvent &e=eventQueue[idx];
//Check if the it has an id attached to it.
if(e.flags|1){
//Loop through the levelObjects and give them the event if they have the right id.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL && obj->id==e.id){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}else{
//Loop through the levelObjects and give them the event.
for(unsigned int i=0;i<levelObjects.size();i++){
if(e.objectType<0 || levelObjects[i]->type==e.objectType){
levelObjects[i]->onEvent(e.eventType);
}
}
}
}
//Done processing the events so clear the queue.
eventQueue.clear();
//Check collision and stuff for the shadow and player.
player.otherCheck(&shadow);
//Check if we won.
if(won){
//Check if it's playing from record
if(player.isPlayFromRecord() && !interlevel){
recordingEnded();
}else{
//the string to store auto-save record path.
string bestTimeFilePath,bestRecordingFilePath;
//and if we can't get test path.
bool filePathError=false;
+ //Get current level
+ LevelPack::Level *level=levels->getLevel();
+
+ //Now check if we should update statistics
+ {
+ //Get previous and current medal
+ int oldMedal=level->won?1:0,newMedal=1;
+
+ int bestTime=level->time;
+ int targetTime=level->targetTime;
+ int bestRecordings=level->recordings;
+ int targetRecordings=level->targetRecordings;
+
+ if(oldMedal){
+ if(targetTime<0){
+ oldMedal=3;
+ }else{
+ if(targetTime<0 || bestTime<=targetTime)
+ oldMedal++;
+ if(targetRecordings<0 || bestRecordings<=targetRecordings)
+ oldMedal++;
+ }
+ }else{
+ bestTime=time;
+ bestRecordings=recordings;
+ }
+
+ if(bestTime==-1 || bestTime>time) bestTime=time;
+ if(bestRecordings==-1 || bestRecordings>recordings) bestRecordings=recordings;
+
+ if(targetTime<0){
+ newMedal=3;
+ }else{
+ if(targetTime<0 || bestTime<=targetTime)
+ newMedal++;
+ if(targetRecordings<0 || bestRecordings<=targetRecordings)
+ newMedal++;
+ }
+
+ //Check if we need to update statistics
+ if(newMedal>oldMedal){
+ switch(oldMedal){
+ case 0:
+ statsMgr.completedLevels++;
+ break;
+ case 2:
+ statsMgr.silverLevels--;
+ break;
+ }
+
+ switch(newMedal){
+ case 2:
+ statsMgr.silverLevels++;
+ break;
+ case 3:
+ statsMgr.goldLevels++;
+ break;
+ }
+ }
+ }
+
//Set the current level won.
- levels->getLevel()->won=true;
- if(levels->getLevel()->time==-1 || levels->getLevel()->time>time){
- levels->getLevel()->time=time;
+ level->won=true;
+ if(level->time==-1 || level->time>time){
+ level->time=time;
//save the best-time game record.
if(bestTimeFilePath.empty()){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestTimeFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestTimeFilePath.c_str());
}
}
- if(levels->getLevel()->recordings==-1 || levels->getLevel()->recordings>recordings){
- levels->getLevel()->recordings=recordings;
+ if(level->recordings==-1 || level->recordings>recordings){
+ level->recordings=recordings;
//save the best-recordings game record.
if(bestRecordingFilePath.empty() && !filePathError){
getCurrentLevelAutoSaveRecordPath(bestTimeFilePath,bestRecordingFilePath,true);
}
if(bestRecordingFilePath.empty()){
cout<<"ERROR: Couldn't get auto-save record file path"<<endl;
filePathError=true;
}else{
saveRecord(bestRecordingFilePath.c_str());
}
}
//Set the next level unlocked if it exists.
if(levels->getCurrentLevel()+1<levels->getLevelCount()){
levels->setLocked(levels->getCurrentLevel()+1);
}
//And save the progress.
levels->saveLevelProgress();
//Now go to the interlevel screen.
replayPlay();
+ //Update achievements
+ if(levels->levelpackName=="tutorial") statsMgr.updateTutorialAchievements();
+ statsMgr.updateLevelAchievements();
+
//NOTE: We set isReset false to prevent the user from getting a best time of 0.00s and 0 recordings.
}
}
won=false;
//Check if we should reset.
if(isReset)
reset(false);
isReset=false;
}
/////////////////RENDER//////////////////
void Game::render(){
//First of all render the background.
{
//Get a pointer to the background.
ThemeBackground* bg=background;
//Check if the background is null, but there are themes.
if(bg==NULL && objThemes.themeCount()>0){
//Get the background from the first theme in the stack.
bg=objThemes[0]->getBackground(false);
}
//Check if the background isn't null.
if(bg){
//It isn't so draw it.
bg->draw(screen);
//And if it's the loaded background then also update the animation.
//FIXME: Updating the animation in the render method?
if(bg==background)
bg->updateAnimation();
}else{
//There's no background so fill the screen with white.
SDL_Rect r={0,0,SCREEN_WIDTH,SCREEN_HEIGHT};
SDL_FillRect(screen,&r,-1);
}
}
//Now we draw the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
levelObjects[o]->show();
}
//Followed by the player and the shadow.
//NOTE: We draw the shadow second because he needs to be behind the player.
shadow.show();
player.show();
//Show the levelName if it isn't the level editor.
if(stateID!=STATE_LEVEL_EDITOR && bmTips[0]!=NULL && !interlevel){
drawGUIBox(-2,SCREEN_HEIGHT-bmTips[0]->h-4,bmTips[0]->w+8,bmTips[0]->h+6,screen,0xDDDDDDDD);
applySurface(2,SCREEN_HEIGHT-bmTips[0]->h,bmTips[0],screen,NULL);
}
//Check if there's a tooltip.
//NOTE: gameTipIndex 0 is used for the levelName, 1 for shadow death, 2 for restart text, 3 for restart+checkpoint.
if(gameTipIndex>3 && gameTipIndex<TYPE_MAX){
//Check if there's a tooltip for the type.
if(bmTips[gameTipIndex]==NULL){
//There isn't thus make it.
string s;
string keyCode=_(inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_ACTION,false)));
transform(keyCode.begin(),keyCode.end(),keyCode.begin(),::toupper);
switch(gameTipIndex){
case TYPE_CHECKPOINT:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to save the game."),keyCode);
break;
case TYPE_SWAP:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to swap the position of player and shadow."),keyCode);
break;
case TYPE_SWITCH:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to activate the switch."),keyCode);
break;
case TYPE_PORTAL:
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with current action key
s=tfm::format(_("Press %s key to teleport."),keyCode);
break;
}
//If we have a string then it's a supported GameObject type.
if(!s.empty()){
SDL_Color fg={0,0,0,0};
bmTips[gameTipIndex]=TTF_RenderUTF8_Blended(fontText,s.c_str(),fg);
}
}
//We already have a gameTip for this type so draw it.
if(bmTips[gameTipIndex]!=NULL){
drawGUIBox(-2,-2,bmTips[gameTipIndex]->w+8,bmTips[gameTipIndex]->h+6,screen,0xDDDDDDDD);
applySurface(2,2,bmTips[gameTipIndex],screen,NULL);
}
}
//Set the gameTip to 0.
gameTipIndex=0;
//Pointer to the sdl surface that will contain a message, if any.
SDL_Surface* bm=NULL;
//Check if the player is dead, meaning we draw a message.
if(player.dead){
//Get user configured restart key
string keyCodeRestart=inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_RESTART,false));
transform(keyCodeRestart.begin(),keyCodeRestart.end(),keyCodeRestart.begin(),::toupper);
//The player is dead, check if there's a state that can be loaded.
if(player.canLoadState()){
//Now check if the tip is already made, if not make it.
if(bmTips[3]==NULL){
//Get user defined key for loading checkpoint
string keyCodeLoad=inputMgr.getKeyCodeName(inputMgr.getKeyCode(INPUTMGR_LOAD,false));
transform(keyCodeLoad.begin(),keyCodeLoad.end(),keyCodeLoad.begin(),::toupper);
//Draw string
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[3]=TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - first %s means currently configured key to restart game
/// - Second %s means configured key to load from last save
tfm::format(_("Press %s to restart current level or press %s to load the game."),
keyCodeRestart,keyCodeLoad).c_str(),
fg);
}
bm=bmTips[3];
}else{
//Now check if the tip is already made, if not make it.
if(bmTips[2]==NULL){
SDL_Color fg={0,0,0,0};
bmTips[2]=TTF_RenderUTF8_Blended(fontText,
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with currently configured key to restart game
tfm::format(_("Press %s to restart current level."),keyCodeRestart).c_str(),
fg);
}
bm=bmTips[2];
}
}
//Check if the shadow has died (and there's no other notification).
//NOTE: We use the shadow's jumptime as countdown, this variable isn't used when the shadow is dead.
if(shadow.dead && bm==NULL && shadow.jumpTime>0){
//Now check if the tip is already made, if not make it.
if(bmTips[1]==NULL){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
bmTips[1]=TTF_RenderUTF8_Blended(fontText,
_("Your shadow has died."),
fg);
}
bm=bmTips[1];
//NOTE: Logic in the render loop, we substract the shadow's jumptime by one.
shadow.jumpTime--;
//return view to player and keep it there
shadowCam=false;
}
//Draw the tip.
if(bm!=NULL){
int x=(SCREEN_WIDTH-bm->w)/2;
int y=32;
drawGUIBox(x-8,y-8,bm->w+16,bm->h+14,screen,0xDDDDDDDD);
applySurface(x,y,bm,screen,NULL);
}
//Show the number of collectables the user has collected if there are collectables in the level.
//We hide this when interlevel.
if(currentCollectables<=totalCollectables && totalCollectables!=0 && !interlevel && time>0){
//Temp stringstream just to addup all the text nicely
stringstream temp;
temp << currentCollectables << "/" << totalCollectables;
SDL_Color black={0,0,0,0};
SDL_Rect r;
SDL_Surface* bm=TTF_RenderText_Blended(fontText,temp.str().c_str(),black);
//Align the text properly
r.x=SCREEN_WIDTH-collectable->w-bm->w+22;
r.y=SCREEN_HEIGHT-bm->h;
//Draw background
drawGUIBox(SCREEN_WIDTH-bm->w-34,SCREEN_HEIGHT-bm->h-4,bm->w+34+2,bm->h+4+2,screen,0xDDDDDDDD);
//Draw the collectable icon
applySurface(SCREEN_WIDTH-collectable->w+12,SCREEN_HEIGHT-collectable->h+10,collectable,screen,NULL);
//Draw text
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
//show time and records used in level editor.
if(stateID==STATE_LEVEL_EDITOR && time>0){
SDL_Color fg={0,0,0,0},bg={255,255,255,0};
SDL_Surface *bm;
int y=SCREEN_HEIGHT;
bm=TTF_RenderUTF8_Shaded(fontText,tfm::format(_("%d recordings"),recordings).c_str(),fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
char c[32];
sprintf(c,"%-.2fs",time/40.0f);
bm=TTF_RenderUTF8_Shaded(fontText,c,fg,bg);
SDL_SetAlpha(bm,SDL_SRCALPHA,160);
y-=bm->h;
applySurface(0,y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
//Draw the current action in the upper right corner.
if(player.record){
applySurface(SCREEN_WIDTH-50,0,action,screen,NULL);
}else if(shadow.state!=0){
SDL_Rect r={50,0,50,50};
applySurface(SCREEN_WIDTH-50,0,action,screen,&r);
}
//if the game is play from record then draw something indicates it
if(player.isPlayFromRecord()){
//Dim the screen if interlevel is true.
if(interlevel){
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA,220);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
//Check if the GUI isn't null.
if(GUIObjectRoot){
//==Create first box==
//Create the title
SDL_Color black={0,0,0,0};
SDL_Rect r;
/// TRANSLATORS: This is caption for finished level
SDL_Surface* bm=TTF_RenderUTF8_Blended(fontGUI,_("You've finished:"),black);
//Recreate the level string.
string s;
if (levels->getLevelCount()>0){
/// TRANSLATORS: Please do not remove %s or %d from your translation:
/// - %d means the level number in a levelpack
/// - %s means the name of current level
s=tfm::format(_("Level %d %s"),levels->getCurrentLevel()+1,_C(levels->getDictionaryManager(),levelName));
}
SDL_Surface* bm2=TTF_RenderUTF8_Blended(fontText,s.c_str(),black);
//Now draw the first gui box so that it's bigger than longer text.
int width;
if(bm->w>bm2->w)
width=bm->w+32;
else
width=bm2->w+32;
drawGUIBox((SCREEN_WIDTH-width)/2,4,width,68,screen,0xDDDDDDA1);
// Now draw title.
r.x=(SCREEN_WIDTH-bm->w)/2;
r.y=8-GUI_FONT_RAISE;
SDL_BlitSurface(bm,NULL,screen,&r);
// And then level name.
r.x=(SCREEN_WIDTH-bm2->w)/2;
r.y=44;
SDL_BlitSurface(bm2,NULL,screen,&r);
//Free drawed texts
SDL_FreeSurface(bm);
SDL_FreeSurface(bm2);
//==Create second box==
//Now draw the second gui box.
drawGUIBox(GUIObjectRoot->left,GUIObjectRoot->top,GUIObjectRoot->width,GUIObjectRoot->height,screen,0xDDDDDDA1);
//Draw the medal.
int medal=GUIObjectRoot->value;
r.x=(medal-1)*30;
r.y=0;
r.w=30;
r.h=30;
applySurface(GUIObjectRoot->left+16,GUIObjectRoot->top+92,medals,screen,&r);
applySurface(GUIObjectRoot->left+medalX,GUIObjectRoot->top+92,medals,screen,&r);
}
}else if((time & 0x10)==0x10){
SDL_Rect r={50,0,50,50};
applySurface(0,0,action,screen,&r);
applySurface(0,SCREEN_HEIGHT-50,action,screen,&r);
applySurface(SCREEN_WIDTH-50,SCREEN_HEIGHT-50,action,screen,&r);
}
}else if(player.objNotificationBlock){
//If the player is in front of a notification block show the message.
//And it isn't a replay.
std::string &untranslated_message=(dynamic_cast<Block*>(player.objNotificationBlock))->message;
std::string message=_C(levels->getDictionaryManager(),untranslated_message);
std::vector<char> string_data(message.begin(), message.end());
string_data.push_back('\0');
int maxWidth = 0;
int y = 20;
vector<SDL_Surface*> lines;
//Now process the prompt.
{
//Pointer to the string.
char* lps=&string_data[0];
//Pointer to a character.
char* lp=NULL;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Integer used to center the sentence horizontally.
int x;
TTF_SizeText(fontText,lps,&x,NULL);
//Find out largest width
if(x>maxWidth)
maxWidth=x;
x=(SCREEN_WIDTH-x)/2;
//Color the text will be: black.
SDL_Color black={0,0,0,0};
lines.push_back(TTF_RenderUTF8_Blended(fontText,lps,black));
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
}
maxWidth+=SCREEN_WIDTH*0.15;
drawGUIBox((SCREEN_WIDTH-maxWidth)/2,SCREEN_HEIGHT-y-25,maxWidth,y+20,screen,0xDDDDDDA1);
while(!lines.empty()){
SDL_Surface* bm=lines[0];
if(bm!=NULL){
applySurface(100+((SCREEN_WIDTH-200-bm->w)/2),SCREEN_HEIGHT-y,bm,screen,NULL);
SDL_FreeSurface(bm);
}
y-=25;
lines.erase(lines.begin());
}
}
}
void Game::resize(){
//Check if the interlevel popup is shown.
if(interlevel && GUIObjectRoot){
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
}
void Game::replayPlay(){
//Reset the number of collectables
currentCollectables=currentCollectablesSaved=0;
//We show the interlevel popup so interlevel must be true.
interlevel=true;
//Make the cursor visible when the interlevel popup is up.
SDL_ShowCursor(SDL_ENABLE);
//Create the gui if it isn't already done.
if(!GUIObjectRoot){
GUIObjectRoot=new GUIObject(0,SCREEN_HEIGHT-140,570,135,GUIObjectNone);
//NOTE: We put the medal in the value of the GUIObjectRoot.
//The different values.
int bestTime=levels->getLevel()->time;
int targetTime=levels->getLevel()->targetTime;
int bestRecordings=levels->getLevel()->recordings;
int targetRecordings=levels->getLevel()->targetRecordings;
int medal=1;
if(targetTime<0){
medal=3;
}else{
if(targetTime<0 || bestTime<=targetTime)
medal++;
if(targetRecordings<0 || bestRecordings<=targetRecordings)
medal++;
}
//Add it to the GUIObjectRoot.
GUIObjectRoot->value=medal;
int maxWidth=0;
int x=20;
//Is there a target time for this level?
int timeY=0;
bool isTargetTime=true;
if(targetTime<=0){
isTargetTime=false;
timeY=12;
}
//Create the labels with the time and best time.
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
GUIObject* obj=new GUIObject(x,10+timeY,-1,36,GUIObjectLabel,tfm::format(_("Time: %-.2fs"),time/40.0f).c_str());
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
obj=new GUIObject(x,34+timeY,-1,36,GUIObjectLabel,tfm::format(_("Best time: %-.2fs"),bestTime/40.0f).c_str());
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %-.2f from your translation:
/// - %-.2f means time in seconds
/// - s is shortened form of a second. Try to keep it so.
if(isTargetTime){
obj=new GUIObject(x,58,-1,36,GUIObjectLabel,tfm::format(_("Target time: %-.2fs"),targetTime/40.0f).c_str());
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth+20;
//Is there target recordings for this level?
int recsY=0;
bool isTargetRecs=true;
if(targetRecordings<0){
isTargetRecs=false;
recsY=12;
}
//Now the ones for the recordings.
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUIObject(x,10+recsY,-1,36,GUIObjectLabel,tfm::format(_("Recordings: %d"),recordings).c_str());
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
obj=new GUIObject(x,34+recsY,-1,36,GUIObjectLabel,tfm::format(_("Best recordings: %d"),bestRecordings).c_str());
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
/// TRANSLATORS: Please do not remove %d from your translation:
/// - %d means the number of recordings user has made
if(isTargetRecs){
obj=new GUIObject(x,58,-1,36,GUIObjectLabel,tfm::format(_("Target recordings: %d"),targetRecordings).c_str());
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
if(obj->width>maxWidth)
maxWidth=obj->width;
}
x+=maxWidth;
//The medal that is earned.
/// TRANSLATORS: Please do not remove %s from your translation:
/// - %s will be replaced with name of a prize medal (gold, silver or bronze)
string s1=tfm::format(_("You earned the %s medal"),(medal>1)?(medal==3)?_("GOLD"):_("SILVER"):_("BRONZE"));
obj=new GUIObject(50,92,-1,36,GUIObjectLabel,s1.c_str(),0,true,true,GUIGravityCenter);
GUIObjectRoot->childControls.push_back(obj);
obj->render(0,0,false);
if(obj->left+obj->width>x){
x=obj->left+obj->width+30;
}else{
obj->left=20+(x-20-obj->width)/2;
}
medalX=x-24;
//Create the three buttons, Menu, Restart, Next.
/// TRANSLATORS: used as return to the level selector menu
GUIObject* b1=new GUIObject(x,10,-1,36,GUIObjectButton,_("Menu"),0,true,true,GUIGravityCenter);
b1->name="cmdMenu";
b1->eventCallback=this;
GUIObjectRoot->childControls.push_back(b1);
b1->render(0,0,true);
/// TRANSLATORS: used as restart level
GUIObject* b2=new GUIObject(x,50,-1,36,GUIObjectButton,_("Restart"),0,true,true,GUIGravityCenter);
b2->name="cmdRestart";
b2->eventCallback=this;
GUIObjectRoot->childControls.push_back(b2);
b2->render(0,0,true);
/// TRANSLATORS: used as next level
GUIObject* b3=new GUIObject(x,90,-1,36,GUIObjectButton,_("Next"),0,true,true,GUIGravityCenter);
b3->name="cmdNext";
b3->eventCallback=this;
GUIObjectRoot->childControls.push_back(b3);
b3->render(0,0,true);
maxWidth=b1->width;
if(b2->width>maxWidth)
maxWidth=b2->width;
if(b3->width>maxWidth)
maxWidth=b3->width;
b1->left=b2->left=b3->left=x+maxWidth/2;
x+=maxWidth;
GUIObjectRoot->width=x;
GUIObjectRoot->left=(SCREEN_WIDTH-GUIObjectRoot->width)/2;
}
//We only need to reset a few things so we don't call reset().
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(true);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(true);
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
}
//Make a copy of the playerButtons.
vector<int> recordCopy=player.recordButton;
player.reset(true);
shadow.reset(true);
player.recordButton=recordCopy;
//Now play the recording.
player.playRecord();
}
void Game::recordingEnded(){
//Check if it's a normal replay, if so just stop.
if(!interlevel){
//Show the cursor so that the user can press the ok button.
SDL_ShowCursor(SDL_ENABLE);
//Now show the message box.
msgBox(_("Game replay is done."),MsgBoxOKOnly,_("Game Replay"));
//Go to the level select menu.
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else{
//Instead of directly replaying we set won true to let the Game handle the replaying at the end of the update cycle.
won=true;
}
}
bool Game::canSaveState(){
return (player.canSaveState() && shadow.canSaveState());
}
bool Game::saveState(){
//Check if the player and shadow can save the current state.
if(player.canSaveState() && shadow.canSaveState()){
//Let the player and the shadow save their state.
player.saveState();
shadow.saveState();
//Save the stats.
timeSaved=time;
recordingsSaved=recordings;
//Save the current collectables
currentCollectablesSaved=currentCollectables;
//Save other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->saveState();
}
//Also save the background animation, if any.
if(background)
background->saveAnimation();
//Return true.
return true;
}
//We can't save the state so return false.
return false;
}
bool Game::loadState(){
//Check if there's a state that can be loaded.
if(player.canLoadState() && shadow.canLoadState()){
//Let the player and the shadow load their state.
player.loadState();
shadow.loadState();
//Load the stats.
time=timeSaved;
recordings=recordingsSaved;
//Load the current collactbles
currentCollectables=currentCollectablesSaved;
//Load other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->loadState();
}
//Also load the background animation, if any.
if(background)
background->loadAnimation();
//Return true.
return true;
}
//We can't load the state so return false.
return false;
}
void Game::reset(bool save){
//We need to reset the game so we also reset the player and the shadow.
player.reset(save);
shadow.reset(save);
saveStateNextTime=false;
loadStateNextTime=false;
//Reset the stats.
time=0;
recordings=0;
//Reset the number of collectables
currentCollectables=0;
if(save)
currentCollectablesSaved=0;
//There is no last checkpoint so set it to NULL.
if(save)
objLastCheckPoint=NULL;
//Reset other state, for example moving blocks.
for(unsigned int i=0;i<levelObjects.size();i++){
levelObjects[i]->reset(save);
}
//Also reset the background animation, if any.
if(background)
background->resetAnimation(save);
//Close exit(s) if there are any collectables
if (totalCollectables>0){
for(unsigned int i=0;i<levelObjects.size();i++){
if(levelObjects[i]->type==TYPE_EXIT){
Block *obj=dynamic_cast<Block*>(levelObjects[i]);
if(obj!=NULL){
levelObjects[i]->onEvent(GameObjectEvent_OnSwitchOff);
}
}
}
}
//Check if interlevel is true, if so we might need to delete the gui.
if(interlevel){
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Also set interlevel to false.
interlevel=false;
//Hide the cursor (if not the leveleditor).
if(stateID!=STATE_LEVEL_EDITOR)
SDL_ShowCursor(SDL_DISABLE);
}
void Game::broadcastObjectEvent(int eventType,int objectType,const char* id){
//Create a typeGameObjectEvent that can be put into the queue.
typeGameObjectEvent e;
//Set the event type.
e.eventType=eventType;
//Set the object type.
e.objectType=objectType;
//By default flags=0.
e.flags=0;
//Unless there's an id.
if(id){
//Set flags to 0x1 and set the id.
e.flags|=1;
e.id=id;
}
//Add the event to the queue.
eventQueue.push_back(e);
}
void Game::getCurrentLevelAutoSaveRecordPath(std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
levels->getLevelAutoSaveRecordPath(-1,bestTimeFilePath,bestRecordingFilePath,createPath);
}
void Game::gotoNextLevel(){
//Goto the next level.
levels->nextLevel();
//Check if the level exists.
if(levels->getCurrentLevel()<levels->getLevelCount()){
setNextState(STATE_GAME);
//Don't forget the music.
getMusicManager()->pickMusic();
}else{
if(!levels->congratulationText.empty()){
msgBox(_C(levels->getDictionaryManager(),levels->congratulationText),MsgBoxOKOnly,_("Congratulations"));
}else{
msgBox(_("You have finished the levelpack!"),MsgBoxOKOnly,_("Congratulations"));
}
//Now go back to the levelselect screen.
setNextState(STATE_LEVEL_SELECT);
//And set the music back to menu.
getMusicManager()->playMusic("menu");
}
}
void Game::GUIEventCallback_OnEvent(string name,GUIObject* obj,int eventType){
if(name=="cmdMenu"){
setNextState(STATE_LEVEL_SELECT);
//And change the music back to the menu music.
getMusicManager()->playMusic("menu");
}else if(name=="cmdRestart"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And reset the game.
//new: we don't need to clear the save game because
//in level replay the game won't be saved
//TODO: it doesn't work; better leave for next release
reset(/*false*/ true);
}else if(name=="cmdNext"){
//No matter what, clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And goto the next level.
gotoNextLevel();
}
}
diff --git a/src/StatisticsManager.cpp b/src/StatisticsManager.cpp
index 23a9cf9..b3d312d 100644
--- a/src/StatisticsManager.cpp
+++ b/src/StatisticsManager.cpp
@@ -1,486 +1,530 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StatisticsManager.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "Functions.h"
#include "LevelPackManager.h"
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
using namespace std;
StatisticsManager statsMgr;
static const int achievementDisplayTime=100;
static const int achievementIntervalTime=120;
//internal struct for achievement info
struct AchievementInfo{
//achievement id for save to statistics file
const char* id;
//achievement name for display
const char* name;
//achievement image. NULL for no image. will be loaded at getDataPath()+imageFile
const char* imageFile;
//SDL_Surface of achievement image.
SDL_Surface* imageSurface;
//image offset and size.
SDL_Rect r;
//achievement description. supports multi-line text
const char* description;
};
static AchievementInfo achievementList[]={
{"newbie",__("Newbie"),"themes/Cloudscape/player.png",NULL,{0,0,23,40},__("Congratulations, you completed one level!")},
{"experienced",__("Experienced player"),"themes/Cloudscape/player.png",NULL,{0,0,23,40},__("Completed 50 levels.")},
+ {"goodjob",__("Good job!"),"gfx/medals.png",NULL,{60,0,30,30},__("Get your first gold medal.")},
{"expert",__("Expert"),"gfx/medals.png",NULL,{60,0,30,30},__("Earned 50 gold medal.")},
{"tutorial",__("Graduate"),"gfx/medals.png",NULL,{60,0,30,30},__("Complete the tutorial level pack.")},
{"tutorialGold",__("Outstanding graduate"),"gfx/medals.png",NULL,{60,0,30,30},__("Complete the tutorial level pack with all levels gold medal.")},
//test only
{"hello","Hello, World!","themes/Cloudscape/player.png",NULL,{0,0,23,40},"Welcome to Me and My Shadow!\n123\n456\n\n789"},
{"123","123","themes/Cloudscape/shadow.png",NULL,{0,0,23,40},"Welcome to Me and My Shadow!\n123\n456\n\n789"},
//end of achievements
{NULL,NULL,NULL,NULL,{0,0,0,0},NULL}
};
static map<string,AchievementInfo*> avaliableAchievements;
//================================================================
StatisticsManager::StatisticsManager(){
bmDropShadow=NULL;
bmAchievement=NULL;
clear();
}
StatisticsManager::~StatisticsManager(){
if(bmAchievement){
SDL_FreeSurface(bmAchievement);
bmAchievement=NULL;
}
}
void StatisticsManager::clear(){
playerTravelingDistance=shadowTravelingDistance=0.0f;
playerJumps=shadowJumps
=playerDies=shadowDies
=completedLevels=silverLevels=goldLevels
=recordTimes=switchTimes=swapTimes
=playTime=levelEditTime
=createdLevels=0;
achievements.clear();
queuedAchievements.clear();
achievementTime=0;
currentAchievement=0;
if(bmAchievement){
SDL_FreeSurface(bmAchievement);
bmAchievement=NULL;
}
}
#define LOAD_STATS(var,func) { \
vector<string> &v=node.attributes[ #var ]; \
if(!v.empty() && !v[0].empty()) \
var=func(v[0].c_str()); \
}
void StatisticsManager::loadFile(const std::string& fileName){
clear();
ifstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
POASerializer serializer;
if(!serializer.readNode(file,&node,true)) return;
//load statistics
LOAD_STATS(playerTravelingDistance,atof);
LOAD_STATS(shadowTravelingDistance,atof);
LOAD_STATS(playerJumps,atoi);
LOAD_STATS(shadowJumps,atoi);
LOAD_STATS(playerDies,atoi);
LOAD_STATS(shadowDies,atoi);
LOAD_STATS(recordTimes,atoi);
LOAD_STATS(switchTimes,atoi);
LOAD_STATS(swapTimes,atoi);
LOAD_STATS(playTime,atoi);
LOAD_STATS(levelEditTime,atoi);
LOAD_STATS(createdLevels,atoi);
//load achievements.
{
vector<string> &v=node.attributes["achievements"];
for(unsigned int i=0;i<v.size();i++){
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(v[i]);
if(it!=avaliableAchievements.end()){
achievements[it->first]=it->second;
}
}
}
}
#define SAVE_STATS(var,pattern) { \
sprintf(s,pattern,var); \
node.attributes[ #var ].push_back(s); \
}
void StatisticsManager::saveFile(const std::string& fileName){
char s[64];
ofstream file(fileName.c_str());
if(!file) return;
TreeStorageNode node;
//save statistics
SAVE_STATS(playerTravelingDistance,"%.2f");
SAVE_STATS(shadowTravelingDistance,"%.2f");
SAVE_STATS(playerJumps,"%d");
SAVE_STATS(shadowJumps,"%d");
SAVE_STATS(playerDies,"%d");
SAVE_STATS(shadowDies,"%d");
SAVE_STATS(recordTimes,"%d");
SAVE_STATS(switchTimes,"%d");
SAVE_STATS(swapTimes,"%d");
SAVE_STATS(playTime,"%d");
SAVE_STATS(levelEditTime,"%d");
SAVE_STATS(createdLevels,"%d");
//save achievements.
{
vector<string>& v=node.attributes["achievements"];
for(map<string,AchievementInfo*>::iterator it=achievements.begin();it!=achievements.end();++it){
v.push_back(it->first);
}
}
POASerializer serializer;
serializer.writeNode(&node,file,true,true);
}
void StatisticsManager::loadPicture(){
//Load drop shadow picture
bmDropShadow=loadImage(getDataPath()+"gfx/dropshadow.png");
}
void StatisticsManager::registerAchievements(){
if(!avaliableAchievements.empty()) return;
for(int i=0;achievementList[i].id!=NULL;i++){
avaliableAchievements[achievementList[i].id]=&achievementList[i];
if(achievementList[i].imageFile!=NULL){
achievementList[i].imageSurface=loadImage(getDataPath()+achievementList[i].imageFile);
}
}
}
void StatisticsManager::render(){
//debug
if(achievementTime==0){
- if(SDL_GetKeyState(NULL)[SDLK_1]) newAchievement("Hello, World!",false);
+ if(SDL_GetKeyState(NULL)[SDLK_1]) newAchievement("hello",false);
if(SDL_GetKeyState(NULL)[SDLK_2]) newAchievement("123",false);
}
if(achievementTime==0 && bmAchievement==NULL && currentAchievement<(int)queuedAchievements.size()){
//create surface
bmAchievement=createAchievementSurface(queuedAchievements[currentAchievement++]);
//check if queue is empty
if(currentAchievement>=(int)queuedAchievements.size()){
queuedAchievements.clear();
currentAchievement=0;
}
//play a sound
if(getSettings()->getBoolValue("sound")){
Mix_PlayChannel(-1,achievementSound,0);
}
}
//check if we need to display achievements
if(bmAchievement){
achievementTime++;
if(achievementTime<=0){
return;
}else if(achievementTime<=5){
drawAchievement(achievementTime);
}else if(achievementTime<=achievementDisplayTime-5){
drawAchievement(5);
}else if(achievementTime<achievementDisplayTime){
drawAchievement(achievementDisplayTime-achievementTime);
}else if(achievementTime>=achievementIntervalTime){
if(bmAchievement){
SDL_FreeSurface(bmAchievement);
bmAchievement=NULL;
}
achievementTime=0;
}
}
}
void StatisticsManager::newAchievement(const std::string& id,bool save){
//check avaliable achievements
map<string,AchievementInfo*>::iterator it=avaliableAchievements.find(id);
if(it==avaliableAchievements.end()) return;
//check if already have this achievement
if(save){
map<string,AchievementInfo*>::iterator it2=achievements.find(id);
if(it2!=achievements.end()) return;
achievements[id]=it->second;
}
//add it to queue
queuedAchievements.push_back(it->second);
}
SDL_Surface* StatisticsManager::createAchievementSurface(AchievementInfo* info,SDL_Surface* surface,SDL_Rect* rect,bool showTip){
if(info==NULL || info->id==NULL) return NULL;
//prepare text
SDL_Surface *title0=NULL,*title1=NULL;
vector<SDL_Surface*> descSurfaces;
SDL_Color fg={0,0,0};
int fontHeight=TTF_FontLineSkip(fontText);
if(showTip) title0=TTF_RenderUTF8_Blended(fontText,_("New achievement:"),fg);
title1=TTF_RenderUTF8_Blended(fontGUISmall,_(info->name),fg);
if(info->description!=NULL){
string description=_(info->description);
string::size_type lps=0,lpe;
for(;;){
lpe=description.find('\n',lps);
if(lpe==string::npos){
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps)+' ').c_str(),fg));
break;
}else{
descSurfaces.push_back(TTF_RenderUTF8_Blended(fontText,(description.substr(lps,lpe-lps)+' ').c_str(),fg));
lps=lpe+1;
}
}
}
//calculate the size
int w=0,h=0,w1=8,h1=0;
if(title0!=NULL){
if(title0->w>w) w=title0->w;
h1+=title0->h;
}
if(title1!=NULL){
if(title1->w>w) w=title1->w;
h1+=title1->h;
}
if(info->imageSurface!=NULL){
w1+=info->r.w+8;
w+=info->r.w+8;
if(info->r.h>h1) h1=info->r.h;
}
h=h1+8;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
if(descSurfaces[i]->w>w) w=descSurfaces[i]->w;
}
}
h+=descSurfaces.size()*fontHeight;
w+=16;
h+=16;
//check if size is specified
int left=0,top=0;
if(rect!=NULL){
if(surface!=NULL){
left=rect->x;
top=rect->y;
}
if(rect->w>0) w=rect->w;
else rect->w=w;
rect->h=h;
}
//create surface if necessary
if(surface==NULL){
surface=SDL_CreateRGBSurface(SDL_HWSURFACE,w,h,
screen->format->BitsPerPixel,screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);
}
//draw background
drawGUIBox(left,top,w,h,surface,0xFFFFFFFFU);
//draw picture
if(info->imageSurface!=NULL){
SDL_Rect r={left+8,top+8+(h1-info->r.h)/2,0,0};
SDL_BlitSurface(info->imageSurface,&info->r,surface,&r);
}
//draw text
h=8;
if(title0!=NULL){
SDL_Rect r={left+w1,top+h,0,0};
SDL_BlitSurface(title0,NULL,surface,&r);
h+=title0->h;
}
if(title1!=NULL){
SDL_Rect r={left+w1,top+h,0,0};
SDL_BlitSurface(title1,NULL,surface,&r);
}
h=h1+16;
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_Rect r={left+8,top+h+i*fontHeight,0,0};
SDL_BlitSurface(descSurfaces[i],NULL,surface,&r);
}
}
//clean up
if(title0) SDL_FreeSurface(title0);
if(title1) SDL_FreeSurface(title1);
for(unsigned int i=0;i<descSurfaces.size();i++){
if(descSurfaces[i]!=NULL){
SDL_FreeSurface(descSurfaces[i]);
}
}
//over
return surface;
}
void StatisticsManager::drawAchievement(int alpha){
if(bmAchievement==NULL) return;
if(alpha<=0) return;
if(alpha>5) alpha=5;
SDL_Rect r={screen->w-32-bmAchievement->w,32,
bmAchievement->w,bmAchievement->h};
//draw the surface
SDL_SetAlpha(bmAchievement,SDL_SRCALPHA,alpha*40);
SDL_BlitSurface(bmAchievement,NULL,screen,&r);
//draw drop shadow - corner
{
int w1=r.w/2,w2=r.w-w1,h1=r.h/2,h2=r.h-h1;
if(w1>16) w1=16;
if(w2>16) w2=16;
if(h1>16) h1=16;
if(h2>16) h2=16;
int x=(5-alpha)*64;
//top-left
SDL_Rect r1={x,0,w1+16,h1+16},r2={r.x-16,r.y-16,0,0};
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
//top-right
r1.x=x+48-w2;r1.w=w2+16;r2.x=r.x+r.w-w2;
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
//bottom-right
r1.y=48-h2;r1.h=h2+16;r2.y=r.y+r.h-h2;
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
//bottom-left
r1.x=x;r1.w=w1+16;r2.x=r.x-16;
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
}
//draw drop shadow - border
int i=r.w-32;
while(i>0){
int ii=i>128?128:i;
//top
SDL_Rect r1={0,256-alpha*16,ii,16},r2={r.x+r.w-16-i,r.y-16,0,0};
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
//bottom
r1.x=128;r2.y=r.y+r.h;
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
i-=ii;
}
i=r.h-32;
while(i>0){
int ii=i>128?128:i;
//top
SDL_Rect r1={512-alpha*16,0,16,ii},r2={r.x-16,r.y+r.h-16-i,0,0};
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
//bottom
r1.y=128;r2.x=r.x+r.w;
SDL_BlitSurface(bmDropShadow,&r1,screen,&r2);
i-=ii;
}
}
-void StatisticsManager::updateCompletedLevelsAndAchievements(){
+void StatisticsManager::reloadCompletedLevelsAndAchievements(){
completedLevels=silverLevels=goldLevels=0;
LevelPackManager *lpm=getLevelPackManager();
vector<string> v=lpm->enumLevelPacks();
bool tutorial=false,tutorialGold=false;
for(unsigned int i=0;i<v.size();i++){
string& s=v[i];
LevelPack *levels=lpm->getLevelPack(s);
levels->loadProgress(getUserPath(USER_DATA)+"progress/"+s+".progress");
bool b=false;
if(s=="tutorial"){
b=tutorial=tutorialGold=true;
}
for(int n=0,m=levels->getLevelCount();n<m;n++){
LevelPack::Level *lv=levels->getLevel(n);
int medal=lv->won;
if(medal){
if(lv->targetTime<0 || lv->time<=lv->targetTime)
medal++;
if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
medal++;
completedLevels++;
if(medal==2) silverLevels++;
if(medal==3) goldLevels++;
if(medal!=3 && b) tutorialGold=false;
}else if(b){
tutorial=tutorialGold=false;
}
}
}
//upadte achievements
+ updateLevelAchievements();
+ updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialGold?2:0));
+}
+
+//Update level specified achievements.
+//Make sure the completed level count is correct.
+void StatisticsManager::updateLevelAchievements(){
if(completedLevels>=1) newAchievement("newbie");
- if(tutorial) newAchievement("tutorial");
- if(tutorialGold) newAchievement("tutorialGold");
+ if(goldLevels>=1) newAchievement("goodjob");
if(completedLevels>=50) newAchievement("experienced");
if(goldLevels>=50) newAchievement("expert");
}
+
+//Update tutorial specified achievements.
+//Make sure the level progress of tutorial is correct.
+void StatisticsManager::updateTutorialAchievements(){
+ //find tutorial level pack
+ LevelPackManager *lpm=getLevelPackManager();
+ LevelPack *levels=lpm->getLevelPack("tutorial");
+ if(levels==NULL) return;
+
+ bool tutorial=true,tutorialGold=true;
+
+ for(int n=0,m=levels->getLevelCount();n<m;n++){
+ LevelPack::Level *lv=levels->getLevel(n);
+ int medal=lv->won;
+ if(medal){
+ if(lv->targetTime<0 || lv->time<=lv->targetTime)
+ medal++;
+ if(lv->targetRecordings<0 || lv->recordings<=lv->targetRecordings)
+ medal++;
+
+ if(medal!=3) tutorialGold=false;
+ }else{
+ tutorial=tutorialGold=false;
+ break;
+ }
+ }
+
+ //upadte achievements
+ updateTutorialAchievementsInternal((tutorial?1:0)|(tutorialGold?2:0));
+}
+
+//internal function
+//flags: a bit-field value indicates which achievements we have.
+void StatisticsManager::updateTutorialAchievementsInternal(int flags){
+ if(flags&1) newAchievement("tutorial");
+ if(flags&2) newAchievement("tutorialGold");
+}
diff --git a/src/StatisticsManager.h b/src/StatisticsManager.h
index ddce635..333927d 100644
--- a/src/StatisticsManager.h
+++ b/src/StatisticsManager.h
@@ -1,112 +1,123 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STATISTICSMANAGER_H
#define STATISTICSMANAGER_H
#include <SDL/SDL.h>
#include <string>
#include <vector>
#include <map>
//internal struct for achievement info
struct AchievementInfo;
class StatisticsScreen;
class StatisticsManager{
friend class StatisticsScreen;
public:
//Player and shadow traveling distance (m), 1 block = 1 meter
float playerTravelingDistance,shadowTravelingDistance;
//Player and shadow jumps
int playerJumps,shadowJumps;
//Player and shadow dies
int playerDies,shadowDies;
//Completed levels. Note: this is dynamically calculated, and doesn't save to file. (TODO:)
int completedLevels,silverLevels,goldLevels;
//Record times
int recordTimes;
//number of switched pulled
int switchTimes;
//swap times
int swapTimes;
//play time (s)
int playTime;
//level edit time (s)
int levelEditTime;
//created levels
int createdLevels;
private:
//current achievement displayed time
int achievementTime;
//some picture
SDL_Surface *bmDropShadow;
//SDL_Surface for current achievement (excluding drop shadow)
SDL_Surface *bmAchievement;
//currently owned achievements
std::map<std::string,AchievementInfo*> achievements;
//queued achievements for display
std::vector<AchievementInfo*> queuedAchievements;
//currently displayed achievement
int currentAchievement;
public:
StatisticsManager();
~StatisticsManager();
//clear the statistics and achievements.
void clear();
//load needed picture
void loadPicture();
//register avaliable achievements
static void registerAchievements();
//load statistics file.
void loadFile(const std::string& fileName);
//save statistics file.
void saveFile(const std::string& fileName);
//add or display a new achievement.
//name: the achievement id. if can't find it in avaliable achievement, nothing happens.
//save: if true then save to currently owned achievements. if it already exists in
//currently owned achievements, nothing happens.
//if false then just added it to queue, including duplicated achievements.
void newAchievement(const std::string& id,bool save=true);
//if there are new achievements, draw it on the screen,
//otherwise do nothing.
void render();
//Call this function to update completed levels.
- //Completed level count is not realtime-updated, due to lazy programmers. :)
- void updateCompletedLevelsAndAchievements();
+ //Note: Level progress files are reloaded, so it's slow.
+ void reloadCompletedLevelsAndAchievements();
+
+ //Update level specified achievements.
+ //Make sure the completed level count is correct.
+ void updateLevelAchievements();
+
+ //Update tutorial specified achievements.
+ //Make sure the level progress of tutorial is correct.
+ void updateTutorialAchievements();
//create a SDL_Surface contains specified achievements or draw to existing surface.
//info: achievement info.
//surface: specifies SDL_Surface to draw on. if NULL then new surface will be created.
//rect [in, out, optional]: specifies position and optionally width to draw on. height will be returned.
//if NULL then will be drawn on top-left corner. if surface is NULL then rect->x and rect->y are ignored.
//showTip: shows "New achievement" tip
//return value: SDL_Surface contains specified achievements or NULL if any error occured.
SDL_Surface* createAchievementSurface(AchievementInfo* info,SDL_Surface* surface=NULL,SDL_Rect* rect=NULL,bool showTip=true);
private:
+ //internal function
+ //flags: a bit-field value indicates which achievements we have.
+ void updateTutorialAchievementsInternal(int flags);
//internal function. alpha should be 1-5, 5 means fully opaque (not really)
void drawAchievement(int alpha);
};
extern StatisticsManager statsMgr;
#endif
\ No newline at end of file
diff --git a/src/StatisticsScreen.cpp b/src/StatisticsScreen.cpp
index d686d01..c6527dd 100644
--- a/src/StatisticsScreen.cpp
+++ b/src/StatisticsScreen.cpp
@@ -1,395 +1,395 @@
/*
* Copyright (C) 2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Globals.h"
#include "Functions.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include <SDL/SDL_image.h>
using namespace std;
//GUI events are handled here.
//name: The name of the element that invoked the event.
//obj: Pointer to the object that invoked the event.
//eventType: Integer containing the type of event.
void StatisticsScreen::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Goto the main menu.
setNextState(STATE_MENU);
}
}
}
#define DRAW_PLAYER_STATISTICS(name,var,format) { \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(0,y,surface,stats,NULL); \
y+=(h1=surface->h); \
SDL_FreeSurface(surface); \
sprintf(s,format,statsMgr.player##var+statsMgr.shadow##var); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(400-surface->w,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
sprintf(s,format,statsMgr.player##var); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(520-surface->w,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
sprintf(s,format,statsMgr.shadow##var); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(640-surface->w,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
}
#define DRAW_MISC_STATISTICS_1(name1,var1,format1) { \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name1,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(0,y,surface,stats,NULL); \
x=surface->w+8; \
y+=(h1=surface->h); \
SDL_FreeSurface(surface); \
sprintf(s,format1,statsMgr.var1); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
}
//we are so lazy that we just use height of the first one, ignore second one
#define DRAW_MISC_STATISTICS_2(name1,var1,format1,name2,var2,format2) { \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name1,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(0,y,surface,stats,NULL); \
x=surface->w+8; \
y+=(h1=surface->h); \
SDL_FreeSurface(surface); \
sprintf(s,format1,statsMgr.var1); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
surface=TTF_RenderUTF8_Blended(fontGUISmall,name2,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(320,y-surface->h,surface,stats,NULL); \
x=surface->w+328; \
SDL_FreeSurface(surface); \
sprintf(s,format2,statsMgr.var2); \
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor); \
SDL_SetAlpha(surface,0,0xFF); \
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL); \
SDL_FreeSurface(surface); \
}
//Constructor.
StatisticsScreen::StatisticsScreen(){
//Load needed pictures.
//Note: we don't use ImageManager because we need to process these pictures.
SDL_Surface *bmPlayer=IMG_Load((getDataPath()+"themes/Cloudscape/player.png").c_str());
SDL_Surface *bmShadow=IMG_Load((getDataPath()+"themes/Cloudscape/shadow.png").c_str());
SDL_Surface *bmMedal=IMG_Load((getDataPath()+"gfx/medals.png").c_str());
//disable the alpha channel
SDL_SetAlpha(bmPlayer,0,0xFF);
SDL_SetAlpha(bmShadow,0,0xFF);
SDL_SetAlpha(bmMedal,0,0xFF);
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Achievements and Statistics"),themeTextColor);
//Render stats.
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
stats=SDL_CreateRGBSurface(SDL_SWSURFACE,640,400,32,rmask,gmask,bmask,amask);
char s[64];
SDL_Surface *surface;
SDL_Rect r;
int x,y=0,h1;
Uint32 clr=SDL_MapRGB(stats->format,themeTextColor.r,themeTextColor.g,themeTextColor.b);
//Player and shadow specific statistics
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Total"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(400-surface->w,40-surface->h,surface,stats,NULL);
SDL_FreeSurface(surface);
r.x=0;r.y=0;r.w=23;r.h=40;
applySurface(520-r.w,0,bmPlayer,stats,&r);
applySurface(640-r.w,0,bmShadow,stats,&r);
y+=40;
DRAW_PLAYER_STATISTICS(_("Traveling distance (m)"),TravelingDistance,"%0.2f");
DRAW_PLAYER_STATISTICS(_("Jump times"),Jumps,"%d");
DRAW_PLAYER_STATISTICS(_("Die times"),Dies,"%d");
//Game specific statistics
r.x=0;r.y=y;r.w=stats->w;r.h=2;
SDL_FillRect(stats,&r,clr);
y+=2;
- DRAW_MISC_STATISTICS_2(_("Record times:"),recordTimes,"%d",_("Switch pulled times:"),switchTimes,"%d");
+ DRAW_MISC_STATISTICS_2(_("Recordings:"),recordTimes,"%d",_("Switch pulled times:"),switchTimes,"%d");
DRAW_MISC_STATISTICS_1(_("Swap times:"),swapTimes,"%d");
//Level specific statistics
r.x=0;r.y=y;r.w=stats->w;r.h=2;
SDL_FillRect(stats,&r,clr);
y+=2;
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Completed levels:"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(0,y,surface,stats,NULL);
x=surface->w+8;
y+=(h1=surface->h);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.completedLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.completedLevels-statsMgr.goldLevels-statsMgr.silverLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(400-surface->w,y-(surface->h+h1)/2,surface,stats,NULL);
r.x=0;r.y=0;r.w=30;r.h=30;
applySurface(400-surface->w-30,y-(30+h1)/2,bmMedal,stats,&r);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.silverLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(520-surface->w,y-(surface->h+h1)/2,surface,stats,NULL);
r.x+=30;
applySurface(520-surface->w-30,y-(30+h1)/2,bmMedal,stats,&r);
SDL_FreeSurface(surface);
sprintf(s,"%d",statsMgr.goldLevels);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(640-surface->w,y-(surface->h+h1)/2,surface,stats,NULL);
r.x+=30;
applySurface(640-surface->w-30,y-(30+h1)/2,bmMedal,stats,&r);
SDL_FreeSurface(surface);
//Other statistics
r.x=0;r.y=y;r.w=stats->w;r.h=2;
SDL_FillRect(stats,&r,clr);
y+=2;
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Playing time:"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(0,y,surface,stats,NULL);
x=surface->w+8;
y+=(h1=surface->h);
SDL_FreeSurface(surface);
sprintf(s,"%02d:%02d:%02d",statsMgr.playTime/3600,(statsMgr.playTime/60)%60,statsMgr.playTime%60);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
surface=TTF_RenderUTF8_Blended(fontGUISmall,_("Level editing time:"),themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(320,y-surface->h,surface,stats,NULL);
x=surface->w+328;
SDL_FreeSurface(surface);
sprintf(s,"%02d:%02d:%02d",statsMgr.levelEditTime/3600,(statsMgr.levelEditTime/60)%60,statsMgr.levelEditTime%60);
surface=TTF_RenderUTF8_Blended(fontText,s,themeTextColor);
SDL_SetAlpha(surface,0,0xFF);
applySurface(x,y-(surface->h+h1)/2,surface,stats,NULL);
SDL_FreeSurface(surface);
DRAW_MISC_STATISTICS_1(_("Created levels:"),createdLevels,"%d");
//Free loaded surface
SDL_FreeSurface(bmPlayer);
SDL_FreeSurface(bmShadow);
SDL_FreeSurface(bmMedal);
//Create GUI
achievements=NULL;
createGUI();
}
//Destructor.
StatisticsScreen::~StatisticsScreen(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free images
SDL_FreeSurface(title);
SDL_FreeSurface(stats);
SDL_FreeSurface(achievements);
}
//Method that will create the GUI for the options menu.
void StatisticsScreen::createGUI(){
//Draw achievements
if(achievements) SDL_FreeSurface(achievements);
vector<SDL_Surface*> surfaces;
int w=SCREEN_WIDTH-128-16,h=0;
for(map<string,AchievementInfo*>::iterator it=statsMgr.achievements.begin();
it!=statsMgr.achievements.end();it++)
{
SDL_Rect r={0,0,w,0};
SDL_Surface *surface=statsMgr.createAchievementSurface(it->second,NULL,&r,false);
if(surface!=NULL){
surfaces.push_back(surface);
h+=r.h;
}
}
if(surfaces.empty()){
- achievements=TTF_RenderUTF8_Blended(fontText,_("You don't have any achievements."),themeTextColor);
+ achievements=TTF_RenderUTF8_Blended(fontText,_("You don't have any achievements now. Play the game and try to earn some!"),themeTextColor);
}else{
achievements=SDL_CreateRGBSurface(SDL_HWSURFACE,w,h,
screen->format->BitsPerPixel,screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);
h=0;
for(unsigned int i=0;i<surfaces.size();i++){
SDL_Rect r={0,h,0,0};
SDL_BlitSurface(surfaces[i],NULL,achievements,&r);
h+=surfaces[i]->h;
SDL_FreeSurface(surfaces[i]);
}
}
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
//Create back button.
GUIObject* obj=new GUIObject(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Back"),0,true,true,GUIGravityCenter);
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//Create list box
listBox=new GUISingleLineListBox((SCREEN_WIDTH-500)/2,104,500,32);
listBox->item.push_back(_("Achievements"));
listBox->item.push_back(_("Statistics"));
listBox->value=0;
GUIObjectRoot->addChild(listBox);
//Create vertical scrollbar.
h-=SCREEN_HEIGHT-144-80;
if(h<0) h=0;
scrollbarV=new GUIScrollBar(SCREEN_WIDTH-64-16,144,16,SCREEN_HEIGHT-144-80,1,0,0,h,16,SCREEN_HEIGHT-144-80,true,false);
GUIObjectRoot->addChild(scrollbarV);
}
//In this method all the key and mouse events should be handled.
//Note: The GUIEvents won't be handled here.
void StatisticsScreen::handleEvents(){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
//Check for scrolling down and up with mouse scroll wheel.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN && scrollbarV->visible){
if(scrollbarV->value<scrollbarV->maxValue)
scrollbarV->value+=scrollbarV->smallChange*4;
if(scrollbarV->value>scrollbarV->maxValue)
scrollbarV->value=scrollbarV->maxValue;
return;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP && scrollbarV->visible){
if(scrollbarV->value>0)
scrollbarV->value-=scrollbarV->smallChange*4;
if(scrollbarV->value<0)
scrollbarV->value=0;
return;
}
}
//All the logic that needs to be done should go in this method.
void StatisticsScreen::logic(){
}
//This method handles all the rendering.
void StatisticsScreen::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Draw title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
switch(listBox->value){
case 0:
//achievements
{
scrollbarV->visible=(scrollbarV->maxValue>0);
SDL_Rect r1={0,scrollbarV->value,achievements->w,SCREEN_HEIGHT-144-80};
SDL_Rect r2={64,144,0,0};
SDL_BlitSurface(achievements,&r1,screen,&r2);
}
break;
case 1:
//statistics
scrollbarV->visible=false;
applySurface((SCREEN_WIDTH-stats->w)/2,144,stats,screen,NULL);
break;
}
}
//Method that will be called when the screen size has been changed in runtime.
void StatisticsScreen::resize(){
//Recreate the gui to fit the new resolution.
createGUI();
}
diff --git a/src/TitleMenu.cpp b/src/TitleMenu.cpp
index c86b097..b5722ba 100644
--- a/src/TitleMenu.cpp
+++ b/src/TitleMenu.cpp
@@ -1,907 +1,910 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "GameState.h"
#include "Globals.h"
#include "TitleMenu.h"
#include "GUIListBox.h"
#include "InputManager.h"
+#include "StatisticsManager.h"
#include <iostream>
#include <algorithm>
#include <sstream>
#include "libs/tinygettext/tinygettext.hpp"
using namespace std;
/////////////////////////MAIN_MENU//////////////////////////////////
//Integer containing the highlighted/selected menu option.
static int highlight=0;
Menu::Menu(){
highlight=0;
animation=0;
//Load the title image.
title=loadImage(getDataPath()+"gfx/menu/title.png");
//Now render the five entries.
//SDL_Color black={0,0,0};
entries[0]=TTF_RenderUTF8_Blended(fontTitle,_("Play"),themeTextColor);
entries[1]=TTF_RenderUTF8_Blended(fontTitle,_("Options"),themeTextColor);
entries[2]=TTF_RenderUTF8_Blended(fontTitle,_("Map Editor"),themeTextColor);
entries[3]=TTF_RenderUTF8_Blended(fontTitle,_("Addons"),themeTextColor);
entries[4]=TTF_RenderUTF8_Blended(fontTitle,_("Quit"),themeTextColor);
entries[5]=TTF_RenderUTF8_Blended(fontTitle,">",themeTextColor);
entries[6]=TTF_RenderUTF8_Blended(fontTitle,"<",themeTextColor);
//Load the credits icon.
statisticsIcon=loadImage(getDataPath()+"gfx/menu/statistics.png");
creditsIcon=loadImage(getDataPath()+"gfx/menu/credits.png");
}
Menu::~Menu(){
//We need to free the five text surfaceses.
for(unsigned int i=0;i<7;i++)
SDL_FreeSurface(entries[i]);
}
void Menu::handleEvents(){
//Get the x and y location of the mouse.
int x,y;
SDL_GetMouseState(&x,&y);
//Calculate which option is highlighted using the location of the mouse.
//Only if mouse is 'doing something'
if(event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONDOWN){
highlight=0;
if(x>=200&&x<SCREEN_WIDTH-200&&y>=(SCREEN_HEIGHT-250)/2&&y<(SCREEN_HEIGHT-200)/2+320){
highlight=(y-((SCREEN_HEIGHT-200)/2-64))/64;
if(highlight>5) highlight=0;
}
//Also check the icons.
if(y>=SCREEN_HEIGHT-56&&y<SCREEN_HEIGHT-8){
if(x>=SCREEN_WIDTH-8){
//do nothing
}else if(x>=SCREEN_WIDTH-56){
highlight=7;
}else if(x>=SCREEN_WIDTH-104){
highlight=6;
}
}
}
//Down/Up -arrows move highlight
if(inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
highlight++;
if(highlight>7)
highlight=0;
}
if(inputMgr.isKeyDownEvent(INPUTMGR_UP)){
highlight--;
if(highlight<1)
highlight=7;
}
//Check if there's a press event.
if((event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) ||
(inputMgr.isKeyUpEvent(INPUTMGR_SELECT))){
//We have one so check which selected/highlighted option needs to be done.
switch(highlight){
case 1:
//Enter the levelSelect state.
setNextState(STATE_LEVEL_SELECT);
break;
case 2:
//Enter the options state.
setNextState(STATE_OPTIONS);
break;
case 3:
//Enter the levelEditor, but first set the level to a default leveledit map.
levelName="";
setNextState(STATE_LEVEL_EDIT_SELECT);
break;
case 4:
//Check if internet is enabled.
if(!getSettings()->getBoolValue("internet")){
msgBox(_("Enable internet in order to install addons."),MsgBoxOKOnly,_("Internet disabled"));
break;
}
//Enter the addons state.
setNextState(STATE_ADDONS);
break;
case 5:
//We quit, so we enter the exit state.
setNextState(STATE_EXIT);
break;
case 6:
//Show the statistics screen.
setNextState(STATE_STATISTICS);
break;
case 7:
//Show the credits screen.
setNextState(STATE_CREDITS);
break;
}
}
//We also need to quit the menu when escape is pressed.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_EXIT);
}
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
}
//Nothing to do here
void Menu::logic(){
animation++;
if(animation>10)
animation=-10;
}
void Menu::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Draw the title.
applySurface((SCREEN_WIDTH-title->w)/2,40,title,screen,NULL);
//Draw the menu entries.
for(unsigned int i=0;i<5;i++){
applySurface((SCREEN_WIDTH-entries[i]->w)/2,(SCREEN_HEIGHT-200)/2+64*i+(64-entries[i]->h)/2,entries[i],screen,NULL);
}
//Check if an option is selected/highlighted.
if(highlight>0 && highlight<=5){
//Draw the '>' sign, which is entry 5.
int x=(SCREEN_WIDTH-entries[highlight-1]->w)/2-(25-abs(animation)/2)-entries[5]->w;
int y=(SCREEN_HEIGHT-200)/2-64+64*highlight+(64-entries[5]->h)/2;
applySurface(x,y,entries[5],screen,NULL);
//Draw the '<' sign, which is entry 6.
x=(SCREEN_WIDTH-entries[highlight-1]->w)/2+entries[highlight-1]->w+(25-abs(animation)/2);
y=(SCREEN_HEIGHT-200)/2-64+64*highlight+(64-entries[6]->h)/2;
applySurface(x,y,entries[6],screen,NULL);
}
//Check if an icon is selected/highlighted and draw tooltip
if(highlight==6){
SDL_Color fg={0,0,0};
SDL_Surface *surface=TTF_RenderUTF8_Blended(fontText,_("Achievements and Statistics"),fg);
drawGUIBox(SCREEN_WIDTH-64-surface->w-2,SCREEN_HEIGHT-56-surface->h-2,surface->w+4,surface->h+4,screen,0xFFFFFF00|230);
applySurface(SCREEN_WIDTH-64-surface->w,SCREEN_HEIGHT-56-surface->h,surface,screen,NULL);
SDL_FreeSurface(surface);
}
if(highlight==7){
SDL_Color fg={0,0,0};
SDL_Surface *surface=TTF_RenderUTF8_Blended(fontText,_("Credits"),fg);
drawGUIBox(SCREEN_WIDTH-16-surface->w-2,SCREEN_HEIGHT-56-surface->h-2,surface->w+4,surface->h+4,screen,0xFFFFFF00|230);
applySurface(SCREEN_WIDTH-16-surface->w,SCREEN_HEIGHT-56-surface->h,surface,screen,NULL);
SDL_FreeSurface(surface);
}
//Draw the credits icon.
applySurface(SCREEN_WIDTH-96,SCREEN_HEIGHT-48,statisticsIcon,screen,NULL);
applySurface(SCREEN_WIDTH-48,SCREEN_HEIGHT-48,creditsIcon,screen,NULL);
}
void Menu::resize(){}
/////////////////////////OPTIONS_MENU//////////////////////////////////
//Some varables for the options.
static bool fullscreen,leveltheme,internet;
static string themeName,languageName;
static int lastLang,lastRes;
static bool useProxy;
static string internetProxy;
static bool restartFlag;
static _res currentRes;
static vector<_res> resolutionList;
Options::Options(){
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Settings"),themeTextColor);
lastJumpSound=0;
//Set some default settings.
fullscreen=getSettings()->getBoolValue("fullscreen");
languageName=getSettings()->getValue("lang");
themeName=processFileName(getSettings()->getValue("theme"));
leveltheme=getSettings()->getBoolValue("leveltheme");
internet=getSettings()->getBoolValue("internet");
internetProxy=getSettings()->getValue("internet-proxy");
useProxy=!internetProxy.empty();
//Set the restartFlag false.
restartFlag=false;
//Now create the gui.
createGUI();
}
Options::~Options(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free the title image.
SDL_FreeSurface(title);
}
void Options::createGUI(){
//Variables for positioning
int x = (SCREEN_WIDTH-540)/2;
int liftY=40; //TODO: This is variable for laziness of maths...
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
//Now we create GUIObjects for every option.
GUIObject* obj=new GUIObject(x,150-liftY,240,36,GUIObjectLabel,_("Music"));
GUIObjectRoot->addChild(obj);
musicSlider=new GUISlider(x+220,150-liftY,256,36,atoi(getSettings()->getValue("music").c_str()),0,128,15);
musicSlider->name="sldMusic";
musicSlider->eventCallback=this;
GUIObjectRoot->addChild(musicSlider);
obj=new GUIObject(x,190-liftY,240,36,GUIObjectLabel,_("Sound"));
GUIObjectRoot->addChild(obj);
soundSlider=new GUISlider(x+220,190-liftY,256,36,atoi(getSettings()->getValue("sound").c_str()),0,128,15);
soundSlider->name="sldSound";
soundSlider->eventCallback=this;
GUIObjectRoot->addChild(soundSlider);
obj=new GUIObject(x,230-liftY,240,36,GUIObjectCheckBox,_("Fullscreen"),fullscreen?1:0);
obj->name="chkFullscreen";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
obj=new GUIObject(x,270-liftY,240,36,GUIObjectLabel,_("Resolution"));
obj->name="lstResolution";
GUIObjectRoot->addChild(obj);
//Create list with many different resolutions
resolutions = new GUISingleLineListBox(x+220,270-liftY,300,36);
resolutions->value=-1;
//Enumerate avaliable resolutions using SDL_ListModes()
//Note: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SDL_HWSURFACE);
if(modes==NULL || ((intptr_t)modes) == -1){
cout<<"Error: Can't enumerate avaliable screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const _res predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
for(unsigned int i=0;modes[i]!=NULL;i++){
//Check if the resolution is big enough
if(modes[i]->w>=800 && modes[i]->h>=600){
_res res={modes[i]->w, modes[i]->h};
resolutionList.push_back(res);
}
}
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Get current resolution from config file. Thus it can be user defined
currentRes.w=atoi(getSettings()->getValue("width").c_str());
currentRes.h=atoi(getSettings()->getValue("height").c_str());
for (int i=0; i<(int)resolutionList.size();i++){
//Create a string from width and height and then add it to list
ostringstream out;
out << resolutionList[i].w << "x" << resolutionList[i].h;
resolutions->item.push_back(out.str());
//Check if current resolution matches, select it
if (resolutionList[i].w==currentRes.w && resolutionList[i].h==currentRes.h){
resolutions->value=i;
}
}
//Add current resolution if it isn't already in the list
if(resolutions->value==-1){
ostringstream out;
out << currentRes.w << "x" << currentRes.h;
resolutions->item.push_back(out.str());
resolutions->value=resolutions->item.size()-1;
}
lastRes=resolutions->value;
GUIObjectRoot->addChild(resolutions);
obj=new GUIObject(x,310-liftY,240,36,GUIObjectLabel,_("Language"));
obj->name="lstResolution";
GUIObjectRoot->addChild(obj);
//Create GUI list with available languages
langs = new GUISingleLineListBox(x+220,310-liftY,300,36);
langs->name="lstLanguages";
/// TRANSLATORS: as detect user's language automatically
langs->item.push_back(_("Auto-Detect"));
langValues.push_back("");
langs->item.push_back("English");
langValues.push_back("en");
//Get a list of every available language
set<tinygettext::Language> languages = dictionaryManager->get_languages();
for (set<tinygettext::Language>::iterator s0 = languages.begin(); s0 != languages.end(); ++s0){
//If language in loop is the same in config file, then select it
if(getSettings()->getValue("lang")==s0->str()){
lastLang=distance(languages.begin(),s0)+2;
}
//Add language in loop to list and listbox
langs->item.push_back(s0->get_name());
langValues.push_back(s0->str());
}
//If Auto or English are selected
if(getSettings()->getValue("lang")==""){
lastLang=0;
}else if(getSettings()->getValue("lang")=="en"){
lastLang=1;
}
langs->value=lastLang;
GUIObjectRoot->addChild(langs);
obj=new GUIObject(x,350-liftY,240,36,GUIObjectLabel,_("Theme"));
obj->name="theme";
GUIObjectRoot->addChild(obj);
//Create the theme option gui element.
theme=new GUISingleLineListBox(x+220,350-liftY,300,36);
theme->name="lstTheme";
vector<string> v=enumAllDirs(getUserPath(USER_DATA)+"themes/");
for(vector<string>::iterator i = v.begin(); i != v.end(); ++i){
themeLocations[*i]=getUserPath(USER_DATA)+"themes/"+*i;
}
vector<string> v2=enumAllDirs(getDataPath()+"themes/");
for(vector<string>::iterator i = v2.begin(); i != v2.end(); ++i){
themeLocations[*i]=getDataPath()+"themes/"+*i;
}
v.insert(v.end(), v2.begin(), v2.end());
//Try to find the configured theme so we can display it.
int value=-1;
for(vector<string>::iterator i = v.begin(); i != v.end(); ++i){
if(themeLocations[*i]==themeName) {
value=i-v.begin();
}
}
theme->item=v;
if(value==-1)
value=theme->item.size()-1;
theme->value=value;
//NOTE: We call the event handling method to correctly set the themename.
GUIEventCallback_OnEvent("lstTheme",theme,GUIEventChange);
theme->eventCallback=this;
GUIObjectRoot->addChild(theme);
obj=new GUIObject(x,390-liftY,240,36,GUIObjectCheckBox,_("Level themes"),leveltheme?1:0);
obj->name="chkLeveltheme";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
obj=new GUIObject(x,430-liftY,240,36,GUIObjectCheckBox,_("Internet"),internet?1:0);
obj->name="chkInternet";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//new: proxy settings
obj=new GUIObject(x,470-liftY,240,36,GUIObjectLabel,_("Internet proxy"));
obj->name="chkProxy";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
obj=new GUIObject(x+220,470-liftY,300,36,GUIObjectTextBox,internetProxy.c_str());
obj->name="txtProxy";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//new: key settings
GUIObject* b1=new GUIObject(SCREEN_WIDTH*0.3,SCREEN_HEIGHT-120,-1,36,GUIObjectButton,_("Config Keys"),0,true,true,GUIGravityCenter);
b1->name="cmdKeys";
b1->eventCallback=this;
GUIObjectRoot->addChild(b1);
//Reset progress settings.
/// TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
GUIObject* b2=new GUIObject(SCREEN_WIDTH*0.7,SCREEN_HEIGHT-120,-1,36,GUIObjectButton,_("Clear Progress"),0,true,true,GUIGravityCenter);
b2->name="cmdReset";
b2->eventCallback=this;
GUIObjectRoot->addChild(b2);
b1->render(0,0,false);
b2->render(0,0,false);
if(b2->left-b2->gravityX < b1->left+b1->width-b1->gravityX){
b1->smallFont=true;
b1->width=-1;
b2->smallFont=true;
b2->width=-1;
}
b1=new GUIObject(SCREEN_WIDTH*0.3,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
b1->name="cmdBack";
b1->eventCallback=this;
GUIObjectRoot->addChild(b1);
b2=new GUIObject(SCREEN_WIDTH*0.7,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Save Changes"),0,true,true,GUIGravityCenter);
b2->name="cmdSave";
b2->eventCallback=this;
GUIObjectRoot->addChild(b2);
b1->render(0,0,false);
b2->render(0,0,false);
if(b2->left-b2->gravityX < b1->left+b1->width-b1->gravityX){
b1->smallFont=true;
b1->width=-1;
b2->smallFont=true;
b2->width=-1;
}
}
static string convertInt(int i){
stringstream ss;
ss << i;
return ss.str();
}
void Options::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//TODO: Reset the key changes.
//Reset the music volume.
getMusicManager()->setVolume(atoi(getSettings()->getValue("music").c_str()));
Mix_Volume(-1,atoi(getSettings()->getValue("sound").c_str()));
//And goto the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdSave"){
//Save is pressed thus save
char s[64];
sprintf(s,"%d",soundSlider->value);
getSettings()->setValue("sound",s);
sprintf(s,"%d",musicSlider->value);
getSettings()->setValue("music",s);
getMusicManager()->setEnabled(musicSlider->value>0);
Mix_Volume(-1,soundSlider->value);
getSettings()->setValue("fullscreen",fullscreen?"1":"0");
getSettings()->setValue("leveltheme",leveltheme?"1":"0");
getSettings()->setValue("internet",internet?"1":"0");
getSettings()->setValue("theme",themeName);
loadTheme(themeName);
if(!useProxy)
internetProxy.clear();
getSettings()->setValue("internet-proxy",internetProxy);
getSettings()->setValue("lang",langValues.at(langs->value));
//Is resolution from the list or is it user defined in config file
if(resolutions->value<(int)resolutionList.size()){
getSettings()->setValue("width",convertInt(resolutionList[resolutions->value].w));
getSettings()->setValue("height",convertInt(resolutionList[resolutions->value].h));
}else{
getSettings()->setValue("width",convertInt(currentRes.w));
getSettings()->setValue("height",convertInt(currentRes.h));
}
//Save the key configuration.
inputMgr.saveConfig();
//Save the settings.
saveSettings();
//Before we return check if some .
if(restartFlag || resolutions->value!=lastRes){
//The resolution changed so we need to recreate the screen.
if(!createScreen()){
//Screen creation failed so set to safe settings.
getSettings()->setValue("fullscreen","0");
getSettings()->setValue("width",convertInt(resolutionList[lastRes].w));
getSettings()->setValue("height",convertInt(resolutionList[lastRes].h));
if(!createScreen()){
//Everything fails so quit.
setNextState(STATE_EXIT);
return;
}
}
//The screen is created, now load the (menu) theme.
if(!loadTheme("")){
//Loading the theme failed so quit.
setNextState(STATE_EXIT);
return;
}
}
if(langs->value!=lastLang){
//We set the language.
language=langValues.at(langs->value);
dictionaryManager->set_language(tinygettext::Language::from_name(langValues.at(langs->value)));
getLevelPackManager()->updateLanguage();
//And reload the font.
if(!loadFonts()){
//Loading failed so quit.
setNextState(STATE_EXIT);
return;
}
}
//Now return to the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdKeys"){
inputMgr.showConfig();
}else if(name=="cmdReset"){
if(msgBox(_("Do you really want to reset level progress?"),MsgBoxYesNo,_("Warning"))==MsgBoxYes){
//We delete the progress folder.
#ifdef WIN32
removeDirectory((getUserPath()+"progress").c_str());
createDirectory((getUserPath()+"progress").c_str());
#else
removeDirectory((getUserPath(USER_DATA)+"/progress").c_str());
createDirectory((getUserPath(USER_DATA)+"/progress").c_str());
#endif
+ //Resets statistics.
+ statsMgr.reloadCompletedLevelsAndAchievements();
}
return;
}else if(name=="chkFullscreen"){
fullscreen=obj->value?true:false;
//Check if fullscreen changed.
if(fullscreen==getSettings()->getBoolValue("fullscreen")){
//We disable the restart message flag.
restartFlag=false;
}else{
//We set the restart message flag.
restartFlag=true;
}
}else if(name=="chkLeveltheme"){
leveltheme=obj->value?true:false;
}else if(name=="chkInternet"){
internet=obj->value?true:false;
}else if(name=="chkProxy"){
useProxy=obj->value?true:false;
}
}
if(name=="lstTheme"){
if(theme!=NULL && theme->value>=0 && theme->value<(int)theme->item.size()){
//Check if the theme is installed in the data path.
if(themeLocations[theme->item[theme->value]].find(getDataPath())!=string::npos){
themeName="%DATA%/themes/"+fileNameFromPath(themeLocations[theme->item[theme->value]]);
}else if(themeLocations[theme->item[theme->value]].find(getUserPath(USER_DATA))!=string::npos){
themeName="%USER%/themes/"+fileNameFromPath(themeLocations[theme->item[theme->value]]);
}else{
themeName=themeLocations[theme->item[theme->value]];
}
}
}else if(name=="txtProxy"){
internetProxy=obj->caption;
//Check if the internetProxy field is empty.
useProxy=!internetProxy.empty();
}else if(name=="sldMusic"){
getMusicManager()->setEnabled(musicSlider->value>0);
getMusicManager()->setVolume(musicSlider->value);
}else if(name=="sldSound"){
Mix_Volume(-1,soundSlider->value);
if(lastJumpSound==0){
Mix_PlayChannel(-1,jumpSound,0);
lastJumpSound=15;
}
}
}
void Options::handleEvents(){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
void Options::logic(){
//Increase the lastJumpSound variable if needed.
if(lastJumpSound!=0){
lastJumpSound--;
}
}
void Options::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Now render the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//NOTE: The rendering of the GUI is done in Main.
}
void Options::resize(){
//Recreate the gui to fit the new resolution.
createGUI();
}
/////////////////////////CREDITS_MENU//////////////////////////////////
Credits::Credits(){
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Credits"),themeTextColor);
//Vector that will hold every line of the credits.
vector<string> credits;
//Open the AUTHORS file and read every line.
{
ifstream fin((getDataPath()+"/../AUTHORS").c_str());
if(!fin.is_open()) {
cerr<<"ERROR: Unable to open the AUTHORS file."<<endl;
credits.push_back("ERROR: Unable to open the AUTHORS file.");
credits.push_back("");
}
//Loop the lines of the file.
string line;
while(getline(fin,line)){
credits.push_back(line);
}
}
//Enter a new line between the two files.
credits.push_back("");
//Open the Credits.text file and read every line.
{
ifstream fin((getDataPath()+"/Credits.txt").c_str());
if(!fin.is_open()) {
cerr<<"ERROR: Unable to open the Credits.txt file."<<endl;
credits.push_back("ERROR: Unable to open the Credits.txt file.");
credits.push_back("");
}
//Loop the lines of the file.
string line;
while(getline(fin,line)){
credits.push_back(line);
//NOTE: Some sections point to other credits files.
if(line=="music/") {
vector<string> musicCredits=getMusicManager()->createCredits();
credits.insert(credits.end(),musicCredits.begin(),musicCredits.end());
}
}
}
//Now determine the number of lines and calculate the height of the resulting credits surface.
int lines=credits.size();
int fontHeight=TTF_FontLineSkip(fontText);
int maxW=0;
//Find out the width of the longest line
for(int i=0;i<lines;i++){
if(credits[i][0]!='\0'){
int w;
TTF_SizeUTF8(fontText,credits[i].c_str(),&w,NULL);
if(w>maxW)
maxW=w;
}
}
//Make sure that the surface works both on little and big endian systems which have different 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
//Finally create the surface and draw every line of text there
creditsText=SDL_CreateRGBSurface(SDL_SWSURFACE,maxW,lines*fontHeight,32,rmask,gmask,bmask,amask);
for(int i=0;i<lines;i++){
if(credits[i][0]!='\0'){
SDL_Surface* lineSurf=TTF_RenderUTF8_Blended(fontText,credits[i].c_str(),themeTextColor);
SDL_SetAlpha(lineSurf,0,0xFF);
SDL_SetAlpha(creditsText,SDL_SRCALPHA,SDL_ALPHA_TRANSPARENT);
applySurface(0,fontHeight*i,lineSurf,creditsText,NULL);
SDL_FreeSurface(lineSurf);
}
}
//Create GUI
createGUI();
}
Credits::~Credits(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free images
SDL_FreeSurface(title);
SDL_FreeSurface(creditsText);
}
void Credits::createGUI(){
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
//Create back button.
GUIObject* obj=new GUIObject(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Back"),0,true,true,GUIGravityCenter);
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
//Create vertical scrollbar.
scrollbarV=new GUIScrollBar(SCREEN_WIDTH-64-16,128,16,SCREEN_HEIGHT-128-92,1,0,0,creditsText->h/8-(SCREEN_HEIGHT-128-92)/8);
GUIObjectRoot->addChild(scrollbarV);
//If text is too long, create horizontal scrollbar.
if(creditsText->w>SCREEN_WIDTH-128){
scrollbarH=new GUIScrollBar(64,SCREEN_HEIGHT-92,SCREEN_WIDTH-128-16,16,0,0,0,creditsText->w/8-(SCREEN_WIDTH-64-92)/8);
GUIObjectRoot->addChild(scrollbarH);
}else{
scrollbarH=NULL;
}
}
void Credits::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//Goto the main menu.
setNextState(STATE_MENU);
}
}
}
void Credits::handleEvents(){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
//Check for scrolling down and up with mouse scroll wheel.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN && scrollbarV){
if(scrollbarV->value<scrollbarV->maxValue)
scrollbarV->value+=scrollbarV->smallChange;
if(scrollbarV->value>scrollbarV->maxValue)
scrollbarV->value=scrollbarV->maxValue;
return;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP && scrollbarV){
if(scrollbarV->value>0)
scrollbarV->value-=scrollbarV->smallChange;
if(scrollbarV->value<0)
scrollbarV->value=0;
return;
}
}
void Credits::logic(){
}
void Credits::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
objThemes.getBackground(true)->updateAnimation();
//Now render the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//Clip and draw text accoring to scrollbars' values.
SDL_Rect r;
if(scrollbarH)
r.x = scrollbarH->value*8;
else
r.x = 0;
r.y = scrollbarV->value*8;
r.w = SCREEN_WIDTH-128-16;
r.h = SCREEN_HEIGHT-128-92;
applySurface(64,128,creditsText,screen,&r);
//NOTE: The rendering of the GUI is done in Main.
}
void Credits::resize(){
//Recreate the gui to fit the new resolution.
createGUI();
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, May 17, 4:27 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
64205
Default Alt Text
(188 KB)

Event Timeline