1 /*
2 *
3 * Copyright (c) 1994, 2002, 2003 Johannes Prix
4 * Copyright (c) 1994, 2002 Reinhard Prix
5 * Copyright (c) 2004-2010 Arthur Huillet
6 *
7 *
8 * This file is part of Freedroid
9 *
10 * Freedroid is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * Freedroid is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Freedroid; see the file COPYING. If not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 * MA 02111-1307 USA
24 *
25 */
26 /**
27 * This file contains miscellaeous helpful functions for FreedroidRPG.
28 */
29 #define _misc_c
30
31 #include "system.h"
32
33 #include "defs.h"
34 #include "struct.h"
35 #include "global.h"
36 #include "proto.h"
37 #include "savestruct.h"
38
39 #include "widgets/widgets.h"
40 #include "lvledit/lvledit.h"
41
42 #include <stdlib.h>
43 #if HAVE_EXECINFO_H
44 # include <execinfo.h>
45 #endif
46 #if HAVE_SIGNAL_H
47 # include <signal.h>
48 #endif
49
50 static int world_is_frozen = 0;
51 long oneframedelay = 0;
52 float FPSover1 = 10;
53 Uint32 Now_SDL_Ticks;
54 Uint32 One_Frame_SDL_Ticks;
55 Uint32 Ten_Frame_SDL_Ticks;
56 Uint32 Onehundred_Frame_SDL_Ticks;
57 int framenr = 0;
58 long Overall_Frames_Displayed = 0;
59
60 char *our_homedir = NULL;
61 char *our_config_dir = NULL;
62
63 struct data_dir data_dirs[] = {
64 [GRAPHICS_DIR]= { "graphics", "" },
65 [FONT_DIR]= { "graphics/font", "" },
66 [SOUND_DIR]= { "sound", "" },
67 [MUSIC_DIR]= { "sound/music", "" },
68 [MAP_DIR]= { "map", "" },
69 [TITLES_DIR]= { "map/titles", "" },
70 [DIALOG_DIR]= { "dialogs", "" },
71 #ifdef ENABLE_NLS
72 [LOCALE_DIR]= { "locale", "" },
73 #endif
74 [LUA_MOD_DIR]= { "lua_modules", "" }
75 };
76 #define WELL_KNOWN_DATA_FILE "lua_modules/FDdialog.lua"
77
78 mouse_press_button AllMousePressButtons[MAX_MOUSE_PRESS_BUTTONS] = {
79 [UP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/UpButton.png", {600, 94, 40, 40}, TRUE},
80 [DOWN_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/DownButton.png", {600, 316, 40, 40}, TRUE},
81
82 [ITEM_BROWSER_LEFT_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {280, 44, 37, 37}, TRUE},
83 [ITEM_BROWSER_RIGHT_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {536, 44, 37, 37}, TRUE},
84 [ITEM_BROWSER_EXIT_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {201, 340, 47, 47}, TRUE},
85
86 [LEFT_SHOP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/LeftButton.png", {23, 446, 23, 23}, TRUE},
87 [RIGHT_SHOP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/RightButton.png", {580, 447, 23, 23}, TRUE},
88 [LEFT_TUX_SHOP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/LeftShopButton.png", {6, 15, 23, 23}, TRUE},
89 [RIGHT_TUX_SHOP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/RightShopButton.png", {584, 15, 23, 23}, TRUE},
90 [LEFT_LEVEL_EDITOR_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/LevelEditorObjectSelectorLeft.png", {3, 8, 15, 60}, FALSE},
91 [LEFT_LEVEL_EDITOR_BUTTON_PUSHED] =
92 {EMPTY_IMAGE, "mouse_buttons/LevelEditorObjectSelectorLeftPushed.png", {2, 7, 15, 60}, FALSE},
93 [RIGHT_LEVEL_EDITOR_BUTTON] =
94 {EMPTY_IMAGE, "mouse_buttons/LevelEditorObjectSelectorRight.png", {-16, 8, 15, 60}, FALSE},
95 [RIGHT_LEVEL_EDITOR_BUTTON_PUSHED] =
96 {EMPTY_IMAGE, "mouse_buttons/LevelEditorObjectSelectorRightPushed.png", {-17, 7, 15, 60}, FALSE},
97
98 [NUMBER_SELECTOR_OK_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/number_selector_ok_button.png", {308, 288, 48, 48}, TRUE},
99 [NUMBER_SELECTOR_LEFT_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {148, 244, 35, 35}, TRUE},
100 [NUMBER_SELECTOR_RIGHT_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {404, 244, 35, 35}, TRUE},
101
102 [BUY_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/buy_button.png", {199, 98, 47, 47}, TRUE},
103 [SELL_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/sell_button.png", {199, 153, 47, 47}, TRUE},
104 [REPAIR_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/repair_button.png", {199, 225, 47, 47}, TRUE},
105
106 [OPEN_CLOSE_SKILL_EXPLANATION_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {0 + 17, 424, 33, 33}, FALSE},
107
108
109 /* lower right area next to the mini map*/
110 [LEVEL_EDITOR_DELETE_OBSTACLE_BUTTON] =
111 {EMPTY_IMAGE, "mouse_buttons/LevelEditorDeleteObstacleButton.png", {-270, -30, 0, 0}, FALSE},
112 [LEVEL_EDITOR_DELETE_OBSTACLE_BUTTON_PUSHED] =
113 {EMPTY_IMAGE, "mouse_buttons/LevelEditorDeleteObstacleButtonPushed.png", {-271, -31, 0, 0}, FALSE},
114 [LEVEL_EDITOR_NEXT_OBJECT_BUTTON] =
115 {EMPTY_IMAGE, "mouse_buttons/LevelEditorNextObstacleButton.png", {-240, -30, 0, 0}, FALSE},
116 [LEVEL_EDITOR_NEXT_OBJECT_BUTTON_PUSHED] =
117 {EMPTY_IMAGE, "mouse_buttons/LevelEditorNextObstacleButtonPushed.png", {-241, -31, 0, 0}, FALSE},
118
119
120 /* upper right are directly under the object selector*/
121 [LEVEL_EDITOR_SAVE_SHIP_BUTTON] =
122 {EMPTY_IMAGE, "mouse_buttons/LevelEditorSaveShipButton.png", {-60, 80, 0, 0}, FALSE},
123 [LEVEL_EDITOR_SAVE_SHIP_BUTTON_PUSHED] =
124 {EMPTY_IMAGE, "mouse_buttons/LevelEditorSaveShipButtonPushed.png", {-59, 79, 0, 0}, FALSE},
125 [LEVEL_EDITOR_SAVE_SHIP_BUTTON_OFF] =
126 {EMPTY_IMAGE, "mouse_buttons/LevelEditorSaveShipButtonOff.png", {-60, 80, 0, 0}, FALSE},
127 [LEVEL_EDITOR_SAVE_SHIP_BUTTON_OFF_PUSHED] =
128 {EMPTY_IMAGE, "mouse_buttons/LevelEditorSaveShipButtonOffPushed.png", {-59, 79, 0, 0}, FALSE},
129 [LEVEL_EDITOR_QUIT_BUTTON] =
130 {EMPTY_IMAGE, "mouse_buttons/LevelEditorQuitButton.png", {-30, 80, 0, 0}, FALSE},
131 [LEVEL_EDITOR_QUIT_BUTTON_PUSHED] =
132 {EMPTY_IMAGE, "mouse_buttons/LevelEditorQuitButtonPushed.png", {-29, 79, 0, 0}, FALSE},
133
134
135 /* above the upper row, the very upper row */
136 [LEVEL_EDITOR_TOGGLE_MAP_LABELS_BUTTON] =
137 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleMapLabelsButton.png", {-30, -340, 0, 0}, FALSE},
138 [LEVEL_EDITOR_TOGGLE_MAP_LABELS_BUTTON_PUSHED] =
139 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleMapLabelsButtonPushed.png", {-29, -339, 0, 0}, FALSE},
140 [LEVEL_EDITOR_TOGGLE_MAP_LABELS_BUTTON_OFF] =
141 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleMapLabelsButtonOff.png", {-30, -340, 0, 0}, FALSE},
142 [LEVEL_EDITOR_TOGGLE_MAP_LABELS_BUTTON_OFF_PUSHED] =
143 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleMapLabelsButtonOffPushed.png", {-29, -339, 0, 0}, FALSE},
144
145
146 /* above the obstacle selectors, upper row */
147 [LEVEL_EDITOR_EDIT_CHEST_BUTTON] =
148 {EMPTY_IMAGE, "mouse_buttons/LevelEditorEditChestButton.png", {-150, -310, 0, 0}, FALSE},
149 [LEVEL_EDITOR_EDIT_CHEST_BUTTON_PUSHED] =
150 {EMPTY_IMAGE, "mouse_buttons/LevelEditorEditChestButtonPushed.png", {-149, -309, 0, 0}, FALSE},
151 [LEVEL_EDITOR_NEW_OBSTACLE_LABEL_BUTTON] =
152 {EMPTY_IMAGE, "mouse_buttons/LevelEditorNewObstacleLabelButton.png", {-120, -310, 0, 0}, FALSE},
153 [LEVEL_EDITOR_NEW_OBSTACLE_LABEL_BUTTON_PUSHED] =
154 {EMPTY_IMAGE, "mouse_buttons/LevelEditorNewObstacleLabelButtonPushed.png", {-119, -309, 0, 0}, FALSE},
155 [LEVEL_EDITOR_TOGGLE_WAYPOINT_CONNECTIONS_BUTTON] =
156 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleWaypointConnectionsButton.png", {-90, -310, 0, 0}, FALSE},
157 [LEVEL_EDITOR_TOGGLE_WAYPOINT_CONNECTIONS_BUTTON_PUSHED] =
158 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleWaypointConnectionsButtonPushed.png", {-90, -310, 0, 0}, FALSE},
159 [LEVEL_EDITOR_TOGGLE_WAYPOINT_CONNECTIONS_BUTTON_OFF] =
160 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleWaypointConnectionsButtonOff.png", {-90, -310, 0, 0}, FALSE},
161 [LEVEL_EDITOR_TOGGLE_WAYPOINT_CONNECTIONS_BUTTON_OFF_PUSHED] =
162 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleWaypointConnectionsButtonOffPushed.png", {-90, -310, 0, 0}, FALSE},
163 [LEVEL_EDITOR_ZOOM_IN_BUTTON] =
164 {EMPTY_IMAGE, "mouse_buttons/LevelEditorZoomInButton.png", {-60, -310, 0, 0}, FALSE},
165 [LEVEL_EDITOR_ZOOM_IN_BUTTON_PUSHED] =
166 {EMPTY_IMAGE, "mouse_buttons/LevelEditorZoomInButtonPushed.png", {-59, -309, 0, 0}, FALSE},
167 [LEVEL_EDITOR_ZOOM_OUT_BUTTON] =
168 {EMPTY_IMAGE, "mouse_buttons/LevelEditorZoomOutButton.png", {-60, -310, 0, 0}, FALSE},
169 [LEVEL_EDITOR_ZOOM_OUT_BUTTON_PUSHED] =
170 {EMPTY_IMAGE, "mouse_buttons/LevelEditorZoomOutButtonPushed.png", {-59, -309, 0, 0}, FALSE},
171 [LEVEL_EDITOR_BEAUTIFY_GRASS_BUTTON] =
172 {EMPTY_IMAGE, "mouse_buttons/LevelEditorBeautifyGrassButton.png", {-30, -310, 0, 0}, FALSE},
173 [LEVEL_EDITOR_BEAUTIFY_GRASS_BUTTON_PUSHED] =
174 {EMPTY_IMAGE, "mouse_buttons/LevelEditorBeautifyGrassButtonPushed.png", {-29, -309, 0, 0}, FALSE},
175 [LEVEL_EDITOR_ALL_FLOOR_LAYERS_BUTTON] =
176 {EMPTY_IMAGE, "mouse_buttons/LevelEditorAllFloorLayersButton.png", {-120, -310, 0, 0}, FALSE},
177 [LEVEL_EDITOR_ALL_FLOOR_LAYERS_BUTTON_PUSHED] =
178 {EMPTY_IMAGE, "mouse_buttons/LevelEditorAllFloorLayersButtonPushed.png", {-120, -310, 0, 0}, FALSE},
179 [LEVEL_EDITOR_SINGLE_FLOOR_LAYER_BUTTON] =
180 {EMPTY_IMAGE, "mouse_buttons/LevelEditorSingleFloorLayerButton.png", {-120, -310, 0, 0}, FALSE},
181 [LEVEL_EDITOR_SINGLE_FLOOR_LAYER_BUTTON_PUSHED] =
182 {EMPTY_IMAGE, "mouse_buttons/LevelEditorSingleFloorLayerButtonPushed.png", {-120, -310, 0, 0}, FALSE},
183
184
185 /* above the obstacle selector, lower row*/
186 [LEVEL_EDITOR_TOGGLE_GRID_BUTTON_OFF] =
187 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleGridButtonOff.png", {-150, -280, 0, 0}, FALSE},
188 [LEVEL_EDITOR_TOGGLE_GRID_BUTTON_OFF_PUSHED] =
189 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleGridButtonOffPushed.png", {-149, -279, 0, 0}, FALSE},
190 [LEVEL_EDITOR_TOGGLE_GRID_BUTTON] =
191 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleGridButton.png", {-150, -280, 0, 0}, FALSE},
192 [LEVEL_EDITOR_TOGGLE_GRID_BUTTON_PUSHED] =
193 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleGridButtonPushed.png", {-149, -279, 0, 0}, FALSE},
194 [LEVEL_EDITOR_TOGGLE_GRID_BUTTON_FULL] =
195 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleGridButtonFull.png", {-150, -280, 0, 0}, FALSE},
196 [LEVEL_EDITOR_TOGGLE_GRID_BUTTON_FULL_PUSHED] =
197 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleGridButtonFullPushed.png", {-149, -279, 0, 0}, FALSE},
198 [LEVEL_EDITOR_TOGGLE_ENEMIES_BUTTON] =
199 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleEnemiesButton.png", {-120, -280, 0, 0}, FALSE},
200 [LEVEL_EDITOR_TOGGLE_ENEMIES_BUTTON_PUSHED] =
201 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleEnemiesButtonPushed.png", {-119, -279, 0, 0}, FALSE},
202 [LEVEL_EDITOR_TOGGLE_ENEMIES_BUTTON_OFF] =
203 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleEnemiesButtonOff.png", {-120, -280, 0, 0}, FALSE},
204 [LEVEL_EDITOR_TOGGLE_ENEMIES_BUTTON_OFF_PUSHED] =
205 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleEnemiesButtonOffPushed.png", {-119, -279, 0, 0}, FALSE},
206 [LEVEL_EDITOR_TOGGLE_OBSTACLES_BUTTON] =
207 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleObstaclesButton.png", {-90, -280, 0, 0}, FALSE},
208 [LEVEL_EDITOR_TOGGLE_OBSTACLES_BUTTON_PUSHED] =
209 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleObstaclesButtonPushed.png", {-89, -279, 0, 0}, FALSE},
210 [LEVEL_EDITOR_TOGGLE_OBSTACLES_BUTTON_OFF] =
211 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleObstaclesButtonOff.png", {-90, -280, 0, 0}, FALSE},
212 [LEVEL_EDITOR_TOGGLE_OBSTACLES_BUTTON_OFF_PUSHED] =
213 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleObstaclesButtonOffPushed.png", {-89, -279, 0, 0}, FALSE},
214 [LEVEL_EDITOR_TOGGLE_TOOLTIPS_BUTTON] =
215 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleTooltipsButton.png", {-60, -280, 0, 0}, FALSE},
216 [LEVEL_EDITOR_TOGGLE_TOOLTIPS_BUTTON_PUSHED] =
217 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleTooltipsButtonPushed.png", {-59, -279, 0, 0}, FALSE},
218 [LEVEL_EDITOR_TOGGLE_TOOLTIPS_BUTTON_OFF] =
219 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleTooltipsButtonOff.png", {-60, -280, 0, 0}, FALSE},
220 [LEVEL_EDITOR_TOGGLE_TOOLTIPS_BUTTON_OFF_PUSHED] =
221 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleTooltipsButtonOffPushed.png", {-59, -279, 0, 0}, FALSE},
222 [LEVEL_EDITOR_TOGGLE_COLLISION_RECTS_BUTTON] =
223 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleCollisionRectsButton.png", {-30, -280, 0, 0}, FALSE},
224 [LEVEL_EDITOR_TOGGLE_COLLISION_RECTS_BUTTON_PUSHED] =
225 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleCollisionRectsButtonPushed.png", {-29, -279, 0, 0}, FALSE},
226 [LEVEL_EDITOR_TOGGLE_COLLISION_RECTS_BUTTON_OFF] =
227 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleCollisionRectsButtonOff.png", {-30, -280, 0, 0}, FALSE},
228 [LEVEL_EDITOR_TOGGLE_COLLISION_RECTS_BUTTON_OFF_PUSHED] =
229 {EMPTY_IMAGE, "mouse_buttons/LevelEditorToggleCollisionRectsButtonOffPushed.png", {-29, -279, 0, 0}, FALSE},
230
231
232 [LEVEL_EDITOR_NEXT_ITEM_GROUP_BUTTON] =
233 {EMPTY_IMAGE, "mouse_buttons/RightButton.png", {55 + 64 * 8, 32 + 5 * 66, 0, 0}, TRUE},
234 [LEVEL_EDITOR_PREV_ITEM_GROUP_BUTTON] =
235 {EMPTY_IMAGE, "mouse_buttons/LeftButton.png", {55, 32 + 5 * 66, 0, 0}, TRUE},
236
237 [LEVEL_EDITOR_CANCEL_ITEM_DROP_BUTTON] =
238 {EMPTY_IMAGE, "mouse_buttons/LevelEditorCancelItemDrop.png", {55 + 80, 32 + 5 * 66, 0, 0}, TRUE},
239 [LEVEL_EDITOR_UNDO_BUTTON] =
240 {EMPTY_IMAGE, "mouse_buttons/LevelEditorUndoButton.png", {-330, -30, 0, 0}, FALSE},
241 [LEVEL_EDITOR_UNDO_BUTTON_PUSHED] =
242 {EMPTY_IMAGE, "mouse_buttons/LevelEditorUndoButtonPushed.png", {-331, -31, 0, 0}, FALSE},
243
244 [LEVEL_EDITOR_REDO_BUTTON] =
245 {EMPTY_IMAGE, "mouse_buttons/LevelEditorRedoButton.png", {-300, -30, 0, 0}, FALSE},
246 [LEVEL_EDITOR_REDO_BUTTON_PUSHED] =
247 {EMPTY_IMAGE, "mouse_buttons/LevelEditorRedoButtonPushed.png", {-301, -31, 0, 0}, FALSE},
248
249 [LEVEL_EDITOR_TYPESELECT_OBSTACLE_BUTTON] =
250 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton.png", {-152, -250, 0, 0}, FALSE},
251 [LEVEL_EDITOR_TYPESELECT_OBSTACLE_BUTTON_PUSHED] =
252 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButtonPushed.png", {-152, -250, 0, 0}, FALSE},
253 [LEVEL_EDITOR_TYPESELECT_OBSTACLE_BUTTON_OFF] =
254 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButtonOff.png", {-152, -250, 0, 0}, FALSE},
255 [LEVEL_EDITOR_TYPESELECT_OBSTACLE_BUTTON_OFF_PUSHED] =
256 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButtonOffPushed.png", {-152, -250, 0, 0}, FALSE},
257
258 [LEVEL_EDITOR_TYPESELECT_ENEMY_BUTTON] =
259 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3.png", {-90, -174, 0, 0}, FALSE},
260 [LEVEL_EDITOR_TYPESELECT_ENEMY_BUTTON_PUSHED] =
261 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3Pushed.png", {-90, -174, 0, 0}, FALSE},
262 [LEVEL_EDITOR_TYPESELECT_ENEMY_BUTTON_OFF] =
263 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3Off.png", {-90, -174, 0, 0}, FALSE},
264 [LEVEL_EDITOR_TYPESELECT_ENEMY_BUTTON_OFF_PUSHED] =
265 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3OffPushed.png", {-90, -174, 0, 0}, FALSE},
266
267 [LEVEL_EDITOR_TYPESELECT_FLOOR_BUTTON] =
268 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton.png", {-152, -212, 0, 0}, FALSE},
269 [LEVEL_EDITOR_TYPESELECT_FLOOR_BUTTON_PUSHED] =
270 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButtonPushed.png", {-152, -212, 0, 0}, FALSE},
271 [LEVEL_EDITOR_TYPESELECT_FLOOR_BUTTON_OFF] =
272 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButtonOff.png", {-152, -212, 0, 0}, FALSE},
273 [LEVEL_EDITOR_TYPESELECT_FLOOR_BUTTON_OFF_PUSHED] =
274 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButtonOffPushed.png", {-152, -212, 0, 0}, FALSE},
275
276 [LEVEL_EDITOR_TYPESELECT_ITEM_BUTTON] =
277 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2.png", {-152, -174, 0, 0}, FALSE},
278 [LEVEL_EDITOR_TYPESELECT_ITEM_BUTTON_PUSHED] =
279 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2Pushed.png", {-152, -174, 0, 0}, FALSE},
280 [LEVEL_EDITOR_TYPESELECT_ITEM_BUTTON_OFF] =
281 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2Off.png", {-152, -174, 0, 0}, FALSE},
282 [LEVEL_EDITOR_TYPESELECT_ITEM_BUTTON_OFF_PUSHED] =
283 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2OffPushed.png", {-152, -174, 0, 0}, FALSE},
284
285 [LEVEL_EDITOR_TYPESELECT_WAYPOINT_BUTTON] =
286 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2.png", {-152, -136, 0, 0}, FALSE},
287 [LEVEL_EDITOR_TYPESELECT_WAYPOINT_BUTTON_PUSHED] =
288 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2Pushed.png", {-152, -136, 0, 0}, FALSE},
289 [LEVEL_EDITOR_TYPESELECT_WAYPOINT_BUTTON_OFF] =
290 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2Off.png", {-152, -136, 0, 0}, FALSE},
291 [LEVEL_EDITOR_TYPESELECT_WAYPOINT_BUTTON_OFF_PUSHED] =
292 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton2OffPushed.png", {-152, -136, 0, 0}, FALSE},
293
294 [LEVEL_EDITOR_TYPESELECT_MAP_LABEL_BUTTON] =
295 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3.png", {-90, -136, 0, 0}, FALSE},
296 [LEVEL_EDITOR_TYPESELECT_MAP_LABEL_BUTTON_PUSHED] =
297 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3Pushed.png", {-90, -136, 0, 0}, FALSE},
298 [LEVEL_EDITOR_TYPESELECT_MAP_LABEL_BUTTON_OFF] =
299 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3Off.png", {-90, -136, 0, 0}, FALSE},
300 [LEVEL_EDITOR_TYPESELECT_MAP_LABEL_BUTTON_OFF_PUSHED] =
301 {EMPTY_IMAGE, "mouse_buttons/LevelEditorTypeSelectorButton3OffPushed.png", {-90, -136, 0, 0}, FALSE},
302
303
304 [WEAPON_RECT_BUTTON] =
305 {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {WEAPON_RECT_X, WEAPON_RECT_Y, WEAPON_RECT_WIDTH, WEAPON_RECT_HEIGHT}, FALSE},
306 [DRIVE_RECT_BUTTON] =
307 {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {DRIVE_RECT_X, DRIVE_RECT_Y, DRIVE_RECT_WIDTH, DRIVE_RECT_HEIGHT}, FALSE},
308 [SHIELD_RECT_BUTTON] =
309 {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {SHIELD_RECT_X, SHIELD_RECT_Y, SHIELD_RECT_WIDTH, SHIELD_RECT_HEIGHT}, FALSE},
310 [HELMET_RECT_BUTTON] =
311 {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {HELMET_RECT_X, HELMET_RECT_Y, HELMET_RECT_WIDTH, HELMET_RECT_HEIGHT}, FALSE},
312 [ARMOUR_RECT_BUTTON] =
313 {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {ARMOUR_RECT_X, ARMOUR_RECT_Y, ARMOUR_RECT_WIDTH, ARMOUR_RECT_HEIGHT}, FALSE},
314
315 [MORE_STR_BUTTON] =
316 {EMPTY_IMAGE, "mouse_buttons/AttributePlusButton.png", {0 + STR_X + 53, STR_Y - 5, 38, 22}, FALSE},
317 [MORE_MAG_BUTTON] =
318 {EMPTY_IMAGE, "mouse_buttons/AttributePlusButton.png", {0 + STR_X + 53, MAG_Y - 5, 38, 22}, FALSE},
319 [MORE_DEX_BUTTON] =
320 {EMPTY_IMAGE, "mouse_buttons/AttributePlusButton.png", {0 + STR_X + 53, DEX_Y - 5, 38, 22}, FALSE},
321 [MORE_VIT_BUTTON] =
322 {EMPTY_IMAGE, "mouse_buttons/AttributePlusButton.png", {0 + STR_X + 53, VIT_Y - 5, 38, 22}, FALSE},
323
324 // These two buttons are for the scrolling text during the
325 // title display, the credits menu and the level editor
326 // keyboard explanation...
327 //
328 [SCROLL_TEXT_UP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/arrow_up_for_scroll_text.png", {-65, 10, 73, 98}, FALSE},
329 [SCROLL_TEXT_DOWN_BUTTON] =
330 {EMPTY_IMAGE, "mouse_buttons/arrow_down_for_scroll_text.png", {-65, -10 - 98, 73, 98}, FALSE},
331
332 [DESCRIPTION_WINDOW_UP_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {607, 99, 26, 26}, TRUE},
333 [DESCRIPTION_WINDOW_DOWN_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {607, 347, 26, 26}, TRUE},
334
335 [DROID_SHOW_EXIT_BUTTON] = {EMPTY_IMAGE, "THIS_DOESNT_NEED_BLITTING", {202, 311, 47, 47}, TRUE},
336
337 [QUEST_BROWSER_ITEM_SHORT_BUTTON] =
338 {EMPTY_IMAGE, "mouse_buttons/quest_browser_item_short.png", {108, 86, 300, 26}, FALSE},
339 [QUEST_BROWSER_ITEM_LONG_BUTTON] =
340 {EMPTY_IMAGE, "mouse_buttons/quest_browser_item_long.png", {108, 86, 300, 26}, FALSE},
341
342 [TAKEOVER_HELP_BUTTON] = {EMPTY_IMAGE, "mouse_buttons/takeover_help_button.png", {78, 23, 153, 38}, FALSE},
343
344 // Buttons of the item upgrade UI.
345 [ITEM_UPGRADE_APPLY_BUTTON] = { EMPTY_IMAGE, "item_upgrade/button_apply.png",
346 { ITEM_UPGRADE_RECT_X + 250, ITEM_UPGRADE_RECT_Y + 389, 48, 48 }, FALSE},
347 [ITEM_UPGRADE_APPLY_BUTTON_DISABLED] = { EMPTY_IMAGE, "item_upgrade/button_apply_disabled.png",
348 { ITEM_UPGRADE_RECT_X + 250, ITEM_UPGRADE_RECT_Y + 389, 48, 48 }, FALSE},
349 [ITEM_UPGRADE_CLOSE_BUTTON] = { EMPTY_IMAGE, "item_upgrade/button_close.png",
350 { ITEM_UPGRADE_RECT_X + 215, ITEM_UPGRADE_RECT_Y + 406, 32, 32 }, FALSE},
351
352 // Buttons of the add-on crafting UI.
353 [ADDON_CRAFTING_APPLY_BUTTON] = { EMPTY_IMAGE, "item_upgrade/button_apply.png",
354 { ADDON_CRAFTING_RECT_X + 250, ADDON_CRAFTING_RECT_Y + 389, 48, 48 }, FALSE},
355 [ADDON_CRAFTING_APPLY_BUTTON_DISABLED] = { EMPTY_IMAGE, "item_upgrade/button_apply_disabled.png",
356 { ADDON_CRAFTING_RECT_X + 250, ADDON_CRAFTING_RECT_Y + 389, 48, 48 }, FALSE},
357 [ADDON_CRAFTING_CLOSE_BUTTON] = { EMPTY_IMAGE, "item_upgrade/button_close.png",
358 { ADDON_CRAFTING_RECT_X + 215, ADDON_CRAFTING_RECT_Y + 406, 32, 32 }, FALSE},
359 [ADDON_CRAFTING_SCROLL_UP_BUTTON] = { EMPTY_IMAGE, "mouse_buttons/crafting_scroll_up.png",
360 { ADDON_CRAFTING_RECT_X + 264, ADDON_CRAFTING_RECT_Y + 70, 32, 32 }, FALSE},
361 [ADDON_CRAFTING_SCROLL_DOWN_BUTTON] = { EMPTY_IMAGE, "mouse_buttons/crafting_scroll_down.png",
362 { ADDON_CRAFTING_RECT_X + 264, ADDON_CRAFTING_RECT_Y + 198, 32, 32 }, FALSE},
363 [ADDON_CRAFTING_SCROLL_DESC_UP_BUTTON] = { EMPTY_IMAGE, "mouse_buttons/crafting_scroll_up.png",
364 { ADDON_CRAFTING_RECT_X + 260, ADDON_CRAFTING_RECT_Y + 290, 32, 32 }, FALSE},
365 [ADDON_CRAFTING_SCROLL_DESC_DOWN_BUTTON] = { EMPTY_IMAGE, "mouse_buttons/crafting_scroll_down.png",
366 { ADDON_CRAFTING_RECT_X + 260, ADDON_CRAFTING_RECT_Y + 350, 32, 32 }, FALSE},
367
368 }; // mouse_press_button AllMousePressButtons[ MAX_MOUSE_PRESS_BUTTONS ]
369
370 //--------------------
371 // We make these global variables here, as we might want to use
372 // this function inside a signal handler and maybe also it's better
373 // not to mess too much around with the stack while trying to read
374 // out the stack...
375 //
376 #define MAX_CALLS_IN_BACKTRACE 200
377 void *backtrace_array[MAX_CALLS_IN_BACKTRACE];
378 size_t backtrace_size;
379 char **backtrace_strings;
380 size_t backtrace_counter;
381
382 /**
383 * Obtain a backtrace and print it to stdout.
384 * If signum != 0, call Terminate()
385 */
print_trace(int signum)386 void print_trace(int signum)
387 {
388
389 #if (!defined __WIN32__) && (!defined __APPLE__) && (defined HAVE_BACKTRACE)
390
391 // fprintf ( stderr , "print_trace: Now attempting backtrace from within the code!\n" );
392 // fprintf ( stderr , "print_trace: Allowing a maximum of %d function calls on the stack!\n" , MAX_CALLS_IN_BACKTRACE );
393
394 // We attempt to get a backtrace of all function calls so far, even
395 // including the operating system (or rather libc) call to main() in
396 // the beginning of execution.
397 //
398 backtrace_size = backtrace(backtrace_array, MAX_CALLS_IN_BACKTRACE);
399
400 fprintf(stderr, "print_trace: Obtained %zu stack frames.\n", backtrace_size);
401
402 // Now we attempt to translate the trace information we've got to the
403 // symbol names that might still reside in the binary.
404 //
405 // NOTE: that in order for this to work, the -rdynamic switch must have
406 // been passed as on option to the LINKER!
407 // Also there might be a problem with non-ELF binaries, but let's
408 // hope that it still works...
409 //
410 backtrace_strings = backtrace_symbols(backtrace_array, backtrace_size);
411
412 fprintf(stderr, "print_trace: Obtaining symbols now done.\n");
413
414 for (backtrace_counter = 0; backtrace_counter < backtrace_size; backtrace_counter++)
415 fprintf(stderr, "%s\n", backtrace_strings[backtrace_counter]);
416
417 // The strings generated in the backtrace_symbols function need to
418 // get freed. Well, this isn't terribly important, but clean.
419 //
420 free(backtrace_strings);
421
422 #endif
423
424 switch (signum) {
425 case 0:
426 return;
427 break;
428 case SIGSEGV:
429 fprintf(stderr, "\n%s(): received SIGSEGV!\n", __FUNCTION__);
430 break;
431 case SIGFPE:
432 fprintf(stderr, "\n%s(): received SIGFPE!\n", __FUNCTION__);
433 break;
434 default:
435 fprintf(stderr, "\n%s(): received UNKNOWN SIGNAL %d! ERROR! \n", __FUNCTION__, signum);
436 }
437
438 Terminate(EXIT_FAILURE);
439
440 }; // void print_trace ( int sig_num )
441
442 /**
443 * If we want the screen resolution to be a runtime option and not a
444 * compile time option any more, we must not use it as a constant. That
445 * means we must adapt the button positions to the current screen
446 * resolution at runtime to, so we do it in this function, which will be
447 * involved at program startup.
448 */
adapt_button_positions_to_screen_resolution(void)449 void adapt_button_positions_to_screen_resolution(void)
450 {
451 int i;
452
453 for (i = 0; i < MAX_MOUSE_PRESS_BUTTONS; i++) {
454 if (AllMousePressButtons[i].button_rect.x < 0)
455 AllMousePressButtons[i].button_rect.x += GameConfig.screen_width;
456 if (AllMousePressButtons[i].button_rect.y < 0)
457 AllMousePressButtons[i].button_rect.y += GameConfig.screen_height;
458 }
459
460 AllMousePressButtons[OPEN_CLOSE_SKILL_EXPLANATION_BUTTON].button_rect.x += CHARACTERRECT_X;
461
462 AllMousePressButtons[MORE_STR_BUTTON].button_rect.x += CHARACTERRECT_X;
463 AllMousePressButtons[MORE_MAG_BUTTON].button_rect.x += CHARACTERRECT_X;
464 AllMousePressButtons[MORE_DEX_BUTTON].button_rect.x += CHARACTERRECT_X;
465 AllMousePressButtons[MORE_VIT_BUTTON].button_rect.x += CHARACTERRECT_X;
466
467 Droid_Image_Window.x = 48 * GameConfig.screen_width / 640;
468 Droid_Image_Window.y = 44 * GameConfig.screen_height / 480;
469 Droid_Image_Window.w = 130 * GameConfig.screen_width / 640;
470 Droid_Image_Window.h = 172 * GameConfig.screen_height / 480;
471
472 Full_User_Rect.x = 0;
473 Full_User_Rect.y = 0;
474 Full_User_Rect.w = GameConfig.screen_width;
475 Full_User_Rect.h = GameConfig.screen_height;
476
477 Cons_Text_Rect.x = 175;
478 Cons_Text_Rect.y = 180;
479 Cons_Text_Rect.w = GameConfig.screen_width - 175;
480 Cons_Text_Rect.h = 305;
481
482 }; // void adapt_button_positions_to_screen_resolution( void )
483
484 /**
485 * This is a useful utility sub-function for the checks whether the
486 * mouse cursor is on an enemy, a closed chest or a barrel, or any other object.
487 * The function detects if the current mouse cursor is over the graphics
488 * mentioned in that iso image, using the given position from the
489 * parameter list.
490 *
491 * TRUE or FALSE is returned, depending on whether the cursor IS or
492 * IS NOT on that particular iso_image, if positioned on that given spot.
493 */
mouse_cursor_is_on_that_image(float pos_x,float pos_y,struct image * our_image)494 int mouse_cursor_is_on_that_image(float pos_x, float pos_y, struct image *our_image)
495 {
496 // our_iso_image = & ( enemy_iso_images [ RotationModel ] [ RotationIndex ] [ (int) this_bot -> animation_phase ] ) ;
497 SDL_Rect screen_rectangle;
498
499 screen_rectangle.x = translate_map_point_to_screen_pixel_x(pos_x, pos_y) + our_image->offset_x;
500 screen_rectangle.y = translate_map_point_to_screen_pixel_y(pos_x, pos_y) + our_image->offset_y;
501 screen_rectangle.w = our_image->w;
502 screen_rectangle.h = our_image->h;
503
504 if (MouseCursorIsInRect(&(screen_rectangle),
505 input_axis.x + User_Rect.w / 2 + User_Rect.x, input_axis.y + User_Rect.h / 2 + User_Rect.y)) {
506 return (TRUE);
507 }
508
509 return (FALSE);
510 }
511
512 /**
513 * This function checks if a given screen position lies within the
514 * inventory screen toggle button or not.
515 */
MouseCursorIsInRect(const SDL_Rect * our_rect,int x,int y)516 int MouseCursorIsInRect(const SDL_Rect *our_rect, int x, int y)
517 {
518 // Now we can start to check if the mouse cursor really is on that
519 // rectangle or not.
520 //
521 if (x > our_rect->x + our_rect->w)
522 return (FALSE);
523 if (x < our_rect->x)
524 return (FALSE);
525 if (y > our_rect->y + our_rect->h)
526 return (FALSE);
527 if (y < our_rect->y)
528 return (FALSE);
529
530 // So since the cursor is not outside of this rectangle, it must
531 // we inside, and so we'll return this answer.
532 //
533 return (TRUE);
534
535 }; // int MouseCursorIsInRect( SDL_rect* our_rect , int x , int y )
536
537 /**
538 * This function checks if a given screen position lies within the
539 * inventory screen toggle button or not.
540 */
MouseCursorIsOnButton(int ButtonIndex,int x,int y)541 int MouseCursorIsOnButton(int ButtonIndex, int x, int y)
542 {
543 SDL_Rect temp_rect;
544
545 // First a sanity check if the button index given does make
546 // some sense.
547 //
548 if ((ButtonIndex >= MAX_MOUSE_PRESS_BUTTONS) || (ButtonIndex < 0)) {
549 error_message(__FUNCTION__, "\
550 A Button that should be checked for mouse contact was requested, but the\n\
551 button index given exceeds the number of buttons defined in FreedroidRPG.", PLEASE_INFORM | IS_FATAL);
552 }
553
554 Copy_Rect(AllMousePressButtons[ButtonIndex].button_rect, temp_rect);
555 // If this button needs scaling still, then we do it now...
556 //
557 if (AllMousePressButtons[ButtonIndex].scale_this_button) {
558 temp_rect.x *= ((float)GameConfig.screen_width) / 640.0;
559 temp_rect.w *= ((float)GameConfig.screen_width) / 640.0;
560 temp_rect.y *= ((float)GameConfig.screen_height) / 480.0;
561 temp_rect.h *= ((float)GameConfig.screen_height) / 480.0;
562 }
563
564 if (y < AllMousePressButtons[ButtonIndex].button_rect.y)
565 return (FALSE);
566
567 // So since the cursor is not outside of this rectangle, it must
568 // we inside, and so we'll return this answer.
569 //
570 return (MouseCursorIsInRect(&(temp_rect), x, y));
571
572 }; // int MouseCursorIsOnButton( int ButtonIndex , int x , int y )
573
574 /**
575 * Draw a button on the screen.
576 */
ShowGenericButtonFromList(int ButtonIndex)577 void ShowGenericButtonFromList(int ButtonIndex)
578 {
579 struct mouse_press_button *btn;
580
581 // Safety check
582 if ((ButtonIndex >= MAX_MOUSE_PRESS_BUTTONS) || (ButtonIndex < 0)) {
583 error_message(__FUNCTION__, "Request to display button index %d could not be fulfilled: the\n\
584 button index given exceeds the number of buttons defined in FreedroidRPG.", PLEASE_INFORM, ButtonIndex);
585 return;
586 }
587
588 btn = &AllMousePressButtons[ButtonIndex];
589
590 // Some buttons have no graphics, in this case there is nothing to do.
591 if (!strcmp(AllMousePressButtons[ButtonIndex].button_image_file_name, "THIS_DOESNT_NEED_BLITTING")) {
592 return;
593 }
594
595 // Compute scaling factors for button
596 float scale_x = 1.0, scale_y = 1.0;
597 if (btn->scale_this_button) {
598 scale_x = ((float)GameConfig.screen_width) / 640.0;
599 scale_y = ((float)GameConfig.screen_height) / 480.0;
600 }
601
602 // Load button image if required
603 struct image *img = &btn->button_image;
604 if (!image_loaded(img)) {
605 load_image(img, btn->button_image_file_name, NO_MOD);
606
607 // Maybe we had '0' entries for the height or width of this button in the list.
608 // This means that we will take the real width and the real height from the image
609 // and overwrite the 0 entries with this.
610 //
611 if (!btn->button_rect.w) {
612 btn->button_rect.w = img->w;
613 }
614
615 if (!btn->button_rect.h) {
616 btn->button_rect.h = img->h;
617 }
618 }
619
620 display_image_on_screen(img, AllMousePressButtons[ButtonIndex].button_rect.x * scale_x, AllMousePressButtons[ButtonIndex].button_rect.y * scale_y, set_image_transformation(scale_x, scale_y, 1.0, 1.0, 1.0, 1.0, 0));
621 }
622
init_data_dirs_path()623 int init_data_dirs_path()
624 {
625 int i, j;
626 FILE *f;
627 char file_path[PATH_MAX];
628
629 // Reset the data dirs paths
630 for (i = 0; i < LAST_DATA_DIR; i++) {
631 data_dirs[i].path[0] = '\0';
632 }
633
634 // Directories to look for the data dirs
635 char *top_data_dir[] = { ".", "..", "../..", FD_DATADIR };
636 #ifdef ENABLE_NLS
637 char *top_locale_dir[] = { ".", "..", "../..", LOCALEDIR };
638 #endif
639 int slen = sizeof(top_data_dir)/sizeof(top_data_dir[0]);
640
641 // To find the root of the data dirs, we search a well known file that is
642 // always needed for the game to work.
643 for (i = 0; i < slen ; i++) {
644 sprintf(file_path, "%s/" WELL_KNOWN_DATA_FILE, top_data_dir[i]);
645
646 if ((f = fopen(file_path, "r")) != NULL) {
647 // File found, so now fill the data dir paths
648 for (j = 0; j < LAST_DATA_DIR; j++) {
649 char *dir = top_data_dir[i];
650 const char *subdir = data_dirs[j].name;
651 #ifdef ENABLE_NLS
652 if (j == LOCALE_DIR) {
653 dir = top_locale_dir[i];
654 // LOCALEDIR envvar already points to the locale subdir
655 if (!strcmp(dir, LOCALEDIR))
656 subdir = "";
657 }
658 #endif
659 int nb = snprintf(data_dirs[j].path, PATH_MAX, "%s/%s", dir, subdir);
660 if (nb >= PATH_MAX) {
661 error_message(__FUNCTION__, "data_dirs[].path is not big enough to store the following path: %s/%s",
662 PLEASE_INFORM | IS_FATAL, dir, data_dirs[j].name);
663 }
664 }
665 fclose(f);
666 return 1;
667 }
668 }
669
670 // The searched file was not found. Complain.
671 if (getcwd(file_path, PATH_MAX)) {
672 error_message(__FUNCTION__, "Data dirs not found ! (current directory: %s)",
673 PLEASE_INFORM | IS_FATAL, file_path);
674 } else {
675 error_message(__FUNCTION__, "Data dirs not found ! (and cannot find the current working directory)",
676 PLEASE_INFORM | IS_FATAL);
677 }
678
679 return 0;
680 }
681
682 /* -----------------------------------------------------------------
683 * check if a given filename exists in subdir.
684 *
685 * fills in the (ALLOC'd) string and returns 1 if okay, 0 on error.
686 * file_path's length HAS to be PATH_MAX.
687 * ----------------------------------------------------------------- */
_file_exists(const char * fname,const char * subdir,char * file_path)688 static int _file_exists(const char *fname, const char *subdir, char *file_path)
689 {
690 int nb = snprintf(file_path, PATH_MAX, "%s/%s", subdir, fname);
691 if (nb >= PATH_MAX) {
692 *file_path = 0;
693 error_message(__FUNCTION__, "Pathname too long (max is %d): %s/%s",
694 NO_REPORT, PATH_MAX, subdir, fname);
695 return 0;
696 }
697
698 FILE *fp = fopen(file_path, "r");
699 if (!fp) {
700 /* not found */
701 *file_path = 0;
702 return 0;
703 }
704
705 fclose(fp);
706 return 1;
707 }
708
709 /* -----------------------------------------------------------------
710 * Find a filename in subdir (using a data_dir handle).
711 *
712 * fills in the (ALLOC'd) string and returns 1 if okay, 0 on error.
713 * file_path's length HAS to be PATH_MAX.
714 * ----------------------------------------------------------------- */
find_file(const char * fname,int subdir_handle,char * file_path,int error_report)715 int find_file(const char *fname, int subdir_handle, char *file_path, int error_report)
716 {
717 if (subdir_handle < 0 || subdir_handle >= LAST_DATA_DIR) {
718 error_message(__FUNCTION__, "Called with a wrong subdir handle (%d)",
719 error_report | PLEASE_INFORM, subdir_handle);
720 return 0;
721 }
722
723 if (!_file_exists(fname, data_dirs[subdir_handle].path, file_path)) {
724 error_message(__FUNCTION__, "File %s not found in %s",
725 error_report, fname, data_dirs[subdir_handle].name);
726 return 0;
727 }
728
729 return 1;
730 }
731
732 /* -----------------------------------------------------------------
733 * Find a suffixed filename in subdir (using a data_dir handle).
734 *
735 * The 'suffix' is added before the filename extension.
736 *
737 * fills in the (ALLOC'd) string and returns 1 if okay, 0 on error.
738 * file_path's length HAS to be PATH_MAX.
739 * ----------------------------------------------------------------- */
find_suffixed_file(const char * fname,const char * suffix,int subdir_handle,char * file_path,int error_report)740 int find_suffixed_file(const char *fname, const char *suffix, int subdir_handle, char *file_path, int error_report)
741 {
742 char suffixed_fname[PATH_MAX];
743 char *actual_fname = (char *)fname;
744
745 if (suffix) {
746 int fname_length = strlen(fname);
747 int suffix_length = strlen(suffix);
748 if ((fname_length + suffix_length + 1) >= PATH_MAX) {
749 *file_path = 0;
750 error_message(__FUNCTION__, "Filename + suffix too long (max is %d): %s, with suffix: %s",
751 PLEASE_INFORM, PATH_MAX, fname, suffix);
752 return 0;
753 }
754
755 int pos = strrchr(fname, '.') - fname;
756
757 memcpy(suffixed_fname, fname, pos);
758 memcpy(suffixed_fname + pos, suffix, suffix_length);
759 memcpy(suffixed_fname + pos + suffix_length, fname + pos, fname_length - pos + 1);
760 suffixed_fname[fname_length + suffix_length] = '\0';
761
762 actual_fname = suffixed_fname;
763 }
764
765 return find_file(actual_fname, subdir_handle, file_path, error_report);
766 }
767
768 /* -----------------------------------------------------------------
769 * Find a localized version of a filename in subdir (using a data_dir handle).
770 *
771 * The localized versions are to be put in subdirs, using locale names.
772 * For instances, map/titles/fr ou map/titles/de.
773 *
774 * As with gettext(), generalizations of the locale name are tried
775 * in turn. So if the locale is 'fr_FR', but the 'fr_FR' subdir does
776 * not exists, then the 'fr' subdir is checked.
777 *
778 * fills in the (ALLOC'd) string and returns 1 if okay, 0 on error.
779 * file_path's length HAS to be PATH_MAX.
780 * ----------------------------------------------------------------- */
find_localized_file(const char * fname,int subdir_handle,char * file_path,int error_report)781 int find_localized_file(const char *fname, int subdir_handle, char *file_path, int error_report)
782 {
783 #ifdef ENABLE_NLS
784 if (subdir_handle < 0 || subdir_handle >= LAST_DATA_DIR) {
785 error_message(__FUNCTION__, "Called with a wrong subdir handle (%d)",
786 error_report | PLEASE_INFORM, subdir_handle);
787 return 0;
788 }
789
790 char *used_locale = lang_get();
791
792 if (!used_locale || strlen(used_locale) == 0) {
793 return find_file(fname, subdir_handle, file_path, error_report);
794 }
795
796 // A locale name is typically of the form language[_territory][.codeset][@modifier]
797 // We try each possible locale name in turn from the whole one to 'language' only.
798 char *locale = strdup(used_locale);
799 char *sep = "@._";
800 int i;
801
802 for (i = -1; i < (int)strlen(sep); i++) {
803 // 'i == -1' is a special case, to use the full locale name
804 if (i != -1) {
805 char *ptr = strchr(locale, sep[i]);
806 if (!ptr) {
807 continue;
808 }
809 *ptr = '\0';
810 }
811
812 char l10ndir[PATH_MAX];
813 int nb = snprintf(l10ndir, PATH_MAX, "%s/%s", data_dirs[subdir_handle].path, locale);
814 if (nb >= PATH_MAX) {
815 error_message(__FUNCTION__, "Dirname too long (max is %d): %s/%s - Using untranslated version of %s",
816 error_report, PATH_MAX, data_dirs[subdir_handle].path, locale, fname);
817 break;
818 }
819 if (_file_exists(fname, l10ndir, file_path)) {
820 free(locale);
821 return 1;
822 }
823 }
824
825 free(locale);
826 #endif
827
828 // Localized version not found. Use untranslated version.
829 return find_file(fname, subdir_handle, file_path, error_report);
830 }
831
find_encoded_file(const char * fname,int subdir_handle,char * file_path,int error_report)832 int find_encoded_file(const char *fname, int subdir_handle, char *file_path, int error_report)
833 {
834 #ifdef ENABLE_NLS
835 if (subdir_handle < 0 || subdir_handle >= LAST_DATA_DIR) {
836 error_message(__FUNCTION__, "Called with a wrong subdir handle (%d)",
837 error_report | PLEASE_INFORM, subdir_handle);
838 return 0;
839 }
840
841 char *used_encoding = lang_get_encoding();
842
843 if (!used_encoding || !strlen(used_encoding) || !strcmp(used_encoding, "ASCII")) {
844 return find_file(fname, subdir_handle, file_path, error_report);
845 }
846
847 char encoded_dir[PATH_MAX];
848 int nb = snprintf(encoded_dir, PATH_MAX, "%s/%s", data_dirs[subdir_handle].path, used_encoding);
849 if (nb >= PATH_MAX) {
850 error_message(__FUNCTION__, "Dirname too long (max is %d): %s/%s - Using default encoding version of %s",
851 error_report, PATH_MAX, data_dirs[subdir_handle].path, used_encoding, fname);
852 return find_file(fname, subdir_handle, file_path, error_report);
853 }
854 if (_file_exists(fname, encoded_dir, file_path))
855 return TRUE;
856 #endif
857
858 // Encoded version not found. Use default encoding version.
859 return find_file(fname, subdir_handle, file_path, error_report);
860 }
861
862 /**
863 * This function realizes the Pause-Mode: the game process is halted,
864 * while the graphics and animations are not. This mode
865 * can further be toggled from PAUSE to CHEESE, which is
866 * a feature from the original program that should probably
867 * allow for better screenshots.
868 */
Pause(void)869 void Pause(void)
870 {
871 int Pause = TRUE;
872 int cheese = FALSE; /* cheese mode: do not display GAME PAUSED - nicer screenshots */
873 SDL_Event event;
874 SDLKey key;
875
876 Activate_Conservative_Frame_Computation();
877
878 AssembleCombatPicture(DO_SCREEN_UPDATE | SHOW_ITEMS);
879
880 input_get_keybind("pause", &key, NULL);
881
882 while (Pause) {
883 SDL_WaitEvent(&event);
884
885 if (event.type == SDL_QUIT) {
886 Terminate(EXIT_SUCCESS);
887 }
888
889 AssembleCombatPicture(SHOW_ITEMS);
890 if (!cheese) {
891 put_string_centered(Menu_Font, 200, _("GAME PAUSED"));
892 put_string_centered(Menu_Font, 230, _("press p to resume"));
893 }
894 our_SDL_flip_wrapper();
895
896 if (event.type == SDL_KEYDOWN) {
897 if (event.key.keysym.sym == key) {
898 Pause = FALSE;
899 } else if (event.key.keysym.sym == SDLK_c) {
900 cheese = !cheese;
901 }
902 }
903
904 SDL_Delay(10);
905 }
906
907 return;
908 }
909
910 /**
911 * This function prevents any action from taking place in the game world.
912 *
913 * Interaction with the user interface is unaffected.
914 */
freeze_world()915 void freeze_world()
916 {
917 // Because different UI elements may try to freeze the game world,
918 // it's necessary to count how many times this function has been
919 // called.
920 world_is_frozen++;
921 }
922
923 /**
924 * This function unfreezes the game world.
925 *
926 * NOTE: unfreeze_world() must be called for each call to freeze_world().
927 */
unfreeze_world()928 void unfreeze_world()
929 {
930 if (world_is_frozen > 0)
931 world_is_frozen--;
932 }
933
934 /**
935 * This function returns the current state of the game world.
936 * @return TRUE if the game world is currently frozen.
937 */
world_frozen()938 int world_frozen()
939 {
940 return (world_is_frozen > 0);
941 }
942
943 /**
944 * This function starts the time-taking process. Later the results
945 * of this function will be used to calculate the current framerate
946 */
StartTakingTimeForFPSCalculation(void)947 void StartTakingTimeForFPSCalculation(void)
948 {
949 /* This ensures, that 0 is never an encountered framenr,
950 * therefore count to 100 here
951 * Take the time now for calculating the frame rate
952 * (DO NOT MOVE THIS COMMAND PLEASE!) */
953 framenr++;
954
955 One_Frame_SDL_Ticks = SDL_GetTicks();
956 if (framenr % 10 == 1)
957 Ten_Frame_SDL_Ticks = SDL_GetTicks();
958 if (framenr % 100 == 1) {
959 Onehundred_Frame_SDL_Ticks = SDL_GetTicks();
960 }
961 }; // void StartTakingTimeForFPSCalculation(void)
962
963 /**
964 * This function computes the framerate that has been experienced
965 * in this frame. It will be used to correctly calibrate all
966 * movements of game objects.
967 *
968 * NOTE: To query the actual framerate a DIFFERENT function must
969 * be used, namely Frame_Time().
970 */
ComputeFPSForThisFrame(void)971 void ComputeFPSForThisFrame(void)
972 {
973
974 Now_SDL_Ticks = SDL_GetTicks();
975 oneframedelay = Now_SDL_Ticks - One_Frame_SDL_Ticks;
976
977 if (!oneframedelay)
978 FPSover1 = 1000 * 1 / 0.5;
979 else
980 FPSover1 = 1000 * 1 / (float)oneframedelay;
981 }
982
983 /**
984 *
985 * This function is the key to independence of the framerate for various game elements.
986 * It returns the average time needed to draw one frame.
987 * Other functions use this to calculate new positions of moving objects, etc..
988 *
989 * Also there is of course a serious problem when some interruption occurs, like e.g.
990 * the options menu is called or the debug menu is called or the console or the elevator
991 * is entered or a takeover game takes place. This might cause HUGE framerates, that could
992 * box the influencer out of the ship if used to calculate the new position.
993 *
994 * To counter unwanted effects after such events we have the SkipAFewFramerates counter,
995 * which instructs Rate_To_Be_Returned to return only the overall default framerate since
996 * no better substitute exists at this moment. But on the other hand, this seems to
997 * work REALLY well this way.
998 *
999 * This counter is most conveniently set via the function Activate_Conservative_Frame_Computation,
1000 * which can be conveniently called from eveywhere.
1001 *
1002 */
Frame_Time(void)1003 float Frame_Time(void)
1004 {
1005 float Rate_To_Be_Returned;
1006
1007 if (SkipAFewFrames) {
1008 Rate_To_Be_Returned = Overall_Average;
1009 return Rate_To_Be_Returned;
1010 }
1011
1012 Rate_To_Be_Returned = (1.0 / FPSover1);
1013
1014 return Rate_To_Be_Returned;
1015 }
1016
1017 /**
1018 *
1019 * With framerate computation, there is a problem when some interruption occurs, like e.g.
1020 * the options menu is called or the debug menu is called or the console or the elevator
1021 * is entered or a takeover game takes place. This might cause HUGE framerates, that could
1022 * box the influencer out of the ship if used to calculate the new position.
1023 *
1024 * To counter unwanted effects after such events we have the SkipAFewFramerates counter,
1025 * which instructs Rate_To_Be_Returned to return only the overall default framerate since
1026 * no better substitute exists at this moment.
1027 *
1028 * This counter is most conveniently set via the function Activate_Conservative_Frame_Computation,
1029 * which can be conveniently called from everywhere.
1030 *
1031 */
Activate_Conservative_Frame_Computation(void)1032 void Activate_Conservative_Frame_Computation(void)
1033 {
1034 // SkipAFewFrames=212;
1035 // SkipAFewFrames=22;
1036 SkipAFewFrames = 3;
1037
1038 DebugPrintf(1, "\nConservative_Frame_Computation activated!");
1039
1040 }; // void Activate_Conservative_Frame_Computation(void)
1041
1042 /*
1043 * Should be called in every frame when counting FPS
1044 */
update_frames_displayed(void)1045 void update_frames_displayed(void)
1046 {
1047 // The next couter counts the frames displayed by FreedroidRPG during this
1048 // whole run!! DO NOT RESET THIS COUNTER WHEN THE GAME RESTARTS!!
1049 Overall_Frames_Displayed++;
1050 Overall_Average = (Overall_Average * (Overall_Frames_Displayed - 1)
1051 + Frame_Time()) / Overall_Frames_Displayed;
1052
1053 if (SkipAFewFrames)
1054 SkipAFewFrames--;
1055 }
1056
1057 /**
1058 * This function is used to generate an integer in range of all
1059 * numbers from 0 to UpperBound.
1060 */
MyRandom(int UpperBound)1061 int MyRandom(int UpperBound)
1062 {
1063
1064 if (!UpperBound)
1065 return 0;
1066
1067 float tmp;
1068 int PureRandom;
1069 int dice_val; /* the result in [0, UpperBound] */
1070
1071 PureRandom = rand();
1072 tmp = 1.0 * PureRandom / RAND_MAX; /* random number in [0;1] */
1073
1074 /*
1075 * we always round OFF for the resulting int, therefore
1076 * we first add 0.99999 to make sure that UpperBound has
1077 * roughly the same probablity as the other numbers
1078 */
1079 dice_val = (int)(tmp * (1.0 * UpperBound + 0.99999));
1080
1081 return (dice_val);
1082
1083 }; // int MyRandom ( int UpperBound )
1084
1085 /**
1086 * This function teleports the influencer to a new position on the
1087 * ship. THIS CAN BE A POSITION ON A DIFFERENT LEVEL.
1088 */
Teleport(int LNum,float X,float Y,int with_sound_and_fading,int with_animation_reset)1089 void Teleport(int LNum, float X, float Y, int with_sound_and_fading, int with_animation_reset)
1090 {
1091 int old_lvl = Me.pos.z;
1092
1093 // Check if we are in editor and not in game test mode then we store the level number
1094 //
1095 if(game_root_mode == ROOT_IS_LVLEDIT && game_status != INSIDE_GAME)
1096 GameConfig.last_edited_level = LNum;
1097 // Maybe the 'teleport' really comes from a teleportation device or
1098 // teleport spell or maybe even from accessing some sewer access way.
1099 // In that case we'll fade out the screen a bit using the gamma ramp
1100 // and then later back in again. (Note that this is a blocking function
1101 // call, i.e. it will take a second or so each.)
1102 //
1103 if (with_sound_and_fading) {
1104 fade_out_screen();
1105 }
1106
1107 if (LNum != Me.pos.z) {
1108
1109 // In case a real level change has happened,
1110 // we need to do a lot of work. Therefore we start by activating
1111 // the conservative frame time computation to avoid a 'jump'.
1112 //
1113 Activate_Conservative_Frame_Computation();
1114
1115 Me.pos.x = X;
1116 Me.pos.y = Y;
1117 Me.pos.z = LNum;
1118
1119 item_held_in_hand = NULL;
1120
1121 // We add some sanity check against teleporting to non-allowed
1122 // locations (like outside of map that is)
1123 //
1124 if (!level_exists(LNum) || !pos_inside_level(Me.pos.x, Me.pos.y, curShip.AllLevels[LNum])) {
1125 fprintf(stderr, "\n\ntarget location was: lev=%d x=%f y=%f.\n", LNum, X, Y);
1126 fprintf(stderr, "source location was: lev=%d x=%f y=%f.", Me.pos.z, Me.pos.x, Me.pos.y);
1127 error_message(__FUNCTION__, "\
1128 A Teleport was requested, but the location to teleport to lies beyond\n\
1129 the bounds of this 'ship' which means the current collection of levels.\n\
1130 This indicates an error in the map system of FreedroidRPG.", PLEASE_INFORM | IS_FATAL);
1131 }
1132
1133 // Refresh some speed-up data structures
1134 get_visible_levels();
1135
1136 } else {
1137 // If no real level change has occurred, everything
1138 // is simple and we just need to set the new coordinates, haha
1139 //
1140 Me.pos.x = X;
1141 Me.pos.y = Y;
1142
1143 // Teleport could have been called by the leveleditor, due to
1144 // some changes in the current level (light values, for example),
1145 // so we refresh the speed-up data structures
1146 get_visible_levels();
1147 }
1148
1149 // After the teleport, the mouse move target might be
1150 // completely out of date. Therefore we simply delete it. In cases
1151 // where the jump came from crossing a jump threshold (levels glued
1152 // together) we can still restore the move target in that (the calling!)
1153 // function.
1154 //
1155 Me.mouse_move_target.x = Me.pos.x;
1156 Me.mouse_move_target.y = Me.pos.y;
1157 Me.mouse_move_target.z = Me.pos.z;
1158
1159 Me.mouse_move_target_combo_action_type = NO_COMBO_ACTION_SET;
1160
1161 // Animate Tux as standing still
1162 if (with_animation_reset) {
1163 Me.walk_cycle_phase = 0.0;
1164 Me.phase = tux_anim.standing_keyframe;
1165 }
1166
1167 if (with_sound_and_fading) {
1168 teleport_arrival_sound();
1169 }
1170 // Perhaps the player is visiting this level for the first time. Then, the
1171 // tux should make it's initial statement about the location, if there is one.
1172 //
1173 if (!Me.HaveBeenToLevel[Me.pos.z]) {
1174 Me.HaveBeenToLevel[Me.pos.z] = TRUE;
1175 }
1176
1177 switch_background_music(CURLEVEL()->Background_Song_Name);
1178
1179 // Since we've mightily changed position now, we should clear the
1180 // position history, so that no one gets confused...
1181 //
1182 InitInfluPositionHistory();
1183
1184 if (with_sound_and_fading) {
1185 append_new_game_message(_("Arrived at %s."), D_(curShip.AllLevels[Me.pos.z]->Levelname));
1186 fade_in_screen();
1187 }
1188
1189 if (game_status == INSIDE_GAME) {
1190 // Notify level change events on this level.
1191 if (LNum != old_lvl)
1192 event_level_changed(old_lvl, LNum);
1193
1194 // Notify position changed.
1195 event_position_changed(Me.pos, TRUE);
1196 }
1197 }
1198
1199 /**
1200 * Teleport the influencer to the center of a level on the ship
1201 * \param level_num The number of the level where we want to be teleported
1202 */
teleport_to_level_center(int level_num)1203 void teleport_to_level_center(int level_num)
1204 {
1205 // Calculate the center of the level
1206 float x = curShip.AllLevels[level_num]->xlen / 2;
1207 float y = curShip.AllLevels[level_num]->ylen / 2;
1208
1209 // Teleporting to the center of the level
1210 Teleport(level_num, x, y, FALSE, TRUE);
1211 }
1212
1213 /**
1214 * Check if a level exists.
1215 * \param level_num The number of the level.
1216 * \return TRUE if the level exists.
1217 */
level_exists(int level_num)1218 int level_exists(int level_num)
1219 {
1220 if (level_num < 0 || level_num >= curShip.num_levels) {
1221 return FALSE;
1222 }
1223
1224 if (curShip.AllLevels[level_num] == NULL) {
1225 return FALSE;
1226 }
1227
1228 return TRUE;
1229 }
1230
1231 /*----------------------------------------------------------------------
1232 * LoadGameConfig(): load saved options from config-file
1233 *
1234 * this should be the first of all load/save functions called
1235 * as here we read the $HOME-dir and create the config-subdir if necessary
1236 *
1237 *----------------------------------------------------------------------*/
LoadGameConfig(void)1238 int LoadGameConfig(void)
1239 {
1240 char fname[5000];
1241 FILE *configfile;
1242
1243 if (!our_config_dir) {
1244 DebugPrintf(1, "No useble config-dir. No config-loading possible\n");
1245 return (OK);
1246 }
1247
1248 sprintf(fname, "%s/fdrpg.cfg", our_config_dir);
1249 if ((configfile = fopen(fname, "rb")) == NULL) {
1250 fprintf(stderr, "\nUnable to open configuration file %s\n", fname);
1251 lang_set(GameConfig.locale, NULL);
1252 return (ERR);
1253 }
1254
1255 char *stuff = (char *)malloc(FS_filelength(configfile) + 1);
1256 if (fread(stuff, FS_filelength(configfile), 1, configfile) != 1) {
1257 error_message(__FUNCTION__, "\nFailed to read config file: %s.", NO_REPORT, fname);
1258 fclose(configfile);
1259 free(stuff);
1260 return ERR;
1261 }
1262 stuff[FS_filelength(configfile)] = 0;
1263 fclose(configfile);
1264
1265 if (setjmp(saveload_jmpbuf)) {
1266 error_message(__FUNCTION__, "Failed to read config file: %s.", NO_REPORT, fname);
1267 configfile = NULL;
1268 free(stuff);
1269 ResetGameConfigToDefaultValues();
1270 return ERR;
1271 }
1272
1273 load_freedroid_configuration(stuff);
1274 lang_set(GameConfig.locale, NULL);
1275
1276 configfile = NULL;
1277 free(stuff);
1278
1279 if (!GameConfig.freedroid_version_string || strcmp(GameConfig.freedroid_version_string, VERSION)) {
1280 error_message(__FUNCTION__, "\
1281 Settings file found in your ~/.freedroid_rpg dir does not\n\
1282 seem to be from the same version as this installation of FreedroidRPG.\n\
1283 This is perfectly normal if you have just upgraded your version of\n\
1284 FreedroidRPG. However, the loading of your settings will be canceled now,\n\
1285 because the format of the settings file is no longer supported.\n\
1286 No need to panic. The default settings will be used instead and a new\n\
1287 settings file will be generated.", NO_REPORT);
1288 ResetGameConfigToDefaultValues();
1289 return (ERR);
1290 };
1291
1292 // Now we will turn off the skills and inventory screen and that, cause
1293 // this should be off when the game starts...
1294 //
1295 GameConfig.CharacterScreen_Visible = FALSE;
1296 GameConfig.Inventory_Visible = FALSE;
1297 GameConfig.SkillScreen_Visible = FALSE;
1298 GameConfig.skill_explanation_screen_visible = FALSE;
1299 GameConfig.Automap_Visible = TRUE;
1300
1301 return (OK);
1302 }
1303
1304 /*----------------------------------------------------------------------
1305 * SaveGameConfig: do just that
1306 * Return: -1 on error, -2 if immediate exit is needed, 0 otherwise
1307 *----------------------------------------------------------------------*/
SaveGameConfig(void)1308 int SaveGameConfig(void)
1309 {
1310 char fname[5000];
1311 int current_width;
1312 int current_height;
1313 FILE *config_file;
1314
1315 // Maybe the Terminate function was invoked BEFORE the startup process
1316 // was complete at all (like e.g. some illegal command line parameter).
1317 // Then the config dir is not initialized. We catch this case and return
1318 // control to the operating system immediately if that happens...
1319
1320 if (our_config_dir == NULL || our_config_dir[0] == '\0') {
1321 DebugPrintf(-4, "It seems that the game couldn't start up at all... therefore we need not save any configuration information.\n");
1322 return -2;
1323 }
1324
1325 // Now we know, that the config dir has been initialized already.
1326 // That indicates, that the game did start up already.
1327 // Therefore we can do the normal save config stuff...
1328
1329 sprintf(fname, "%s/fdrpg.cfg", our_config_dir);
1330 if ((config_file = fopen(fname, "wb")) == NULL) {
1331 DebugPrintf(-4, "Unable to open configuration file %s for writing\n", fname);
1332 return -1;
1333 }
1334
1335 // We put the current version number of FreedroidRPG into the
1336 // version number string. This will be useful so that later
1337 // versions of FreedroidRPG can identify old config files and decide
1338 // not to use them in some cases.
1339 //
1340 if (GameConfig.freedroid_version_string) {
1341 free(GameConfig.freedroid_version_string);
1342 }
1343 GameConfig.freedroid_version_string = strdup(VERSION);
1344
1345 // We preserve the current resolution, modify it a bit, such that
1346 // the preselected resolution will come to effect next time, save
1347 // it and then we restore the current settings again.
1348 //
1349 current_width = GameConfig.screen_width;
1350 current_height = GameConfig.screen_height;
1351 GameConfig.screen_width = GameConfig.next_time_width_of_screen;
1352 GameConfig.screen_height = GameConfig.next_time_height_of_screen;
1353
1354 // Now write the actual data
1355 savestruct_autostr = alloc_autostr(4096);
1356 save_freedroid_configuration(savestruct_autostr);
1357 if (fwrite(savestruct_autostr->value, savestruct_autostr->length, 1, config_file) != 1) {
1358 error_message(__FUNCTION__, "Failed to write configuration file: %s", NO_REPORT, fname);
1359 free_autostr(savestruct_autostr);
1360 fclose(config_file);
1361 return -1;
1362 }
1363
1364 free_autostr(savestruct_autostr);
1365
1366 GameConfig.screen_width = current_width;
1367 GameConfig.screen_height = current_height;
1368 fclose(config_file);
1369
1370 return 0;
1371 }
1372
free_memory_before_exit(void)1373 static void free_memory_before_exit(void)
1374 {
1375 // free the entities
1376 clear_volatile_obstacles();
1377 clear_enemies();
1378 clear_npcs();
1379 free_tux();
1380
1381 // free the widgets
1382 free_game_ui();
1383 free_lvledit_ui();
1384 free_chat_widgets();
1385 widget_free_image_resources();
1386
1387 // free animations lists and visible levels
1388 reset_visible_levels();
1389 clear_animated_floor_tile_list();
1390
1391 // other stuff
1392 delete_events();
1393 free_current_ship();
1394 leveleditor_cleanup();
1395 free_error_msg_store();
1396 }
1397
1398 /**
1399 * This function is used for terminating freedroid. It will close
1400 * the SDL submodules and exit.
1401 */
Terminate(int exit_code)1402 void Terminate(int exit_code)
1403 {
1404 if (!do_benchmark) {
1405 printf("\n---------------------------------------------------------------------------------");
1406 printf("\nTermination of freedroidRPG initiated... ");
1407 }
1408
1409 // Save the config file only in case of success.
1410
1411 if (exit_code == EXIT_SUCCESS) {
1412 if (SaveGameConfig() == -2) {
1413 exit_code = -1;
1414 goto IMMEDIATE_EXIT;
1415 }
1416 }
1417
1418 // Close active lua states, to force a call to garbage collector, in order
1419 // to call Lua binding 'destructors', and clean all the stuff
1420
1421 close_lua();
1422 close_audio();
1423 free_memory_before_exit();
1424
1425 if (!do_benchmark) {
1426 printf("Thank you for playing freedroidRPG.\n\n");
1427 }
1428
1429 IMMEDIATE_EXIT:
1430
1431 if (SDL_WasInit(SDL_INIT_EVERYTHING))
1432 SDL_Quit();
1433
1434 // If the game was not run from the command line, we should open an editor with
1435 // the last debug output, since people in general won't know how and where
1436 // to find the material for proper reporting of bugs.
1437
1438 if (!run_from_term) {
1439 if (exit_code == EXIT_FAILURE) {
1440 int rtn;
1441 fflush(stdout);
1442 fflush(stderr);
1443 char *cmd = MyMalloc(strlen(our_config_dir) + strlen(OPENTXT_CMD) + 20);
1444 sprintf(cmd, "%s %s/fdrpg_out.txt", OPENTXT_CMD, our_config_dir);
1445 rtn = system(cmd);
1446 if (rtn == -1) // We use the return value mainly to avoid a compilation warning
1447 DebugPrintf(-1, "system call failed: \"%s\" returned %d", cmd, rtn);
1448 free(cmd);
1449 }
1450 }
1451
1452 // Now we drop control back to the operating system. The FreedroidRPG
1453 // program has finished.
1454
1455 exit(exit_code);
1456 }
1457
1458 /**
1459 * Return a pointer towards the obstacle designated by the given (unique) label.
1460 * \param level_number If not NULL, this is set to the levelnumber where the obstacle was found.
1461 */
give_pointer_to_obstacle_with_label(const char * obstacle_label,int * level_number)1462 obstacle *give_pointer_to_obstacle_with_label(const char *obstacle_label, int *level_number)
1463 {
1464 int i, j;
1465
1466 // On each level, browse the obstacle extensions until we find the label we are looking for
1467 for (i = 0; i < curShip.num_levels; i++) {
1468 level *l = curShip.AllLevels[i];
1469
1470 if (l == NULL)
1471 continue;
1472
1473 for (j = 0; j < l->obstacle_extensions.size; j++) {
1474 struct obstacle_extension *ext = &ACCESS_OBSTACLE_EXTENSION(l->obstacle_extensions, j);
1475
1476 if (ext->type == OBSTACLE_EXTENSION_LABEL) {
1477 if (!strcmp(ext->data, obstacle_label)) {
1478 if (level_number) {
1479 *level_number = l->levelnum;
1480 }
1481 return ext->obs;
1482 }
1483 }
1484
1485 }
1486 }
1487
1488 error_message(__FUNCTION__, "Obstacle label \"%s\" was not found on the map.", PLEASE_INFORM | IS_FATAL, obstacle_label);
1489 return NULL;
1490 }
1491
1492 /*----------------------------------------------------------------------
1493 * Get the power of 2 greater than of equal to the argument
1494 * http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
1495 *----------------------------------------------------------------------*/
pot_gte(uint32_t v)1496 uint32_t pot_gte(uint32_t v)
1497 {
1498 uint32_t pot = v;
1499 --pot;
1500 pot |= pot >> 1;
1501 pot |= pot >> 2;
1502 pot |= pot >> 4;
1503 pot |= pot >> 8;
1504 pot |= pot >> 16;
1505 return (++pot);
1506
1507 } // uint32_t pot_gte( uint32_t v)
1508
1509 /*
1510 * Some systems do not have setenv().
1511 * On those systems, we use putenv().
1512 */
fd_setenv(const char * var,const char * val,int overwrite)1513 int fd_setenv(const char *var, const char *val, int overwrite)
1514 {
1515 int ret;
1516
1517 #ifdef HAVE_SETENV
1518 ret = setenv(var, val, overwrite);
1519 #else
1520 // putenv does not make a copy of its argument, and inserts the pointer to
1521 // the argument into the environment array.
1522 // The following not trivial code is needed to avoid memleak.
1523 // (see POS34-C on https://www.securecoding.cert.org)
1524 static char *oldenv = NULL;
1525 const size_t len = strlen(var) + 1 + strlen(val) + 2;
1526 char *env = (char *)MyMalloc(len);
1527 snprintf(env, len, "%s=%s", var, val);
1528 ret = putenv(env);
1529 if (ret != 0) {
1530 free(env);
1531 error_message(__FUNCTION__, "Error when calling putenv() to set %s to %s\n", PLEASE_INFORM, var, val);
1532 return ret;
1533 }
1534 if (oldenv != NULL) {
1535 free(oldenv);
1536 }
1537 oldenv = env;
1538 #endif
1539 return ret;
1540 }
1541
1542 /*
1543 * Some systems do not have unsetenv().
1544 * On those systems, we use putenv().
1545 */
fd_unsetenv(const char * var)1546 int fd_unsetenv(const char *var)
1547 {
1548 int ret;
1549
1550 #ifdef HAVE_UNSETENV
1551 ret = unsetenv(var);
1552 #else
1553 // See fdrpg_setenv() comment
1554
1555 static char *oldenv = NULL;
1556 const size_t len = strlen(var) + 2;
1557 char *env = (char *)MyMalloc(len);
1558 snprintf(env, len, "%s=", var);
1559 ret = putenv(env);
1560 if (ret != 0) {
1561 free(env);
1562 error_message(__FUNCTION__, "Error when calling putenv() to unset %s\n", PLEASE_INFORM, var);
1563 return ret;
1564 }
1565 if (oldenv != NULL) {
1566 free(oldenv);
1567 }
1568 oldenv = env;
1569 #endif
1570 return ret;
1571 }
1572
1573 #undef _misc_c
1574