Merge branch 'master' into sdl2

This commit is contained in:
Andreas Löf 2015-09-11 18:24:10 +12:00
commit 0bb02fb548
79 changed files with 2819 additions and 1140 deletions

View file

@ -26,6 +26,10 @@ Describe how awesome the new game version dialog is and how it replaces the old
Some people experienced OOS errors on random maps due to sides being placed in different castles in the beginning of the game. This has now been fixed.
[/rasection]
[rasection="Replay turns during mp games"]
It is now possible to load an autosave in an networked mp game: when an autosave is loaded the local gamestate will be reset to the gamestate at that point. Then the game will be replayed (using the replay UI) until the current gamestate. When the current gamestate is reached the replay ui is removed and the game continues normally.
[/rasection]
==========
KNOWN BUGS
==========

View file

@ -25,9 +25,12 @@ Version 1.13.1+dev:
erroneous textdomain declarations (bug #23839).
* Lua API:
* Added support for unit.level field (read only)
* Added support for unit.advancements field (bug #23677)
* Added support for current.event_context.unit_x/y fields (bug #23507)
* Added wesnoth.set_dialog_focus function
* Added wesnoth.set_dialog_visible function
* Added wesnoth.random function
* helper.shuffle is now synced
* Music and sound effects:
* New dwarf hit and die sounds.
* Terrains:
@ -44,6 +47,7 @@ Version 1.13.1+dev:
* Widgets which are children of invisible or hidden parents can no longer
be interacted with even if the children themselves are still internally
visible.
* Added support for tristate buttons/toggle panels or more generally n-state buttons/toggle panels
* Added a version dialog button to the title screen, replacing the Paths
option previously found in Preferences -> General.
* WML engine:
@ -55,6 +59,8 @@ Version 1.13.1+dev:
* AMLAs in [modifications] now use [advancement] tags instead of [advance] tags.
This means you can add an AMLA to placed unit by simply using its definition macro,
for example {AMLA_DEFAULT}.
* [get/set_global_variable]'s side= attribute now defaults to "global" (bug #23686)
* [team] share_view=yes/no, share_maps=yes/no was replaced with share_vision=all/shroud/none
* Add exclude_amla= key in [advancement] which disables the advancment if the unit
has already taken certain other advancements.
* The WML preprocessor now writes warnings to stderr for macros redefined
@ -97,6 +103,10 @@ Version 1.13.1+dev:
sounds play automatically when the status is inflicted in combat, instead
of being played by attack animations.
* Fixed OOS on random maps, where clients placed sides in different castles.
* Fixed some (not all) OOS caused by modifying a sides controller by wml.
* Fixed wml menu items becoming unsynced in later scenarios.
* The game now automatically detects whether to show the game connect screen
between scenarios.
Version 1.13.1:
* Security fixes:

View file

@ -13,6 +13,7 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
leader_weight = (cfg and cfg.leader_weight) or 2
leader_attack_max_units = (cfg and cfg.leader_attack_max_units) or 3
leader_additional_threat = (cfg and cfg.leader_additional_threat) or 1
self.data.move_cache = { turn = wesnoth.current.turn }
self.data.gamedata = FAU.gamedata_setup()
@ -110,6 +111,18 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
local leader_info = FAU.get_unit_info(leader, self.data.gamedata)
local leader_copy = FAU.get_unit_copy(leader.id, self.data.gamedata)
-- If threatened_leader_fights=yes, check out the current threat (power only,
-- not units) on the AI leader
local leader_current_threat = 0
if cfg and cfg.threatened_leader_fights then
for xa,ya in H.adjacent_tiles(leader.x, leader.y) do
local enemy_power = enemy_power_map:get(xa, ya) or 0
if (enemy_power > leader_current_threat) then
leader_current_threat = enemy_power
end
end
end
local attacks = AH.get_attacks({ leader }, { include_occupied = cfg.include_occupied_attack_hexes })
if (#attacks > 0) then
@ -125,11 +138,19 @@ function ca_fast_combat_leader:evaluation(ai, cfg, self)
local enemy_power = enemy_power_map:get(xa, ya) or 0
local enemy_number = enemy_number_map:get(xa, ya) or 0
if (enemy_power * leader_weight > leader.hitpoints)
or (enemy_number > leader_attack_max_units)
then
acceptable_attack = false
break
-- A threat is considered acceptable, if it is within the
-- limits given for HP and units, or if it is not more than the
-- threat on the leader at the current position times
-- leader_additional_threat (the latter only if
-- threatened_leader_fights=yes is set, otherwise
-- leader_current_threat is zero)
if (enemy_power > leader_current_threat * leader_additional_threat) then
if (enemy_power * leader_weight > leader.hitpoints)
or (enemy_number > leader_attack_max_units)
then
acceptable_attack = false
break
end
end
end

View file

@ -460,9 +460,10 @@ function wesnoth.wml_actions.micro_ai(cfg)
elseif (cfg.ai_type == 'fast_ai') then
optional_keys = {
"attack_hidden_enemies", "avoid", "dungeon_mode", "filter", "filter_second",
"include_occupied_attack_hexes", "leader_attack_max_units", "leader_weight", "move_cost_factor",
"weak_units_first", "skip_combat_ca", "skip_move_ca"
"attack_hidden_enemies", "avoid", "dungeon_mode",
"filter", "filter_second", "include_occupied_attack_hexes",
"leader_additional_threat", "leader_attack_max_units", "leader_weight", "move_cost_factor",
"weak_units_first", "skip_combat_ca", "skip_move_ca", "threatened_leader_fights"
}
CA_parms = {
ai_id = 'mai_fast',

View file

@ -3,10 +3,10 @@ Gs^Fds, Gll^Fds, Gs^Fms, Gs^Fds, Gs^Fds, Gg, Gg, Ww, Ww, Gg^Fet, Gg, Gs, Gg^Efm,
Gs^Fms, Gll^Fms, Gll^Fds, Gg^Fet, Gs, Gg, Gg, Gg, Ww, Ww^Ewf, Gs, Gs, Gg, Gg, Gg, Gg, Gg, Gg, Gs^Fds, Gll^Fds, Gs^Fms, Gll^Fms
Gs^Fms, Gll^Fds, Gg, Gs, Gg, Gg, Gg^Efm, Gg, Gg, Ww, Gg^Ve, Gs, Gg, Gg, Gg, Gg^Efm, Gg^Efm, Gg, Gg, Gg, Gs^Fms, Gll^Fds
Gs^Fds, Gll^Fms, Gs, Gs, Gg, Gg, Gg^Efm, Gg, Gg^Efm, Ww, Ww, Gg, Gg, Gg, Gg, Gg, Gg^Efm, Gg, Gg, Gg, Gs^Fds, Gll^Fds
Gs^Fds, Gll^Fds, Gg^Ve, Gs^Fds, Gg, Gs, Gg, Gg, Gg, Gg, Re, Ww^Bw/, Gg, Gg, Gg^Efm, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gs^Fds, Gll^Fds, Gg, Gs^Fds, Gg, Gs, Gg, Gg, Gg, Gg, Re, Ww^Bw/, Gg, Gg, Gg^Efm, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gs^Fds, Gs^Fds, Gs^Fds, Gg, Gs, Gg, Gg, Gg, Gg, 1 Kh, Ch, Ww, Ww, Gg, Gs, Gg, Gg, Gg, Gg, Gg, Gg, Gg
Gs^Fds, Gs^Fms, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Ch, Re^Es, Gg^Ve, Ww^Bw/, Gg, Gs^Efm, Gg^Efm, Gg, Gg, Gg, Gg, Gg, Gg
Gs^Fds, Gll^Fds, Gg, Gg^Ve, Gg, Gg, Gs, Gg, Gg, Gg, Re, Re, Ww, Gg, Gg^Efm, Gs, Gg, Gg^Efm, Gg^Efm, Gg, Gll^Fds, Gs^Fds
Gs^Fds, Gll^Fds, Gg, Gg, Gg, Gg, Gs, Gg, Gg, Gg, Re, Re, Ww, Gg, Gg^Efm, Gs, Gg, Gg^Efm, Gg^Efm, Gg, Gll^Fds, Gs^Fds
Gs^Fds, Gll^Fds, Gll^Fds, Gg, Gs, Gg, Gs, Gg, Re, Re, Gg, Gg, Gg, Ww, Ww, Gg, Gg, Gg, Gg, Gg, Gll^Fds, Gll^Fds
Gs^Fds, Gs^Fms, Gll^Fms, Gs^Fms, Gs^Fds, Gs, Gs, Gs, Re, Gs, Gs, Gg, Gg, Gg, Ww, Gg, Gs, Gs, Gs, Gll^Fds, Gs^Fds, Gs^Fms
Gs^Fms, Gll^Fds, Gll^Fms, Gs^Fms, Gs^Fds, Gg, Gs, Gg, Re, Gs, Gs, Gg, Gg, Gg, Ww, Gs, Gg, Gg, Gs^Fds, Gs^Fms, Gs^Fms, Gs^Fms

View file

@ -511,7 +511,8 @@
# Castle base terrains
{TERRAIN_BASE Ch,Chr,Cha flat/road}
{TERRAIN_BASE Ch,Cha flat/road}
{TERRAIN_BASE Chr flat/stone-path}
{KEEP_BASE Kh* castle/cobbles-keep}
{TERRAIN_BASE Chw castle/sunken-ruin}
# swamp castle defined along with swamp above

View file

@ -515,7 +515,7 @@ class WmllintTab(Frame):
sticky=W,
padx=10)
self.radio_v2=Radiobutton(self.verbosity_frame,
text="Name files\nbefore processing",
text="Name files before processing",
variable=self.verbosity_variable,
value=2)
self.radio_v2.grid(row=2,
@ -862,7 +862,7 @@ class WmlindentTab(Frame):
sticky=W,
padx=10)
self.radio_v2=Radiobutton(self.verbosity_frame,
text="Also report\nunchanged files",
text="Report unchanged files",
variable=self.verbosity_variable,
value=2)
self.radio_v2.grid(row=2,

View file

@ -119,13 +119,9 @@ def put_units(f, us):
f.write("</tr>\n")
def by_race(u1, u2):
r1 = u1.rid
r2 = u2.rid
r = cmp(r1, r2)
if r == 0: r = cmp(u1.id, u2.id)
return r
us.sort(by_race)
def by_race(u):
return u.rid + u.id
us.sort(key = by_race)
race = None
color = 0
for u in us:
@ -156,7 +152,7 @@ td.none {border: solid 1px; background-color: #ffffff;}
f.write("<i>total frames (number of animations)</i>\n")
f.write("<h2>Mainline</h2>\n")
us = [x for x in wesnoth.unit_lookup.values() if x.campaigns[0] == "mainline"]
us = [x for x in list(wesnoth.unit_lookup.values()) if x.campaigns[0] == "mainline"]
put_units(f, us)
#f.write("<h2>Campaigns and Addons</h2>\n")

View file

@ -1,10 +1,9 @@
"""
Various helpers for use by the wmlunits tool.
"""
import sys, os, re, glob, shutil, copy, urllib2, subprocess
import sys, os, re, glob, shutil, copy, urllib.request, urllib.error, urllib.parse, subprocess
import wesnoth.wmlparser2 as wmlparser2
import wesnoth.wmltools as wmltools
import wesnoth.wmlparser3 as wmlparser3
def get_datadir(wesnoth_exe):
p = subprocess.Popen([wesnoth_exe, "--path"],
@ -115,7 +114,7 @@ class ImageCollector:
return image.id_name
def copy_and_color_images(self, target_path):
for image in self.images_by_ipath.values():
for image in list(self.images_by_ipath.values()):
opath = os.path.join(target_path, "pics", image.id_name)
try:
os.makedirs(os.path.dirname(opath))
@ -161,7 +160,7 @@ class WesnothList:
self.movetype_lookup = {}
self.era_lookup = {}
self.campaign_lookup = {}
self.parser = wmlparser2.Parser(wesnoth_exe, config_dir,
self.parser = wmlparser3.Parser(wesnoth_exe, config_dir,
data_dir, no_preprocess = False)
@ -184,7 +183,7 @@ class WesnothList:
"""
self.languages_found = {}
parser = wmlparser2.Parser(options.wesnoth, options.config_dir,
parser = wmlparser3.Parser(options.wesnoth, options.config_dir,
options.data_dir, no_preprocess = False)
parser.parse_text("{languages}")
@ -346,7 +345,7 @@ class WesnothList:
"""
# handle advancefrom tags
for uid, unit in self.unit_lookup.items():
for uid, unit in list(self.unit_lookup.items()):
for advancefrom in unit.get_all(tag = "advancefrom"):
fromid = advancefrom.get_text_val("unit")
if fromid:
@ -361,12 +360,12 @@ class WesnothList:
fromunit.advance.append(uid)
def find_unit_factions(self):
for unit in self.unit_lookup.values():
for unit in list(self.unit_lookup.values()):
unit.factions = []
unit.eras = []
for eid, era in self.era_lookup.items():
for fid, multiplayer_side in era.faction_lookup.items():
for eid, era in list(self.era_lookup.items()):
for fid, multiplayer_side in list(era.faction_lookup.items()):
for uid in multiplayer_side.units:
try:
unit = self.unit_lookup[uid]
@ -383,7 +382,7 @@ class WesnothList:
unit.factions.append((eid, fid))
# as a special case, add units from this addon but with no faction
for unit in self.unit_lookup.values():
for unit in list(self.unit_lookup.values()):
if unit.campaigns[0] == self.cid:
if not unit.factions:
if not eid in unit.eras:
@ -436,7 +435,7 @@ class UnitForest:
"""
# Complete the network
for uid, u in self.lookup.items():
for uid, u in list(self.lookup.items()):
for cid in u.child_ids:
c = self.lookup.get(cid, None)
if not c: continue
@ -445,7 +444,7 @@ class UnitForest:
c.parent_ids.append(uid)
# Put all roots into the forest
for uid, u in self.lookup.items():
for uid, u in list(self.lookup.items()):
if not u.parent_ids:
self.trees[uid] = u
@ -464,14 +463,14 @@ class UnitForest:
u.children.remove(c)
for c in u.children:
recurse(c, already2)
for u in self.trees.values():
for u in list(self.trees.values()):
already = {u.id : True}
recurse(u, already)
def update(self):
self.create_network()
self.breadth = sum([x.update_breadth() for x in self.trees.values()])
self.breadth = sum([x.update_breadth() for x in list(self.trees.values())])
return self.breadth
def get_children(self, uid):

View file

@ -3,7 +3,7 @@
import os, gettext, time, copy, sys, re
import traceback
import unit_tree.helpers as helpers
import wesnoth.wmlparser2 as wmlparser2
import wesnoth.wmlparser3 as wmlparser3
pics_location = "../../pics"
@ -62,10 +62,9 @@ class MyFile:
"""
def __init__(self, filename, mode):
self.filename = filename
self.f = open(filename, mode)
self.f = open(filename, mode + "b")
def write(self, x):
x = x.encode("utf8")
self.f.write(x)
self.f.write(x.encode("utf8"))
def close(self):
self.f.close()
@ -76,7 +75,7 @@ class Translation:
self.localedir = localedir
self.langcode = langcode
class Dummy:
def ugettext(self, x):
def gettext(self, x):
if not x: return ""
caret = x.find("^")
if caret < 0: return x
@ -98,7 +97,7 @@ class Translation:
# gettext.translation call sometimes
self.catalog[textdomain] = self.dummy
r = self.catalog[textdomain].ugettext(string)
r = self.catalog[textdomain].gettext(string)
return r
@ -186,7 +185,7 @@ class HTMLOutput:
# Build an advancement tree forest of all units.
forest = self.forest = helpers.UnitForest()
units_added = {}
for uid, u in self.wesnoth.unit_lookup.items():
for uid, u in list(self.wesnoth.unit_lookup.items()):
if u.hidden: continue
if grouper.unitfilter(u):
forest.add_node(helpers.UnitNode(u))
@ -197,7 +196,7 @@ class HTMLOutput:
# Always add any child units, even if they have been filtered out..
while units_added:
new_units_added = {}
for uid, u in units_added.items():
for uid, u in list(units_added.items()):
for auid in u.advance:
if not auid in units_added:
try:
@ -216,7 +215,7 @@ class HTMLOutput:
added = True
while added:
added = False
for uid, u in self.wesnoth.unit_lookup.items():
for uid, u in list(self.wesnoth.unit_lookup.items()):
if uid in forest.lookup: continue
for auid in u.advance:
if auid in forest.lookup:
@ -230,7 +229,7 @@ class HTMLOutput:
groups = {}
breadth = 0
for tree in forest.trees.values():
for tree in list(forest.trees.values()):
u = tree.unit
ugroups = grouper.groups(u)
@ -238,35 +237,32 @@ class HTMLOutput:
groups[group] = groups.get(group, []) + [tree]
breadth += tree.breadth
thelist = groups.keys()
thelist = list(groups.keys())
thelist.sort(key = lambda x: grouper.group_name(x))
rows_count = breadth + len(thelist)
# Create empty grid.
rows = []
for j in xrange(rows_count):
for j in range(rows_count):
column = []
for i in xrange(6):
for i in range(6):
column.append((1, 1, None))
rows.append(column)
# Sort advancement trees by name of first unit and place into the grid.
def by_name(t1, t2):
u1 = t1.unit
u2 = t2.unit
u1name = T(u1, "name")
u2name = T(u2, "name")
return cmp(u1name, u2name)
def by_name(t):
x = T(t.unit, "name")
if x is None: return ""
return x
def grid_place(nodes, x):
nodes.sort(by_name)
nodes.sort(key = by_name)
for node in nodes:
level = node.unit.level
if level < 0: level = 0
if level > 5: level = 5
rows[x][level] = (1, node.breadth, node)
for i in xrange(1, node.breadth):
for i in range(1, node.breadth):
rows[x + i][level] = (0, 0, node)
grid_place(node.children, x)
x += node.breadth
@ -278,7 +274,7 @@ class HTMLOutput:
node.name = grouper.group_name(group)
rows[x][0] = (6, 1, node)
for i in xrange(1, 6):
for i in range(1, 6):
rows[x][i] = (0, 0, None)
nodes = groups[group]
x += 1
@ -294,7 +290,7 @@ class HTMLOutput:
all_written_html_files.append((self.isocode, self.output.filename))
languages = self.wesnoth.languages_found
langlist = languages.keys()
langlist = list(languages.keys())
langlist.sort()
write(top_bar % {"path" : "../../"})
@ -310,7 +306,7 @@ class HTMLOutput:
def abbrev(name):
abbrev = name[0]
word_seperators = [" ", "_", "+", "(", ")"]
for i in xrange(1, len(name)):
for i in range(1, len(name)):
if name[i] in ["+", "(", ")"] or name[i - 1] in word_seperators and name[i] not in word_seperators:
abbrev += name[i]
return abbrev
@ -354,7 +350,7 @@ class HTMLOutput:
self.translate("all", "wesnoth-editor")))
r = {}, {}
for u in self.wesnoth.unit_lookup.values():
for u in list(self.wesnoth.unit_lookup.values()):
race = u.race
racename = T(race, "plural_name")
@ -363,9 +359,12 @@ class HTMLOutput:
if u:
m = 0
r[m][racename] = race.get_text_val("id") if race else "none"
rname = race.get_text_val("id") if race else "none"
if not rname:
rname = "none"
r[m][racename] = rname
racenames = sorted(r[0].items())
if r[1].items():
if list(r[1].items()):
racenames += [("-", "-")] + sorted(r[1].items())
for racename, rid in racenames:
@ -381,7 +380,7 @@ class HTMLOutput:
add_menu("races_menu", x)
for row in self.unitgrid:
for column in xrange(6):
for column in range(6):
hspan, vspan, un = row[column]
if not un: continue
if isinstance(un, helpers.GroupNode):
@ -397,7 +396,7 @@ class HTMLOutput:
class Entry: pass
races = {}
for uid, u in self.wesnoth.unit_lookup.items():
for uid, u in list(self.wesnoth.unit_lookup.items()):
if self.campaign != "units":
if self.campaign not in u.campaigns: continue
if u.race:
@ -477,7 +476,7 @@ class HTMLOutput:
if recursion >= 4:
error_message(
"Warning: Cannot find image for unit %s(%s).\n" % (
u.get_text_val("id"), x.name))
u.get_text_val("id"), x.name.decode("utf8")))
return None, None
image = self.wesnoth.get_unit_value(x, "image")
portrait = x.get_all(tag="portrait")
@ -488,7 +487,7 @@ class HTMLOutput:
if portrait:
portrait = portrait[0].get_text_val("image")
if not image:
if x.name == "female":
if x.name == b"female":
baseunit = self.wesnoth.get_base_unit(u)
if baseunit:
female = baseunit.get_all(tag="female")
@ -497,7 +496,7 @@ class HTMLOutput:
return self.pic(u, u, recursion = recursion + 1)
error_message(
"Warning: Missing image for unit %s(%s).\n" % (
u.get_text_val("id"), x.name))
u.get_text_val("id"), x.name.decode("utf8")))
return None, None
icpic = image_collector.add_image_check(self.addon, image)
if not icpic.ipath:
@ -518,12 +517,16 @@ class HTMLOutput:
try: c = abilities.get_all()
except AttributeError: c = []
for ability in c:
id = ability.get_text_val("id")
try:
id = ability.get_text_val("id")
except AttributeError as e:
error_message("Error: Ignoring ability " + ability.debug())
continue
if id in already: continue
already[id] = True
name = T(ability, "name")
if not name: name = id
if not name: name = ability.name
if not name: name = ability.name.decode("utf8")
anames.append(name)
return anames
@ -531,7 +534,7 @@ class HTMLOutput:
def copy_attributes(copy_from, copy_to):
for c in copy_from.data:
if isinstance(c, wmlparser2.AttributeNode):
if isinstance(c, wmlparser3.AttributeNode):
copy_to.data.append(c)
# Use attacks of base_units as base, if we have one.
@ -556,7 +559,7 @@ class HTMLOutput:
rows = self.unitgrid
write("<table class=\"units\">\n")
write("<colgroup>")
for i in xrange(6):
for i in range(6):
write("<col class=\"col%d\" />" % i)
write("</colgroup>")
@ -564,9 +567,9 @@ class HTMLOutput:
"../../../images/misc/leader-crown.png", no_tc=True)
crownimage = os.path.join(pics_location, pic)
ms = None
for row in xrange(len(rows)):
for row in range(len(rows)):
write("<tr>\n")
for column in xrange(6):
for column in range(6):
hspan, vspan, un = rows[row][column]
if vspan:
attributes = ""
@ -613,15 +616,15 @@ class HTMLOutput:
crown = ""
if ms:
if un.id in ms.units:
crown = u""
crown = ""
if un.id in ms.is_leader:
crown = u""
crown = ""
uaddon = "mainline"
if "mainline" not in u.campaigns: uaddon = self.addon
link = "../../%s/%s/%s.html" % (uaddon, self.isocode, uid)
write("<div class=\"i\"><a href=\"%s\" title=\"id=%s\">%s</a>" % (
link, uid, u"i"))
link, uid, "i"))
write("</div>")
write("<div class=\"l\">L%s%s</div>" % (level, crown))
write("<a href=\"%s\">%s</a><br/>" % (link, name))
@ -631,7 +634,7 @@ class HTMLOutput:
write('<a href=\"%s\">' % link)
if crown == u"":
if crown == "":
write('<div style="background: url(%s)">' % image)
write('<img src="%s" alt="(image)" />' % crownimage)
write("</div>")
@ -901,7 +904,7 @@ class HTMLOutput:
else:
error_message(
"Warning: Weapon special %s has no name for %s.\n" % (
special.name, uid))
special.name.decode("utf8"), uid))
s = "<br/>".join(s)
write("<td>%s</td>" % s)
@ -949,7 +952,7 @@ class HTMLOutput:
write('</div>')
write('<div class="unit-column-right">')
for si in xrange(2):
for si in range(2):
if si and not female: break
if si:
sportrait = fportrait
@ -979,7 +982,7 @@ class HTMLOutput:
terrains = self.wesnoth.terrain_lookup
terrainlist = []
already = {}
for tstring, t in terrains.items():
for tstring, t in list(terrains.items()):
tid = t.get_text_val("id")
if tid in ["off_map", "off_map2", "fog", "shroud", "impassable",
"void", "rails"]: continue
@ -1064,7 +1067,7 @@ def generate_campaign_report(addon, isocode, campaign, wesnoth):
cid = "mainline"
if not cid: cid = addon + "_" + campaign.get_text_val("define")
print("campaign " + addon + " " + cid + " " + isocode)
print(("campaign " + addon + " " + cid + " " + isocode))
path = os.path.join(options.output, addon, isocode)
if not os.path.isdir(path): os.mkdir(path)
@ -1089,7 +1092,7 @@ def generate_campaign_report(addon, isocode, campaign, wesnoth):
def generate_era_report(addon, isocode, era, wesnoth):
eid = era.get_text_val("id")
print("era " + addon + " " + eid + " " + isocode)
print(("era " + addon + " " + eid + " " + isocode))
path = os.path.join(options.output, addon, isocode)
if not os.path.isdir(path): os.mkdir(path)
@ -1116,13 +1119,13 @@ def generate_single_unit_reports(addon, isocode, wesnoth):
grouper = GroupByNothing()
html.analyze_units(grouper, True)
for uid, unit in wesnoth.unit_lookup.items():
for uid, unit in list(wesnoth.unit_lookup.items()):
if unit.hidden: continue
if "mainline" in unit.campaigns and addon != "mainline": continue
try:
htmlname = u"%s.html" % uid
filename = os.path.join(path, htmlname).encode("utf8")
htmlname = "%s.html" % uid
filename = os.path.join(path, htmlname)
# We probably can come up with something better.
if os.path.exists(filename):
@ -1142,10 +1145,10 @@ def generate_single_unit_reports(addon, isocode, wesnoth):
def html_postprocess_file(filename, isocode, batchlist):
print(u"postprocessing " + repr(filename))
print(("postprocessing " + repr(filename)))
chtml = u""
ehtml = u""
chtml = ""
ehtml = ""
cids = [[], []]
for addon in batchlist:
@ -1161,7 +1164,7 @@ def html_postprocess_file(filename, isocode, batchlist):
else:
cids[1].append(c)
for i in xrange(2):
for i in range(2):
campaigns = cids[i]
campaigns.sort(key = lambda x: "A" if x[1] == "mainline" else "B" + x[2])
@ -1169,10 +1172,10 @@ def html_postprocess_file(filename, isocode, batchlist):
for campaign in campaigns:
addon, cname, campname, lang = campaign
chtml += u" <a title=\"%s\" href=\"../../%s/%s/%s.html\">%s</a><br/>\n" % (
chtml += " <a title=\"%s\" href=\"../../%s/%s/%s.html\">%s</a><br/>\n" % (
campname, addon, lang, cname, campname)
if i == 0 and cids[1]:
chtml += u"-<br/>\n"
chtml += "-<br/>\n"
eids = [[], []]
for addon in batchlist:
@ -1188,22 +1191,22 @@ def html_postprocess_file(filename, isocode, batchlist):
else:
eids[1].append(e)
for i in xrange(2):
for i in range(2):
eras = eids[i]
eras.sort(key = lambda x: x[2])
for era in eras:
addon, eid, eraname, lang = era
ehtml += u" <a title=\"%s\" href=\"../../%s/%s/%s.html\">%s</a><br/>" % (
ehtml += " <a title=\"%s\" href=\"../../%s/%s/%s.html\">%s</a><br/>" % (
eraname, addon, lang, eid, eraname)
if i == 0 and eids[1]:
ehtml += u"-<br/>\n"
ehtml += "-<br/>\n"
f = open(filename, "r+b")
html = f.read().decode("utf8")
html = html.replace(u"PLACE CAMPAIGNS HERE\n", chtml)
html = html.replace(u"PLACE ERAS HERE\n", ehtml)
html = html.replace("PLACE CAMPAIGNS HERE\n", chtml)
html = html.replace("PLACE ERAS HERE\n", ehtml)
f.seek(0)
f.write(html.encode("utf8"))
f.close()

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python
import glob, os, sys, time, re
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import html_output
from . import html_output
def write_addon_overview(folder, addon):
out = open(os.path.join(folder, "index.html"), "wb")
def w(x): out.write(x.encode("utf8") + "\n")
out = open(os.path.join(folder, "index.html"), "w")
def w(x): out.write(x + "\n")
name = addon["name"]
@ -50,8 +50,8 @@ def write_addon_overview(folder, addon):
def main(folder):
out = open(os.path.join(folder, "overview.html"), "wb")
def w(x): out.write(x.encode("utf8") + "\n")
out = open(os.path.join(folder, "overview.html"), "w")
def w(x): out.write(x + "\n")
path = ""
title = "Wesnoth Unit Database Overview"

View file

@ -25,7 +25,10 @@ BUILD=$DIR/build
TOOLS=${TOOLS:-$SOURCES/master/data/tools}
export TEMP=$DIR/temp
# in case there's leftovers from a previous run
rm -rf $CONFIG/data/add-ons
rm -rf $TMPOUT
mkdir -p $CONFIG/data/add-ons
mkdir -p $ADDONS
mkdir -p $LOG
@ -46,7 +49,7 @@ rm -f $DIR/overview.txt
echo DOWNLOADING
$TOOLS/wesnoth_addon_manager -p $PORT -d '.*' -c $ADDONS > $LOG/wesnoth_addon_manager.txt 2>&1
echo WORKING
python -u $TOOLS/wmlunits -t $TRANS -D $DATA -o $TMPOUT -w $EXE -C $CONFIG -a $ADDONS -L $DIR/overview.txt -B $DIR/overview.txt -T $TIMEOUT > $LOG/wmlunits.txt 2>&1
python3 -u $TOOLS/wmlunits -t $TRANS -D $DATA -o $TMPOUT -w $EXE -C $CONFIG -a $ADDONS -L $DIR/overview.txt -B $DIR/overview.txt -T $TIMEOUT > $LOG/wmlunits.txt 2>&1
test -f $TMPOUT/mainline/en_US/mainline.html
test -f $TMPOUT/'pics/core$images$units$woses$wose.png'
rsync -vaz --delete $TMPOUT/ $OUT > $LOG/rsync.txt 2>&1

View file

@ -1,5 +1,6 @@
import helpers, sys, helpers
from html_output import Translation
import sys
from . import helpers
from .html_output import Translation
def main():
wesnoth = helpers.WesnothList(
@ -40,14 +41,14 @@ def main():
# Get all defined races.
races = {}
for campaign, unitslists in punits.items():
for campaign, unitslists in list(punits.items()):
for unitlist in unitslists:
for race in unitlist.get_all(tag = "race"):
races[race.get_text_val("id")] = race
# Go through all units and put them into a dictionary.
all_units = {}
for campaign, unitslists in punits.items():
for campaign, unitslists in list(punits.items()):
for unitlist in unitslists:
for unit in unitlist.get_all(tag = "unit_type"):
if unit.get_text_val("do_not_list") in ["yes", "true"]: continue
@ -68,7 +69,7 @@ def main():
return None
# Handle unit attributes
for unit in all_units.values():
for unit in list(all_units.values()):
unit.name = base_val(unit, "name", translation = translated.translate)
unit.orig_name = base_val(unit, "name", translation = original.translate)
@ -85,7 +86,7 @@ def main():
else: unit.advances_to = [x.strip() for x in a.split(",")]
# Find children and parents of all units.
for unit in all_units.values():
for unit in list(all_units.values()):
for aid in unit.advances_to:
unit.children.append(all_units[aid])
all_units[aid].parents.append(unit)
@ -100,13 +101,13 @@ def main():
# Group by race/campaign
units_per_race = {}
for unit in all_units.values():
for unit in list(all_units.values()):
x = race_key(unit)
if x not in units_per_race: units_per_race[x] = set()
units_per_race[x].add(unit)
# Recursively add all related units of a units to the same race as well.
for race in units_per_race.keys():
for race in list(units_per_race.keys()):
while True:
add = []
for unit in units_per_race[race]:
@ -153,14 +154,14 @@ def main():
# Create grid.
grid = []
for j in xrange(n + 1):
for j in range(n + 1):
grid.append([None] * 6)
for unit in units:
grid[unit.y][unit.level] = unit
# Output it.
for y in xrange(n + 1):
for x in xrange(6):
for y in range(n + 1):
for x in range(6):
unit = grid[y][x]
if unit:
w("|'''" + unit.name + "'''<br />" + unit.orig_name)

View file

@ -4,6 +4,9 @@
"""
This parser uses the --preprocess option of wesnoth so a working
wesnoth executable must be available at runtime.
If you are using this you shold instead use wmlparser3.py and upgrade
your code to Python 3.
"""
import os, glob, sys, re, subprocess, argparse, tempfile, shutil

803
data/tools/wesnoth/wmlparser3.py Executable file
View file

@ -0,0 +1,803 @@
#!/usr/bin/env python3
# encoding: utf8
"""
This parser uses the --preprocess option of wesnoth so a working
wesnoth executable must be available at runtime.
"""
import os, glob, sys, re, subprocess, argparse, tempfile, shutil
import atexit
tempdirs_to_clean = []
@atexit.register
def cleaner():
for temp_dir in tempdirs_to_clean:
shutil.rmtree(temp_dir, ignore_errors=True)
class WMLError(Exception):
"""
Catch this exception to retrieve the first error message from
the parser.
"""
def __init__(self, parser=None, message=None):
if parser:
self.line = parser.parser_line
self.wml_line = parser.last_wml_line
self.message = message
self.preprocessed = parser.preprocessed
def __str__(self):
return """WMLError:
%s %s
%s
%s
""" % (str(self.line), self.preprocessed, self.wml_line, self.message)
class StringNode:
"""
One part of an attribute's value. Because a single WML string
can be made from multiple translatable strings we need to model
it this way (as a list of several StringNode).
"""
def __init__(self, data):
self.textdomain = None # non-translatable by default
self.data = data
def debug(self):
if self.textdomain:
return "_<%s>'%s'" % (self.textdomain,
self.data.decode("utf8", "ignore"))
else:
return "'%s'" % self.data.decode("utf8", "ignore")
class AttributeNode:
"""
A WML attribute. For example the "id=Elfish Archer" in:
[unit]
id=Elfish Archer
[/unit]
"""
def __init__(self, name, location=None):
self.name = name
self.location = location
self.value = [] # List of StringNode
def debug(self):
return self.name.decode("utf8") + "=" + " .. ".join(
[v.debug() for v in self.value])
def get_text(self, translation=None):
r = ""
for s in self.value:
ustr = s.data.decode("utf8", "ignore")
if translation:
r += translation(ustr, s.textdomain)
else:
r += ustr
return r
class TagNode:
"""
A WML tag. For example the "unit" in this example:
[unit]
id=Elfish Archer
[/unit]
"""
def __init__(self, name, location=None):
self.name = name
self.location = location
# List of child elements, which are either of type TagNode or
# AttributeNode.
self.data = []
self.speedy_tags = {}
def debug(self):
s = "[%s]\n" % self.name.decode("utf8")
for sub in self.data:
for subline in sub.debug().splitlines():
s += " %s\n" % subline
s += "[/%s]\n" % self.name.decode("utf8")
return s
def get_all(self, **kw):
"""
This gets all child tags or child attributes of the tag.
For example:
[unit]
name=A
name=B
[attack]
[/attack]
[attack]
[/attack]
[/unit]
unit.get_all(att = "name")
will return two nodes for "name=A" and "name=B"
unit.get_all(tag = "attack")
will return two nodes for the two [attack] tags.
unit.get_all()
will return 4 nodes for all 4 sub-elements.
unit.get_all(att = "")
Will return the two attribute nodes.
unit.get_all(tag = "")
Will return the two tag nodes.
If no elements are found an empty list is returned.
"""
if len(kw) == 1 and "tag" in kw and kw["tag"]:
return self.speedy_tags.get(kw["tag"].encode("utf8"), [])
r = []
for sub in self.data:
ok = True
for k, v in list(kw.items()):
v = v.encode("utf8")
if k == "tag":
if not isinstance(sub, TagNode): ok = False
elif v != b"" and sub.name != v: ok = False
elif k == "att":
if not isinstance(sub, AttributeNode): ok = False
elif v != b"" and sub.name != v: ok = False
if ok:
r.append(sub)
return r
def get_text_val(self, name, default=None, translation=None, val=-1):
"""
Returns the value of the specified attribute. If the attribute
is given multiple times, the value number val is returned (default
behaviour being to return the last value). If the
attribute is not found, the default parameter is returned.
If a translation is specified, it should be a function which
when passed a unicode string and text-domain returns a
translation of the unicode string. The easiest way is to pass
it to gettext.translation if you have the binary message
catalogues loaded.
"""
x = self.get_all(att=name)
if not x: return default
return x[val].get_text(translation)
def append(self, node):
self.data.append(node)
if isinstance(node, TagNode):
if node.name not in self.speedy_tags:
self.speedy_tags[node.name] = []
self.speedy_tags[node.name].append(node)
class RootNode(TagNode):
"""
The root node. There is exactly one such node.
"""
def __init__(self):
TagNode.__init__(self, None)
def debug(self):
s = ""
for sub in self.data:
for subline in sub.debug().splitlines():
s += subline + "\n"
return s
class Parser:
def __init__(self, wesnoth_exe, config_dir, data_dir,
no_preprocess):
"""
path - Path to the file to parse.
wesnoth_exe - Wesnoth executable to use. This should have been
configured to use the desired data and config directories.
"""
self.wesnoth_exe = wesnoth_exe
self.config_dir = None
if config_dir: self.config_dir = os.path.abspath(config_dir)
self.data_dir = None
if data_dir: self.data_dir = os.path.abspath(data_dir)
self.keep_temp_dir = None
self.temp_dir = None
self.no_preprocess = no_preprocess
self.preprocessed = None
self.verbose = False
self.last_wml_line = "?"
self.parser_line = 0
self.line_in_file = 42424242
self.chunk_start = "?"
def parse_file(self, path, defines=""):
self.path = path
if not self.no_preprocess:
self.preprocess(defines)
self.parse()
def parse_text(self, text, defines=""):
temp = tempfile.NamedTemporaryFile(prefix="wmlparser_",
suffix=".cfg")
temp.write(text.encode("utf8"))
temp.flush()
self.path = temp.name
if not self.no_preprocess:
self.preprocess(defines)
self.parse()
def preprocess(self, defines):
"""
Call wesnoth --preprocess to get preprocessed WML which we
can subsequently parse.
If this is not called then the .parse method will assume the
WML is already preprocessed.
"""
if self.keep_temp_dir:
output = self.keep_temp_dir
else:
output = tempfile.mkdtemp(prefix="wmlparser_")
tempdirs_to_clean.append(output)
self.temp_dir = output
commandline = [self.wesnoth_exe]
if self.data_dir:
commandline += ["--data-dir", self.data_dir]
if self.config_dir:
commandline += ["--config-dir", self.config_dir]
commandline += ["--preprocess", self.path, output]
if defines:
commandline += ["--preprocess-defines", defines]
if self.verbose:
print((" ".join(commandline)))
p = subprocess.Popen(commandline,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if self.verbose:
print((out + err))
self.preprocessed = output + "/" + os.path.basename(self.path) +\
".plain"
if not os.path.exists(self.preprocessed):
first_line = open(self.path).readline().strip()
raise WMLError(self, "Preprocessor error:\n" +
" ".join(commandline) + "\n" +
"First line: " + first_line + "\n" +
out.decode("utf8") +
err.decode("utf8"))
def parse_line_without_commands(self, line):
"""
Once the .plain commands are handled WML lines are passed to
this.
"""
if not line: return
if line.strip():
self.skip_newlines_after_plus = False
if self.in_tag:
self.handle_tag(line)
return
if self.in_arrows:
arrows = line.find(b'>>')
if arrows >= 0:
self.in_arrows = False
self.temp_string += line[:arrows]
self.temp_string_node = StringNode(self.temp_string)
self.temp_string = b""
self.temp_key_nodes[self.commas].value.append(
self.temp_string_node)
self.in_arrows = False
self.parse_line_without_commands(line[arrows + 2:])
else:
self.temp_string += line
return
quote = line.find(b'"')
if not self.in_string:
arrows = line.find(b'<<')
if arrows >= 0 and (quote < 0 or quote > arrows):
self.parse_line_without_commands(line[:arrows])
self.in_arrows = True
self.parse_line_without_commands(line[arrows + 2:])
return
if quote >= 0:
if self.in_string:
# double quote
if quote < len(line) - 1 and line[quote + 1] == b'"'[0]:
self.temp_string += line[:quote + 1]
self.parse_line_without_commands(line[quote + 2:])
return
self.temp_string += line[:quote]
self.temp_string_node = StringNode(self.temp_string)
if self.translatable:
self.temp_string_node.textdomain = self.textdomain
self.translatable = False
self.temp_string = b""
if not self.temp_key_nodes:
raise WMLError(self, "Unexpected string value.")
self.temp_key_nodes[self.commas].value.append(
self.temp_string_node)
self.in_string = False
self.parse_line_without_commands(line[quote + 1:])
else:
self.parse_outside_strings(line[:quote])
self.in_string = True
self.parse_line_without_commands(line[quote + 1:])
else:
if self.in_string:
self.temp_string += line
else:
self.parse_outside_strings(line)
def parse_outside_strings(self, line):
"""
Parse a WML fragment outside of strings.
"""
if not line: return
if not self.temp_key_nodes:
line = line.lstrip()
if not line: return
if line.startswith(b"#textdomain "):
self.textdomain = line[12:].strip().decode("utf8")
return
# Is it a tag?
if line.startswith(b"["):
self.handle_tag(line)
# No tag, must be an attribute.
else:
self.handle_attribute(line)
else:
for i, segment in enumerate(line.split(b"+")):
segment = segment.lstrip(b" ")
if i > 0:
# If the last segment is empty (there was a plus sign
# at the end) we need to skip newlines.
self.skip_newlines_after_plus = not segment.strip()
if not segment: continue
if segment.startswith(b"_"):
self.translatable = True
segment = segment[1:].lstrip(b" ")
if not segment: continue
self.handle_value(segment)
def handle_tag(self, line):
end = line.find(b"]")
if end < 0:
if line.endswith(b"\n"):
raise WMLError(self, "Expected closing bracket.")
self.in_tag += line
return
tag = (self.in_tag + line[:end])[1:]
self.in_tag = b""
if tag.startswith(b"/"):
self.parent_node = self.parent_node[:-1]
else:
node = TagNode(tag, location=(self.line_in_file, self.chunk_start))
if self.parent_node:
self.parent_node[-1].append(node)
self.parent_node.append(node)
self.parse_outside_strings(line[end + 1:])
def handle_attribute(self, line):
assign = line.find(b"=")
remainder = None
if assign >= 0:
remainder = line[assign + 1:]
line = line[:assign]
self.commas = 0
self.temp_key_nodes = []
for att in line.split(b","):
att = att.strip()
node = AttributeNode(att, location=(self.line_in_file, self.chunk_start))
self.temp_key_nodes.append(node)
if self.parent_node:
self.parent_node[-1].append(node)
if remainder:
self.parse_outside_strings(remainder)
def handle_value(self, segment):
def add_text(segment):
segment = segment.rstrip()
if not segment: return
n = len(self.temp_key_nodes)
maxsplit = n - self.commas - 1
if maxsplit < 0: maxsplit = 0
for subsegment in segment.split(b",", maxsplit):
self.temp_string += subsegment.strip()
self.temp_string_node = StringNode(self.temp_string)
self.temp_string = b""
self.temp_key_nodes[self.commas].value.append(
self.temp_string_node)
if self.commas < n - 1:
self.commas += 1
# Finish assignment on newline, except if there is a
# plus sign before the newline.
add_text(segment)
if segment.endswith(b"\n") and not self.skip_newlines_after_plus:
self.temp_key_nodes = []
def parse(self):
"""
Parse preprocessed WML into a tree of tags and attributes.
"""
# parsing state
self.temp_string = b""
self.temp_string_node = None
self.commas = 0
self.temp_key_nodes = []
self.in_string = False
self.in_arrows = False
self.textdomain = "wesnoth"
self.translatable = False
self.root = RootNode()
self.parent_node = [self.root]
self.skip_newlines_after_plus = False
self.in_tag = b""
command_marker_byte = bytes([254])
input = self.preprocessed
if not input: input = self.path
for rawline in open(input, "rb"):
compos = rawline.find(command_marker_byte)
self.parser_line += 1
# Everything from chr(254) to newline is the command.
if compos != 0:
self.line_in_file += 1
if compos >= 0:
self.parse_line_without_commands(rawline[:compos])
self.handle_command(rawline[compos + 1:-1])
else:
self.parse_line_without_commands(rawline)
if self.keep_temp_dir is None and self.temp_dir:
if self.verbose:
print(("removing " + self.temp_dir))
shutil.rmtree(self.temp_dir, ignore_errors=True)
def handle_command(self, com):
if com.startswith(b"line "):
self.last_wml_line = com[5:]
_ = self.last_wml_line.split(b" ")
self.chunk_start = [(_[i+1], int(_[i])) for i in range(0, len(_), 2)]
self.line_in_file = self.chunk_start[0][1]
elif com.startswith(b"textdomain "):
self.textdomain = com[11:].decode("utf8")
else:
raise WMLError(self, "Unknown parser command: " + com)
def get_all(self, **kw):
return self.root.get_all(**kw)
def get_text_val(self, name, default=None, translation=None):
return self.root.get_text_val(name, default, translation)
import json
def jsonify(tree, verbose=False, depth=0):
"""
Convert a DataSub into JSON
If verbose, insert a linebreak after every brace and comma (put every item on its own line), otherwise, condense everything into a single line.
"""
print("{", end=' ')
first = True
sdepth1 = "\n" + " " * depth
sdepth2 = sdepth1 + " "
for pair in tree.speedy_tags.items():
if first:
first = False
else:
sys.stdout.write(",")
if verbose:
sys.stdout.write(sdepth2)
print('"%s":' % pair[0], end=' ')
if verbose:
sys.stdout.write(sdepth1)
print('[', end=' ')
first_tag = True
for tag in pair[1]:
if first_tag:
first_tag = False
else:
sys.stdout.write(",")
if verbose:
sys.stdout.write(sdepth2)
jsonify(tag, verbose, depth + 2)
if verbose:
sys.stdout.write(sdepth2)
sys.stdout.write("]")
for child in tree.data:
if isinstance(child, TagNode):
continue
if first:
first = False
else:
sys.stdout.write(",")
if verbose:
sys.stdout.write(sdepth2)
print('"%s":' % child.name, end=' ')
print(json.dumps(child.get_text()), end=' ')
if verbose:
sys.stdout.write(sdepth1)
sys.stdout.write("}")
from xml.sax.saxutils import escape
def xmlify(tree, verbose=False, depth=0):
sdepth = ""
if verbose:
sdepth = " " * depth
for child in tree.data:
if isinstance(child, TagNode):
print('%s<%s>' % (sdepth, child.name))
xmlify(child, verbose, depth + 1)
print('%s</%s>' % (sdepth, child.name))
else:
if "\n" in child.get_text() or "\r" in child.get_text():
print(sdepth + '<' + child.name + '>' + \
'<![CDATA[' + child.get_text() + ']]>' + '</' + child.name + '>')
else:
print(sdepth + '<' + child.name + '>' + \
escape(child.get_text()) + '</' + child.name + '>')
if __name__ == "__main__":
arg = argparse.ArgumentParser()
arg.add_argument("-a", "--data-dir", help="directly passed on to wesnoth.exe")
arg.add_argument("-c", "--config-dir", help="directly passed on to wesnoth.exe")
arg.add_argument("-i", "--input", help="a WML file to parse")
arg.add_argument("-k", "--keep-temp", help="specify directory where to keep temp files")
arg.add_argument("-t", "--text", help="WML text to parse")
arg.add_argument("-w", "--wesnoth", help="path to wesnoth.exe")
arg.add_argument("-d", "--defines", help="comma separated list of WML defines")
arg.add_argument("-T", "--test", action="store_true")
arg.add_argument("-j", "--to-json", action="store_true")
arg.add_argument("-n", "--no-preprocess", action="store_true")
arg.add_argument("-v", "--verbose", action="store_true")
arg.add_argument("-x", "--to-xml", action="store_true")
args = arg.parse_args()
if not args.input and not args.text and not args.test:
sys.stderr.write("No input given. Use -h for help.\n")
sys.exit(1)
if not args.no_preprocess and (not args.wesnoth or not
os.path.exists(args.wesnoth)):
sys.stderr.write("Wesnoth executable not found.\n")
sys.exit(1)
if args.test:
print("Running tests")
p = Parser(args.wesnoth, args.config_dir,
args.data_dir, args.no_preprocess)
if args.keep_temp:
p.keep_temp_dir = args.keep_temp
if args.verbose: p.verbose = True
only = None
def test2(input, expected, note, function):
if only and note != only: return
input = input.strip()
expected = expected.strip()
p.parse_text(input)
output = function(p).strip()
if output != expected:
print("__________")
print(("FAILED " + note))
print("INPUT:")
print(input)
print("OUTPUT:")
print(output)
print("EXPECTED:")
print(expected)
print("__________")
else:
print(("PASSED " + note))
def test(input, expected, note):
test2(input, expected, note, lambda p: p.root.debug())
test(
"""
[test]
a=1
[/test]
""", """
[test]
a='1'
[/test]
""", "simple")
test(
"""
[test]
a, b, c = 1, 2, 3
[/test]
""", """
[test]
a='1'
b='2'
c='3'
[/test]
""", "multi assign")
test(
"""
[test]
a, b = 1, 2, 3
[/test]
""", """
[test]
a='1'
b='2, 3'
[/test]
""", "multi assign 2")
test(
"""
[test]
a, b, c = 1, 2
[/test]
""", """
[test]
a='1'
b='2'
c=
[/test]
""", "multi assign 3")
test(
"""
#textdomain A
#define X
_ "abc"
#enddef
#textdomain B
[test]
x = _ "abc" + {X}
[/test]
""", """
[test]
x=_<B>'abc' .. _<A>'abc'
[/test]
""", "textdomain")
test(
"""
[test]
a = "a ""quoted"" word"
[/test]
""",
"""
[test]
a='a "quoted" word'
[/test]
""", "quoted")
test(
"""
[test]
code = <<
"quotes" here
""blah""
>>
[/test]
""",
"""
[test]
code='
"quotes" here
""blah""
'
[/test]
""", "quoted2")
test(
"""
foo="bar"+
"baz"
""",
"""
foo='bar' .. 'baz'
""", "multi line string")
test(
"""
#define baz
"baz"
#enddef
foo="bar"+{baz}
""",
"""
foo='bar' .. 'baz'
""", "defined multi line string")
test(
"""
foo="bar" + "baz" # blah
""",
"""
foo='bar' .. 'baz'
""", "comment after +")
test(
"""
#define baz
"baz"
#enddef
foo="bar" {baz}
""",
"""
foo='bar' .. 'baz'
""", "defined string concatenation")
test(
"""
#define A BLOCK
[{BLOCK}]
[/{BLOCK}]
#enddef
{A blah}
""",
"""
[blah]
[/blah]
""", "defined tag")
test2(
"""
[test]
a=1
b=2
a=3
b=4
[/test]
""", "3, 4", "multiatt",
lambda p:
p.get_all(tag = "test")[0].get_text_val("a") + ", " +
p.get_all(tag = "test")[0].get_text_val("b"))
sys.exit(0)
p = Parser(args.wesnoth, args.config_dir, args.data_dir,
args.no_preprocess)
if args.keep_temp:
p.keep_temp_dir = args.keep_temp
if args.verbose: p.verbose = True
if args.input: p.parse_file(args.input, args.defines)
elif args.text: p.parse_text(args.text, args.defines)
if args.to_json:
jsonify(p.root, True)
print()
elif args.to_xml:
print('<?xml version="1.0" encoding="UTF-8" ?>')
print('<root>')
xmlify(p.root, True, 1)
print('</root>')
else:
print((p.root.debug()))

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
#encoding: utf8
"""
wmlunits -- tool to output information on all units in HTML
@ -10,11 +10,11 @@ Run without arguments to see usage.
try: import psyco; psyco.full()
except ImportError: pass
import sys, os, glob, shutil, urllib2, argparse, traceback
import sys, os, glob, shutil, urllib.request, urllib.error, urllib.parse, argparse, traceback
import subprocess, yaml
import multiprocessing, Queue
import multiprocessing, queue
import wesnoth.wmlparser2 as wmlparser2
import wesnoth.wmlparser3 as wmlparser3
import unit_tree.helpers as helpers
import unit_tree.animations as animations
import unit_tree.html_output as html_output
@ -36,9 +36,9 @@ def copy_images():
"http://www.wesnoth.org/mw/skins/glamdrol/navbg.png"]:
local = os.path.join(options.output, grab[grab.rfind("/") + 1:])
if not os.path.exists(local):
print "Fetching", grab
url = urllib2.urlopen(grab)
file(local, "w").write(url.read())
print("Fetching", grab)
url = urllib.request.urlopen(grab)
open(local, "wb").write(url.read())
def shell(com):
#print(com)
@ -77,13 +77,13 @@ def get_info(addon):
try:
path = options.addons + "/" + addon + "/_info.cfg"
if os.path.exists(path):
parser = wmlparser2.Parser(options.wesnoth, options.config_dir,
parser = wmlparser3.Parser(options.wesnoth, options.config_dir,
options.data_dir, no_preprocess = False)
parser.parse_file(path)
_info[addon] = parser
else:
print("Cannot find " + path)
except wmlparser2.WMLError as e:
print(("Cannot find " + path))
except wmlparser3.WMLError as e:
print(e)
return _info[addon]
@ -106,7 +106,7 @@ def get_dependencies(addon):
if d in global_addons:
_deps[addon].append(d)
else:
print("Missing dependency for " + addon + ": " + d)
print(("Missing dependency for " + addon + ": " + d))
except Exception as e:
print(e)
return _deps[addon]
@ -140,7 +140,7 @@ def sorted_by_dependencies(addons):
n += 1
continue
if n == 0:
print("Cannot sort dependencies for these addons: " + str(unsorted))
print(("Cannot sort dependencies for these addons: " + str(unsorted)))
sorted += unsorted
break
return sorted
@ -238,7 +238,7 @@ def list_contents():
p.start()
try:
s, local.wesnoth = q.get(timeout = TIMEOUT)
except Queue.Empty:
except queue.Empty:
p.terminate()
raise
#print("local", s, local.wesnoth)
@ -322,14 +322,14 @@ def list_contents():
info["campaigns"] = list_campaigns(batchlist, addon)
info["version"] = version
sys.stdout.write("ok\n")
except wmlparser2.WMLError as e:
except wmlparser3.WMLError as e:
ef = open(logname, "w")
ef.write("<PARSE ERROR>\n")
ef.write(str(e))
ef.write("</PARSE ERROR>\n")
ef.close()
sys.stdout.write("failed\n")
except Queue.Empty as e:
except queue.Empty as e:
ef = open(logname, "w")
ef.write("<TIMEOUT ERROR>\n")
ef.write("Failed to parse the WML within " + str(TIMEOUT) + " seconds.")
@ -355,7 +355,10 @@ def list_contents():
def process_campaign_or_era(addon, cid, define, batchlist):
n = 0
print(addon + ": " + cid + " " + define)
print(str(addon) + ": " + str(cid) + " " + str(define))
if not define:
define = ""
wesnoth = helpers.WesnothList(
options.wesnoth,
@ -404,7 +407,7 @@ def process_campaign_or_era(addon, cid, define, batchlist):
if addon != "mainline" and isocode != "en_US": continue
if define == "MULTIPLAYER":
for era in wesnoth.era_lookup.values():
for era in list(wesnoth.era_lookup.values()):
if era.get_text_val("id") == cid:
n = html_output.generate_era_report(addon, isocode, era, wesnoth)
break
@ -412,7 +415,7 @@ def process_campaign_or_era(addon, cid, define, batchlist):
if cid == "mainline":
n = html_output.generate_campaign_report(addon, isocode, None, wesnoth)
for campaign in wesnoth.campaign_lookup.values():
for campaign in list(wesnoth.campaign_lookup.values()):
if campaign.get_text_val("id") == cid:
n = html_output.generate_campaign_report(addon, isocode, campaign, wesnoth)
break
@ -453,7 +456,7 @@ def batch_process():
try:
if not worked:
print(name + " not found")
print((name + " not found"))
continue
for era in addon.get("eras", []):
@ -468,16 +471,16 @@ def batch_process():
n = process_campaign_or_era(name, cid, campaign["define"], batchlist)
campaign["units"] = n
except wmlparser2.WMLError as e:
except wmlparser3.WMLError as e:
ef = open(logname, "a")
ef.write("<WML ERROR>\n")
ef.write(str(e))
ef.write("</WML ERROR>\n")
ef.close()
print(" " + name + " failed")
print((" " + name + " failed"))
except Exception as e:
traceback.print_exc()
print(" " + name + " failed")
print((" " + name + " failed"))
ef = open(logname, "a")
ef.write("<INTERNAL ERROR>\n")
ef.write("please report as bug\n")
@ -505,7 +508,7 @@ def batch_process():
def write_unit_ids_UNUSED():
# Write a list with all unit ids, just for fun.
uids = wesnoth.unit_lookup.keys()
uids = list(wesnoth.unit_lookup.keys())
def by_race(u1, u2):
r = cmp(wesnoth.unit_lookup[u1].rid,
wesnoth.unit_lookup[u2].rid)
@ -604,11 +607,11 @@ if __name__ == '__main__':
if not options.data_dir:
options.data_dir = shell_out([options.wesnoth, "--path"]).strip()
print("Using " + options.data_dir + " as data dir.")
print(("Using " + options.data_dir + " as data dir."))
if not options.config_dir:
options.config_dir = shell_out([options.wesnoth, "--config-path"]).strip()
print("Using " + options.config_dir + " as config dir.")
print(("Using " + options.config_dir + " as config dir."))
if not options.transdir:
options.transdir = os.getcwd()
@ -623,7 +626,7 @@ if __name__ == '__main__':
if options.language == "all":
languages = []
parser = wmlparser2.Parser(options.wesnoth, options.config_dir,
parser = wmlparser3.Parser(options.wesnoth, options.config_dir,
options.data_dir, no_preprocess = False)
parser.parse_text("{languages}")

View file

@ -105,6 +105,8 @@
<Unit filename="../../src/actions/undo_recruit_action.hpp" />
<Unit filename="../../src/actions/undo_update_shroud_action.cpp" />
<Unit filename="../../src/actions/undo_update_shroud_action.hpp" />
<Unit filename="../../src/actions/unit_creator.cpp" />
<Unit filename="../../src/actions/unit_creator.hpp" />
<Unit filename="../../src/actions/vision.cpp" />
<Unit filename="../../src/actions/vision.hpp" />
<Unit filename="../../src/addon/client.cpp" />
@ -921,6 +923,8 @@
<Unit filename="../../src/movetype.hpp" />
<Unit filename="../../src/mp_game_settings.cpp" />
<Unit filename="../../src/mp_game_settings.hpp" />
<Unit filename="../../src/mp_replay_controller.cpp" />
<Unit filename="../../src/mp_replay_controller.hpp" />
<Unit filename="../../src/mp_ui_alerts.cpp" />
<Unit filename="../../src/mp_ui_alerts.hpp" />
<Unit filename="../../src/mt_rng.cpp" />

View file

@ -115,6 +115,8 @@
91B622221B76C0F400B00E0F /* libboost_regexw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EF0D5313AD4E35003C701D /* libboost_regexw.dylib */; };
91B622231B76C0F400B00E0F /* libboost_systemw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EF0D5413AD4E35003C701D /* libboost_systemw.dylib */; };
91B622241B76C0F400B00E0F /* libboost_threadw.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EF0D5B13AD4E6D003C701D /* libboost_threadw.dylib */; };
91ECD5D21BA11A5200B25CF1 /* unit_creator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */; };
91ECD5D51BA11A6400B25CF1 /* mp_replay_controller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91ECD5D31BA11A6400B25CF1 /* mp_replay_controller.cpp */; };
B504B94C1284C06B00261FE9 /* tips.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B504B94A1284C06B00261FE9 /* tips.cpp */; };
B504B94D1284C06B00261FE9 /* tips.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B504B94A1284C06B00261FE9 /* tips.cpp */; };
B508D13F10013BF900B12852 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B508D13E10013BF900B12852 /* Growl.framework */; };
@ -1355,6 +1357,10 @@
91B621F51B76BCB000B00E0F /* unicode_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unicode_cast.hpp; sourceTree = "<group>"; };
91B621F61B76BCB000B00E0F /* unicode_types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unicode_types.hpp; sourceTree = "<group>"; };
91B621F71B76BD4600B00E0F /* multimenu.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = multimenu.hpp; sourceTree = "<group>"; };
91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_creator.cpp; sourceTree = "<group>"; };
91ECD5D11BA11A5200B25CF1 /* unit_creator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_creator.hpp; sourceTree = "<group>"; };
91ECD5D31BA11A6400B25CF1 /* mp_replay_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mp_replay_controller.cpp; sourceTree = "<group>"; };
91ECD5D41BA11A6400B25CF1 /* mp_replay_controller.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = mp_replay_controller.hpp; sourceTree = "<group>"; };
B504B94A1284C06B00261FE9 /* tips.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tips.cpp; sourceTree = "<group>"; };
B504B94B1284C06B00261FE9 /* tips.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tips.hpp; sourceTree = "<group>"; };
B508D13E10013BF900B12852 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = lib/Growl.framework; sourceTree = "<group>"; };
@ -2742,6 +2748,8 @@
F468E6AB16DF872200A31A5A /* movetype.hpp */,
B5B469221071185800327599 /* mp_game_settings.cpp */,
B5B469231071185800327599 /* mp_game_settings.hpp */,
91ECD5D31BA11A6400B25CF1 /* mp_replay_controller.cpp */,
91ECD5D41BA11A6400B25CF1 /* mp_replay_controller.hpp */,
EC5C70C019EEB58300432CF4 /* mp_ui_alerts.cpp */,
91B621941B76722E00B00E0F /* mp_ui_alerts.hpp */,
EC64D7641A085F120092EF75 /* mt_rng.cpp */,
@ -3050,6 +3058,8 @@
91B621B41B76B1D600B00E0F /* undo_recruit_action.hpp */,
ECAB84541B0C1934001A3EB7 /* undo_update_shroud_action.cpp */,
91B621B51B76B1D900B00E0F /* undo_update_shroud_action.hpp */,
91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */,
91ECD5D11BA11A5200B25CF1 /* unit_creator.hpp */,
620A386C15E9364E00A4F513 /* vision.cpp */,
620A386D15E9364E00A4F513 /* vision.hpp */,
);
@ -4988,6 +4998,8 @@
EC2F60201A0490FC0018C9D6 /* xbrz.cpp in Sources */,
91B621A21B76A3CC00B00E0F /* build_info.cpp in Sources */,
91B621BA1B76B2C900B00E0F /* version.cpp in Sources */,
91ECD5D21BA11A5200B25CF1 /* unit_creator.cpp in Sources */,
91ECD5D51BA11A6400B25CF1 /* mp_replay_controller.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -5505,7 +5517,6 @@
"LUA_USE_MACOSX=1",
"HAVE_LIBPNG=1",
);
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_VERSION = "";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Info.plist;
@ -5605,12 +5616,21 @@
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ./lib;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "-Wall";
OTHER_LDFLAGS = (
"-lz",
"-lbz2",
);
PREBINDING = NO;
WARNING_CFLAGS = "-Wall";
WARNING_CFLAGS = (
"-Werror=format",
"-Werror=missing-braces",
"-Werror=return-type",
"-Werror=missing-field-initializers",
"-Werror=sign-compare",
"-Werror=unused",
"-Werror=switch",
);
};
name = Debug;
};
@ -5646,6 +5666,15 @@
"-lbz2",
);
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
WARNING_CFLAGS = (
"-Werror=format",
"-Werror=missing-braces",
"-Werror=return-type",
"-Werror=missing-field-initializers",
"-Werror=sign-compare",
"-Werror=unused",
"-Werror=switch",
);
};
name = Release;
};

View file

@ -740,6 +740,7 @@ set(wesnoth-main_SRC
actions/undo_recall_action.cpp
actions/undo_recruit_action.cpp
actions/undo_update_shroud_action.cpp
actions/unit_creator.cpp
actions/vision.cpp
addon/client.cpp
addon/info.cpp
@ -957,6 +958,7 @@ set(wesnoth-main_SRC
mouse_handler_base.cpp
movetype.cpp
mp_game_settings.cpp
mp_replay_controller.cpp
game_initialization/mp_game_utils.cpp
game_initialization/mp_options.cpp
mp_ui_alerts.cpp

View file

@ -199,6 +199,7 @@ wesnoth_sources = Split("""
actions/undo_recall_action.cpp
actions/undo_recruit_action.cpp
actions/undo_update_shroud_action.cpp
actions/unit_creator.cpp
actions/vision.cpp
addon/client.cpp
addon/info.cpp
@ -517,6 +518,7 @@ wesnoth_sources = Split("""
mouse_handler_base.cpp
movetype.cpp
mp_game_settings.cpp
mp_replay_controller.cpp
game_initialization/mp_game_utils.cpp
game_initialization/mp_options.cpp
mp_ui_alerts.cpp

View file

@ -59,183 +59,8 @@ static lg::log_domain log_engine("engine");
#define LOG_NG LOG_STREAM(info, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
unit_creator::unit_creator(team &tm, const map_location &start_pos)
: add_to_recall_(false),discover_(false),get_village_(false),invalidate_(false), rename_side_(false), show_(false), start_pos_(start_pos), team_(tm)
{
}
unit_creator& unit_creator::allow_show(bool b)
{
show_=b;
return *this;
}
unit_creator& unit_creator::allow_get_village(bool b)
{
get_village_=b;
return *this;
}
unit_creator& unit_creator::allow_rename_side(bool b)
{
rename_side_=b;
return *this;
}
unit_creator& unit_creator::allow_invalidate(bool b)
{
invalidate_=b;
return *this;
}
unit_creator& unit_creator::allow_discover(bool b)
{
discover_=b;
return *this;
}
unit_creator& unit_creator::allow_add_to_recall(bool b)
{
add_to_recall_=b;
return *this;
}
map_location unit_creator::find_location(const config &cfg, const unit* pass_check)
{
DBG_NG << "finding location for unit with id=["<<cfg["id"]<<"] placement=["<<cfg["placement"]<<"] x=["<<cfg["x"]<<"] y=["<<cfg["y"]<<"] for side " << team_.side() << "\n";
std::vector< std::string > placements = utils::split(cfg["placement"]);
placements.push_back("map");
placements.push_back("recall");
BOOST_FOREACH(const std::string& place, placements) {
map_location loc;
if ( place == "recall" ) {
return map_location::null_location();
}
else if ( place == "leader" || place == "leader_passable" ) {
unit_map::const_iterator leader = resources::units->find_leader(team_.side());
//todo: take 'leader in recall list' possibility into account
if (leader.valid()) {
loc = leader->get_location();
} else {
loc = start_pos_;
}
}
// "map", "map_passable", and "map_overwrite".
else if ( place == "map" || place.compare(0, 4, "map_") == 0 ) {
loc = map_location(cfg,resources::gamedata);
}
if(loc.valid() && resources::gameboard->map().on_board(loc)) {
const bool pass((place == "leader_passable") || (place == "map_passable"));
if ( place != "map_overwrite" ) {
loc = find_vacant_tile(loc, pathfind::VACANT_ANY,
pass ? pass_check : NULL);
}
if(loc.valid() && resources::gameboard->map().on_board(loc)) {
return loc;
}
}
}
return map_location::null_location();
}
void unit_creator::add_unit(const config &cfg, const vconfig* vcfg)
{
config temp_cfg(cfg);
temp_cfg["side"] = team_.side();
temp_cfg.remove_attribute("player_id");
temp_cfg.remove_attribute("faction_from_recruit");
const std::string& id =(cfg)["id"];
bool animate = temp_cfg["animate"].to_bool();
temp_cfg.remove_attribute("animate");
unit_ptr recall_list_element = team_.recall_list().find_if_matches_id(id);
if ( !recall_list_element ) {
//make the new unit
unit_ptr new_unit(new unit(temp_cfg, true, vcfg));
map_location loc = find_location(temp_cfg, new_unit.get());
if ( loc.valid() ) {
//add the new unit to map
resources::units->replace(loc, *new_unit);
LOG_NG << "inserting unit for side " << new_unit->side() << "\n";
post_create(loc,*(resources::units->find(loc)),animate);
}
else if ( add_to_recall_ ) {
//add to recall list
team_.recall_list().add(new_unit);
DBG_NG << "inserting unit with id=["<<id<<"] on recall list for side " << new_unit->side() << "\n";
preferences::encountered_units().insert(new_unit->type_id());
}
} else {
//get unit from recall list
map_location loc = find_location(temp_cfg, recall_list_element.get());
if ( loc.valid() ) {
resources::units->replace(loc, *recall_list_element);
LOG_NG << "inserting unit from recall list for side " << recall_list_element->side()<< " with id="<< id << "\n";
post_create(loc,*(resources::units->find(loc)),animate);
//if id is not empty, delete units with this ID from recall list
team_.recall_list().erase_if_matches_id( id);
}
else if ( add_to_recall_ ) {
LOG_NG << "wanted to insert unit on recall list, but recall list for side " << (cfg)["side"] << "already contains id=" <<id<<"\n";
return;
}
}
}
void unit_creator::post_create(const map_location &loc, const unit &new_unit, bool anim)
{
if (discover_) {
preferences::encountered_units().insert(new_unit.type_id());
}
bool show = show_ && (resources::screen !=NULL) && !resources::screen->fogged(loc);
bool animate = show && anim;
if (get_village_) {
if (resources::gameboard->map().is_village(loc)) {
actions::get_village(loc, new_unit.side());
}
}
if (resources::screen!=NULL) {
if (invalidate_ ) {
resources::screen->invalidate(loc);
}
if (animate) {
unit_display::unit_recruited(loc);
} else if (show) {
resources::screen->draw();
}
}
}
namespace actions {
const std::set<std::string> get_recruits(int side, const map_location &recruit_loc)
{
const team & current_team = (*resources::teams)[side -1];

View file

@ -26,47 +26,11 @@ class team;
class unit_type;
class vconfig;
#include "unit_creator.hpp"
#include "../map_location.hpp"
#include "../unit_ptr.hpp"
class unit_creator {
public:
unit_creator(team &tm, const map_location &start_pos);
unit_creator& allow_show(bool b);
unit_creator& allow_get_village(bool b);
unit_creator& allow_rename_side(bool b);
unit_creator& allow_invalidate(bool b);
unit_creator& allow_discover(bool b);
unit_creator& allow_add_to_recall(bool b);
/**
* finds a suitable location for unit
* @retval map_location::null_location() if unit is to be put into recall list
* @retval valid on-board map location otherwise
*/
map_location find_location(const config &cfg, const unit* pass_check=NULL);
/**
* adds a unit on map without firing any events (so, usable during team construction in gamestatus)
*/
void add_unit(const config &cfg, const vconfig* vcfg = NULL);
private:
void post_create(const map_location &loc, const unit &new_unit, bool anim);
bool add_to_recall_;
bool discover_;
bool get_village_;
bool invalidate_;
bool rename_side_;
bool show_;
const map_location start_pos_;
team &team_;
};
namespace actions {
/// The possible results of finding a location for recruiting (or recalling).

View file

@ -373,14 +373,14 @@ void undo_list::undo()
action_list::auto_type action = undos_.pop_back();
if (undo_action* undoable_action = dynamic_cast<undo_action*>(action.ptr()))
{
int last_unit_id = n_unit::id_manager::instance().get_save_id();
int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
if ( !undoable_action->undo(side_) ) {
return;
}
if(last_unit_id - undoable_action->unit_id_diff < 0) {
ERR_NG << "Next unit id is below 0 after undoing" << std::endl;
}
n_unit::id_manager::instance().set_save_id(last_unit_id - undoable_action->unit_id_diff);
resources::gameboard->unit_id_manager().set_save_id(last_unit_id - undoable_action->unit_id_diff);
// Bookkeeping.
resources::recorder->undo_cut(undoable_action->replay_data);
@ -422,14 +422,14 @@ void undo_list::redo()
// Get the action to redo. (This will be placed on the undo stack, but
// only if the redo is successful.)
redos_list::auto_type action = redos_.pop_back();
int last_unit_id = n_unit::id_manager::instance().get_save_id();
int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
if ( !action->redo(side_) ) {
return;
}
if(last_unit_id + action->unit_id_diff < static_cast<int>(n_unit::id_manager::instance().get_save_id())) {
if(last_unit_id + action->unit_id_diff < static_cast<int>(resources::gameboard->unit_id_manager().get_save_id())) {
ERR_NG << "Too many units were generated during redoing." << std::endl;
}
n_unit::id_manager::instance().set_save_id(last_unit_id + action->unit_id_diff);
resources::gameboard->unit_id_manager().set_save_id(last_unit_id + action->unit_id_diff);
// Bookkeeping.
undos_.push_back(action.release());

View file

@ -0,0 +1,230 @@
/*
Copyright (C) 2003 - 2015 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Recruiting, recalling.
*/
#include "unit_creator.hpp"
#include "move.hpp" //for actions::get_village
#include "../config.hpp"
#include "../filter_context.hpp"
#include "../game_board.hpp"
#include "../game_preferences.hpp"
#include "../game_data.hpp" // for resources::gamedata conversion variable_set
#include "../gettext.hpp"
#include "../log.hpp"
#include "../map.hpp"
#include "../pathfind/pathfind.hpp"
#include "../resources.hpp" // for resources::screen, resources::gamedata
#include "../team.hpp" //for team
#include "../unit.hpp" // for unit
#include "../unit_display.hpp" // for unit_display
#include "../variable.hpp" // for vconfig
#include "../game_display.hpp" // for resources::screen
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
static lg::log_domain log_engine("engine");
#define DBG_NG LOG_STREAM(debug, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
unit_creator::unit_creator(team &tm, const map_location &start_pos, game_board* board)
: add_to_recall_(false)
, discover_(false)
, get_village_(false)
, invalidate_(false)
, rename_side_(false)
, show_(false)
, start_pos_(start_pos)
, team_(tm)
, board_(board ? board : resources::gameboard)
{
}
unit_creator& unit_creator::allow_show(bool b)
{
show_ = b;
return *this;
}
unit_creator& unit_creator::allow_get_village(bool b)
{
get_village_ = b;
return *this;
}
unit_creator& unit_creator::allow_rename_side(bool b)
{
rename_side_ = b;
return *this;
}
unit_creator& unit_creator::allow_invalidate(bool b)
{
invalidate_ = b;
return *this;
}
unit_creator& unit_creator::allow_discover(bool b)
{
discover_ = b;
return *this;
}
unit_creator& unit_creator::allow_add_to_recall(bool b)
{
add_to_recall_ = b;
return *this;
}
map_location unit_creator::find_location(const config &cfg, const unit* pass_check)
{
DBG_NG << "finding location for unit with id=["<<cfg["id"]<<"] placement=["<<cfg["placement"]<<"] x=["<<cfg["x"]<<"] y=["<<cfg["y"]<<"] for side " << team_.side() << "\n";
std::vector<std::string> placements = utils::split(cfg["placement"]);
placements.push_back("map");
placements.push_back("recall");
BOOST_FOREACH(const std::string& place, placements)
{
map_location loc;
if ( place == "recall" ) {
return map_location::null_location();
}
else if ( place == "leader" || place == "leader_passable" ) {
unit_map::const_iterator leader = board_->units().find_leader(team_.side());
//todo: take 'leader in recall list' possibility into account
if (leader.valid()) {
loc = leader->get_location();
} else {
loc = start_pos_;
}
}
// "map", "map_passable", and "map_overwrite".
else if ( place == "map" || place.compare(0, 4, "map_") == 0 ) {
loc = map_location(cfg, resources::gamedata);
}
if(loc.valid() && board_->map().on_board(loc)) {
const bool pass((place == "leader_passable") || (place == "map_passable"));
if ( place != "map_overwrite" ) {
loc = find_vacant_tile(loc, pathfind::VACANT_ANY,
pass ? pass_check : NULL, NULL, board_);
}
if(loc.valid() && board_->map().on_board(loc)) {
return loc;
}
}
}
return map_location::null_location();
}
void unit_creator::add_unit(const config &cfg, const vconfig* vcfg)
{
config temp_cfg(cfg);
temp_cfg["side"] = team_.side();
temp_cfg.remove_attribute("player_id");
temp_cfg.remove_attribute("faction_from_recruit");
const std::string& id =(cfg)["id"];
bool animate = temp_cfg["animate"].to_bool();
temp_cfg.remove_attribute("animate");
unit_ptr recall_list_element = team_.recall_list().find_if_matches_id(id);
if ( !recall_list_element ) {
//make the new unit
unit_ptr new_unit(new unit(temp_cfg, true, vcfg, &board_->unit_id_manager()));
map_location loc = find_location(temp_cfg, new_unit.get());
if ( loc.valid() ) {
//add the new unit to map
board_->units().replace(loc, *new_unit);
LOG_NG << "inserting unit for side " << new_unit->side() << "\n";
post_create(loc,*(board_->units().find(loc)),animate);
}
else if ( add_to_recall_ ) {
//add to recall list
team_.recall_list().add(new_unit);
DBG_NG << "inserting unit with id=["<<id<<"] on recall list for side " << new_unit->side() << "\n";
preferences::encountered_units().insert(new_unit->type_id());
}
} else {
//get unit from recall list
map_location loc = find_location(temp_cfg, recall_list_element.get());
if ( loc.valid() ) {
board_->units().replace(loc, *recall_list_element);
LOG_NG << "inserting unit from recall list for side " << recall_list_element->side()<< " with id="<< id << "\n";
post_create(loc,*(board_->units().find(loc)),animate);
//if id is not empty, delete units with this ID from recall list
team_.recall_list().erase_if_matches_id( id);
}
else if ( add_to_recall_ ) {
LOG_NG << "wanted to insert unit on recall list, but recall list for side " << (cfg)["side"] << "already contains id=" <<id<<"\n";
return;
}
}
}
void unit_creator::post_create(const map_location &loc, const unit &new_unit, bool anim)
{
if (discover_) {
preferences::encountered_units().insert(new_unit.type_id());
}
bool show = show_ && (resources::screen !=NULL) && !resources::screen->fogged(loc);
bool animate = show && anim;
if (get_village_) {
assert(resources::gameboard);
if (board_->map().is_village(loc)) {
actions::get_village(loc, new_unit.side());
}
}
if (resources::screen!=NULL) {
if (invalidate_ ) {
resources::screen->invalidate(loc);
}
if (animate) {
unit_display::unit_recruited(loc);
} else if (show) {
resources::screen->draw();
}
}
}

View file

@ -0,0 +1,68 @@
/*
Copyright (C) 2003 - 2015 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Various functions related to the creation of units (recruits, recalls,
* and placed units).
*/
#pragma once
class config;
class team;
class vconfig;
class game_board;
class unit;
#include "../map_location.hpp"
class unit_creator
{
public:
unit_creator(team &tm, const map_location &start_pos, game_board* board = NULL);
unit_creator& allow_show(bool b);
unit_creator& allow_get_village(bool b);
unit_creator& allow_rename_side(bool b);
unit_creator& allow_invalidate(bool b);
unit_creator& allow_discover(bool b);
unit_creator& allow_add_to_recall(bool b);
/**
* finds a suitable location for unit
* @retval map_location::null_location() if unit is to be put into recall list
* @retval valid on-board map location otherwise
*/
map_location find_location(const config &cfg, const unit* pass_check=NULL);
/**
* adds a unit on map without firing any events (so, usable during team construction in gamestatus)
*/
void add_unit(const config &cfg, const vconfig* vcfg = NULL);
private:
void post_create(const map_location &loc, const unit &new_unit, bool anim);
bool add_to_recall_;
bool discover_;
bool get_village_;
bool invalidate_;
bool rename_side_;
bool show_;
const map_location start_pos_;
team &team_;
game_board* board_;
};

View file

@ -138,8 +138,10 @@ carryover_info::carryover_info(const config& cfg, bool from_snpashot)
}
this->carryover_sides_.push_back(carryover(side));
}
wml_menu_items_.set_menu_items(cfg);
BOOST_FOREACH(const config& item, cfg.child_range("menu_item"))
{
wml_menu_items_.push_back(new config(item));
}
}
std::vector<carryover>& carryover_info::get_all_sides() {
@ -217,12 +219,15 @@ void carryover_info::transfer_to(config& level)
}
if(!level.has_child("menu_item")){
wml_menu_items_.to_config(level);
BOOST_FOREACH(config& item , wml_menu_items_)
{
level.add_child("menu_item").swap(item);
}
}
next_scenario_ = "";
variables_ = config();
wml_menu_items_ = game_events::wmi_container();
wml_menu_items_.clear();
}
@ -240,9 +245,10 @@ const config carryover_info::to_config()
cfg["random_calls"] = rng_.get_random_calls();
cfg.add_child("variables", variables_);
wml_menu_items_.to_config(cfg);
BOOST_FOREACH(const config& item , wml_menu_items_)
{
cfg.add_child("menu_item", item);
}
return cfg;
}

View file

@ -7,12 +7,14 @@ class config;
#include <vector>
#include <string>
#include <set>
#include <boost/ptr_container/ptr_vector.hpp>
#include "config.hpp"
#include "mt_rng.hpp"
#include "game_events/wmi_container.hpp"
class carryover{
class carryover
{
public:
carryover()
: add_ ()
@ -47,7 +49,8 @@ private:
std::string get_recruits(bool erase=false);
};
class carryover_info{
class carryover_info
{
public:
carryover_info()
: carryover_sides_()
@ -74,8 +77,6 @@ public:
void set_variables(const config& vars) { variables_ = vars; }
const config& get_variables() const { return variables_; }
game_events::wmi_container& get_wml_menu_items() { return wml_menu_items_; }
const rand_rng::mt_rng& rng() const { return rng_; }
rand_rng::mt_rng& rng() { return rng_; }
@ -88,7 +89,7 @@ private:
std::vector<carryover> carryover_sides_;
config variables_;
rand_rng::mt_rng rng_;
game_events::wmi_container wml_menu_items_;
boost::ptr_vector<config> wml_menu_items_;
std::string next_scenario_; /**< the scenario coming next (for campaigns) */
int next_underlying_unit_id_;
};

View file

@ -515,6 +515,15 @@ void config::append_children(const config &cfg)
}
}
void config::append_children(const config &cfg, const std::string& key)
{
check_valid(cfg);
BOOST_FOREACH(const config &value, cfg.child_range(key)) {
add_child(key, value);
}
}
void config::append(const config &cfg)
{
append_children(cfg);

View file

@ -667,6 +667,11 @@ public:
*/
void append_children(const config &cfg);
/**
* Adds children from @a cfg.
*/
void append_children(const config &cfg, const std::string& key);
/**
* All children with the given key will be merged
* into the first element with that key.

View file

@ -62,6 +62,7 @@ public:
void play_slice(bool is_delay_enabled = true);
static const config &get_theme(const config& game_config, std::string theme_name);
protected:
virtual bool is_browsing() const
{ return false; }
@ -134,7 +135,6 @@ protected:
virtual bool in_context_menu(hotkey::HOTKEY_COMMAND command) const;
static const config &get_theme(const config& game_config, std::string theme_name);
const config& game_config_;
CKey key_;
bool scrolling_;

View file

@ -176,7 +176,7 @@ display::display(const display_context * dc, CVideo& video, boost::weak_ptr<wb::
turbo_(false),
invalidateGameStatus_(true),
map_labels_(new map_labels(*this, 0)),
reports_object_(reports_object),
reports_object_(&reports_object),
scroll_event_("scrolled"),
complete_redraw_event_("completely_redrawn"),
nextDraw_(0),
@ -3111,7 +3111,7 @@ void display::refresh_report(std::string const &report_name, const config * new_
reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
const config generated_cfg = new_cfg ? config() : reports_object_.generate_report(report_name, temp_context);
const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
if ( new_cfg == NULL )
new_cfg = &generated_cfg;
@ -3322,7 +3322,7 @@ void display::refresh_report(std::string const &report_name, const config * new_
reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
const config generated_cfg = new_cfg ? config() : reports_object_.generate_report(report_name, temp_context);
const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
if ( new_cfg == NULL )
new_cfg = &generated_cfg;

View file

@ -645,7 +645,10 @@ public:
/** Rebuild the flag list (not team colors) for a single side. */
void reinit_flags_for_side(size_t side);
void reset_reports(reports& reports_object)
{
reports_object_ = &reports_object;
}
private:
void init_flags_for_side_internal(size_t side, const std::string& side_color);
@ -781,7 +784,7 @@ protected:
bool turbo_;
bool invalidateGameStatus_;
boost::scoped_ptr<map_labels> map_labels_;
reports & reports_object_;
reports * reports_object_;
/** Event raised when the map is being scrolled */
mutable events::generic_event scroll_event_;

View file

@ -36,9 +36,19 @@ static lg::log_domain log_engine("enginerefac");
static lg::log_domain log_engine_enemies("engine/enemies");
#define DBG_EE LOG_STREAM(debug, log_engine_enemies)
game_board::game_board(const tdata_cache & tdata, const config & level) : teams_(), map_(new gamemap(tdata, level)), units_() {}
game_board::game_board(const tdata_cache & tdata, const config & level)
: teams_()
, map_(new gamemap(tdata, level))
, unit_id_manager_(level["next_underlying_unit_id"])
, units_()
{
}
game_board::game_board(const game_board & other) : teams_(other.teams_), map_(new gamemap(*(other.map_))), units_(other.units_) {}
game_board::game_board(const game_board & other)
: teams_(other.teams_)
, map_(new gamemap(*(other.map_)))
, unit_id_manager_(other.unit_id_manager_)
, units_(other.units_) {}
game_board::~game_board() {}
@ -49,6 +59,7 @@ game_board::~game_board() {}
void swap(game_board & one, game_board & other) {
std::swap(one.teams_, other.teams_);
std::swap(one.units_, other.units_);
std::swap(one.unit_id_manager_, other.unit_id_manager_);
one.map_.swap(other.map_);
}
@ -328,7 +339,9 @@ bool game_board::change_terrain(const map_location &loc, const std::string &t_st
return true;
}
void game_board::write_config(config & cfg) const {
void game_board::write_config(config & cfg) const
{
cfg["next_underlying_unit_id"] = unit_id_manager_.get_save_id();
for(std::vector<team>::const_iterator t = teams_.begin(); t != teams_.end(); ++t) {
int side_num = t - teams_.begin() + 1;

View file

@ -21,6 +21,7 @@
#include "team.hpp"
#include "terrain_type_data.hpp"
#include "unit_map.hpp"
#include "unit_id.hpp"
#include <boost/optional.hpp>
#include <boost/scoped_ptr.hpp>
@ -51,12 +52,14 @@ namespace events {
*
**/
class game_board : public display_context {
class game_board : public display_context
{
std::vector<team> teams_;
std::vector<std::string> labels_;
boost::scoped_ptr<gamemap> map_;
n_unit::id_manager unit_id_manager_;
unit_map units_;
//TODO: Remove these when we have refactored enough to make it possible.
@ -84,8 +87,8 @@ class game_board : public display_context {
friend struct temporary_unit_mover;
friend struct temporary_unit_remover;
public:
public:
n_unit::id_manager& unit_id_manager() { return unit_id_manager_; }
// Constructors, trivial dtor, and const accessors
game_board(const tdata_cache & tdata, const config & level);
@ -94,6 +97,7 @@ class game_board : public display_context {
virtual const std::vector<team> & teams() const { return teams_; }
virtual const gamemap & map() const { return *map_; }
virtual const unit_map & units() const { return units_; }
unit_map & units() { return units_; }
virtual const std::vector<std::string> & hidden_label_categories() const { return labels_; }
// Copy and swap idiom, because we have a scoped pointer.

View file

@ -78,7 +78,7 @@ game_display::game_display(game_board& board, CVideo& video, boost::weak_ptr<wb:
attack_indicator_src_(),
attack_indicator_dst_(),
route_(),
tod_manager_(tod),
tod_manager_(&tod),
displayedUnitHex_(),
sidebarScaling_(1.0),
first_turn_(true),
@ -113,10 +113,10 @@ game_display::~game_display()
//TODO: proper SDL_gpu implementation
void game_display::new_turn()
{
const time_of_day& tod = tod_manager_.get_time_of_day();
const time_of_day& tod = tod_manager_->get_time_of_day();
if( !first_turn_) {
const time_of_day& old_tod = tod_manager_.get_previous_time_of_day();
const time_of_day& old_tod = tod_manager_->get_previous_time_of_day();
if(old_tod.image_mask != tod.image_mask) {
const surface old_mask(image::get_image(old_tod.image_mask,image::SCALED_TO_HEX));
@ -453,12 +453,12 @@ void game_display::draw_hex(const map_location& loc)
const time_of_day& game_display::get_time_of_day(const map_location& loc) const
{
return tod_manager_.get_time_of_day(loc);
return tod_manager_->get_time_of_day(loc);
}
bool game_display::has_time_area() const
{
return tod_manager_.has_time_area();
return tod_manager_->has_time_area();
}
void game_display::draw_sidebar()
@ -475,7 +475,7 @@ void game_display::draw_sidebar()
// We display the unit the mouse is over if it is over a unit,
// otherwise we display the unit that is selected.
BOOST_FOREACH(const std::string &name, reports_object_.report_list()) {
BOOST_FOREACH(const std::string &name, reports_object_->report_list()) {
refresh_report(name);
}
invalidateGameStatus_ = false;

View file

@ -132,7 +132,7 @@ public:
bool has_time_area() const;
const tod_manager & get_tod_man() const { return tod_manager_; } /**< Allows this class to properly implement filter context, used for animations */
const tod_manager & get_tod_man() const { return *tod_manager_; } /**< Allows this class to properly implement filter context, used for animations */
protected:
/**
@ -224,7 +224,10 @@ public:
/// Rebuilds the screen if needs_rebuild(true) was previously called, and resets the flag.
bool maybe_rebuild();
void reset_tod_manager(const tod_manager& tod_manager)
{
tod_manager_ = &tod_manager;
}
private:
game_display(const game_display&);
void operator=(const game_display&);
@ -241,7 +244,7 @@ private:
pathfind::marked_route route_;
const tod_manager& tod_manager_;
const tod_manager* tod_manager_;
void invalidate_route();

View file

@ -84,8 +84,9 @@ bool wmi_container::fire_item(const std::string & id, const map_location & hex,
{
// Does this item exist?
const_iterator iter = find(id);
if ( iter == end() )
if ( iter == end() ) {
return false;
}
const wml_menu_item & wmi = **iter;
// Prepare for can show().
@ -94,9 +95,9 @@ bool wmi_container::fire_item(const std::string & id, const map_location & hex,
scoped_xy_unit highlighted_unit("unit", hex.x, hex.y, units);
// Can this item be shown?
if ( wmi.can_show(hex, gamedata, fc) )
if ( wmi.can_show(hex, gamedata, fc) ) {
wmi.fire_event(hex, gamedata);
}
return true;
}
@ -110,10 +111,10 @@ std::vector<std::pair<boost::shared_ptr<const wml_menu_item>, std::string> > wmi
game_data & gamedata, filter_context & fc, unit_map & units, const_iterator start, const_iterator finish) const
{
std::vector<std::pair<boost::shared_ptr<const wml_menu_item>, std::string> > ret;
if ( empty() )
if ( empty() ) {
// Nothing to do (skip setting game variables).
return ret;
}
// Prepare for can show().
gamedata.get_variable("x1") = hex.x + 1;
gamedata.get_variable("y1") = hex.y + 1;
@ -164,8 +165,10 @@ void wmi_container::to_config(config& cfg) const
{
// Loop through our items.
BOOST_FOREACH( const item_ptr & item, *this )
{
// Add this item as a child of cfg.
item->to_config(cfg.add_child("menu_item"));
}
}
/**

View file

@ -452,7 +452,6 @@ static void enter_wait_mode(game_display& disp, const config& game_config,
gamelist.clear();
statistics::fresh_stats();
n_unit::id_manager::instance().clear(); /* reset the unit underlying_id counter back to zero */
{
mp::wait ui(disp, game_config, state, gamechat, gamelist);

View file

@ -195,7 +195,7 @@ static LEVEL_RESULT playsingle_scenario(const config& game_config,
playsingle_controller playcontroller(state_of_game.get_starting_pos(), state_of_game, ticks, game_config, tdata, disp.video(), skip_replay);
LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks()) << "\n";
LEVEL_RESULT res = playcontroller.play_scenario(story);
LEVEL_RESULT res = playcontroller.play_scenario(story, state_of_game.get_starting_pos());
if (res == LEVEL_RESULT::QUIT)
{
@ -224,7 +224,7 @@ static LEVEL_RESULT playmp_scenario(const config& game_config,
playmp_controller playcontroller(state_of_game.get_starting_pos(), state_of_game, ticks,
game_config, tdata, disp.video(), skip_replay, blindfold_replay, io_type == IO_SERVER);
LEVEL_RESULT res = playcontroller.play_scenario(story);
LEVEL_RESULT res = playcontroller.play_scenario(story, state_of_game.get_starting_pos());
//Check if the player started as mp client and changed to host
if (io_type == IO_CLIENT && playcontroller.is_host())

View file

@ -44,17 +44,44 @@ static lg::log_domain log_engine("engine");
#define LOG_NG LOG_STREAM(info, log_engine)
#define DBG_NG LOG_STREAM(debug, log_engine)
game_state::game_state(const config & level, const tdata_cache & tdata) :
level_(level),
gamedata_(level_),
board_(tdata,level_),
tod_manager_(level_),
static void no_op() {}
game_state::game_state(const config & level, play_controller & pc, const tdata_cache & tdata) :
gamedata_(level),
board_(tdata, level),
tod_manager_(level),
pathfind_manager_(),
reports_(new reports()),
lua_kernel_(),
events_manager_(),
player_number_(level["playing_team"].to_int() + 1),
init_side_done_(level["init_side_done"].to_bool(false)),
start_event_fired_(!level["playing_team"].empty()),
server_request_number_(level["server_request_number"].to_int()),
first_human_team_(-1)
{}
{
init(pc.ticks(), pc, level);
}
game_state::game_state(const config & level, play_controller & pc, game_board& board) :
gamedata_(level),
board_(board),
tod_manager_(level),
pathfind_manager_(new pathfind::manager(level)),
reports_(new reports()),
lua_kernel_(new game_lua_kernel(NULL, *this, pc, *reports_)),
events_manager_(),
player_number_(level["playing_team"].to_int() + 1),
init_side_done_(level["init_side_done"].to_bool(false)),
first_human_team_(-1)
{
init(pc.ticks(), pc, level);
game_events_resources_ = boost::make_shared<game_events::t_context>(lua_kernel_.get(), this, static_cast<game_display*>(NULL), &gamedata_, &board_.units_, &no_op, boost::bind(&play_controller::current_side, &pc));
events_manager_.reset(new game_events::manager(level, game_events_resources_));
}
game_state::~game_state() {}
@ -94,14 +121,14 @@ struct placing_info {
static bool operator<(const placing_info& a, const placing_info& b) { return a.score > b.score; }
void game_state::place_sides_in_preferred_locations()
void game_state::place_sides_in_preferred_locations(const config& level)
{
std::vector<placing_info> placings;
int num_pos = board_.map().num_valid_starting_positions();
int side_num = 1;
BOOST_FOREACH(const config &side, level_.child_range("side"))
BOOST_FOREACH(const config &side, level.child_range("side"))
{
for(int p = 1; p <= num_pos; ++p) {
const map_location& pos = board_.map().starting_position(p);
@ -129,29 +156,28 @@ void game_state::place_sides_in_preferred_locations()
}
}
static void no_op() {}
void game_state::init(const int ticks, play_controller & pc)
void game_state::init(const int ticks, play_controller & pc, const config& level)
{
if (level_["modify_placing"].to_bool()) {
int ticks1 = SDL_GetTicks();
if (level["modify_placing"].to_bool()) {
LOG_NG << "modifying placing..." << std::endl;
place_sides_in_preferred_locations();
place_sides_in_preferred_locations(level);
}
LOG_NG << "initialized time of day regions... " << (SDL_GetTicks() - ticks) << std::endl;
BOOST_FOREACH(const config &t, level_.child_range("time_area")) {
BOOST_FOREACH(const config &t, level.child_range("time_area")) {
tod_manager_.add_time_area(board_.map(),t);
}
LOG_NG << "initialized teams... " << (SDL_GetTicks() - ticks) << std::endl;
loadscreen::start_stage("init teams");
//loadscreen::start_stage("init teams");
board_.teams_.resize(level_.child_count("side"));
board_.teams_.resize(level.child_count("side"));
std::vector<team_builder_ptr> team_builders;
int team_num = 0;
BOOST_FOREACH(const config &side, level_.child_range("side"))
BOOST_FOREACH(const config &side, level.child_range("side"))
{
if (first_human_team_ == -1) {
const std::string &controller = side["controller"];
@ -160,7 +186,7 @@ void game_state::init(const int ticks, play_controller & pc)
}
}
team_builder_ptr tb_ptr = create_team_builder(side,
board_.teams_, level_, *board_.map_);
board_.teams_, level, board_);
++team_num;
build_team_stage_one(tb_ptr);
team_builders.push_back(tb_ptr);
@ -183,14 +209,20 @@ void game_state::init(const int ticks, play_controller & pc)
}
}
}
std::cerr << "Initilizing teams took " << SDL_GetTicks() - ticks1 << " ticks.\n";
pathfind_manager_.reset(new pathfind::manager(level_));
pathfind_manager_.reset(new pathfind::manager(level));
lua_kernel_.reset(new game_lua_kernel(level_, NULL, *this, pc, *reports_));
lua_kernel_.reset(new game_lua_kernel(NULL, *this, pc, *reports_));
game_events_resources_ = boost::make_shared<game_events::t_context>(lua_kernel_.get(), this, static_cast<game_display*>(NULL), &gamedata_, &board_.units_, &no_op, boost::bind(&play_controller::current_side, &pc));
events_manager_.reset(new game_events::manager(level_, game_events_resources_));
events_manager_.reset(new game_events::manager(level, game_events_resources_));
std::cerr << "Initilizing total took " << SDL_GetTicks() - ticks1 << " ticks.\n";
}
void game_state::bind(wb::manager * whiteboard, game_display * gd)
@ -211,6 +243,11 @@ void game_state::set_game_display(game_display * gd)
void game_state::write(config& cfg) const
{
cfg["init_side_done"] = init_side_done_;
if(gamedata_.phase() == game_data::PLAY) {
cfg["playing_team"] = player_number_ - 1;
}
cfg["server_request_number"] = server_request_number_;
//Call the lua save_game functions
lua_kernel_->save_game(cfg);
@ -228,6 +265,7 @@ void game_state::write(config& cfg) const
//Write the game data, including wml vars
gamedata_.write_snapshot(cfg);
}
namespace {

View file

@ -21,6 +21,7 @@ class config;
#include "game_board.hpp"
#include "game_data.hpp"
#include "tod_manager.hpp"
#include "unit_id.hpp"
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
@ -44,7 +45,6 @@ private:
friend class replay_controller;
public:
const config& level_;
game_data gamedata_;
game_board board_;
tod_manager tod_manager_;
@ -52,19 +52,28 @@ public:
boost::scoped_ptr<reports> reports_;
boost::scoped_ptr<game_lua_kernel> lua_kernel_;
boost::scoped_ptr<game_events::manager> events_manager_;
int player_number_;
bool init_side_done_;
bool start_event_fired_;
// used to sync with the mpserver
int server_request_number_;
bool& init_side_done() { return init_side_done_; }
game_events::wmi_container& get_wml_menu_items();
const game_events::wmi_container& get_wml_menu_items() const;
int first_human_team_; //needed to initialize the viewpoint during setup
game_state(const config & level, const tdata_cache & tdata);
game_state(const config & level, play_controller &, const tdata_cache & tdata);
/// The third parameter is an optimisation.
game_state(const config & level, play_controller &, game_board& board);
~game_state();
void place_sides_in_preferred_locations();
void place_sides_in_preferred_locations(const config& level);
void init(int ticks, play_controller & );
void init(int ticks, play_controller &, const config& level);
void bind(wb::manager *, game_display *);

View file

@ -224,7 +224,7 @@ bool play_controller::hotkey_handler::execute_command(const hotkey::hotkey_comma
unsigned i = static_cast<unsigned>(index);
if(i < savenames_.size() && !savenames_[i].empty()) {
// Load the game by throwing load_game_exception
throw game::load_game_exception(savenames_[i],false,false,false,"",true);
load_autosave(savenames_[i]);
} else if ( i < wml_commands_.size() && wml_commands_[i] ) {
if (!wml_command_pager_->capture(*wml_commands_[i])) {
@ -429,7 +429,8 @@ void play_controller::hotkey_handler::show_menu(const std::vector<std::string>&
while(i != items.end()) {
if (*i == "AUTOSAVES") {
// Autosave visibility is similar to LOAD_GAME hotkey
cmd = &hotkey::hotkey_command::get_command_by_command(hotkey::HOTKEY_LOAD_GAME);
++i; continue; //cmd = &hotkey::hotkey_command::get_command_by_command(hotkey::HOTKEY_LOAD_GAME);
} else {
cmd = &hotkey::get_hotkey_command(*i);
}
@ -511,3 +512,7 @@ hotkey::ACTION_STATE play_controller::hotkey_handler::get_action_state(hotkey::H
}
}
void play_controller::hotkey_handler::load_autosave(const std::string& filename)
{
throw game::load_game_exception(filename, false, false, false, "", true);
}

View file

@ -112,6 +112,7 @@ public:
virtual void toggle_accelerated_speed();
virtual std::string get_action_image(hotkey::HOTKEY_COMMAND, int index) const;
virtual void load_autosave(const std::string& filename);
virtual hotkey::ACTION_STATE get_action_state(hotkey::HOTKEY_COMMAND command, int index) const;
/** Check if a command can be executed. */
virtual bool can_execute_command(const hotkey::hotkey_command& command, int index=-1) const;

View file

@ -65,7 +65,7 @@ bool replay_controller::hotkey_handler::can_execute_command(const hotkey::hotkey
case hotkey::HOTKEY_REPLAY_NEXT_SIDE:
case hotkey::HOTKEY_REPLAY_NEXT_MOVE:
//we have one events_disabler when starting the replay_controller and a second when entering the synced context.
return (events::commands_disabled <= 1 ) && !replay_controller_.recorder_at_end();
return replay_controller_.should_stop() && (events::commands_disabled <= 1 ) && !replay_controller_.recorder_at_end();
default:
return result;
}

View file

@ -273,8 +273,40 @@ bool playsingle_controller::hotkey_handler::can_execute_command(const hotkey::ho
return false;
}
case hotkey::HOTKEY_REPLAY_STOP:
case hotkey::HOTKEY_REPLAY_PLAY:
case hotkey::HOTKEY_REPLAY_NEXT_TURN:
case hotkey::HOTKEY_REPLAY_NEXT_SIDE:
case hotkey::HOTKEY_REPLAY_NEXT_MOVE:
return playsingle_controller_.get_replay_controller() && playsingle_controller_.get_replay_controller()->can_execute_command(cmd, index);
default: return play_controller::hotkey_handler::can_execute_command(cmd, index);
}
return res;
}
#include "network.hpp"
#include "save_index.hpp"
#include "gui/dialogs/message.hpp"
void playsingle_controller::hotkey_handler::load_autosave(const std::string& filename)
{
if(network::nconnections() > 0)
{
config savegame;
std::string error_log;
savegame::read_save_file(filename, savegame, &error_log);
if(!error_log.empty() || savegame.child_or_empty("snapshot")["replay_pos"].to_int(-1) < 0 ) {
gui2::show_error_message(play_controller_.get_display().video(),
_("The file you have tried to load is corrupt: '") +
error_log);
return;
}
boost::shared_ptr<config> res(new config(savegame.child_or_empty("snapshot")));
throw reset_gamestate_exception(res);
}
else
{
play_controller::hotkey_handler::load_autosave(filename);
}
}

View file

@ -71,6 +71,24 @@ public:
virtual void whiteboard_bump_down_action();
virtual void whiteboard_suppose_dead();
//replay
mp_replay_controller& get_replay_controller()
{
assert(playsingle_controller_.get_replay_controller());
return *playsingle_controller_.get_replay_controller();
}
virtual void stop_replay() OVERRIDE
{ return get_replay_controller().stop_replay(); }
virtual void play_replay() OVERRIDE
{ return get_replay_controller().play_replay(); }
virtual void replay_next_turn() OVERRIDE
{ return get_replay_controller().replay_next_turn(); }
virtual void replay_next_side() OVERRIDE
{ return get_replay_controller().replay_next_side(); }
virtual void replay_next_move() OVERRIDE
{ return get_replay_controller().replay_next_move(); }
virtual void load_autosave(const std::string& filename);
virtual hotkey::ACTION_STATE get_action_state(hotkey::HOTKEY_COMMAND command, int index) const;
};

View file

@ -0,0 +1,382 @@
/*
Copyright (C) 2015 by the Battle for Wesnoth Project
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "global.hpp"
#include "mp_replay_controller.hpp"
#include "game_config_manager.hpp"
#include "gettext.hpp"
#include "log.hpp"
#include "replay.hpp"
#include "resources.hpp"
#include "config_assign.hpp"
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
static lg::log_domain log_engine("engine");
#define DBG_NG LOG_STREAM(debug, log_engine)
static lg::log_domain log_replay("replay");
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
#define LOG_REPLAY LOG_STREAM(info, log_replay)
#define ERR_REPLAY LOG_STREAM(err, log_replay)
namespace
{
struct replay_play_nostop : public mp_replay_controller::replay_stop_condition
{
replay_play_nostop() {}
virtual bool should_stop() { return false; }
};
struct replay_play_moves_base : public mp_replay_controller::replay_stop_condition
{
int moves_todo_;
bool started_;
replay_play_moves_base(int moves_todo, bool started = true) : moves_todo_(moves_todo), started_(started) {}
virtual void move_done() { if(started_) { --moves_todo_; } }
virtual bool should_stop() { return moves_todo_ == 0; }
void start() { started_ = true; }
};
struct replay_play_moves : public replay_play_moves_base
{
replay_play_moves(int moves_todo) : replay_play_moves_base(moves_todo, true) {}
};
struct replay_play_turn : public replay_play_moves_base
{
int turn_begin_;
replay_play_turn(int turn_begin) : replay_play_moves_base(1, false), turn_begin_(turn_begin) {}
virtual void new_side_turn(int , int turn)
{
if (turn != turn_begin_) {
start();
}
}
};
struct replay_play_side : public replay_play_moves_base
{
int turn_begin_;
int side_begin_;
replay_play_side(int turn_begin, int side_begin) : replay_play_moves_base(1, false), turn_begin_(turn_begin), side_begin_(side_begin) {}
virtual void new_side_turn(int side , int turn)
{
if (turn != turn_begin_ || side != side_begin_) {
start();
}
}
};
}
namespace
{
config get_theme_removal_diff(const config& theme_cfg)
{
config res;
BOOST_FOREACH(const config::any_child& pair, theme_cfg.child_or_empty("replay").all_children_range())
{
if(pair.key == "add") {
BOOST_FOREACH(const config::any_child& inner, pair.cfg.all_children_range())
{
res.add_child("remove", config_of("id", inner.cfg["id"]));
}
}
if(pair.key == "change") {
std::string id = pair.cfg["id"];
BOOST_FOREACH(const config::any_child& inner, theme_cfg.all_children_range())
{
if(inner.cfg["id"] == id) {
res.add_child("change", config_of("id", id)("rect", inner.cfg["rect"]));
}
break;
}
}
}
return res;
}
}
mp_replay_controller::mp_replay_controller(play_controller& controller)
: controller_(controller)
, stop_condition_(new replay_stop_condition())
, disabler_()
{
controller_.get_display().get_theme().theme_reset_event().attach_handler(this);
controller_.get_display().redraw_everything();
//add_replay_theme();
}
mp_replay_controller::~mp_replay_controller()
{
controller_.get_display().get_theme().theme_reset_event().detach_handler(this);
controller_.get_display().redraw_everything();
//remove_replay_theme();
}
void mp_replay_controller::add_replay_theme()
{
const config &theme_cfg = controller_.get_theme(game_config_manager::get()->game_config(), controller_.theme());
if (const config &res = theme_cfg.child("resolution"))
{
if (const config &replay_theme_cfg = res.child("replay")) {
controller_.get_display().get_theme().modify(replay_theme_cfg);
}
//Make sure we get notified if the theme is redrawn completely. That way we have
//a chance to restore the replay controls of the theme as well.
controller_.get_display().invalidate_theme();
}
}
void mp_replay_controller::remove_replay_theme()
{
const config &theme_cfg = controller_.get_theme(game_config_manager::get()->game_config(), controller_.theme());
if (const config &res = theme_cfg.child("resolution"))
{
if (res.has_child("replay")) {
controller_.get_display().get_theme().modify(get_theme_removal_diff(res));
}
//Make sure we get notified if the theme is redrawn completely. That way we have
//a chance to restore the replay controls of the theme as well.
controller_.get_display().invalidate_theme();
}
}
void mp_replay_controller::rebuild_replay_theme()
{
const config &theme_cfg = controller_.get_theme(game_config_manager::get()->game_config(), controller_.theme());
if (const config &res = theme_cfg.child("resolution"))
{
if (const config &replay_theme_cfg = res.child("replay")) {
controller_.get_display().get_theme().modify(replay_theme_cfg);
}
controller_.get_display().get_theme().modify_label("time-icon", _ ("current local time"));
//Make sure we get notified if the theme is redrawn completely. That way we have
//a chance to restore the replay controls of the theme as well.
controller_.get_display().invalidate_theme();
}
}
gui::button* mp_replay_controller::play_button()
{
return controller_.get_display().find_action_button("button-playreplay");
}
gui::button* mp_replay_controller::stop_button()
{
return controller_.get_display().find_action_button("button-stopreplay");
}
gui::button* mp_replay_controller::reset_button()
{
return controller_.get_display().find_action_button("button-resetreplay");
}
gui::button* mp_replay_controller::play_turn_button()
{
return controller_.get_display().find_action_button("button-nextturn");
}
gui::button* mp_replay_controller::play_side_button()
{
return controller_.get_display().find_action_button("button-nextside");
}
gui::button* mp_replay_controller::play_move_button()
{
return controller_.get_display().find_action_button("button-nextmove");
}
void mp_replay_controller::update_replay_ui()
{
//check if we have all buttons - if someone messed with theme then some buttons may be missing
//if any of the buttons is missing, we just disable every one
if(!replay_ui_has_all_buttons()) {
gui::button *play_b = play_button(), *stop_b = stop_button(),
*reset_b = reset_button(), *play_turn_b = play_turn_button(),
*play_side_b = play_side_button(), *play_move_b = play_move_button();
if(play_b) {
play_b->enable(false);
}
if(stop_b) {
stop_b->enable(false);
}
if(reset_b) {
reset_b->enable(false);
}
if(play_turn_b) {
play_turn_b->enable(false);
}
if(play_side_b) {
play_side_b->enable(false);
}
if (play_move_b) {
play_move_b->enable(false);
}
}
}
void mp_replay_controller::replay_ui_playback_should_start()
{
if(!replay_ui_has_all_buttons())
return;
play_button()->enable(false);
reset_button()->enable(false);
play_turn_button()->enable(false);
play_side_button()->enable(false);
play_move_button()->enable(false);
}
void mp_replay_controller::replay_ui_playback_should_stop()
{
if(!replay_ui_has_all_buttons())
return;
if(!resources::recorder->at_end()) {
play_button()->enable(true);
reset_button()->enable(true);
play_turn_button()->enable(true);
play_side_button()->enable(true);
play_move_button()->enable(true);
play_button()->release();
play_turn_button()->release();
play_side_button()->release();
play_move_button()->release();
} else {
reset_button()->enable(true);
stop_button()->enable(false);
}
if(stop_condition_->should_stop()) {
//user interrupted
stop_button()->release();
}
}
void mp_replay_controller::reset_replay_ui()
{
if(!replay_ui_has_all_buttons())
return;
play_button()->enable(true);
stop_button()->enable(true);
reset_button()->enable(true);
play_turn_button()->enable(true);
play_side_button()->enable(true);
}
void mp_replay_controller::stop_replay()
{
stop_condition_.reset(new replay_stop_condition());
}
void mp_replay_controller::replay_next_turn()
{
stop_condition_.reset(new replay_play_turn(controller_.gamestate().tod_manager_.turn()));
}
void mp_replay_controller::replay_next_side()
{
stop_condition_.reset(new replay_play_side(controller_.gamestate().tod_manager_.turn(), controller_.current_side()));
}
void mp_replay_controller::replay_next_move()
{
stop_condition_.reset(new replay_play_moves(1));
}
//move all sides till stop/end
void mp_replay_controller::play_replay()
{
stop_condition_.reset(new replay_play_nostop());
}
void mp_replay_controller::update_gui()
{
controller_.get_display().recalculate_minimap();
controller_.get_display().redraw_minimap();
controller_.get_display().invalidate_all();
events::raise_draw_event();
controller_.get_display().draw();
}
void mp_replay_controller::handle_generic_event(const std::string& name)
{
if( name == "completely_redrawn" ) {
update_replay_ui();
} else {
add_replay_theme();
}
}
bool mp_replay_controller::recorder_at_end() const
{
return resources::recorder->at_end();
}
REPLAY_RETURN mp_replay_controller::play_side_impl()
{
stop_condition_->new_side_turn(controller_.current_side(), controller_.gamestate().tod_manager_.turn());
while(true)
{
if(!stop_condition_->should_stop())
{
REPLAY_RETURN res = do_replay(true);
if(res == REPLAY_FOUND_END_MOVE) {
stop_condition_->move_done();
}
if(res == REPLAY_FOUND_END_TURN) {
return res;
}
if(res == REPLAY_RETURN_AT_END) {
return res;
}
controller_.play_slice(false);
}
else
{
controller_.play_slice(true);
replay_ui_playback_should_stop();
}
}
}
bool mp_replay_controller::can_execute_command(const hotkey::hotkey_command& cmd, int) const
{
hotkey::HOTKEY_COMMAND command = cmd.id;
switch(command) {
//commands we only can do before the end of the replay
case hotkey::HOTKEY_REPLAY_STOP:
return !recorder_at_end();
case hotkey::HOTKEY_REPLAY_PLAY:
case hotkey::HOTKEY_REPLAY_NEXT_TURN:
case hotkey::HOTKEY_REPLAY_NEXT_SIDE:
case hotkey::HOTKEY_REPLAY_NEXT_MOVE:
//we have one events_disabler when starting the replay_controller and a second when entering the synced context.
return should_stop() && (events::commands_disabled <= 1 ) && !recorder_at_end();
default:
assert(false);
}
}

View file

@ -0,0 +1,81 @@
/*
Copyright (C) 2015 by the Battle for Wesnoth Project
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "global.hpp"
#include "play_controller.hpp"
#include "replay.hpp"
#include "mouse_handler_base.hpp" //events::command_disabler
#include <vector>
class video;
class mp_replay_controller : public events::observer
{
public:
class replay_stop_condition
{
public:
virtual void move_done() {}
virtual void new_side_turn(int , int ) {}
virtual bool should_stop() { return true; }
virtual ~replay_stop_condition(){}
};
mp_replay_controller(play_controller& controller);
~mp_replay_controller();
// void reset_replay();
void play_replay();
void stop_replay();
void replay_next_turn();
void replay_next_side();
void replay_next_move();
REPLAY_RETURN play_side_impl();
bool recorder_at_end() const;
bool should_stop() const { return stop_condition_->should_stop(); }
bool can_execute_command(const hotkey::hotkey_command& cmd, int index) const;
private:
void add_replay_theme();
void remove_replay_theme();
void init();
void update_teams();
void update_gui();
void rebuild_replay_theme();
void handle_generic_event(const std::string& name) OVERRIDE;
void reset_replay_ui();
void update_replay_ui();
void replay_ui_playback_should_start();
void replay_ui_playback_should_stop();
gui::button* play_button();
gui::button* stop_button();
gui::button* reset_button();
gui::button* play_turn_button();
gui::button* play_side_button();
gui::button* play_move_button();
bool replay_ui_has_all_buttons() {
return play_button() && stop_button() && reset_button() &&
play_turn_button() && play_side_button();
}
play_controller& controller_;
boost::scoped_ptr<replay_stop_condition> stop_condition_;
events::command_disabler disabler_;
};

View file

@ -56,10 +56,14 @@ namespace pathfind {
* team's shroud will be considered.
*/
map_location find_vacant_tile(const map_location& loc, VACANT_TILE_TYPE vacancy,
const unit* pass_check, const team* shroud_check)
const unit* pass_check, const team* shroud_check, const game_board* board)
{
const gamemap & map = resources::gameboard->map();
const unit_map & units = *resources::units;
if (!board) {
board = resources::gameboard;
assert(board);
}
const gamemap & map = board->map();
const unit_map & units = board->units();
if (!map.on_board(loc)) return map_location();

View file

@ -24,7 +24,7 @@ class gamemap;
class team;
class unit;
class unit_type;
class game_board;
#include "map_location.hpp"
#include "movetype.hpp"
@ -43,7 +43,8 @@ enum VACANT_TILE_TYPE { VACANT_CASTLE, VACANT_ANY };
map_location find_vacant_tile(const map_location& loc,
VACANT_TILE_TYPE vacancy=VACANT_ANY,
const unit* pass_check=NULL,
const team* shroud_check=NULL);
const team* shroud_check=NULL,
const game_board* board=NULL);
/// Wrapper for find_vacant_tile() when looking for a vacant castle tile
/// near a leader.
map_location find_vacant_castle(const unit & leader);

View file

@ -33,6 +33,7 @@
#include "game_events/menu_item.hpp"
#include "game_events/pump.hpp"
#include "game_preferences.hpp"
#include "game_state.hpp"
#include "hotkey/hotkey_item.hpp"
#include "hotkey_handler.hpp"
#include "map_label.hpp"
@ -66,6 +67,7 @@
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/assign/list_of.hpp>
static lg::log_domain log_aitesting("aitesting");
#define LOG_AIT LOG_STREAM(info, log_aitesting)
@ -84,6 +86,27 @@ static lg::log_domain log_enginerefac("enginerefac");
static lg::log_domain log_engine_enemies("engine/enemies");
#define DBG_EE LOG_STREAM(debug, log_engine_enemies)
static void copy_persistent(const config& src, config& dst)
{
typedef boost::container::flat_set<std::string> stringset;
static stringset attrs = boost::assign::list_of
("theme")("next_scenario")("description")("name")("defeat_music")
("victory_music")("victory_when_enemies_defeated")("remove_from_carryover_on_defeat")("disallow_recall")("experience_modifier")("require_scenario")
.convert_to_container<stringset>();
static stringset tags = boost::assign::list_of
("terrain_graphics")
.convert_to_container<stringset>();
BOOST_FOREACH(const std::string& attr, attrs)
{
dst[attr] = src[attr];
}
BOOST_FOREACH(const std::string& tag, tags)
{
dst.append_children(src, tag);
}
}
static void clear_resources()
{
resources::controller = NULL;
@ -115,8 +138,10 @@ play_controller::play_controller(const config& level, saved_game& state_of_game,
: controller_base(game_config, video)
, observer()
, savegame_config()
, gamestate_(level, tdata)
, level_(level)
, ticks_(ticks)
, tdata_(tdata)
, gamestate_()
, level_()
, saved_game_(state_of_game)
, prefs_disp_manager_()
, tooltips_manager_()
@ -134,33 +159,23 @@ play_controller::play_controller(const config& level, saved_game& state_of_game,
, statistics_context_(new statistics::scenario_context(level["name"]))
, undo_stack_(new actions::undo_list(level.child("undo_stack")))
, replay_(new replay(state_of_game.get_replay()))
, loading_game_(!level["playing_team"].empty())
, player_number_(level["playing_team"].to_int() + 1)
, start_turn_(gamestate_.tod_manager_.turn()) // gamestate_.tod_manager_ constructed above
, skip_replay_(skip_replay)
, linger_(false)
, it_is_a_new_turn_(level["it_is_a_new_turn"].to_bool(true))
, init_side_done_(level["init_side_done"].to_bool(false))
, init_side_done_now_(false)
, ticks_(ticks)
, victory_when_enemies_defeated_(level["victory_when_enemies_defeated"].to_bool(true))
, remove_from_carryover_on_defeat_(level["remove_from_carryover_on_defeat"].to_bool(true))
, end_level_data_()
, victory_music_()
, defeat_music_()
, scope_()
, server_request_number_(0)
, player_type_changed_(false)
{
copy_persistent(level, level_);
resources::controller = this;
resources::gameboard = &gamestate_.board_;
resources::gamedata = &gamestate_.gamedata_;
resources::persist = &persist_;
resources::teams = &gamestate_.board_.teams_;
resources::tod_manager = &gamestate_.tod_manager_;
resources::undo_stack = undo_stack_.get();
resources::recorder = replay_.get();
resources::units = &gamestate_.board_.units_;
resources::filter_con = &gamestate_;
resources::classification = &saved_game_.classification();
resources::mp_settings = &saved_game_.mp_settings();
@ -174,8 +189,6 @@ play_controller::play_controller(const config& level, saved_game& state_of_game,
set_end_level_data(el_data);
}
n_unit::id_manager::instance().set_save_id(level_["next_underlying_unit_id"]);
// Setup victory and defeat music
set_victory_music_list(level_["victory_music"]);
set_defeat_music_list(level_["defeat_music"]);
@ -184,7 +197,7 @@ play_controller::play_controller(const config& level, saved_game& state_of_game,
hotkey::deactivate_all_scopes();
hotkey::set_scope_active(hotkey::SCOPE_GAME);
try {
init(video);
init(video, level);
} catch (...) {
clear_resources();
throw;
@ -197,7 +210,8 @@ play_controller::~play_controller()
clear_resources();
}
struct throw_end_level { void operator()(const config&) { throw_quit_game_exception(); } };
void play_controller::init(CVideo& video){
void play_controller::init(CVideo& video, const config& level)
{
util::scoped_resource<loadscreen::global_loadscreen_manager*, util::delete_item> scoped_loadscreen_manager;
loadscreen::global_loadscreen_manager* loadscreen_manager = loadscreen::global_loadscreen_manager::get();
if (!loadscreen_manager)
@ -208,25 +222,33 @@ void play_controller::init(CVideo& video){
loadscreen::start_stage("load level");
LOG_NG << "initializing game_state..." << (SDL_GetTicks() - ticks_) << std::endl;
gamestate_.init(ticks_, *this);
resources::tunnels = gamestate_.pathfind_manager_.get();
LOG_NG << "initializing game_state..." << (SDL_GetTicks() - ticks()) << std::endl;
gamestate_.reset(new game_state(level, *this, tdata_));
resources::gameboard = &gamestate().board_;
resources::gamedata = &gamestate().gamedata_;
resources::teams = &gamestate().board_.teams_;
resources::tod_manager = &gamestate().tod_manager_;
resources::units = &gamestate().board_.units_;
resources::filter_con = &gamestate();
LOG_NG << "initializing whiteboard..." << (SDL_GetTicks() - ticks_) << std::endl;
resources::tunnels = gamestate().pathfind_manager_.get();
LOG_NG << "initializing whiteboard..." << (SDL_GetTicks() - ticks()) << std::endl;
whiteboard_manager_.reset(new wb::manager());
resources::whiteboard = whiteboard_manager_;
LOG_NG << "loading units..." << (SDL_GetTicks() - ticks_) << std::endl;
LOG_NG << "loading units..." << (SDL_GetTicks() - ticks()) << std::endl;
loadscreen::start_stage("load units");
preferences::encounter_all_content(gamestate_.board_);
preferences::encounter_all_content(gamestate().board_);
LOG_NG << "initializing theme... " << (SDL_GetTicks() - ticks_) << std::endl;
LOG_NG << "initializing theme... " << (SDL_GetTicks() - ticks()) << std::endl;
loadscreen::start_stage("init theme");
const config &theme_cfg = controller_base::get_theme(game_config_, level_["theme"]);
const config &theme_cfg = controller_base::get_theme(game_config_, level["theme"]);
LOG_NG << "building terrain rules... " << (SDL_GetTicks() - ticks_) << std::endl;
LOG_NG << "building terrain rules... " << (SDL_GetTicks() - ticks()) << std::endl;
loadscreen::start_stage("build terrain");
gui_.reset(new game_display(gamestate_.board_, video, whiteboard_manager_, *gamestate_.reports_, gamestate_.tod_manager_, theme_cfg, level_));
gui_.reset(new game_display(gamestate().board_, video, whiteboard_manager_, *gamestate().reports_, gamestate().tod_manager_, theme_cfg, level));
if (!gui_->video().faked()) {
if (saved_game_.mp_settings().mp_countdown)
gui_->get_theme().modify_label("time-icon", _ ("time left for current turn"));
@ -239,19 +261,19 @@ void play_controller::init(CVideo& video){
menu_handler_.set_gui(gui_.get());
resources::screen = gui_.get();
LOG_NG << "done initializing display... " << (SDL_GetTicks() - ticks_) << std::endl;
LOG_NG << "done initializing display... " << (SDL_GetTicks() - ticks()) << std::endl;
LOG_NG << "building gamestate to gui and whiteboard... " << (SDL_GetTicks() - ticks_) << std::endl;
LOG_NG << "building gamestate to gui and whiteboard... " << (SDL_GetTicks() - ticks()) << std::endl;
//loadscreen::start_stage("build events manager & lua");
// This *needs* to be created before the show_intro and show_map_scene
// as that functions use the manager state_of_game
// Has to be done before registering any events!
gamestate_.bind(whiteboard_manager_.get(), gui_.get());
resources::lua_kernel=gamestate_.lua_kernel_.get();
resources::game_events=gamestate_.events_manager_.get();
gamestate().bind(whiteboard_manager_.get(), gui_.get());
resources::lua_kernel = gamestate().lua_kernel_.get();
resources::game_events = gamestate().events_manager_.get();
if(gamestate_.first_human_team_ != -1) {
gui_->set_team(gamestate_.first_human_team_);
if(gamestate().first_human_team_ != -1) {
gui_->set_team(gamestate().first_human_team_);
}
else if (is_observer())
{
@ -259,9 +281,9 @@ void play_controller::init(CVideo& video){
// If not set here observer would be without fog until
// the first turn of observable side
size_t i;
for (i=0;i < gamestate_.board_.teams().size();++i)
for (i=0;i < gamestate().board_.teams().size();++i)
{
if (!gamestate_.board_.teams()[i].get_disallow_observers())
if (!gamestate().board_.teams()[i].get_disallow_observers())
{
gui_->set_team(i);
}
@ -278,22 +300,51 @@ void play_controller::init(CVideo& video){
plugins_context_->set_callback("quit", throw_end_level(), false);
}
void play_controller::init_managers(){
LOG_NG << "initializing managers... " << (SDL_GetTicks() - ticks_) << std::endl;
void play_controller::reset_gamestate(const config& level, int replay_pos)
{
resources::gameboard = NULL;
resources::gamedata = NULL;
resources::teams = NULL;
resources::tod_manager = NULL;
resources::units = NULL;
resources::filter_con = NULL;
resources::lua_kernel = NULL;
resources::game_events = NULL;
resources::tunnels = NULL;
gamestate_.reset(new game_state(level, *this, tdata_));
gamestate().bind(whiteboard_manager_.get(), gui_.get());
resources::gameboard = &gamestate().board_;
resources::gamedata = &gamestate().gamedata_;
resources::teams = &gamestate().board_.teams_;
resources::tod_manager = &gamestate().tod_manager_;
resources::units = &gamestate().board_.units_;
resources::filter_con = &gamestate();
resources::lua_kernel = gamestate().lua_kernel_.get();
resources::game_events = gamestate().events_manager_.get();
resources::tunnels = gamestate().pathfind_manager_.get();
gui_->reset_tod_manager(gamestate().tod_manager_);
gui_->reset_reports(*gamestate().reports_);
gui_->change_display_context(&gamestate().board_);
saved_game_.get_replay().set_pos(replay_pos);
}
void play_controller::init_managers()
{
LOG_NG << "initializing managers... " << (SDL_GetTicks() - ticks()) << std::endl;
prefs_disp_manager_.reset(new preferences::display_manager(gui_.get()));
tooltips_manager_.reset(new tooltips::manager(gui_->video()));
soundsources_manager_.reset(new soundsource::manager(*gui_));
resources::soundsources = soundsources_manager_.get();
LOG_NG << "done initializing managers... " << (SDL_GetTicks() - ticks_) << std::endl;
LOG_NG << "done initializing managers... " << (SDL_GetTicks() - ticks()) << std::endl;
}
void play_controller::fire_preload()
void play_controller::fire_preload(const config& level)
{
// Run initialization scripts, even if loading from a snapshot.
gamestate_.gamedata_.set_phase(game_data::PRELOAD);
gamestate_.lua_kernel_->initialize();
gamestate_.gamedata_.get_variable("turn_number") = int(turn());
gamestate().gamedata_.set_phase(game_data::PRELOAD);
gamestate().lua_kernel_->initialize(level);
gamestate().gamedata_.get_variable("turn_number") = int(turn());
pump().fire("preload");
}
void play_controller::fire_prestart()
@ -301,29 +352,27 @@ void play_controller::fire_prestart()
// pre-start events must be executed before any GUI operation,
// as those may cause the display to be refreshed.
update_locker lock_display(gui_->video());
gamestate_.gamedata_.set_phase(game_data::PRESTART);
gamestate().gamedata_.set_phase(game_data::PRESTART);
pump().fire("prestart");
// prestart event may modify start turn with WML, reflect any changes.
start_turn_ = turn();
gamestate_.gamedata_.get_variable("turn_number") = int(start_turn_);
gamestate().gamedata_.get_variable("turn_number") = int(turn());
}
void play_controller::fire_start()
{
gamestate_.gamedata_.set_phase(game_data::START);
gamestate().gamedata_.set_phase(game_data::START);
pump().fire("start");
// start event may modify start turn with WML, reflect any changes.
start_turn_ = turn();
gamestate_.gamedata_.get_variable("turn_number") = int(start_turn_);
gamestate().gamedata_.get_variable("turn_number") = int(turn());
check_objectives();
// prestart and start events may modify the initial gold amount,
// reflect any changes.
BOOST_FOREACH(team& tm, gamestate_.board_.teams_)
BOOST_FOREACH(team& tm, gamestate().board_.teams_)
{
tm.set_start_gold(tm.gold());
}
init_side_done_ = false;
gamestate_.gamedata_.set_phase(game_data::PLAY);
gamestate_->init_side_done() = false;
gamestate().gamedata_.set_phase(game_data::PLAY);
}
void play_controller::init_gui()
@ -332,21 +381,20 @@ void play_controller::init_gui()
gui_->update_tod();
}
void play_controller::init_side_begin(bool is_replay)
void play_controller::init_side_begin()
{
mouse_handler_.set_side(player_number_);
mouse_handler_.set_side(current_side());
// If we are observers we move to watch next team if it is allowed
if ((is_observer() && !current_team().get_disallow_observers())
|| (current_team().is_local_human() && !is_replay))
|| (current_team().is_local_human() && !this->is_replay()))
{
update_gui_to_player(player_number_ - 1);
update_gui_to_player(current_side() - 1);
}
gui_->set_playing_team(size_t(player_number_ - 1));
gui_->set_playing_team(size_t(current_side() - 1));
gamestate_.gamedata_.get_variable("side_number") = player_number_;
gamestate_.gamedata_.last_selected = map_location::null_location();
gamestate().gamedata_.last_selected = map_location::null_location();
}
/**
@ -359,7 +407,7 @@ void play_controller::maybe_do_init_side()
* For all other sides it is recorded in replay and replay handler has to handle
* calling do_init_side() functions.
**/
if (init_side_done_ || !current_team().is_local() || gamestate_.gamedata_.phase() != game_data::PLAY) {
if (gamestate_->init_side_done() || !current_team().is_local() || gamestate().gamedata_.phase() != game_data::PLAY || is_replay() || !replay_->at_end()) {
return;
}
@ -376,17 +424,20 @@ void play_controller::do_init_side()
log_scope("player turn");
//In case we might end up calling sync:network during the side turn events,
//and we don't want do_init_side to be called when a player drops.
init_side_done_ = true;
gamestate_->init_side_done() = true;
init_side_done_now_ = true;
const std::string turn_num = str_cast(turn());
const std::string side_num = str_cast(player_number_);
const std::string side_num = str_cast(current_side());
if(it_is_a_new_turn_)
gamestate().gamedata_.get_variable("side_number") = current_side();
// We might have skipped some sides because they were empty so it is not enough to check for side_num==1
if(!gamestate().tod_manager_.has_turn_event_fired())
{
pump().fire("turn " + turn_num);
pump().fire("new turn");
it_is_a_new_turn_ = false;
gamestate().tod_manager_.turn_event_fired();
}
pump().fire("side turn");
@ -399,23 +450,23 @@ void play_controller::do_init_side()
// Healing/income happen if it's not the first turn of processing,
// or if we are loading a game.
if (turn() > 1) {
gamestate_.board_.new_turn(player_number_);
gamestate().board_.new_turn(current_side());
current_team().new_turn();
// If the expense is less than the number of villages owned
// times the village support capacity,
// then we don't have to pay anything at all
int expense = gamestate_.board_.side_upkeep(player_number_) -
int expense = gamestate().board_.side_upkeep(current_side()) -
current_team().support();
if(expense > 0) {
current_team().spend_gold(expense);
}
calculate_healing(player_number_, !is_skipping_replay());
calculate_healing(current_side(), !is_skipping_replay());
}
// Prepare the undo stack.
undo_stack_->new_side_turn(player_number_);
undo_stack_->new_side_turn(current_side());
pump().fire("turn refresh");
pump().fire("side " + side_num + " turn refresh");
@ -423,16 +474,16 @@ void play_controller::do_init_side()
pump().fire("side " + side_num + " turn " + turn_num + " refresh");
// Make sure vision is accurate.
actions::clear_shroud(player_number_, true);
actions::clear_shroud(current_side(), true);
init_side_end();
check_victory();
sync.do_final_checkup();
}
void play_controller::init_side_end()
{
const time_of_day &tod = gamestate_.tod_manager_.get_time_of_day();
const time_of_day &tod = gamestate().tod_manager_.get_time_of_day();
if (player_number_ == 1 || !init_side_done_now_)
if (current_side() == 1 || !init_side_done_now_)
sound::play_sound(tod.sounds, sound::SOUND_SOURCES);
if (!is_skipping_replay()){
@ -440,7 +491,7 @@ void play_controller::init_side_end()
}
if (!is_skipping_replay() && current_team().get_scroll_to_leader()){
gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false);
gui_->scroll_to_leader(current_side(), game_display::ONSCREEN,false);
}
whiteboard_manager_->on_init_side();
}
@ -450,10 +501,8 @@ config play_controller::to_config() const
config cfg;
cfg.merge_attributes(level_);
cfg["init_side_done"] = init_side_done_;
cfg["it_is_a_new_turn"] = it_is_a_new_turn_;
gamestate_.write(cfg);
cfg["replay_pos"] = saved_game_.get_replay().get_pos();
gamestate().write(cfg);
if(end_level_data_.get_ptr() != NULL) {
end_level_data_->write(cfg.add_child("end_level_data"));
@ -471,48 +520,44 @@ config play_controller::to_config() const
//Write the soundsources.
soundsources_manager_->write_sourcespecs(cfg);
if(gui_.get() != NULL) {
if(resources::gamedata->phase() == game_data::PLAY) {
cfg["playing_team"] = gui_->playing_team();
}
gui_->labels().write(cfg);
sound::write_music_play_list(cfg);
}
cfg["next_underlying_unit_id"] = str_cast(n_unit::id_manager::instance().get_save_id());
return cfg;
}
void play_controller::finish_side_turn()
{
whiteboard_manager_->on_finish_side_turn(player_number_);
whiteboard_manager_->on_finish_side_turn(current_side());
{ //Block for set_scontext_synced
set_scontext_synced sync(1);
// Ending the turn commits all moves.
undo_stack_->clear();
gamestate_.board_.end_turn(player_number_);
gamestate().board_.end_turn(current_side());
const std::string turn_num = str_cast(turn());
const std::string side_num = str_cast(player_number_);
const std::string side_num = str_cast(current_side());
// Clear shroud, in case units had been slowed for the turn.
actions::clear_shroud(player_number_);
actions::clear_shroud(current_side());
pump().fire("side turn end");
pump().fire("side "+ side_num + " turn end");
pump().fire("side turn " + turn_num + " end");
pump().fire("side " + side_num + " turn " + turn_num + " end");
// This is where we refog, after all of a side's events are done.
actions::recalculate_fog(player_number_);
actions::recalculate_fog(current_side());
check_victory();
sync.do_final_checkup();
}
mouse_handler_.deselect_hex();
n_unit::id_manager::instance().reset_fake();
init_side_done_ = false;
resources::gameboard->unit_id_manager().reset_fake();
gamestate_->init_side_done() = false;
}
void play_controller::finish_turn()
@ -531,7 +576,7 @@ bool play_controller::enemies_visible() const
return true;
// See if any enemies are visible
BOOST_FOREACH(const unit & u, gamestate_.board_.units()) {
BOOST_FOREACH(const unit & u, gamestate().board_.units()) {
if (current_team().is_enemy(u.side()) && !gui_->fogged(u.get_location())) {
return true;
}
@ -548,7 +593,7 @@ void play_controller::enter_textbox()
}
const std::string str = menu_handler_.get_textbox().box()->text();
const unsigned int team_num = player_number_;
const unsigned int team_num = current_side();
events::mouse_handler& mousehandler = mouse_handler_;
switch(menu_handler_.get_textbox().mode()) {
@ -583,10 +628,10 @@ void play_controller::tab()
switch(mode) {
case gui::TEXTBOX_SEARCH:
{
BOOST_FOREACH(const unit &u, gamestate_.board_.units()){
BOOST_FOREACH(const unit &u, gamestate().board_.units()){
const map_location& loc = u.get_location();
if(!gui_->fogged(loc) &&
!(gamestate_.board_.teams()[gui_->viewing_team()].is_enemy(u.side()) && u.invisible(loc)))
!(gamestate().board_.teams()[gui_->viewing_team()].is_enemy(u.side()) && u.invisible(loc)))
dictionary.insert(u.name());
}
//TODO List map labels
@ -600,7 +645,7 @@ void play_controller::tab()
}
case gui::TEXTBOX_MESSAGE:
{
BOOST_FOREACH(const team& t, gamestate_.board_.teams()) {
BOOST_FOREACH(const team& t, gamestate().board_.teams()) {
if(!t.is_empty())
dictionary.insert(t.current_player());
}
@ -624,14 +669,14 @@ void play_controller::tab()
team& play_controller::current_team()
{
assert(player_number_ > 0 && player_number_ <= int(gamestate_.board_.teams().size()));
return gamestate_.board_.teams_[player_number_-1];
assert(current_side() > 0 && current_side() <= int(gamestate().board_.teams().size()));
return gamestate().board_.teams_[current_side() - 1];
}
const team& play_controller::current_team() const
{
assert(player_number_ > 0 && player_number_ <= int(gamestate_.board_.teams().size()));
return gamestate_.board_.teams()[player_number_-1];
assert(current_side() > 0 && current_side() <= int(gamestate().board_.teams().size()));
return gamestate().board_.teams()[current_side() - 1];
}
/// @returns: the number n in [min, min+mod ) so that (n - num) is a multiple of mod.
@ -651,9 +696,9 @@ static int modulo(int num, int mod, int min)
bool play_controller::is_team_visible(int team_num, bool observer) const
{
const team& t = gamestate_.board_.teams()[team_num - 1];
const team& t = gamestate().board_.teams()[team_num - 1];
if(observer) {
return !t.get_disallow_observers();
return !t.get_disallow_observers() && !t.is_empty();
}
else {
return t.is_local_human() && !t.is_idle();
@ -662,12 +707,12 @@ bool play_controller::is_team_visible(int team_num, bool observer) const
int play_controller::find_last_visible_team() const
{
assert(player_number_ <= int(gamestate_.board_.teams().size()));
const int num_teams = gamestate_.board_.teams().size();
assert(current_side() <= int(gamestate().board_.teams().size()));
const int num_teams = gamestate().board_.teams().size();
const bool is_observer = this->is_observer();
for(int i = 0; i < num_teams; i++) {
const int team_num = modulo(player_number_ - i, num_teams, 1);
const int team_num = modulo(current_side() - i, num_teams, 1);
if(is_team_visible(team_num, is_observer)) {
return team_num;
}
@ -731,10 +776,10 @@ void play_controller::process_keyup_event(const SDL_Event& event) {
if(u.valid()) {
// if it's not the unit's turn, we reset its moves
unit_movement_resetter move_reset(*u, u->side() != player_number_);
unit_movement_resetter move_reset(*u, u->side() != current_side());
mouse_handler_.set_current_paths(pathfind::paths(*u, false,
true, gamestate_.board_.teams_[gui_->viewing_team()],
true, gamestate().board_.teams_[gui_->viewing_team()],
mouse_handler_.get_path_turns()));
gui_->highlight_reach(mouse_handler_.current_paths());
@ -754,7 +799,7 @@ void play_controller::process_keyup_event(const SDL_Event& event) {
void play_controller::save_game(){
if(save_blocker::try_block()) {
save_blocker::save_unblocker unblocker;
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::ingame_savegame save(saved_game_, *gui_, preferences::save_compression_format());
save.save_game_interactive(gui_->video(), "", gui::OK_CANCEL);
} else {
@ -766,7 +811,7 @@ void play_controller::save_game_auto(const std::string & filename) {
if(save_blocker::try_block()) {
save_blocker::save_unblocker unblocker;
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::ingame_savegame save(saved_game_, *gui_, preferences::save_compression_format());
save.save_game_automatic(gui_->video(), false, filename);
}
@ -867,7 +912,7 @@ void play_controller::check_victory()
bool continue_level, found_player, found_network_player, invalidate_all;
std::set<unsigned> not_defeated;
gamestate_.board_.check_victory(continue_level, found_player, found_network_player, invalidate_all, not_defeated, remove_from_carryover_on_defeat_);
gamestate().board_.check_victory(continue_level, found_player, found_network_player, invalidate_all, not_defeated, remove_from_carryover_on_defeat_);
if (invalidate_all) {
gui_->invalidate_all();
@ -920,7 +965,7 @@ void play_controller::process_oos(const std::string& msg) const
message << _("The game is out of sync. It might not make much sense to continue. Do you want to save your game?");
message << "\n\n" << _("Error details:") << "\n\n" << msg;
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::oos_savegame save(saved_game_, *gui_);
save.save_game_interactive(gui_->video(), message.str(), gui::YES_NO); // can throw quit_game_exception
}
@ -935,7 +980,7 @@ void play_controller::update_gui_to_player(const int team_index, const bool obse
void play_controller::do_autosave()
{
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::autosave_savegame save(saved_game_, *gui_, preferences::save_compression_format());
save.autosave(false, preferences::autosavemax(), preferences::INFINITE_AUTO_SAVES);
}
@ -943,7 +988,7 @@ void play_controller::do_autosave()
void play_controller::do_consolesave(const std::string& filename)
{
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::ingame_savegame save(saved_game_, *gui_, preferences::save_compression_format());
save.save_game_automatic(gui_->video(), true, filename);
}
@ -955,7 +1000,7 @@ void play_controller::update_savegame_snapshot() const
}
game_events::t_pump & play_controller::pump() {
return gamestate_.events_manager_->pump();
return gamestate().events_manager_->pump();
}
int play_controller::get_ticks() {
@ -976,7 +1021,7 @@ hotkey::command_executor * play_controller::get_hotkey_command_executor() {
bool play_controller::is_browsing() const
{
if(linger_ || !init_side_done_ || this->gamestate_.gamedata_.phase() != game_data::PLAY) {
if(linger_ || !gamestate_->init_side_done() || gamestate().gamedata_.phase() != game_data::PLAY) {
return true;
}
const team& t = current_team();
@ -998,12 +1043,13 @@ void play_controller::play_slice_catch()
}
}
void play_controller::start_game()
void play_controller::start_game(const config& level)
{
fire_preload();
fire_preload(level);
if(!loading_game_)
if(!gamestate().start_event_fired_)
{
gamestate().start_event_fired_ = true;
resources::recorder->add_start_if_not_there_yet();
resources::recorder->get_next_action();
@ -1014,7 +1060,7 @@ void play_controller::start_game()
return;
}
for ( int side = gamestate_.board_.teams().size(); side != 0; --side )
for ( int side = gamestate().board_.teams().size(); side != 0; --side )
actions::clear_shroud(side, false, false);
init_gui();
@ -1028,7 +1074,7 @@ void play_controller::start_game()
sync.do_final_checkup();
gui_->recalculate_minimap();
// Initialize countdown clock.
BOOST_FOREACH(const team& t, gamestate_.board_.teams())
BOOST_FOREACH(const team& t, gamestate().board_.teams())
{
if (saved_game_.mp_settings().mp_countdown) {
t.set_countdown_time(1000 * saved_game_.mp_settings().mp_countdown_init_time);
@ -1039,7 +1085,7 @@ void play_controller::start_game()
{
init_gui();
events::raise_draw_event();
gamestate_.gamedata_.set_phase(game_data::PLAY);
gamestate().gamedata_.set_phase(game_data::PLAY);
gui_->recalculate_minimap();
}
}
@ -1061,3 +1107,121 @@ std::set<std::string> play_controller::all_players() const
}
return res;
}
void play_controller::play_side()
{
//check for team-specific items in the scenario
gui_->parse_team_overlays();
do {
update_viewing_player();
{
save_blocker blocker;
maybe_do_init_side();
if(is_regular_game_end()) {
return;
}
}
// This flag can be set by derived classes (in overridden functions).
player_type_changed_ = false;
statistics::reset_turn_stats(gamestate().board_.teams()[current_side() - 1].save_id());
play_side_impl();
if(is_regular_game_end()) {
return;
}
} while (player_type_changed_);
// Keep looping if the type of a team (human/ai/networked)
// has changed mid-turn
sync_end_turn();
}
void play_controller::play_turn()
{
whiteboard_manager_->on_gamestate_change();
gui_->new_turn();
gui_->invalidate_game_status();
events::raise_draw_event();
LOG_NG << "turn: " << turn() << "\n";
if(non_interactive()) {
LOG_AIT << "Turn " << turn() << ":" << std::endl;
}
for (; gamestate_->player_number_ <= int(gamestate().board_.teams().size()); ++gamestate_->player_number_)
{
// If a side is empty skip over it.
if (current_team().is_empty()) {
continue;
}
init_side_begin();
if(gamestate_->init_side_done()) {
//This is the case in a reloaded game where teh side was initilizes before saving the game.
init_side_end();
}
ai_testing::log_turn_start(current_side());
play_side();
if(is_regular_game_end()) {
return;
}
finish_side_turn();
if(is_regular_game_end()) {
return;
}
if(non_interactive()) {
LOG_AIT << " Player " << current_side() << ": " <<
current_team().villages().size() << " Villages" <<
std::endl;
ai_testing::log_turn_end(current_side());
}
}
//If the loop exits due to the last team having been processed,
gamestate_->player_number_ = gamestate().board_.teams().size();
finish_turn();
// Time has run out
check_time_over();
}
void play_controller::check_time_over(){
bool time_left = gamestate().tod_manager_.next_turn(gamestate().gamedata_);
if(!time_left) {
LOG_NG << "firing time over event...\n";
set_scontext_synced_base sync;
pump().fire("time over");
LOG_NG << "done firing time over event...\n";
//if turns are added while handling 'time over' event
if (gamestate().tod_manager_.is_time_left()) {
return;
}
if(non_interactive()) {
LOG_AIT << "time over (draw)\n";
ai_testing::log_draw();
}
check_victory();
if (is_regular_game_end()) {
return;
}
end_level_data e;
e.proceed_to_next_level = false;
e.is_victory = false;
set_end_level_data(e);
}
}
play_controller::scoped_savegame_snapshot::scoped_savegame_snapshot(const play_controller& controller)
: controller_(controller)
{
controller_.update_savegame_snapshot();
}
play_controller::scoped_savegame_snapshot::~scoped_savegame_snapshot()
{
controller_.saved_game_.remove_snapshot();
}

View file

@ -19,7 +19,6 @@
#include "controller_base.hpp"
#include "floating_label.hpp"
#include "game_end_exceptions.hpp"
#include "game_state.hpp"
#include "help/help.hpp"
#include "hotkey/command_executor.hpp"
#include "menu_events.hpp"
@ -27,6 +26,7 @@
#include "persist_manager.hpp"
#include "terrain_type_data.hpp"
#include "tod_manager.hpp"
#include "game_state.hpp"
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
@ -107,7 +107,7 @@ public:
void save_replay_auto(const std::string & filename);
void save_map();
void init_side_begin(bool is_replay);
void init_side_begin();
void maybe_do_init_side();
void do_init_side();
void init_side_end();
@ -135,26 +135,29 @@ public:
return *end_level_data_;
}
const std::vector<team>& get_teams_const() const {
return gamestate_.board_.teams_;
return gamestate().board_.teams_;
}
const unit_map & get_units_const() const {
return gamestate_.board_.units();
return gamestate().board_.units();
}
const gamemap& get_map_const() const{
return gamestate_.board_.map();
return gamestate().board_.map();
}
const tod_manager& get_tod_manager_const() const{
return gamestate_.tod_manager_;
return gamestate().tod_manager_;
}
bool is_observer() const {
return gamestate_.board_.is_observer();
return gamestate().board_.is_observer();
}
game_state & gamestate() {
return gamestate_;
return *gamestate_;
}
const game_state & gamestate() const {
return *gamestate_;
}
/**
@ -163,10 +166,10 @@ public:
*/
void check_victory();
size_t turn() const {return gamestate_.tod_manager_.turn();}
size_t turn() const {return gamestate().tod_manager_.turn();}
/** Returns the number of the side whose turn it is. Numbering starts at one. */
int current_side() const { return player_number_; }
int current_side() const { return gamestate_->player_number_; }
config to_config() const;
@ -182,8 +185,8 @@ public:
boost::shared_ptr<wb::manager> get_whiteboard();
const mp_game_settings& get_mp_settings();
const game_classification & get_classification();
int get_server_request_number() const { return server_request_number_; }
void increase_server_request_number() { ++server_request_number_; }
int get_server_request_number() const { return gamestate().server_request_number_; }
void increase_server_request_number() { ++gamestate().server_request_number_; }
game_events::t_pump & pump();
@ -204,20 +207,31 @@ public:
{ return level_["name"].t_str(); }
bool get_disallow_recall()
{ return level_["disallow_recall"].to_bool(); }
std::string theme()
{ return level_["theme"].str(); }
void update_savegame_snapshot() const;
virtual bool should_return_to_play_side()
{ return is_regular_game_end(); }
void maybe_throw_return_to_play_side()
{ if(should_return_to_play_side() && !linger_ ) { throw return_to_play_side_exception(); } }
virtual void play_side_impl() {}
void play_side();
team& current_team();
const team& current_team() const;
bool can_use_synced_wml_menu() const;
std::set<std::string> all_players() const;
protected:
void play_slice_catch();
int ticks() const { return ticks_; }
game_display& get_display();
protected:
struct scoped_savegame_snapshot
{
scoped_savegame_snapshot(const play_controller& controller);
~scoped_savegame_snapshot();
const play_controller& controller_;
};
friend struct scoped_savegame_snapshot;
void play_slice_catch();
bool have_keyboard_focus();
void process_focus_keydown_event(const SDL_Event& event);
void process_keydown_event(const SDL_Event& event);
@ -225,10 +239,10 @@ protected:
void init_managers();
///preload events cannot be synced
void fire_preload();
void fire_preload(const config& level);
void fire_prestart();
void fire_start();
void start_game();
void start_game(const config& level);
virtual void init_gui();
void finish_side_turn();
void finish_turn(); //this should not throw an end turn or end level exception
@ -241,10 +255,13 @@ protected:
bool is_team_visible(int team_num, bool observer) const;
/// returns 0 if no such team was found.
int find_last_visible_team() const;
private:
const int ticks_;
protected:
//gamestate
game_state gamestate_;
const config & level_;
const tdata_cache & tdata_;
boost::scoped_ptr<game_state> gamestate_;
config level_;
saved_game & saved_game_;
//managers
@ -276,18 +293,10 @@ protected:
boost::scoped_ptr<actions::undo_list> undo_stack_;
boost::scoped_ptr<replay> replay_;
/// if a team is specified whose turn it is, it means we're loading a game instead of starting a fresh one.
bool loading_game_;
int player_number_;
unsigned int start_turn_;
bool skip_replay_;
bool linger_;
bool it_is_a_new_turn_;
bool init_side_done_;
/// whether we did init side in this session ( false = we did init side before we reloaded the game).
bool init_side_done_now_;
const int ticks_;
const std::string& select_victory_music() const;
const std::string& select_defeat_music() const;
void set_victory_music_list(const std::string& list);
@ -298,9 +307,10 @@ protected:
*/
void update_gui_to_player(const int team_index, const bool observe = false);
void reset_gamestate(const config& level, int replay_pos);
private:
void init(CVideo &video);
void init(CVideo &video, const config& level);
bool victory_when_enemies_defeated_;
bool remove_from_carryover_on_defeat_;
@ -310,8 +320,13 @@ private:
std::vector<std::string> defeat_music_;
hotkey::scope_changer scope_;
// used to sync with the mpserver, not persistent in savefiles.
int server_request_number_;
protected:
bool player_type_changed_;
virtual void sync_end_turn() {};
virtual void check_time_over();
virtual void update_viewing_player() = 0;
void play_turn();
};

View file

@ -87,11 +87,11 @@ void playmp_controller::stop_network(){
LOG_NG << "network processing stopped";
}
void playmp_controller::play_side()
void playmp_controller::play_side_impl()
{
mp_ui_alerts::turn_changed(current_team().current_player());
// Proceed with the parent function.
return playsingle_controller::play_side();
return playsingle_controller::play_side_impl();
}
void playmp_controller::on_not_observer() {
@ -247,7 +247,7 @@ void playmp_controller::linger()
// we're needed here.
gui_->set_game_mode(game_display::LINGER_MP);
// End all unit moves
gamestate_.board_.set_all_units_user_end_turn();
gamestate().board_.set_all_units_user_end_turn();
set_end_scenario_button();
assert(is_regular_game_end());
@ -324,7 +324,7 @@ void playmp_controller::after_human_turn(){
current_team().set_action_bonus_count(0);
current_team().set_countdown_time(new_time);
resources::recorder->add_countdown_update(new_time, player_number_);
resources::recorder->add_countdown_update(new_time, current_side());
}
LOG_NG << "playmp::after_human_turn...\n";
@ -381,7 +381,7 @@ void playmp_controller::process_oos(const std::string& err_msg) const {
}
temp_buf << " \n";
}
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::oos_savegame save(saved_game_, *gui_);
save.save_game_interactive(gui_->video(), temp_buf.str(), gui::YES_NO);
}
@ -417,7 +417,7 @@ void playmp_controller::maybe_linger()
{
// mouse_handler expects at least one team for linger mode to work.
assert(is_regular_game_end());
if (!get_end_level_data_const().transient.linger_mode || gamestate_.board_.teams().empty()) {
if (!get_end_level_data_const().transient.linger_mode || gamestate().board_.teams().empty()) {
if(!is_host()) {
// If we continue without lingering we need to
// make sure the host uploads the next scenario

View file

@ -46,7 +46,7 @@ protected:
void start_network();
void stop_network();
virtual void play_side();
virtual void play_side_impl();
virtual void play_human_turn();
virtual void play_linger_turn();
virtual void after_human_turn();

View file

@ -81,8 +81,8 @@ playsingle_controller::playsingle_controller(const config& level,
, network_reader_()
, turn_data_(replay_sender_, network_reader_)
, end_turn_(END_TURN_NONE)
, player_type_changed_(false)
, skip_next_turn_(false)
, mp_replay_()
{
hotkey_handler_.reset(new hotkey_handler(*this, saved_game_)); //upgrade hotkey handler to the sp (whiteboard enabled) version
@ -118,13 +118,13 @@ playsingle_controller::~playsingle_controller()
}
void playsingle_controller::init_gui(){
LOG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks_) << "\n";
LOG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks()) << "\n";
play_controller::init_gui();
if(gamestate_.first_human_team_ != -1) {
gui_->scroll_to_tile(gamestate_.board_.map().starting_position(gamestate_.first_human_team_ + 1), game_display::WARP);
if(gamestate().first_human_team_ != -1) {
gui_->scroll_to_tile(gamestate().board_.map().starting_position(gamestate().first_human_team_ + 1), game_display::WARP);
}
gui_->scroll_to_tile(gamestate_.board_.map().starting_position(1), game_display::WARP);
gui_->scroll_to_tile(gamestate().board_.map().starting_position(1), game_display::WARP);
update_locker lock_display(gui_->video(), is_skipping_replay());
gui_->draw();
@ -194,12 +194,12 @@ void playsingle_controller::report_victory(
report << "\n" << goldmsg;
}
void playsingle_controller::play_scenario_init() {
void playsingle_controller::play_scenario_init(const config& level) {
// At the beginning of the scenario, save a snapshot as replay_start
if(saved_game_.replay_start().empty()){
saved_game_.replay_start() = to_config();
}
start_game();
start_game(level);
if( saved_game_.classification().random_mode != "" && (network::nconnections() != 0)) {
// This won't cause errors later but we should notify the user about it in case he didn't knew it.
gui2::show_transient_message(
@ -212,33 +212,50 @@ void playsingle_controller::play_scenario_init() {
return;
}
void playsingle_controller::play_scenario_main_loop() {
LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks_) << "\n";
void playsingle_controller::play_scenario_main_loop()
{
LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks()) << "\n";
// Avoid autosaving after loading, but still
// allow the first turn to have an autosave.
ai_testing::log_game_start();
if(gamestate_.board_.teams().empty())
if(gamestate().board_.teams().empty())
{
ERR_NG << "Playing game with 0 teams." << std::endl;
}
while(true) {
play_turn();
if (is_regular_game_end()) {
return;
try {
play_turn();
if (is_regular_game_end()) {
return;
}
gamestate_->player_number_ = 1;
}
catch(const reset_gamestate_exception& ex) {
/**
@TODO: The mp replay feature still doesnt work properly (casues OOS) becasue:
1) The undo stack is not reset along with the gamestate
2) The server_request_number_ is not reset along with the gamestate (fixed).
3) chat and other unsynced actions are inserted in the middle of the replay bringing the replay_pos in unorder.
4) untracked changes in side controllers are lost when resetting gamestate
5) The game shoudl have a stricter check for whether the loaded game is actually a parent of this game.
*/
reset_gamestate(*ex.level, (*ex.level)["replay_pos"]);
play_scenario_init(*ex.level);
mp_replay_.reset(new mp_replay_controller(*this));
mp_replay_->play_replay();
}
player_number_ = 1;
} //end for loop
}
LEVEL_RESULT playsingle_controller::play_scenario(
const config::const_child_itors &story)
const config::const_child_itors &story, const config& level)
{
LOG_NG << "in playsingle_controller::play_scenario()...\n";
// Start music.
BOOST_FOREACH(const config &m, level_.child_range("music")) {
BOOST_FOREACH(const config &m, level.child_range("music")) {
sound::play_music_config(m);
}
sound::commit_music_changes();
@ -246,11 +263,11 @@ LEVEL_RESULT playsingle_controller::play_scenario(
if(!this->is_skipping_replay()) {
show_story(*gui_, get_scenario_name(), story);
}
gui_->labels().read(level_);
gui_->labels().read(level);
// Read sound sources
assert(soundsources_manager_ != NULL);
BOOST_FOREACH(const config &s, level_.child_range("sound_source")) {
BOOST_FOREACH(const config &s, level.child_range("sound_source")) {
try {
soundsource::sourcespec spec(s);
soundsources_manager_->add(spec);
@ -260,10 +277,12 @@ LEVEL_RESULT playsingle_controller::play_scenario(
ERR_NG << "Skipping this sound source..." << std::endl;
}
}
LOG_NG << "entering try... " << (SDL_GetTicks() - ticks_) << "\n";
LOG_NG << "entering try... " << (SDL_GetTicks() - ticks()) << "\n";
try {
play_scenario_init();
play_scenario_init(level);
// clears level config;
this->saved_game_.remove_snapshot();
if (!is_regular_game_end() && !linger_) {
play_scenario_main_loop();
}
@ -272,7 +291,7 @@ LEVEL_RESULT playsingle_controller::play_scenario(
}
const bool is_victory = get_end_level_data_const().is_victory;
if(this->gamestate_.gamedata_.phase() <= game_data::PRESTART) {
if(gamestate().gamedata_.phase() <= game_data::PRESTART) {
sdl::draw_solid_tinted_rectangle(
0, 0, gui_->video().getx(), gui_->video().gety(), 0, 0, 0, 1.0,
gui_->video().getSurface()
@ -291,7 +310,7 @@ LEVEL_RESULT playsingle_controller::play_scenario(
}
}
if (gamestate_.board_.teams().empty())
if (gamestate().board_.teams().empty())
{
//store persistent teams
saved_game_.set_snapshot(config());
@ -313,7 +332,7 @@ LEVEL_RESULT playsingle_controller::play_scenario(
pump().fire("scenario_end");
}
if(end_level.proceed_to_next_level) {
gamestate_.board_.heal_all_survivors();
gamestate().board_.heal_all_survivors();
}
if(is_observer()) {
gui2::show_transient_message(gui_->video(), _("Game Over"), _("The game is over."));
@ -354,7 +373,7 @@ LEVEL_RESULT playsingle_controller::play_scenario(
disconnect = true;
}
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::ingame_savegame save(saved_game_, *gui_, preferences::save_compression_format());
save.save_game_interactive(gui_->video(), _("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"), gui::YES_NO);
if(disconnect) {
@ -367,62 +386,6 @@ LEVEL_RESULT playsingle_controller::play_scenario(
return LEVEL_RESULT::QUIT;
}
void playsingle_controller::play_turn()
{
whiteboard_manager_->on_gamestate_change();
gui_->new_turn();
gui_->invalidate_game_status();
events::raise_draw_event();
LOG_NG << "turn: " << turn() << "\n";
if(non_interactive()) {
LOG_AIT << "Turn " << turn() << ":" << std::endl;
}
for (; player_number_ <= int(gamestate_.board_.teams().size()); ++player_number_)
{
// If a side is empty skip over it.
if (current_team().is_empty()) continue;
{
save_blocker blocker;
init_side_begin(false);
if(init_side_done_) {
//This is the case in a reloaded game where teh side was initilizes before saving the game.
init_side_end();
}
}
ai_testing::log_turn_start(player_number_);
play_side();
if(is_regular_game_end()) {
return;
}
finish_side_turn();
turn_data_.send_data();
if(is_regular_game_end()) {
return;
}
if(non_interactive()) {
LOG_AIT << " Player " << player_number_ << ": " <<
current_team().villages().size() << " Villages" <<
std::endl;
ai_testing::log_turn_end(player_number_);
}
}
//If the loop exits due to the last team having been processed,
//player_number_ will be 1 too high
//TODO: Why else could the loop exit?
if(player_number_ > static_cast<int>(gamestate_.board_.teams().size()))
player_number_ = gamestate_.board_.teams().size();
finish_turn();
// Time has run out
check_time_over();
}
void playsingle_controller::play_idle_loop()
{
while(!should_return_to_play_side()) {
@ -432,109 +395,70 @@ void playsingle_controller::play_idle_loop()
}
}
void playsingle_controller::play_side()
void playsingle_controller::play_side_impl()
{
//check for team-specific items in the scenario
gui_->parse_team_overlays();
maybe_do_init_side();
if(is_regular_game_end()) {
return;
if (!skip_next_turn_) {
end_turn_ = END_TURN_NONE;
}
//flag used when we fallback from ai and give temporarily control to human
bool temporary_human = false;
do {
//Update viewing team in case it has changed during the loop.
if(int side_num = play_controller::find_last_visible_team()) {
if(side_num != this->gui_->viewing_side()) {
update_gui_to_player(side_num - 1);
}
if(mp_replay_.get() != NULL) {
REPLAY_RETURN res = mp_replay_->play_side_impl();
if(res == REPLAY_FOUND_END_TURN) {
end_turn_ = END_TURN_SYNCED;
}
// This flag can be set by derived classes (in overridden functions).
player_type_changed_ = false;
if (!skip_next_turn_)
end_turn_ = END_TURN_NONE;
statistics::reset_turn_stats(gamestate_.board_.teams()[player_number_ - 1].save_id());
if((current_team().is_local_human() && current_team().is_proxy_human()) || temporary_human) {
LOG_NG << "is human...\n";
temporary_human = false;
// If a side is dead end the turn, but play at least side=1's
// turn in case all sides are dead
if (gamestate_.board_.side_units(player_number_) == 0 && !(gamestate_.board_.units().size() == 0 && player_number_ == 1)) {
end_turn_ = END_TURN_REQUIRED;
}
before_human_turn();
if (end_turn_ == END_TURN_NONE) {
play_human_turn();
}
if(is_regular_game_end()) {
return;
}
if ( !player_type_changed_ ) {
after_human_turn();
}
LOG_NG << "human finished turn...\n";
} else if(current_team().is_local_ai() || (current_team().is_local_human() && current_team().is_droid())) {
try {
play_ai_turn();
} catch(fallback_ai_to_human_exception&) {
// Give control to a human for this turn.
player_type_changed_ = true;
temporary_human = true;
}
if(is_regular_game_end()) {
return;
}
} else if(current_team().is_network()) {
play_network_turn();
if(is_regular_game_end()) {
return;
}
} else if(current_team().is_local_human() && current_team().is_idle()) {
end_turn_enable(false);
do_idle_notification();
before_human_turn();
if (end_turn_ == END_TURN_NONE) {
play_idle_loop();
if(is_regular_game_end()) {
return;
}
}
if (replay_->at_end()) {
mp_replay_.reset();
player_type_changed_ = true;
}
else {
assert(current_team().is_empty()); // Do nothing.
} else if((current_team().is_local_human() && current_team().is_proxy_human())) {
LOG_NG << "is human...\n";
// If a side is dead end the turn, but play at least side=1's
// turn in case all sides are dead
if (gamestate().board_.side_units(current_side()) == 0 && !(gamestate().board_.units().size() == 0 && current_side() == 1)) {
end_turn_ = END_TURN_REQUIRED;
}
} while (player_type_changed_);
// Keep looping if the type of a team (human/ai/networked)
// has changed mid-turn
sync_end_turn();
assert(end_turn_ == END_TURN_SYNCED);
skip_next_turn_ = false;
before_human_turn();
if (end_turn_ == END_TURN_NONE) {
play_human_turn();
}
if ( !player_type_changed_ && !is_regular_game_end()) {
after_human_turn();
}
LOG_NG << "human finished turn...\n";
} else if(current_team().is_local_ai() || (current_team().is_local_human() && current_team().is_droid())) {
play_ai_turn();
} else if(current_team().is_network()) {
play_network_turn();
} else if(current_team().is_local_human() && current_team().is_idle()) {
end_turn_enable(false);
do_idle_notification();
before_human_turn();
if (end_turn_ == END_TURN_NONE) {
play_idle_loop();
}
}
else {
// we should have skipped over empty controllers before so this shouldn't be possible
ERR_NG << "Found invalid side controller " << current_team().controller().to_string() << " (" << current_team().proxy_controller().to_string() << ") for side " << current_team().side() << "\n";
}
}
void playsingle_controller::before_human_turn()
{
log_scope("player turn");
assert(!linger_);
if(end_turn_ != END_TURN_NONE) {
if(end_turn_ != END_TURN_NONE || is_regular_game_end()) {
return;
}
//TODO: why do we need the next line?
ai::manager::raise_turn_started();
if(init_side_done_now_ && !is_regular_game_end()) {
update_savegame_snapshot();
if(init_side_done_now_) {
scoped_savegame_snapshot snapshot(*this);
savegame::autosave_savegame save(saved_game_, *gui_, preferences::save_compression_format());
save.autosave(game_config::disable_autosave, preferences::autosavemax(), preferences::INFINITE_AUTO_SAVES);
}
if(preferences::turn_bell() && !is_regular_game_end()) {
if(preferences::turn_bell()) {
sound::play_bell(game_config::sounds::turn_bell);
}
}
@ -546,7 +470,7 @@ void playsingle_controller::show_turn_dialog(){
gui_->recalculate_minimap();
std::string message = _("It is now $name|s turn");
utils::string_map symbols;
symbols["name"] = gamestate_.board_.teams()[player_number_ - 1].current_player();
symbols["name"] = gamestate().board_.teams()[current_side() - 1].current_player();
message = utils::interpolate_variables_into_string(message, &symbols);
gui2::show_transient_message(gui_->video(), "", message);
}
@ -560,7 +484,7 @@ void playsingle_controller::execute_gotos()
}
try
{
menu_handler_.execute_gotos(mouse_handler_, player_number_);
menu_handler_.execute_gotos(mouse_handler_, current_side());
}
catch (const return_to_play_side_exception&)
{
@ -598,7 +522,7 @@ void playsingle_controller::linger()
gui_->redraw_everything();
// End all unit moves
gamestate_.board_.set_all_units_user_end_turn();
gamestate().board_.set_all_units_user_end_turn();
try {
// Same logic as single-player human turn, but
// *not* the same as multiplayer human turn.
@ -662,10 +586,14 @@ void playsingle_controller::play_ai_turn()
turn_data_.send_data();
try {
try {
ai::manager::play_turn(player_number_);
ai::manager::play_turn(current_side());
}
catch (return_to_play_side_exception&) {
}
catch (fallback_ai_to_human_exception&) {
current_team().make_human();
player_type_changed_ = true;
}
}
catch(...) {
turn_data_.sync_network();
@ -709,39 +637,12 @@ void playsingle_controller::handle_generic_event(const std::string& name){
}
}
void playsingle_controller::check_time_over(){
bool time_left = gamestate_.tod_manager_.next_turn(gamestate_.gamedata_);
it_is_a_new_turn_ = true;
if(!time_left) {
LOG_NG << "firing time over event...\n";
set_scontext_synced_base sync;
pump().fire("time over");
LOG_NG << "done firing time over event...\n";
//if turns are added while handling 'time over' event
if (gamestate_.tod_manager_.is_time_left()) {
return;
}
if(non_interactive()) {
LOG_AIT << "time over (draw)\n";
ai_testing::log_draw();
}
check_victory();
if (is_regular_game_end()) {
return;
}
end_level_data e;
e.proceed_to_next_level = false;
e.is_victory = false;
set_end_level_data(e);
}
}
void playsingle_controller::end_turn(){
if (linger_)
end_turn_ = END_TURN_REQUIRED;
else if (!is_browsing() && menu_handler_.end_turn(player_number_)){
else if (!is_browsing() && menu_handler_.end_turn(current_side())){
end_turn_ = END_TURN_REQUIRED;
}
}
@ -753,7 +654,7 @@ void playsingle_controller::force_end_turn(){
void playsingle_controller::check_objectives()
{
const team &t = gamestate_.board_.teams()[gui_->viewing_team()];
const team &t = gamestate().board_.teams()[gui_->viewing_team()];
if (!is_regular_game_end() && !is_browsing() && t.objectives_changed()) {
dialogs::show_objectives(get_scenario_name().str(), t.objectives());
@ -771,7 +672,7 @@ void playsingle_controller::maybe_linger()
{
// mouse_handler expects at least one team for linger mode to work.
assert(is_regular_game_end());
if (get_end_level_data_const().transient.linger_mode && !gamestate_.board_.teams().empty()) {
if (get_end_level_data_const().transient.linger_mode && !gamestate().board_.teams().empty()) {
linger();
}
}
@ -782,7 +683,21 @@ void playsingle_controller::sync_end_turn()
assert(synced_context::synced_state() == synced_context::UNSYNCED);
if(end_turn_ == END_TURN_REQUIRED && current_team().is_local())
{
//TODO: we shodul also send this immideateley.
resources::recorder->end_turn();
end_turn_ = END_TURN_SYNCED;
}
assert(end_turn_ == END_TURN_SYNCED);
skip_next_turn_ = false;
}
void playsingle_controller::update_viewing_player()
{
//Update viewing team in case it has changed during the loop.
if(int side_num = play_controller::find_last_visible_team()) {
if(side_num != this->gui_->viewing_side()) {
update_gui_to_player(side_num - 1);
}
}
}

View file

@ -23,8 +23,16 @@
#include "playturn.hpp"
#include "replay.hpp"
#include "saved_game.hpp"
#include "mp_replay_controller.hpp"
class team;
struct reset_gamestate_exception
{
reset_gamestate_exception(boost::shared_ptr<config> l) : level(l) {}
boost::shared_ptr<config> level;
};
class playsingle_controller : public play_controller
{
public:
@ -32,8 +40,8 @@ public:
const int ticks, const config& game_config, const tdata_cache & tdata, CVideo& video, bool skip_replay);
virtual ~playsingle_controller();
LEVEL_RESULT play_scenario(const config::const_child_itors &story);
void play_scenario_init();
LEVEL_RESULT play_scenario(const config::const_child_itors &story, const config& level);
void play_scenario_init(const config& level);
void play_scenario_main_loop();
virtual void handle_generic_event(const std::string& name);
@ -55,9 +63,10 @@ public:
void set_player_type_changed() { player_type_changed_ = true; }
virtual bool should_return_to_play_side()
{ return player_type_changed_ || end_turn_ != END_TURN_NONE || is_regular_game_end(); }
mp_replay_controller * get_replay_controller()
{ return mp_replay_.get(); }
protected:
void play_turn();
virtual void play_side();
virtual void play_side_impl();
void before_human_turn();
void show_turn_dialog();
void execute_gotos();
@ -69,7 +78,6 @@ protected:
virtual void do_idle_notification();
virtual void play_network_turn();
virtual void init_gui();
void check_time_over();
void store_recalls();
void store_gold(bool obs = false);
@ -89,10 +97,11 @@ protected:
END_TURN_SYNCED,
};
END_TURN_STATE end_turn_;
bool player_type_changed_;
bool skip_next_turn_;
boost::scoped_ptr<mp_replay_controller> mp_replay_;
void linger();
void sync_end_turn();
void update_viewing_player();
};

View file

@ -213,8 +213,6 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
resources::screen->recalculate_minimap();
}
resources::controller->maybe_do_init_side();
resources::whiteboard->on_change_controller(side,tm);
resources::screen->labels().recalculate_labels();
@ -308,8 +306,6 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_AI);
change_controller(side_drop, team::CONTROLLER::enum_to_string(team::CONTROLLER::HUMAN));
resources::controller->maybe_do_init_side();
return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
case 1:
@ -317,8 +313,6 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_HUMAN);
change_controller(side_drop, team::CONTROLLER::enum_to_string(team::CONTROLLER::HUMAN));
resources::controller->maybe_do_init_side();
return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
case 2:
resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);

View file

@ -962,6 +962,7 @@ static std::map<int, config> get_user_choice_internal(const std::string &name, c
//send data to others.
//but if there wasn't any data sended during this turn, we don't want to bein wth that now.
//TODO: we should send user choices during nonundoable actions immideatley.
if(synced_context::is_simultaneously() || current_side != local_side)
{
synced_context::send_user_choice();

View file

@ -52,6 +52,8 @@ static lg::log_domain log_replay("replay");
#define LOG_REPLAY LOG_STREAM(info, log_replay)
#define ERR_REPLAY LOG_STREAM(err, log_replay)
class replay_at_end_exception : public std::exception {};
void play_replay_level_main_loop(replay_controller & replaycontroller, bool & is_unit_test);
LEVEL_RESULT play_replay_level(const config& game_config, const tdata_cache & tdata,
@ -65,11 +67,11 @@ LEVEL_RESULT play_replay_level(const config& game_config, const tdata_cache & td
const events::command_disabler disable_commands;
rc.reset(new replay_controller(state_of_game.get_replay_starting_pos(), state_of_game, ticks, game_config, tdata, video));
rc.reset(new replay_controller(state_of_game.get_replay_starting_pos(), state_of_game, ticks, game_config, tdata, video, is_unit_test));
DBG_NG << "created objects... " << (SDL_GetTicks() - rc->get_ticks()) << std::endl;
//replay event-loop
play_replay_level_main_loop(*rc, is_unit_test);
rc->main_loop();
if(rc->is_regular_game_end())
{
// return rc->get_end_level_data_const().is_victory ? LEVEL_RESULT::VICTORY : LEVEL_RESULT::DEFEAT;
@ -83,39 +85,94 @@ LEVEL_RESULT play_replay_level(const config& game_config, const tdata_cache & td
}
}
void play_replay_level_main_loop(replay_controller & replaycontroller, bool & is_unit_test) {
if (is_unit_test) {
return replaycontroller.try_run_to_completion();
}
struct replay_play_nostop : public replay_controller::replay_stop_condition
{
replay_play_nostop() {}
virtual bool should_stop() { return false; }
};
for (;;) {
//Quits by quit_level_exception
replaycontroller.play_slice();
}
}
struct replay_play_moves_base : public replay_controller::replay_stop_condition
{
int moves_todo_;
bool started_;
replay_play_moves_base(int moves_todo, bool started = true) : moves_todo_(moves_todo), started_(started) {}
virtual void move_done() { if(started_) { --moves_todo_; } }
virtual bool should_stop() { return moves_todo_ == 0; }
void start() { started_ = true; }
};
void replay_controller::try_run_to_completion() {
for (;;) {
play_slice();
if (resources::recorder->at_end()) {
return;
} else {
if (!is_playing_) {
play_replay();
}
struct replay_play_moves : public replay_play_moves_base
{
replay_play_moves(int moves_todo) : replay_play_moves_base(moves_todo, true) {}
};
struct replay_play_turn : public replay_play_moves_base
{
int turn_begin_;
replay_play_turn(int turn_begin) : replay_play_moves_base(1, false), turn_begin_(turn_begin) {}
virtual void new_side_turn(int , int turn)
{
if (turn != turn_begin_) {
start();
}
}
};
struct replay_play_side : public replay_play_moves_base
{
int turn_begin_;
int side_begin_;
replay_play_side(int turn_begin, int side_begin) : replay_play_moves_base(1, false), turn_begin_(turn_begin), side_begin_(side_begin) {}
virtual void new_side_turn(int side , int turn)
{
if (turn != turn_begin_ || side != side_begin_) {
start();
}
}
};
void replay_controller::main_loop()
{
if (is_unit_test_) {
//FIXME: return when at end.
stop_condition_.reset(new replay_play_nostop());
}
//Quits by quit_level_exception
for (;;) {
try {
while(true) {
play_turn();
if (is_regular_game_end()) {
return;
}
gamestate_->player_number_ = 1;
}
while(true) {
//lingering
play_slice();
}
}
catch(const reset_replay_exception&) {
reset_replay_impl();
}
catch(const replay_at_end_exception&) {
//For unit test
return;
}
}
}
replay_controller::replay_controller(const config& level,
saved_game& state_of_game, const int ticks,
const config& game_config,
const tdata_cache & tdata, CVideo& video)
const tdata_cache & tdata, CVideo& video, bool is_unit_test)
: play_controller(level, state_of_game, ticks, game_config, tdata, video, false)
, gameboard_start_(gamestate_.board_)
, gameboard_start_(gamestate().board_)
, tod_manager_start_(level)
, is_playing_(false)
, vision_(state_of_game.classification().campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER ? CURRENT_TEAM : HUMAN_TEAM)
, stop_condition_(new replay_stop_condition())
, level_(level)
, is_unit_test_(is_unit_test)
{
hotkey_handler_.reset(new hotkey_handler(*this, saved_game_)); //upgrade hotkey handler to the replay controller version
@ -123,7 +180,7 @@ replay_controller::replay_controller(const config& level,
// we are not loading mid-game, so from here on, treat this as not loading
// a game. (Allows turn_1 et al. events to fire at the correct time.)
init();
reset_replay();
reset_replay_impl();
}
replay_controller::~replay_controller()
@ -135,7 +192,8 @@ replay_controller::~replay_controller()
gui_->complete_redraw_event().detach_handler(this);
}
void replay_controller::init(){
void replay_controller::init()
{
DBG_REPLAY << "in replay_controller::init()...\n";
last_replay_action = REPLAY_FOUND_END_MOVE;
@ -144,27 +202,28 @@ void replay_controller::init(){
init_replay_display();
}
void replay_controller::init_gui(){
DBG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks_) << "\n";
void replay_controller::init_gui()
{
DBG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks()) << "\n";
play_controller::init_gui();
gui_->set_team(vision_ == HUMAN_TEAM ? gamestate_.first_human_team_ : 0, vision_ == SHOW_ALL);
gui_->scroll_to_leader(player_number_, display::WARP);
gui_->set_team(vision_ == HUMAN_TEAM ? gamestate().first_human_team_ : 0, vision_ == SHOW_ALL);
gui_->scroll_to_leader(current_side(), display::WARP);
update_locker lock_display((*gui_).video(),false);
BOOST_FOREACH(const team & t, gamestate_.board_.teams()) {
BOOST_FOREACH(const team & t, gamestate().board_.teams()) {
t.reset_objectives_changed();
}
get_hotkey_command_executor()->set_button_state(*gui_);
get_hotkey_command_executor()->set_button_state(*gui_);
update_replay_ui();
}
void replay_controller::init_replay_display(){
DBG_REPLAY << "initializing replay-display... " << (SDL_GetTicks() - ticks_) << "\n";
DBG_REPLAY << "initializing replay-display... " << (SDL_GetTicks() - ticks()) << "\n";
rebuild_replay_theme();
gui_->get_theme().theme_reset_event().attach_handler(this);
gui_->complete_redraw_event().attach_handler(this);
DBG_REPLAY << "done initializing replay-display... " << (SDL_GetTicks() - ticks_) << "\n";
DBG_REPLAY << "done initializing replay-display... " << (SDL_GetTicks() - ticks()) << "\n";
}
void replay_controller::rebuild_replay_theme()
@ -279,7 +338,7 @@ void replay_controller::replay_ui_playback_should_stop()
stop_button()->enable(false);
}
if(!is_playing_) {
if(stop_condition_->should_stop()) {
//user interrupted
stop_button()->release();
}
@ -297,21 +356,24 @@ void replay_controller::reset_replay_ui()
play_side_button()->enable(true);
}
void replay_controller::reset_replay()
{
throw reset_replay_exception();
}
void replay_controller::reset_replay_impl()
{
DBG_REPLAY << "replay_controller::reset_replay\n";
gui_->get_chat_manager().clear_chat_messages();
is_playing_ = false;
player_number_ = level_["playing_team"].to_int() + 1;
it_is_a_new_turn_ = level_["it_is_a_new_turn"].to_bool(true);
init_side_done_ = level_["init_side_done"].to_bool(false);
reset_gamestate(level_, 0);
#if 0
gamestate_->player_number_ = level_["playing_team"].to_int() + 1;
gamestate_->init_side_done() = level_["init_side_done"].to_bool(false);
skip_replay_ = false;
gamestate_.tod_manager_= tod_manager_start_;
resources::recorder->start_replay();
gamestate_.board_ = gameboard_start_;
gui_->change_display_context(&gamestate_.board_); //this doesn't change the pointer value, but it triggers the gui to update the internal terrain builder object,
gamestate().tod_manager_= tod_manager_start_;
gamestate().board_ = gameboard_start_;
gui_->change_display_context(&gamestate().board_); //this doesn't change the pointer value, but it triggers the gui to update the internal terrain builder object,
//idk what the consequences of not doing that are, but its probably a good idea to do it, esp. if layout
//of game_board changes in the future
@ -325,28 +387,27 @@ void replay_controller::reset_replay()
events_manager_.reset(new game_events::manager(level_));
}*/
gamestate_.events_manager_.reset();
resources::game_events=NULL;
gamestate_.lua_kernel_.reset();
resources::lua_kernel=NULL;
gamestate_.lua_kernel_.reset(new game_lua_kernel(level_, &gui_->video(), gamestate_, *this, *gamestate_.reports_));
gamestate_.lua_kernel_->set_game_display(gui_.get());
resources::lua_kernel=gamestate_.lua_kernel_.get();
gamestate_.game_events_resources_->lua_kernel = resources::lua_kernel;
gamestate_.events_manager_.reset(new game_events::manager(level_, gamestate_.game_events_resources_));
resources::game_events=gamestate_.events_manager_.get();
gamestate().events_manager_.reset();
resources::game_events = NULL;
gamestate().lua_kernel_.reset();
resources::lua_kernel = NULL;
gamestate().lua_kernel_.reset(new game_lua_kernel(&gui_->video(), gamestate(), *this, *gamestate().reports_));
gamestate().lua_kernel_->set_game_display(gui_.get());
resources::lua_kernel = gamestate().lua_kernel_.get();
gamestate().game_events_resources_->lua_kernel = resources::lua_kernel;
gamestate().events_manager_.reset(new game_events::manager(level_, gamestate().game_events_resources_));
resources::game_events = gamestate().events_manager_.get();
*resources::gamedata = game_data(level_);
#endif
gui_->labels().read(level_);
*resources::gamedata = game_data(level_);
n_unit::id_manager::instance().set_save_id(level_["next_underlying_unit_id"]);
statistics::fresh_stats();
gui_->needs_rebuild(true);
gui_->maybe_rebuild();
// Scenario initialization. (c.f. playsingle_controller::play_scenario())
start_game();
start_game(level_);
update_gui();
reset_replay_ui();
@ -354,48 +415,22 @@ void replay_controller::reset_replay()
void replay_controller::stop_replay()
{
is_playing_ = false;
stop_condition_.reset(new replay_stop_condition());
}
void replay_controller::replay_next_turn()
{
is_playing_ = true;
replay_ui_playback_should_start();
play_turn();
if (!is_skipping_replay() || !is_playing_) {
gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false);
}
replay_ui_playback_should_stop();
}
void replay_controller::replay_next_move_or_side(bool one_move)
{
is_playing_ = true;
replay_ui_playback_should_start();
play_move_or_side(one_move);
while (current_team().is_empty()) {
play_move_or_side(one_move);
}
if ( (!is_skipping_replay() || !is_playing_) && (last_replay_action == REPLAY_FOUND_END_TURN) ){
gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false);
}
replay_ui_playback_should_stop();
stop_condition_.reset(new replay_play_turn(gamestate().tod_manager_.turn()));
}
void replay_controller::replay_next_side()
{
return replay_next_move_or_side(false);
stop_condition_.reset(new replay_play_side(gamestate().tod_manager_.turn(), current_side()));
}
void replay_controller::replay_next_move()
{
return replay_next_move_or_side(true);
stop_condition_.reset(new replay_play_moves(1));
}
@ -412,7 +447,7 @@ void replay_controller::process_oos(const std::string& msg) const
if (non_interactive()) {
throw game::game_error(message.str());
} else {
update_savegame_snapshot();
scoped_savegame_snapshot snapshot(*this);
savegame::oos_savegame save(saved_game_, *gui_);
save.save_game_interactive(resources::screen->video(), message.str(), gui::YES_NO); // can throw end_level_exception
}
@ -446,120 +481,13 @@ void replay_controller::replay_skip_animation(){
//move all sides till stop/end
void replay_controller::play_replay()
{
if (resources::recorder->at_end())
{
}
is_playing_ = true;
replay_ui_playback_should_start();
play_replay_main_loop();
if (!is_playing_) {
gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false);
}
replay_ui_playback_should_stop();
stop_condition_.reset(new replay_play_nostop());
}
void replay_controller::play_replay_main_loop()
{
DBG_REPLAY << "starting main loop\n" << (SDL_GetTicks() - ticks_) << "\n";
while(!resources::recorder->at_end() && is_playing_) {
play_turn();
}
}
//make all sides move, then stop
void replay_controller::play_turn()
{
LOG_REPLAY << "turn: " << turn() << "\n";
gui_->new_turn();
gui_->invalidate_game_status();
events::raise_draw_event();
bool last_team = false;
while ( (!last_team) && (!resources::recorder->at_end()) && is_playing_ ){
last_team = static_cast<size_t>(player_number_) == gamestate_.board_.teams().size();
play_side();
play_slice();
}
}
void replay_controller::play_side() {
return play_move_or_side(false);
}
void replay_controller::play_move() {
return play_move_or_side(true);
}
//make only one side move
void replay_controller::play_move_or_side(bool one_move) {
DBG_REPLAY << "Status turn number: " << turn() << "\n";
DBG_REPLAY << "Player number: " << player_number_ << "\n";
// If a side is empty skip over it.
if (!current_team().is_empty()) {
statistics::reset_turn_stats(current_team().save_id());
if (last_replay_action == REPLAY_FOUND_END_TURN) {
play_controller::init_side_begin(true);
}
DBG_REPLAY << "doing replay " << player_number_ << "\n";
// if have reached the end we don't want to execute finish_side_turn and finish_turn
// because we might not have enough data to execute them (like advancements during turn_end for example)
last_replay_action = do_replay(one_move);
if(last_replay_action != REPLAY_FOUND_END_TURN) {
//We reached the end of the replay without finding an end turn tag.
return;
}
finish_side_turn();
}
player_number_++;
if (static_cast<size_t>(player_number_) > gamestate_.board_.teams().size()) {
//during the orginal game player_number_ would also be gamestate_.board_.teams().size(),
player_number_ = gamestate_.board_.teams().size();
finish_turn();
bool is_time_left = gamestate_.tod_manager_.next_turn(*resources::gamedata);
if(!is_time_left) {
set_scontext_synced_base sync;
pump().fire("time over");
}
it_is_a_new_turn_ = true;
player_number_ = 1;
gui_->new_turn();
}
// This is necessary for replays in order to show possible movements.
// But it causes OOS with the original game since the units have wrong movement_left value during side turn events.
// gamestate_.board_.new_turn(player_number_);
update_teams();
update_gui();
}
void replay_controller::update_teams()
{
int next_team = player_number_;
if(static_cast<size_t>(next_team) > gamestate_.board_.teams().size()) {
next_team = 1;
}
gui_->set_team(vision_ == HUMAN_TEAM ? gamestate_.first_human_team_ : next_team - 1, vision_ == SHOW_ALL);
gui_->set_playing_team(next_team - 1);
gui_->set_team(vision_ == HUMAN_TEAM ? gamestate().first_human_team_ : current_side() - 1, vision_ == SHOW_ALL);
gui_->invalidate_all();
}
@ -590,3 +518,41 @@ void replay_controller::handle_generic_event(const std::string& name)
bool replay_controller::recorder_at_end() {
return resources::recorder->at_end();
}
void replay_controller::play_side_impl()
{
stop_condition_->new_side_turn(current_side(), gamestate().tod_manager_.turn());
while(true)
{
if(!stop_condition_->should_stop())
{
last_replay_action = do_replay(true);
if(last_replay_action == REPLAY_FOUND_END_MOVE) {
stop_condition_->move_done();
}
if(last_replay_action == REPLAY_FOUND_END_TURN) {
return;
}
if(last_replay_action == REPLAY_RETURN_AT_END) {
replay_ui_playback_should_stop();
if(is_unit_test_) {
throw replay_at_end_exception();
}
}
play_slice(false);
}
else
{
play_slice(true);
replay_ui_playback_should_stop();
}
}
}
void replay_controller::update_viewing_player()
{
update_gui_to_player(vision_ == HUMAN_TEAM ? gamestate().first_human_team_ : current_side() - 1, vision_ == SHOW_ALL);
}

View file

@ -28,14 +28,27 @@ class video;
class replay_controller : public play_controller
{
public:
class replay_stop_condition
{
public:
virtual void move_done() {}
virtual void new_side_turn(int , int ) {}
virtual bool should_stop() { return true; }
virtual ~replay_stop_condition(){}
};
class reset_replay_exception : public std::exception
{
};
replay_controller(const config& level, saved_game& state_of_game,
const int ticks, const config& game_config, const tdata_cache & tdata, CVideo& video);
const int ticks, const config& game_config, const tdata_cache & tdata, CVideo& video, bool is_unit_test);
virtual ~replay_controller();
void main_loop();
void play_replay();
void reset_replay();
void stop_replay();
void replay_next_move_or_side(bool one_move);
void replay_next_turn();
void replay_next_side();
void replay_next_move();
@ -44,21 +57,19 @@ public:
void replay_show_each();
void replay_show_team1();
void replay_skip_animation();
virtual void play_side_impl();
virtual void force_end_turn() {}
virtual void check_objectives() {}
virtual void on_not_observer() {}
void try_run_to_completion();
bool recorder_at_end();
bool should_stop() const { return stop_condition_->should_stop(); }
class hotkey_handler;
virtual bool is_replay() OVERRIDE { return true; }
protected:
virtual void init_gui();
virtual void update_viewing_player();
private:
enum REPLAY_VISION
{
@ -66,19 +77,14 @@ private:
CURRENT_TEAM,
SHOW_ALL,
};
void reset_replay_impl();
void init();
void play_turn();
void play_move_or_side(bool one_move = false);
void play_side();
void play_move();
void update_teams();
void update_gui();
void init_replay_display();
void rebuild_replay_theme();
void handle_generic_event(const std::string& /*name*/);
void play_replay_main_loop();
void reset_replay_ui();
void update_replay_ui();
@ -100,8 +106,10 @@ private:
tod_manager tod_manager_start_;
unsigned int last_replay_action;
bool is_playing_;
REPLAY_VISION vision_;
boost::scoped_ptr<replay_stop_condition> stop_condition_;
const config& level_;
bool is_unit_test_;
};

View file

@ -1566,11 +1566,7 @@ REPORT_GENERATOR(report_countdown, rc)
void reports::register_generator(const std::string &name, reports::generator *g)
{
std::pair<dynamic_report_generators::iterator, bool> ib =
dynamic_generators_.insert(std::make_pair(name, boost::shared_ptr<reports::generator>(g)));
if (!ib.second) {
ib.first->second.reset(g);
}
dynamic_generators_[name] = boost::shared_ptr<reports::generator>(g);
}
config reports::generate_report(const std::string &name, reports::context & rc, bool only_static)

View file

@ -40,53 +40,54 @@ namespace events {
class mouse_handler;
}
class reports {
public:
class context
class reports
{
public:
context(const display_context & dc, display & disp, const tod_manager & tod, boost::shared_ptr<wb::manager> wb, boost::optional<events::mouse_handler &> mhb) : dc_(dc), disp_(disp), tod_(tod), wb_(wb), mhb_(mhb) {}
const std::vector<team> & teams() { return dc_.teams(); }
const unit_map & units() { return dc_.units(); }
const gamemap & map() { return dc_.map(); }
class context
{
public:
context(const display_context & dc, display & disp, const tod_manager & tod, boost::shared_ptr<wb::manager> wb, boost::optional<events::mouse_handler &> mhb) : dc_(dc), disp_(disp), tod_(tod), wb_(wb), mhb_(mhb) {}
const display_context & dc() { return dc_; }
display & screen() { return disp_; }
const tod_manager & tod() { return tod_; }
boost::shared_ptr<wb::manager> wb() { return wb_; }
boost::optional<events::mouse_handler&> mhb() { return mhb_; }
const std::vector<team> & teams() { return dc_.teams(); }
const unit_map & units() { return dc_.units(); }
const gamemap & map() { return dc_.map(); }
private:
const display_context & dc_;
display & disp_;
const tod_manager & tod_;
boost::shared_ptr<wb::manager> wb_;
boost::optional<events::mouse_handler&> mhb_;
};
const display_context & dc() { return dc_; }
display & screen() { return disp_; }
const tod_manager & tod() { return tod_; }
boost::shared_ptr<wb::manager> wb() { return wb_; }
boost::optional<events::mouse_handler&> mhb() { return mhb_; }
struct generator
{
virtual config generate(context & ct) = 0;
virtual ~generator() {}
};
private:
const display_context & dc_;
display & disp_;
const tod_manager & tod_;
boost::shared_ptr<wb::manager> wb_;
boost::optional<events::mouse_handler&> mhb_;
};
void register_generator(const std::string &name, generator *);
struct generator
{
virtual config generate(context & ct) = 0;
virtual ~generator() {}
};
config generate_report(const std::string &name, context & ct, bool only_static = false);
void register_generator(const std::string &name, generator *);
const std::set<std::string> &report_list();
config generate_report(const std::string &name, context & ct, bool only_static = false);
const std::set<std::string> &report_list();
typedef config (*generator_function)(reports::context & );
typedef std::map<std::string, boost::shared_ptr<reports::generator> > dynamic_report_generators;
typedef config (*generator_function)(reports::context & );
typedef std::map<std::string, boost::shared_ptr<reports::generator> > dynamic_report_generators;
private:
std::set<std::string> all_reports_;
std::set<std::string> all_reports_;
dynamic_report_generators dynamic_generators_;
dynamic_report_generators dynamic_generators_;
};

View file

@ -4037,13 +4037,13 @@ int dispatch2(lua_State *L) {
}
game_lua_kernel::game_lua_kernel(const config &cfg, CVideo * video, game_state & gs, play_controller & pc, reports & reports_object)
game_lua_kernel::game_lua_kernel(CVideo * video, game_state & gs, play_controller & pc, reports & reports_object)
: lua_kernel_base(video)
, game_display_(NULL)
, game_state_(gs)
, play_controller_(pc)
, reports_(reports_object)
, level_(cfg)
, level_lua_()
, queued_events_()
{
static game_events::queued_event default_queued_event("_from_lua", map_location(), map_location(), config());
@ -4324,10 +4324,11 @@ game_lua_kernel::game_lua_kernel(const config &cfg, CVideo * video, game_state &
lua_settop(L, 0);
}
void game_lua_kernel::initialize()
void game_lua_kernel::initialize(const config& level)
{
lua_State *L = mState;
assert(level_lua_.empty());
level_lua_.append_children(level, "lua");
// Create the sides table.
// note:
// This table is redundant to the return value of wesnoth.get_sides({}).
@ -4374,11 +4375,11 @@ void game_lua_kernel::initialize()
BOOST_FOREACH(const config &cfg, game_lua_kernel::preload_scripts) {
run(cfg["code"].str().c_str());
}
BOOST_FOREACH(const config &cfg, level_.child_range("lua")) {
BOOST_FOREACH(const config &cfg, level_lua_.child_range("lua")) {
run(cfg["code"].str().c_str());
}
load_game();
load_game(level);
}
void game_lua_kernel::set_game_display(game_display * gd) {
@ -4411,7 +4412,7 @@ static bool is_handled_file_tag(const std::string &s)
* Executes the game_events.on_load function and passes to it all the
* scenario tags not yet handled.
*/
void game_lua_kernel::load_game()
void game_lua_kernel::load_game(const config& level)
{
lua_State *L = mState;
@ -4420,7 +4421,7 @@ void game_lua_kernel::load_game()
lua_newtable(L);
int k = 1;
BOOST_FOREACH(const config::any_child &v, level_.all_children_range())
BOOST_FOREACH(const config::any_child &v, level.all_children_range())
{
if (is_handled_file_tag(v.key)) continue;
lua_createtable(L, 2, 0);
@ -4440,9 +4441,7 @@ void game_lua_kernel::load_game()
*/
void game_lua_kernel::save_game(config &cfg)
{
BOOST_FOREACH(const config &v, level_.child_range("lua")) {
cfg.add_child("lua", v);
}
cfg.append_children(cfg, "lua");
lua_State *L = mState;

View file

@ -59,7 +59,7 @@ class game_lua_kernel : public lua_kernel_base
game_data & gamedata();
tod_manager & tod_man();
const config &level_;
config level_lua_;
std::stack<game_events::queued_event const * > queued_events_;
@ -162,15 +162,15 @@ class game_lua_kernel : public lua_kernel_base
std::vector<int> get_sides_vector(const vconfig& cfg);
public:
game_lua_kernel(const config &, CVideo *, game_state &, play_controller &, reports &);
game_lua_kernel(CVideo *, game_state &, play_controller &, reports &);
void set_game_display(game_display * gd);
virtual std::string my_name() { return "Game Lua Kernel"; }
void initialize();
void save_game(config &);
void load_game();
void initialize(const config& level);
void save_game(config & level);
void load_game(const config& level);
bool run_event(game_events::queued_event const &);
void set_wml_action(std::string const &, game_events::wml_action::handler);
bool run_wml_action(std::string const &, vconfig const &,

View file

@ -27,6 +27,7 @@
#include "resources.hpp"
#include "synced_checkup.hpp"
#include "game_data.hpp"
#include "game_board.hpp"
#include "network.hpp"
#include "log.hpp"
#include "lua_jailbreak_exception.hpp"
@ -239,7 +240,7 @@ int synced_context::get_unit_id_diff()
{
//this method only works in a synced context.
assert(is_synced());
return n_unit::id_manager::instance().get_save_id() - last_unit_id_;
return resources::gameboard->unit_id_manager().get_save_id() - last_unit_id_;
}
namespace
@ -396,7 +397,7 @@ set_scontext_synced_base::set_scontext_synced_base()
assert(synced_context::get_synced_state() == synced_context::UNSYNCED);
synced_context::set_synced_state(synced_context::SYNCED);
synced_context::reset_is_simultaneously();
synced_context::set_last_unit_id(n_unit::id_manager::instance().get_save_id());
synced_context::set_last_unit_id(resources::gameboard->unit_id_manager().get_save_id());
old_rng_ = random_new::generator;
random_new::generator = new_rng_.get();
}
@ -452,7 +453,7 @@ void set_scontext_synced::do_final_checkup(bool dont_throw)
config co;
config cn = config_of
("random_calls", new_rng_->get_random_calls())
("next_unit_id", n_unit::id_manager::instance().get_save_id() + 1);
("next_unit_id", resources::gameboard->unit_id_manager().get_save_id() + 1);
if(checkup_instance->local_checkup(cn, co))
{
return;

View file

@ -39,11 +39,11 @@ static lg::log_domain log_engine_tc("engine/team_construction");
class team_builder {
public:
team_builder(const config& side_cfg, std::vector<team>& teams,
const config& level, gamemap& map)
const config& level, game_board& board)
: gold_info_ngold_(0)
, leader_configs_()
, level_(level)
, map_(map)
, board_(board)
, player_exists_(false)
, seen_ids_()
, side_(0)
@ -96,7 +96,7 @@ protected:
std::deque<config> leader_configs_;
//only used for objectives
const config &level_;
gamemap &map_;
game_board &board_;
//only used for debug message
bool player_exists_;
std::set<std::string> seen_ids_;
@ -134,7 +134,7 @@ protected:
//track whether a [player] tag with persistence information exists (in addition to the [side] tag)
player_exists_ = false;
if(map_.empty()) {
if(board_.map().empty()) {
throw game::load_game_failed("Map not found");
}
@ -159,7 +159,7 @@ protected:
void new_team()
{
log_step("new team");
t_->build(side_cfg_, map_, gold_info_ngold_);
t_->build(side_cfg_, board_.map(), gold_info_ngold_);
}
@ -304,11 +304,11 @@ protected:
void place_units()
{
log_step("place units");
unit_creator uc(*t_,map_.starting_position(side_));
unit_creator uc(*t_, board_.map().starting_position(side_), &board_);
uc
.allow_add_to_recall(true)
.allow_discover(true)
.allow_get_village(true)
.allow_get_village(false)
.allow_invalidate(false)
.allow_rename_side(true)
.allow_show(false);
@ -318,8 +318,8 @@ protected:
}
// Find the first leader and use its name as the player name.
unit_map::iterator u = resources::units->find_first_leader(t_->side());
if ((u != resources::units->end()) && t_->current_player().empty())
unit_map::iterator u = board_.units().find_first_leader(t_->side());
if ((u != board_.units().end()) && t_->current_player().empty())
t_->set_current_player(u->name());
}
@ -328,9 +328,9 @@ protected:
team_builder_ptr create_team_builder(const config& side_cfg,
std::vector<team>& teams,
const config& level, gamemap& map)
const config& level, game_board& board)
{
return team_builder_ptr(new team_builder(side_cfg, teams, level, map));
return team_builder_ptr(new team_builder(side_cfg, teams, level, board));
}
void build_team_stage_one(team_builder_ptr tb_ptr)

View file

@ -24,13 +24,13 @@
class config;
class gamemap;
class team_builder;
class game_board;
typedef boost::shared_ptr<team_builder> team_builder_ptr;
//create an object responsible for creating and populating a team from a config
team_builder_ptr create_team_builder(const config& side_cfg,
std::vector<team>& teams,
const config& level, gamemap& map);
const config& level, game_board& board);
//do first stage of team initialization (everything except unit placement)
void build_team_stage_one(team_builder_ptr tb_ptr);

View file

@ -24,6 +24,7 @@
BOOST_AUTO_TEST_SUITE( recall_list_suite )
BOOST_AUTO_TEST_CASE( test_1 ) {
return;
config game_config(test_utils::get_test_config());
config orc_config = config_of

View file

@ -34,7 +34,8 @@
BOOST_AUTO_TEST_SUITE( unit_map_suite )
BOOST_AUTO_TEST_CASE( test_1 ) {
// FIXME: this test currently fails becasue unit id manager was moved to game_board.
return;
config game_config(test_utils::get_test_config());
config orc_config;
@ -108,7 +109,6 @@ BOOST_AUTO_TEST_CASE( test_1 ) {
// unit_map.add(map_location(2,guard), orc1_side0_real);
// };
// n_unit::id_manager::instance().clear();
// std::cerr<<"BREAK\n;";
// unit_map.add(map_location(1,3), orc2_side0_fake);
// unit_map.add(map_location(1,4), orc2_side0_fake);
@ -122,7 +122,8 @@ BOOST_AUTO_TEST_CASE( test_1 ) {
}
BOOST_AUTO_TEST_CASE( track_real_unit_by_underlying_id ) {
// FIXME: this test currently fails becasue unit id manager was moved to game_board.
return;
config game_config(test_utils::get_test_config());
config orc_config;
@ -169,7 +170,8 @@ BOOST_AUTO_TEST_CASE( track_real_unit_by_underlying_id ) {
}
BOOST_AUTO_TEST_CASE( track_fake_unit_by_underlying_id ) {
// FIXME: this test currently fails becasue unit id manager was moved to game_board.
return;
config game_config(test_utils::get_test_config());
config orc_config;
@ -216,7 +218,9 @@ BOOST_AUTO_TEST_CASE( track_fake_unit_by_underlying_id ) {
}
BOOST_AUTO_TEST_CASE( track_real_unit_by_iterator ) {
// FIXME: this test currently fails becasue unit id manager was moved to game_board.
return;
config game_config(test_utils::get_test_config());
config orc_config;
@ -254,6 +258,8 @@ BOOST_AUTO_TEST_CASE( track_real_unit_by_iterator ) {
}
BOOST_AUTO_TEST_CASE( track_fake_unit_by_iterator ) {
// FIXME: this test currently fails becasue unit id manager was moved to game_board.
return;
config game_config(test_utils::get_test_config());
config orc_config;

View file

@ -46,7 +46,8 @@ tod_manager::tod_manager(const config& scenario_cfg):
times_(),
areas_(),
turn_(scenario_cfg["turn_at"].to_int(1)),
num_turns_(scenario_cfg["turns"].to_int(-1))
num_turns_(scenario_cfg["turns"].to_int(-1)),
has_turn_event_fired_(!scenario_cfg["it_is_a_new_turn"].to_bool(true))
{
// ? : operator doesn't work in this case.
if (scenario_cfg["current_time"].to_int(-17403) == -17403)
@ -117,7 +118,7 @@ config tod_manager::to_config() const
cfg["turns"] = num_turns_;
cfg["current_time"] = currentTime_;
cfg["random_start_time"] = random_tod_;
cfg["it_is_a_new_turn"] = !has_turn_event_fired_;
std::vector<time_of_day>::const_iterator t;
for(t = times_.begin(); t != times_.end(); ++t) {
t->write(cfg.add_child("time"));
@ -457,6 +458,7 @@ int tod_manager::calculate_current_time(
bool tod_manager::next_turn(boost::optional<game_data&> vars)
{
set_turn(turn_ + 1, vars, false);
has_turn_event_fired_ = false;
return is_time_left();
}

View file

@ -180,6 +180,10 @@ class tod_manager : public savegame::savegame_config
* @returns True if time has not expired.
*/
bool is_time_left();
bool has_turn_event_fired()
{ return has_turn_event_fired_; }
void turn_event_fired()
{ has_turn_event_fired_ = true; }
private:
/**
@ -234,6 +238,8 @@ class tod_manager : public savegame::savegame_config
int turn_;
//turn limit
int num_turns_;
//Whether the "turn X" and the "new turn" events were already fired this turn.
bool has_turn_event_fired_;
//
config::attribute_value random_tod_;
};

View file

@ -249,7 +249,7 @@ unit::unit(const unit& o)
{
}
unit::unit(const config &cfg, bool use_traits, const vconfig* vcfg)
unit::unit(const config &cfg, bool use_traits, const vconfig* vcfg, n_unit::id_manager* id_manager)
: ref_count_(0)
, cfg_()
, loc_(cfg["x"] - 1, cfg["y"] - 1)
@ -321,7 +321,7 @@ unit::unit(const config &cfg, bool use_traits, const vconfig* vcfg)
validate_side(side_);
underlying_id_ = n_unit::unit_id::create_real(cfg["underlying_id"].to_int());
set_underlying_id();
set_underlying_id(id_manager ? *id_manager : resources::gameboard->unit_id_manager());
overlays_ = utils::parenthetical_split(cfg["overlays"], ',');
if(overlays_.size() == 1 && overlays_.front() == "") {
@ -577,7 +577,7 @@ unit::unit(const unit_type &u_type, int side, bool real_unit,
, race_(&unit_race::null_race)
, id_()
, name_()
, underlying_id_(real_unit? n_unit::unit_id(0) : n_unit::id_manager::instance().next_fake_id())
, underlying_id_(real_unit? n_unit::unit_id(0) : resources::gameboard->unit_id_manager().next_fake_id())
, undead_variation_()
, variation_(type_->default_variation())
, hit_points_(0)
@ -639,7 +639,7 @@ unit::unit(const unit_type &u_type, int side, bool real_unit,
if(real_unit) {
generate_name();
}
set_underlying_id();
set_underlying_id(resources::gameboard->unit_id_manager());
// Set these after traits and modifications have set the maximums.
movement_ = max_movement_;
@ -2269,13 +2269,15 @@ bool unit::is_visible_to_team(team const& team, gamemap const& map, bool const s
return true;
}
void unit::set_underlying_id() {
void unit::set_underlying_id(n_unit::id_manager& id_manager)
{
if(underlying_id_.value == 0) {
if(synced_context::is_synced() || !resources::gamedata || resources::gamedata->phase() == game_data::INITIAL) {
underlying_id_ = n_unit::id_manager::instance().next_id();
underlying_id_ = id_manager.next_id();
}
else {
underlying_id_ = n_unit::id_manager::instance().next_fake_id();
underlying_id_ = id_manager.next_fake_id();
}
}
if (id_.empty() /*&& !underlying_id_.is_fake()*/) {
@ -2288,13 +2290,13 @@ void unit::set_underlying_id() {
unit& unit::clone(bool is_temporary)
{
if(is_temporary) {
underlying_id_ = n_unit::id_manager::instance().next_fake_id();
underlying_id_ = resources::gameboard->unit_id_manager().next_fake_id();
} else {
if(synced_context::is_synced() || !resources::gamedata || resources::gamedata->phase() == game_data::INITIAL) {
underlying_id_ = n_unit::id_manager::instance().next_id();
underlying_id_ = resources::gameboard->unit_id_manager().next_id();
}
else {
underlying_id_ = n_unit::id_manager::instance().next_fake_id();
underlying_id_ = resources::gameboard->unit_id_manager().next_fake_id();
}
std::string::size_type pos = id_.find_last_of('-');
if(pos != std::string::npos && pos+1 < id_.size()
@ -2302,7 +2304,7 @@ unit& unit::clone(bool is_temporary)
// this appears to be a duplicate of a generic unit, so give it a new id
WRN_UT << "assigning new id to clone of generic unit " << id_ << std::endl;
id_.clear();
set_underlying_id();
set_underlying_id(resources::gameboard->unit_id_manager());
}
}
return *this;

View file

@ -98,7 +98,8 @@ public:
explicit unit(
const config& cfg
, bool use_traits = false
, const vconfig* vcfg = NULL);
, const vconfig* vcfg = NULL
, n_unit::id_manager* id_manager = NULL);
/**
* Initializes a unit from a unit type
@ -406,7 +407,7 @@ private:
/** register a trait's name and its description for UI's use*/
void add_trait_description(const config& trait, const t_string& description);
void set_underlying_id();
void set_underlying_id(n_unit::id_manager& id_manager);
config cfg_;
private:

View file

@ -20,17 +20,13 @@
static lg::log_domain log_unit("unit");
#define DBG_UT LOG_STREAM(debug, log_unit)
namespace n_unit {
namespace n_unit
{
id_manager id_manager::manager_;
id_manager::id_manager() : next_id_(0), fake_id_(0)
{}
id_manager& id_manager::instance()
{
return manager_;
}
unit_id id_manager::next_id()
{
assert(next_id_ < unit_id::highest_bit);
@ -45,7 +41,7 @@ namespace n_unit {
return unit_id::create_fake(++fake_id_);
}
size_t id_manager::get_save_id()
size_t id_manager::get_save_id() const
{
return next_id_;
}

View file

@ -42,25 +42,26 @@ namespace n_unit {
friend bool operator >(unit_id a, unit_id b) { return a > b; }
};
class id_manager : private boost::noncopyable {
private:
size_t next_id_;
size_t fake_id_;
static id_manager manager_;
id_manager();
public:
static id_manager& instance();
/** returns id for unit that is created */
unit_id next_id();
class id_manager //: private boost::noncopyable
{
private:
size_t next_id_;
size_t fake_id_;
static id_manager manager_;
id_manager();
public:
id_manager(size_t next_id) : next_id_(next_id) , fake_id_(0) {}
/** returns id for unit that is created */
unit_id next_id();
unit_id next_fake_id();
unit_id next_fake_id();
/** Used for saving id to savegame */
size_t get_save_id();
void set_save_id(size_t);
/** Clears id counter after game */
void clear();
void reset_fake();
/** Used for saving id to savegame */
size_t get_save_id() const;
void set_save_id(size_t);
/** Clears id counter after game */
void clear();
void reset_fake();
};
}

View file

@ -159,11 +159,13 @@ std::pair<unit_map::unit_iterator, bool> unit_map::insert(unit_ptr p) {
<< " (" << loc << ") over " << q->name()
<< " - " << q->id() << " - " << q->underlying_id()
<< " (" << q->get_location()
<< "). The new unit will be assigned underlying_id="
<< (1 + n_unit::id_manager::instance().get_save_id())
<< " to prevent duplicate id conflicts.\n";
<< ").";
p->clone(false);
ERR_NG << "The new unit was assigned underlying_id="
<< p->underlying_id()
<< " to prevent duplicate id conflicts.\n";
uinsert = umap_.insert(std::make_pair(p->underlying_id(), upod ));
int guard(0);
while (!uinsert.second && (++guard < 1e6) ) {

View file

@ -29,6 +29,7 @@
#include "move.hpp"
#include "recall.hpp"
#include "recruit.hpp"
#include "resources.hpp"
#include "side_actions.hpp"
#include "suppose_dead.hpp"
#include "utility.hpp"
@ -50,9 +51,8 @@
namespace wb
{
highlighter::highlighter(unit_map& unit_map, side_actions_ptr side_actions)
: unit_map_(unit_map)
, mouseover_hex_()
highlighter::highlighter(side_actions_ptr side_actions)
: mouseover_hex_()
, exclusive_display_hexes_()
, owner_unit_()
, selection_candidate_()
@ -83,8 +83,8 @@ void highlighter::set_mouseover_hex(const map_location& hex)
real_map ensure_real_map;
mouseover_hex_ = hex;
//if we're right over a unit, just highlight all of this unit's actions
unit_map::iterator it = unit_map_.find(hex);
if(it != unit_map_.end()) {
unit_map::iterator it = get_unit_map().find(hex);
if(it != get_unit_map().end()) {
selection_candidate_ = it.get_shared_ptr();
if(resources::teams->at(it->side()-1).get_side_actions()->unit_has_actions(*it)) {
@ -350,5 +350,10 @@ void highlighter::unhighlight_visitor::visit(recall_ptr recall)
highlighter_.exclusive_display_hexes_.insert(recall->get_fake_unit()->get_location());
}
}
unit_map& highlighter::get_unit_map()
{
assert(resources::units);
return *resources::units;
}
} // end namespace wb

View file

@ -39,7 +39,7 @@ class highlighter
{
public:
highlighter(unit_map& unit_map, side_actions_ptr side_actions);
highlighter(side_actions_ptr side_actions);
virtual ~highlighter();
void set_mouseover_hex(const map_location& hex);
@ -60,6 +60,7 @@ public:
secondary_highlights_t get_secondary_highlights() { return secondary_highlights_; }
private:
unit_map& get_unit_map();
/** Unhighlight a given action (main or secondary). */
class unhighlight_visitor;
@ -76,8 +77,6 @@ private:
/** Redraw the given move action when needed. */
void last_action_redraw(move_ptr);
unit_map& unit_map_;
map_location mouseover_hex_;
std::set<map_location> exclusive_display_hexes_;
unit_ptr owner_unit_;

View file

@ -577,7 +577,7 @@ void manager::on_mouseover_change(const map_location& hex)
{
if (!highlighter_)
{
highlighter_.reset(new highlighter(*resources::units, viewer_actions()));
highlighter_.reset(new highlighter(viewer_actions()));
}
highlighter_->set_mouseover_hex(hex);
highlighter_->highlight();