Merge lp:~widelands-dev/widelands/autosave_on_objectives into lp:widelands

Proposed by cghislai
Status: Merged
Merged at revision: 6639
Proposed branch: lp:~widelands-dev/widelands/autosave_on_objectives
Merge into: lp:widelands
Diff against target: 902 lines (+263/-216)
8 files modified
campaigns/tutorial01.wmf/scripting/init.lua (+48/-48)
src/logic/game.h (+0/-4)
src/save_handler.cc (+33/-17)
src/save_handler.h (+12/-4)
src/scripting/lua_game.cc (+19/-4)
src/scripting/lua_root.cc (+27/-27)
src/scripting/lua_root.h (+2/-0)
test/lua/persistence.wmf/scripting/init.lua (+122/-112)
To merge this branch: bzr merge lp:~widelands-dev/widelands/autosave_on_objectives
Reviewer Review Type Date Requested Status
SirVer Approve
Review via email: mp+174546@code.launchpad.net

Description of the change

I just applied the patch provided in the bug report and removed the option checkbox.

Concerning the new lua function, i added it to the const static struct luaL_reg wlgame [] and was able to use it in a newly created win condition, so i guess this was ok?

To post a comment you must log in.
Revision history for this message
SirVer (sirver) wrote :

I pushed my review of this branch in r6613. You can find all my comments by grep-ing for #cghislai. I changed some nits around (see the diff) and added a short comment when I found it appropriate - feel free to just delete them again, they are for your eyes only :). I did not touch anything that would affect the logic because you are more suited to do this as you know your code better.

I think the code looks very reasonable already. The biggest issues is that we now have wl.Game():save and wl.game.save_game - the first one is only used in the persistence.wmf test (see tests/lua/persistence.wmf/scripting/init.lua). I think you should move your logic to the method that is already there and make sure that its new features are exercised in the persistence tests - comments are in the source code as well.

review: Needs Fixing
Revision history for this message
cghislai (charlyghislain) wrote :

I'm a bit lost in the lua tests. It appears I cannot make it run even with trunk code :
"could not load "test/lua/persistence.wmf"

Could you check it? I will try to make a save-in-corountine test afterwards.

I refactored m_allow_autosaving to m_allow_saving, because i guess we have to prevent manual saves as well then, right?

I removed some comments i grasped, and left some that may still be useful

Revision history for this message
SirVer (sirver) wrote :

I looked over the code again and removed more of my comments that you adressed or that I made up my mind about :). One more thing: If you change a Lua method make sure that you update where it is used in the code (i.e. in all *.lua files) and add in a backwards compatible version (I did this for this change). The reason for this is that the Lua interface is a kind of public API that people should rely on and that should not change to quickly so that they have time to adapt - there is no proper deprecation warning for this yet, for now I just simply added a comment.

These methods must be around for at least three builds - the reason is that the lua code is saved into savegames, so if the API changes and a player tries to reload a scenario with older code, the API calls must still be around.

About the test running, the comments in the file where slightly outdated. I updated them to this:
$PATH_TO_WIDELANDS_BINARY --datadir="." --nozip --scenario=test/lua/persistence.wmf && $PATH_TO_WIDELANDS_BINARY --datadir="." --loadgame=~/.widelands/save/lua_persistence.wgf

of course a rm -rf ~/.widelands/save/lua_persistence.wgf should be a good idea as well.

Could you try again if this works for you? You have to be in the root directory of the repository for this to work.

Revision history for this message
cghislai (charlyghislain) wrote :

Running the coroutine test I get a PANIC: unprotected error in call to Lua API (Attempt to persist a C function) which I'm unable to track down...
I'm not very familiar with lua or scripting at that low level (yet).

In the meantime, the former persistence test passed, I think i was trying with the system executable.

Revision history for this message
SirVer (sirver) wrote :

This is the error you get when Lua tries to persist a running coroutine. So this is precisely the error you want to fix :).

In trunk, the save routine must only be called when no coroutine is running, i.e. outside of any coroutine, i.e. when the script is run the first time. This is not very flexible and means that you can essentially not save from inside Lua. I did it because I needed a save routine for the persistence test and this did the job.

Your code now should be able to handle this though, because as soon as you hit the main loop and poll if the game should be saved, there cannot be a Lua coroutine running anymore, so saving should work.

Revision history for this message
cghislai (charlyghislain) wrote :

The autosave seems to work as expected in campaign. Also calling wl.Game():save("test") in a win condition script worked. The coroutine_persistence.wmf test passed, but please check that it tests correctly.

Revision history for this message
SirVer (sirver) wrote :

I did slight modifications to the test and added a bunch of comments - I found the reason why the test did work for you and then didn't and did again: lunit did define some handles to cfunctions, so it needs to be pulled in after loading :).

I will merge this as soon as i get good internet again - seems impossible in this train right now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'campaigns/tutorial01.wmf/scripting/init.lua'
2--- campaigns/tutorial01.wmf/scripting/init.lua 2010-11-12 12:42:11 +0000
3+++ campaigns/tutorial01.wmf/scripting/init.lua 2013-07-19 21:32:25 +0000
4@@ -32,7 +32,7 @@
5 use("map", "texts")
6
7 -- =================
8--- Helper functions
9+-- Helper functions
10 -- =================
11
12 -- A small helper class to disable/enable autosaving and user interaction
13@@ -49,14 +49,14 @@
14 function UserInputDisabler:establish_blocks()
15 self._ui_state = wl.ui.get_user_input_allowed()
16 local game = wl.Game()
17- self._as_state = game.allow_autosaving
18+ self._as_state = game.allow_saving
19
20 wl.ui.set_user_input_allowed(false)
21- wl.Game().allow_autosaving = false
22+ wl.Game().allow_saving = false
23 end
24 function UserInputDisabler:lift_blocks()
25 wl.ui.set_user_input_allowed(self._ui_state)
26- wl.Game().allow_autosaving= self._as_state
27+ wl.Game().allow_saving = self._as_state
28 end
29
30 function _try_add_objective(i)
31@@ -108,7 +108,7 @@
32 sleep(130)
33 return o
34 end
35-
36+
37 function click_on_field(f, g_T, g_sleeptime)
38 local sleeptime = g_sleeptime or 500
39
40@@ -125,7 +125,7 @@
41
42 function click_on_panel(panel, g_T, g_sleeptime)
43 local sleeptime = g_sleeptime or 500
44-
45+
46 local blocker = UserInputDisabler:new()
47
48 sleep(sleeptime)
49@@ -143,7 +143,7 @@
50 function warp_houses(descriptions)
51 local blocker = UserInputDisabler:new()
52
53- for idx, d in ipairs(descriptions) do
54+ for idx, d in ipairs(descriptions) do
55 local name, x, y = d[1], d[2], d[3]
56 mouse_smoothly_to(wl.Game().map:get_field(x, y))
57 sleep(300)
58@@ -167,7 +167,7 @@
59
60 function build_road(field, ...)
61 -- Build a road by clicking the UI. A little faster than before
62-
63+
64 -- Make sure that there is room for the road: Rip all immovables
65 local cf = field
66 for idx, d in ipairs{...} do
67@@ -206,11 +206,11 @@
68 end
69 end
70 end
71-
72+
73 function build_eastern_trainings_area(citadel_field)
74 -- Build some infrastructure as another example
75 local blocker = UserInputDisabler:new()
76-
77+
78 plr:reveal_fields(citadel_field:region(8))
79 scroll_smoothly_to(wl.Game().map:get_field(21,9))
80 scroll_smoothly_to(citadel_field)
81@@ -262,16 +262,16 @@
82 -- add buildwares to the warehouse
83 local ts = map:get_field(31,56).immovable
84 ts:set_wares(ts.valid_wares)
85-
86+
87 scroll_smoothly_to(citadel_field)
88
89 blocker:lift_blocks()
90 end
91-
92+
93 -- Remove all stones in a given environment. This is done
94 -- in a loop for a nice optical effect
95 function remove_all_stones(fields, g_sleeptime)
96- local sleeptime = g_sleeptime or 150
97+ local sleeptime = g_sleeptime or 150
98 local map = wl.Game().map
99 while #fields > 0 do
100 local idx = math.random(#fields)
101@@ -283,7 +283,7 @@
102 if n then
103 n = tonumber(n)
104 f.immovable:remove()
105- if n > 1 then
106+ if n > 1 then
107 remove_field = false
108 map:place_immovable("stones" .. n-1, f)
109 end
110@@ -298,7 +298,7 @@
111 end
112
113 -- ==============
114--- Sentry Thread
115+-- Sentry Thread
116 -- ==============
117 -- This thread makes sure that the player does not build stuff where he
118 -- is not supposed to. He gets a message box when he tries and what he build
119@@ -345,7 +345,7 @@
120 sleep(1000)
121 end
122 end
123-
124+
125 -- Allows constructionsites for the given buildings, all others are invalid
126 -- as is any other immovable build by the player
127 function allow_constructionsite(i, buildings)
128@@ -366,7 +366,7 @@
129 end
130
131 -- ================
132--- Message threads
133+-- Message threads
134 -- ================
135 function starting_infos()
136 sleep(100)
137@@ -418,7 +418,7 @@
138 click_on_field(wl.Game().map.player_slots[1].starting_field.brn)
139
140 msg_box(lumberjack_message_04)
141-
142+
143 register_immovable_as_allowed(first_lumberjack_field.immovable) -- hut + flag
144
145 local f = wl.Game().map:get_field(14,11)
146@@ -429,25 +429,25 @@
147 blocker:lift_blocks()
148
149 sleep(15000)
150-
151+
152 local o = msg_box(lumberjack_message_05)
153
154 local blocker = UserInputDisabler:new()
155-
156+
157 local f = wl.Game().map:get_field(14,11)
158 scroll_smoothly_to(f)
159 mouse_smoothly_to(f)
160-
161+
162 blocker:lift_blocks()
163-
164+
165 -- Wait for flag
166 while not (f.immovable and f.immovable.type == "flag") do sleep(300) end
167 o.done = true
168
169 sleep(300)
170-
171+
172 msg_box(lumberjack_message_06)
173-
174+
175 while #plr:get_buildings("lumberjacks_hut") < 1 do sleep(300) end
176
177 msg_box(lumberjack_message_07)
178@@ -458,7 +458,7 @@
179 function learn_to_move()
180 -- Teaching the user how to scroll on the map
181 local o = msg_box(inform_about_stones)
182-
183+
184 function _wait_for_move()
185 local cx = wl.ui.MapView().viewpoint_x
186 local cy = wl.ui.MapView().viewpoint_y
187@@ -477,12 +477,12 @@
188 _wait_for_move()
189 o.done = true
190 sleep(3000) -- Give the player a chance to try this some more
191-
192+
193 msg_box(congratulate_and_on_to_quarry)
194
195 build_a_quarry()
196 end
197-
198+
199 function build_a_quarry()
200 sleep(200)
201
202@@ -505,12 +505,12 @@
203
204 local function _rip_road()
205 for idx,f in ipairs(cs.fields[1].brn:region(2)) do
206- if f.immovable and f.immovable.type == "road" then
207+ if f.immovable and f.immovable.type == "road" then
208 click_on_field(f)
209 click_on_panel(wl.ui.MapView().windows.
210 field_action.buttons.destroy_road, 300)
211 sleep(200)
212- return
213+ return
214 end
215 end
216 end
217@@ -527,17 +527,17 @@
218 click_on_field(map:get_field(11,12))
219 click_on_field(map:get_field(12,12))
220 click_on_field(map:get_field(12,11))
221-
222+
223 sleep(3000)
224
225 _rip_road()
226-
227+
228 msg_box(talk_about_roadbuilding_01)
229 -- Showoff direct roadbuilding
230 click_on_field(cs.fields[1].brn)
231 click_on_panel(wl.ui.MapView().windows.field_action.buttons.build_road, 300)
232 click_on_field(map.player_slots[1].starting_field.brn)
233-
234+
235 sleep(3000)
236
237 _rip_road()
238@@ -545,12 +545,12 @@
239 blocker:lift_blocks()
240
241 local o = msg_box(talk_about_roadbuilding_02)
242-
243+
244 -- From now on, the player can build whatever he wants
245 terminate_bad_boy_sentinel = true
246-
247+
248 -- Wait a while
249- sleep( 100*1000 )
250+ sleep( 100*1000 )
251
252 -- Interludium: talk about census and statistics
253 census_and_statistics(cs.fields[1])
254@@ -560,17 +560,17 @@
255
256 messages()
257 end
258-
259+
260 function census_and_statistics(field)
261 sleep(15000)
262-
263+
264 local blocker = UserInputDisabler:new()
265
266 wl.ui.MapView().census = false
267 wl.ui.MapView().statistics = false
268-
269+
270 wl.ui.MapView():abort_road_building()
271-
272+
273 msg_box(census_and_statistics_00)
274 -- Pick any empty field
275 local function _pick_empty_field()
276@@ -591,7 +591,7 @@
277 click_on_panel(wl.ui.MapView().windows.field_action.buttons.statistics)
278
279 msg_box(census_and_statistics_01)
280-
281+
282 blocker:lift_blocks()
283 end
284
285@@ -611,7 +611,7 @@
286 -- Wait for messages window to close
287 while wl.ui.MapView().windows.messages do sleep(300) end
288 o.done = true
289-
290+
291 msg_box(closing_msg_window_01)
292
293 -- Remove all stones
294@@ -627,10 +627,10 @@
295 expansion()
296 end
297
298-function expansion()
299+function expansion()
300 -- Teach about expanding your territory.
301 sleep(10)
302-
303+
304 -- This is not really needed since the stones are already removed, but if
305 -- we're debugging and we start with this function it is most useful to have
306 -- the stones away already
307@@ -655,7 +655,7 @@
308 while #fields > 0 do
309 local idx = math.random(#fields)
310 local f = fields[idx]
311-
312+
313 if f.terr:match("berg%d+") and f.terd:match("berg%d+") then
314 if pcall(function() plr:place_flag(f) end) then
315 f.immovable:remove()
316@@ -678,7 +678,7 @@
317 end
318
319 scroll_smoothly_to(conquer_field)
320-
321+
322 local dest = _find_good_flag_position()
323 local start = _find_nearby_flag()
324
325@@ -699,7 +699,7 @@
326 local function _wait_for_some_resi(wanted)
327 while 1 do
328 local cnt = 0
329- for idx, f in ipairs(dest:region(6)) do
330+ for idx, f in ipairs(dest:region(6)) do
331 if f.immovable and f.immovable.name:sub(1,4) == "resi" then
332 cnt = cnt + 1
333 if cnt >= wanted then return end
334@@ -720,7 +720,7 @@
335 function training()
336 -- Teach about trainingsites and soldiers
337 sleep(300)
338-
339+
340 msg_box(warfare_and_training_00)
341
342 local citadel_field = wl.Game().map:get_field(31, 63)
343@@ -734,7 +734,7 @@
344 scroll_smoothly_to(citadel_field)
345
346 local o = msg_box(enhance_fortress)
347- while not (citadel_field.immovable and
348+ while not (citadel_field.immovable and
349 citadel_field.immovable.name == "citadel") do sleep(800) end
350 o.done = true
351
352
353=== modified file 'src/logic/game.h'
354--- src/logic/game.h 2013-07-14 17:05:25 +0000
355+++ src/logic/game.h 2013-07-19 21:32:25 +0000
356@@ -90,10 +90,6 @@
357 friend struct ::Game_Main_Menu_Load_Game;
358 friend struct ::WLApplication;
359
360- // This friend is for legacy reasons and should probably be removed
361- // at least after summer 2008, maybe even earlier.
362- friend struct Game_Interactive_Player_Data_Packet;
363-
364 Game();
365 ~Game();
366
367
368=== modified file 'src/save_handler.cc'
369--- src/save_handler.cc 2013-07-16 13:26:14 +0000
370+++ src/save_handler.cc 2013-07-19 21:32:25 +0000
371@@ -39,19 +39,35 @@
372 */
373 void SaveHandler::think(Widelands::Game & game, int32_t realtime) {
374 initialize(realtime);
375-
376- if (not m_allow_autosaving) // Is autosaving allowed atm?
377- return;
378-
379- int32_t const autosaveInterval =
380- g_options.pull_section("global").get_int
381- ("autosave", DEFAULT_AUTOSAVE_INTERVAL * 60);
382- if (autosaveInterval <= 0)
383- return; // no autosave requested
384-
385- int32_t const elapsed = (realtime - m_last_saved_time) / 1000;
386- if (elapsed < autosaveInterval)
387- return;
388+ std::string filename = "wl_autosave";
389+
390+ if (!m_allow_saving) {
391+ return;
392+ }
393+
394+ if (m_save_requested) {
395+ if (!m_save_filename.empty()) {
396+ filename = m_save_filename;
397+ }
398+
399+ log("Autosave: save requested : %s\n", filename.c_str());
400+ m_save_requested = false;
401+ m_save_filename = "";
402+ } else {
403+ const int32_t autosave_interval_in_seconds =
404+ g_options.pull_section("global").get_int
405+ ("autosave", DEFAULT_AUTOSAVE_INTERVAL * 60);
406+ if (autosave_interval_in_seconds <= 0) {
407+ return; // no autosave requested
408+ }
409+
410+ const int32_t elapsed = (realtime - m_last_saved_time) / 1000;
411+ if (elapsed < autosave_interval_in_seconds) {
412+ return;
413+ }
414+
415+ log("Autosave: interval elapsed (%d s), saving\n", elapsed);
416+ }
417
418 // TODO: defer saving to next tick so that this message is shown
419 // before the actual save, or put the saving logic in another thread
420@@ -59,20 +75,20 @@
421 (_("Saving game..."));
422
423 // save the game
424- std::string complete_filename =
425- create_file_name (get_base_dir(), "wl_autosave");
426+ const std::string complete_filename = create_file_name(get_base_dir(), filename);
427 std::string backup_filename;
428
429 // always overwrite a file
430 if (g_fs->FileExists(complete_filename)) {
431- backup_filename = create_file_name (get_base_dir(), "wl_autosave2");
432+ filename += "2";
433+ backup_filename = create_file_name (get_base_dir(), filename);
434 if (g_fs->FileExists(backup_filename)) {
435 g_fs->Unlink(backup_filename);
436 }
437 g_fs->Rename(complete_filename, backup_filename);
438 }
439
440- static std::string error;
441+ std::string error;
442 if (!save_game(game, complete_filename, &error)) {
443 log("Autosave: ERROR! - %s\n", error.c_str());
444 game.get_ipl()->get_chat_provider()->send_local
445
446=== modified file 'src/save_handler.h'
447--- src/save_handler.h 2013-07-14 11:48:13 +0000
448+++ src/save_handler.h 2013-07-19 21:32:25 +0000
449@@ -32,7 +32,8 @@
450
451 class SaveHandler {
452 public:
453- SaveHandler() : m_last_saved_time(0), m_initialized(false), m_allow_autosaving(true) {}
454+ SaveHandler() : m_last_saved_time(0), m_initialized(false), m_allow_saving(true),
455+ m_save_requested(false), m_save_filename("") {}
456 void think(Widelands::Game &, int32_t currenttime);
457 std::string create_file_name(std::string dir, std::string filename);
458 bool save_game
459@@ -43,13 +44,20 @@
460 static std::string get_base_dir() {return "save";}
461 const std::string get_cur_filename() {return m_current_filename;}
462 void set_current_filename(std::string filename) {m_current_filename = filename;}
463- void set_allow_autosaving(bool t) {m_allow_autosaving = t;}
464- bool get_allow_autosaving() {return m_allow_autosaving;}
465+ void set_allow_saving(bool t) {m_allow_saving = t;}
466+ bool get_allow_saving() {return m_allow_saving;}
467+ void request_save(std::string filename = "")
468+ {
469+ m_save_requested = true;
470+ m_save_filename = filename;
471+ }
472
473 private:
474 int32_t m_last_saved_time;
475 bool m_initialized;
476- bool m_allow_autosaving;
477+ bool m_allow_saving;
478+ bool m_save_requested;
479+ std::string m_save_filename;
480 std::string m_current_filename;
481
482 void initialize(int32_t currenttime);
483
484=== modified file 'src/scripting/lua_game.cc'
485--- src/scripting/lua_game.cc 2013-07-15 05:18:12 +0000
486+++ src/scripting/lua_game.cc 2013-07-19 21:32:25 +0000
487@@ -470,7 +470,7 @@
488 uint32_t cspeed = game.gameController()->desiredSpeed();
489 game.gameController()->setDesiredSpeed(0);
490
491- game.save_handler().set_allow_autosaving(false);
492+ game.save_handler().set_allow_saving(false);
493
494 Story_Message_Box * mb =
495 new Story_Message_Box
496@@ -486,7 +486,7 @@
497
498 game.gameController()->setDesiredSpeed(cspeed);
499
500- game.save_handler().set_allow_autosaving(true);
501+ game.save_handler().set_allow_saving(true);
502
503 return 1;
504 }
505@@ -1062,8 +1062,10 @@
506 .. attribute:: done
507
508 (RW) defines if this objective is already fulfilled. If done is
509- :const`true`, the objective will not be shown to the user, no matter what
510- :attr:`visible` is set to.
511+ :const`true`, the objective will not be shown to the user, no matter what.
512+ :attr:`visible` is set to. A savegame will be created when this attribute
513+ is changed to :const`true`.
514+
515 */
516 int L_Objective::get_done(lua_State * L) {
517 Objective & o = get(L, get_game(L));
518@@ -1073,6 +1075,19 @@
519 int L_Objective::set_done(lua_State * L) {
520 Objective & o = get(L, get_game(L));
521 o.set_done(luaL_checkboolean(L, -1));
522+
523+ const int32_t autosave = g_options.pull_section("global").get_int("autosave", 0);
524+ if (autosave <= 0) {
525+ return 0;
526+ }
527+
528+ if (o.done()) {
529+ std::string filename = get_egbase(L).get_map()->get_name();
530+ char buffer[128];
531+ snprintf(buffer, sizeof(buffer), _(" (achieved %s)"), o.descname().c_str());
532+ filename.append(buffer);
533+ get_game(L).save_handler().request_save(filename);
534+ }
535 return 0;
536 }
537
538
539=== modified file 'src/scripting/lua_root.cc'
540--- src/scripting/lua_root.cc 2012-06-08 22:33:16 +0000
541+++ src/scripting/lua_root.cc 2013-07-19 21:32:25 +0000
542@@ -82,6 +82,7 @@
543 PROP_RO(L_Game, time),
544 PROP_RW(L_Game, desired_speed),
545 PROP_RW(L_Game, allow_autosaving),
546+ PROP_RW(L_Game, allow_saving),
547 {0, 0, 0},
548 };
549
550@@ -131,22 +132,33 @@
551 }
552
553 /* RST
554- .. attribute:: allow_autosaving
555+ .. attribute:: allow_saving
556
557- (RW) Disable or enable auto-saving. When you show off UI features in a
558- tutorial or scenario, you have to disallow auto-saving because UI
559+ (RW) Disable or enable saving. When you show off UI features in a
560+ tutorial or scenario, you have to disallow saving because UI
561 elements can not be saved and therefore reloading a game saved in the
562 meantime would crash the game.
563 */
564 // UNTESTED
565+int L_Game::set_allow_saving(lua_State * L) {
566+ get_game(L).save_handler().set_allow_saving
567+ (luaL_checkboolean(L, -1));
568+ return 0;
569+}
570+// UNTESTED
571+int L_Game::get_allow_saving(lua_State * L) {
572+ lua_pushboolean(L, get_game(L).save_handler().get_allow_saving());
573+ return 1;
574+}
575 int L_Game::set_allow_autosaving(lua_State * L) {
576- get_game(L).save_handler().set_allow_autosaving
577+ // WAS_DEPRECATED_BEFORE(build18), use allow_saving
578+ get_game(L).save_handler().set_allow_saving
579 (luaL_checkboolean(L, -1));
580 return 0;
581 }
582-// UNTESTED
583 int L_Game::get_allow_autosaving(lua_State * L) {
584- lua_pushboolean(L, get_game(L).save_handler().get_allow_autosaving());
585+ // WAS_DEPRECATED_BEFORE(build18), use allow_saving
586+ lua_pushboolean(L, get_game(L).save_handler().get_allow_saving());
587 return 1;
588 }
589
590@@ -192,35 +204,23 @@
591 /* RST
592 .. method:: save(name)
593
594- Saves the game exactly as if the player had entered the save dialog and
595- entered name as an argument. If some error occurred while saving, this
596- will throw an Lua error. Note that this currently doesn't work when
597- called from inside a Coroutine.
598+ Requests a savegame. Note that the actual save will be performed
599+ later, and that you have no control over any error that may happen
600+ by then currently.
601
602- :arg name: name of save game. If this game already exists, it will be
603- silently overwritten
604+ :arg name: name of save game, as if entered in the save dialog.
605+ If this game already exists, it will be silently overwritten.
606+ If empty, the autosave name will be used.
607 :type name: :class:`string`
608 :returns: :const:`nil`
609 */
610-int L_Game::save(lua_State * const L) {
611- Widelands::Game & game = get_game(L);
612-
613- std::string const complete_filename =
614- game.save_handler().create_file_name
615- (SaveHandler::get_base_dir(), luaL_checkstring(L, -1));
616-
617- lua_pop(L, 2); // Make stack empty before persistence starts.
618-
619- if (g_fs->FileExists(complete_filename))
620- g_fs->Unlink(complete_filename);
621- std::string error;
622- if (!game.save_handler().save_game(game, complete_filename, &error))
623- return report_error(L, "save error: %s", error.c_str());
624+int L_Game::save(lua_State * L) {
625+ std::string filename = luaL_checkstring(L, -1);
626+ get_game(L).save_handler().request_save(filename);
627
628 return 0;
629 }
630
631-
632 /*
633 ==========================================================
634 C METHODS
635
636=== modified file 'src/scripting/lua_root.h'
637--- src/scripting/lua_root.h 2012-02-15 21:25:34 +0000
638+++ src/scripting/lua_root.h 2013-07-19 21:32:25 +0000
639@@ -53,6 +53,8 @@
640 int set_desired_speed(lua_State *);
641 int get_allow_autosaving(lua_State *);
642 int set_allow_autosaving(lua_State *);
643+ int get_allow_saving(lua_State *);
644+ int set_allow_saving(lua_State *);
645
646 /*
647 * Lua methods
648
649=== modified file 'test/lua/persistence.wmf/scripting/init.lua'
650--- test/lua/persistence.wmf/scripting/init.lua 2011-02-21 20:36:10 +0000
651+++ test/lua/persistence.wmf/scripting/init.lua 2013-07-19 21:32:25 +0000
652@@ -1,128 +1,138 @@
653 -- =======================================================================
654--- LOADING/SAVING TESTS
655+-- LOADING/SAVING TESTS
656 -- =======================================================================
657--- This tests saving and loading of various Lua objects in the global
658+-- This tests saving and loading of various Lua objects in the global
659 -- environment.
660 --
661--- To run this test use:
662--- ./widelands --nozip --scenario=src/scripting/test/persistence.wmf &&
663--- ./widelands --loadgame=~/.widelands/save/lua_persistence.wgf
664-
665+-- To run this test, go to the top level directory, set the path to your
666+-- widelands binary and run:
667+--
668+-- $PATH_TO_WIDELANDS_BINARY --datadir="." --nozip --scenario=test/lua/persistence.wmf && \
669+-- $PATH_TO_WIDELANDS_BINARY --datadir="." --loadgame=~/.widelands/save/lua_persistence.wgf
670
671 -- ====================
672--- Test Data to persist
673+-- Test Data to persist
674 -- ====================
675 use("aux", "set")
676-
677-my_name = "SirVer"
678-pi = 3.1415
679-eight = 8
680-is_true = true
681-is_false = false
682-
683 game = wl.Game()
684-p = game.players[1]
685-map = game.map
686-a = { "Hallo", "Welt" }
687-c = { func = function(a) return "I say " .. a .. "!" end }
688-field = map:get_field(32,34)
689-tree = map:place_immovable("tree3", field)
690-removed_tree = map:place_immovable("tree4", map:get_field(34,34))
691-removed_tree:remove()
692-corout = coroutine.create(function()
693- local a = 100
694- coroutine.yield("What cool is that?")
695- coroutine.yield(a)
696-end)
697-objective = p:add_objective("lumber", "House", "Ship!")
698-objective.done = true
699-
700-p:send_message("dummy msg1", "dummy msg 1")
701-msg = p:send_message("hello nice", "World", {sender="blah", field = field })
702-player_slot = map.player_slots[1]
703-
704-myset = Set:new{
705- map:get_field(10,10), map:get_field(10,10), map:get_field(10,11)
706-}
707-
708--- ========================
709--- Test after unpersisting
710--- ========================
711-function check_persistence()
712-coroutine.yield(wl.Game().time + 2000)
713-
714-use("map", "lunit")
715-lunit.import "assertions"
716-
717-print("###################### CHECKING FOR CORRECT PERSISTENCE")
718-assert_equal("SirVer", my_name)
719-assert_equal(3.1415, pi)
720-assert_equal(8, eight)
721-assert_equal(true, is_true)
722-assert_equal(false, is_false)
723-
724-assert_equal(1, p.number)
725-
726-assert_table(a)
727-assert_equal(a[1], "Hallo")
728-assert_equal(a[2], "Welt")
729-
730-assert_table(c)
731-assert_function(c.func)
732-assert_equal("I say zonk!", c.func("zonk"))
733-
734-assert_equal("tree3", tree.name)
735-
736-assert_equal(32, field.x)
737-assert_equal(34, field.y)
738-assert_equal(tree, field.immovable)
739-
740-assert_thread(corout)
741-_,rv = coroutine.resume(corout)
742-assert_equal("What cool is that?", rv)
743-_,rv = coroutine.resume(corout)
744-assert_equal(100, rv)
745-
746-assert_table(objective)
747-assert_equal("lumber", objective.name)
748-assert_equal("House", objective.title)
749-assert_equal("Ship!", objective.body)
750-assert_equal(true, objective.done)
751-
752-assert_table(msg)
753-assert_equal("hello nice", msg.title)
754-assert_equal("World", msg.body)
755-assert_equal("blah", msg.sender)
756-assert_equal(field, msg.field)
757-
758-assert_table(map)
759-assert_equal(64, map.width)
760-assert_equal(64, map.height)
761-
762-assert_table(player_slot)
763-assert_equal("barbarians", player_slot.tribe_name)
764-assert_equal("Player 1", player_slot.name)
765-assert_equal(player_slot.name, map.player_slots[1].name)
766-assert_equal(player_slot.tribe_name, map.player_slots[1].tribe_name)
767-
768-assert_equal(2, myset.size)
769-assert_true(myset:contains(map:get_field(10,10)))
770-assert_true(myset:contains(map:get_field(10,11)))
771-
772-print("################### ALL TEST PASS!")
773-
774-wl.ui.MapView():close()
775+
776+function save_coroutine()
777+ game = wl.Game()
778+
779+ my_name = "SirVer"
780+ pi = 3.1415
781+ eight = 8
782+ is_true = true
783+ is_false = false
784+
785+ game = wl.Game()
786+ p = game.players[1]
787+ map = game.map
788+ a = { "Hallo", "Welt" }
789+ c = { func = function(a) return "I say " .. a .. "!" end }
790+ field = map:get_field(32,34)
791+ tree = map:place_immovable("tree3", field)
792+ removed_tree = map:place_immovable("tree4", map:get_field(34,34))
793+ removed_tree:remove()
794+
795+ corout = coroutine.create(function()
796+ local a = 100
797+ coroutine.yield("What cool is that?")
798+ coroutine.yield(a)
799+ end)
800+ objective = p:add_objective("lumber", "House", "Ship!")
801+ objective.done = true
802+
803+ p:send_message("dummy msg1", "dummy msg 1")
804+ msg = p:send_message("hello nice", "World", {sender="blah", field = field })
805+ player_slot = map.player_slots[1]
806+
807+ myset = Set:new{
808+ map:get_field(10,10), map:get_field(10,10), map:get_field(10,11)
809+ }
810+
811+ game:save("lua_persistence")
812+ print("Save requested\n");
813+
814+ -- Stick around, so that we are needed to get loaded too.
815+ coroutine.yield(wl.Game().time + 8000)
816+end
817+
818+function check_coroutine()
819+ -- Sleep till the save routine has done its job.
820+ coroutine.yield(wl.Game().time + 2000)
821+
822+ -- Attention, lunit contains code that can not be persisted (c functions),
823+ -- so it must be imported after reload.
824+ use("map", "lunit")
825+ lunit.import "assertions"
826+
827+ print("###################### CHECKING FOR CORRECT PERSISTENCE")
828+ assert_equal("SirVer", my_name)
829+ assert_equal(3.1415, pi)
830+ assert_equal(8, eight)
831+ assert_equal(true, is_true)
832+ assert_equal(false, is_false)
833+
834+ assert_equal(1, p.number)
835+
836+ assert_table(a)
837+ assert_equal(a[1], "Hallo")
838+ assert_equal(a[2], "Welt")
839+
840+ assert_table(c)
841+ assert_function(c.func)
842+ assert_equal("I say zonk!", c.func("zonk"))
843+
844+ assert_equal("tree3", tree.name)
845+
846+ assert_equal(32, field.x)
847+ assert_equal(34, field.y)
848+ assert_equal(tree, field.immovable)
849+
850+ assert_thread(corout)
851+ _,rv = coroutine.resume(corout)
852+ assert_equal("What cool is that?", rv)
853+ _,rv = coroutine.resume(corout)
854+ assert_equal(100, rv)
855+
856+ assert_table(objective)
857+ assert_equal("lumber", objective.name)
858+ assert_equal("House", objective.title)
859+ assert_equal("Ship!", objective.body)
860+ assert_equal(true, objective.done)
861+
862+ assert_table(msg)
863+ assert_equal("hello nice", msg.title)
864+ assert_equal("World", msg.body)
865+ assert_equal("blah", msg.sender)
866+ assert_equal(field, msg.field)
867+
868+ assert_table(map)
869+ assert_equal(64, map.width)
870+ assert_equal(64, map.height)
871+
872+ assert_table(player_slot)
873+ assert_equal("barbarians", player_slot.tribe_name)
874+ assert_equal("Player 1", player_slot.name)
875+ assert_equal(player_slot.name, map.player_slots[1].name)
876+ assert_equal(player_slot.tribe_name, map.player_slots[1].tribe_name)
877+
878+ assert_equal(2, myset.size)
879+ assert_true(myset:contains(map:get_field(10,10)))
880+ assert_true(myset:contains(map:get_field(10,11)))
881+
882+ print("################### ALL TEST PASS!")
883+
884+ wl.ui.MapView():close()
885 end
886
887
888 -- ==========
889--- Main Code
890+-- Main Code
891 -- ==========
892 -- This starts the test routine, saves the game and exits.
893--- Loading the saved game will check that all objects are
894+-- Loading the saved game will check that all objects are
895 -- correctly unpersisted
896-game = wl.Game()
897-game:launch_coroutine(coroutine.create(check_persistence))
898-game:save("lua_persistence")
899-wl.ui.MapView():close()
900-
901+game:launch_coroutine(coroutine.create(check_coroutine))
902+game:launch_coroutine(coroutine.create(save_coroutine))

Subscribers

People subscribed via source and target branches

to status/vote changes: