Merge branch 'develop' of github.com:eressea/server into develop

This commit is contained in:
Enno Rehling 2016-02-10 13:41:09 +01:00
commit 3fd7a3c486
47 changed files with 465 additions and 166 deletions

View File

@ -59,11 +59,16 @@ local function write_emails(locales)
end end
end end
local function join_path(a, b)
if a then return a .. '/' .. b end
return b
end
local function write_addresses() local function write_addresses()
local file local file
local faction local faction
file = io.open(config.basepath .. "/adressen", "w") file = io.open(join_path(config.basepath, "adressen"), "w")
for faction in factions() do for faction in factions() do
-- print(faction.id .. " - " .. faction.locale) -- print(faction.id .. " - " .. faction.locale)
file:write(tostring(faction) .. ":" .. faction.email .. ":" .. faction.info .. "\n") file:write(tostring(faction) .. ":" .. faction.email .. ":" .. faction.info .. "\n")
@ -76,7 +81,7 @@ local function write_aliases()
local file local file
local faction local faction
file = io.open(config.basepath .. "/aliases", "w") file = io.open(join_path(config.basepath, "aliases"), "w")
for faction in factions() do for faction in factions() do
local unit local unit
if faction.email ~= "" then if faction.email ~= "" then
@ -90,10 +95,23 @@ local function write_aliases()
file:close() file:close()
end end
local function write_htpasswd()
local out = io.open(join_path(config.basepath, "htpasswd"), "w")
if out then
for f in factions() do
if f.password then
out:write(itoa36(f.id) .. ":" .. f.password .. "\n")
end
end
out:close()
end
end
local function write_files(locales) local function write_files(locales)
write_passwords() write_htpasswd()
write_reports() -- write_passwords()
write_summary() write_reports()
write_summary()
end end
local function write_scores() local function write_scores()

View File

@ -1,6 +1,7 @@
PROJECT(attributes C) PROJECT(attributes C)
SET(_TEST_FILES SET(_TEST_FILES
stealth.test.c stealth.test.c
key.test.c
otherfaction.test.c otherfaction.test.c
) )

View File

@ -125,6 +125,7 @@ void register_attributes(void)
at_register(&at_raceprefix); at_register(&at_raceprefix);
at_register(&at_iceberg); at_register(&at_iceberg);
at_register(&at_key); at_register(&at_key);
at_register(&at_keys);
at_register(&at_follow); at_register(&at_follow);
at_register(&at_targetregion); at_register(&at_targetregion);
at_register(&at_orcification); at_register(&at_orcification);

View File

@ -30,6 +30,7 @@ attrib_type at_iceberg = {
NULL, NULL,
a_writeint, a_writeint,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -22,6 +22,69 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <kernel/save.h> #include <kernel/save.h>
#include <util/attrib.h> #include <util/attrib.h>
#include <storage.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static void a_writekeys(const attrib *a, const void *o, storage *store) {
int i, *keys = (int *)a->data.v;
for (i = 0; i <= keys[0]; ++i) {
WRITE_INT(store, keys[i]);
}
}
static int a_readkeys(attrib * a, void *owner, struct storage *store) {
int i, *p = 0;
READ_INT(store, &i);
assert(i < 4096 && i>0);
a->data.v = p = malloc(sizeof(int)*(i + 1));
*p++ = i;
while (i--) {
READ_INT(store, p++);
}
return AT_READ_OK;
}
static int a_readkey(attrib *a, void *owner, struct storage *store) {
int res = a_readint(a, owner, store);
return (res != AT_READ_FAIL) ? AT_READ_DEPR : res;
}
attrib_type at_keys = {
"keys",
NULL,
NULL,
NULL,
a_writekeys,
a_readkeys,
NULL
};
void a_upgradekeys(attrib **alist, attrib *abegin) {
int n = 0, *keys = 0;
int i = 0, val[4];
attrib *a, *ak = a_find(*alist, &at_keys);
if (!ak) {
ak = a_add(alist, a_new(&at_keys));
keys = (int *)ak->data.v;
n = keys ? keys[0] : 0;
}
for (a = abegin; a && a->type == abegin->type; a = a->next) {
val[i++] = a->data.i;
if (i == 4) {
keys = realloc(keys, sizeof(int) * (n + i + 1));
memcpy(keys + n + 1, val, sizeof(int)*i);
n += i;
}
}
if (i > 0) {
keys = realloc(keys, sizeof(int) * (n + i + 1));
memcpy(keys + n + 1, val, sizeof(int)*i);
}
keys[0] = n + i;
}
attrib_type at_key = { attrib_type at_key = {
"key", "key",
@ -29,29 +92,61 @@ attrib_type at_key = {
NULL, NULL,
NULL, NULL,
a_writeint, a_writeint,
a_readint, a_readkey,
a_upgradekeys
}; };
attrib *add_key(attrib ** alist, int key) void key_set(attrib ** alist, int key)
{ {
attrib *a = find_key(*alist, key); int *keys, n = 1;
if (a == NULL) attrib *a;
a = a_add(alist, make_key(key)); assert(key != 0);
return a; a = a_find(*alist, &at_keys);
} if (!a) {
a = a_add(alist, a_new(&at_keys));
attrib *make_key(int key)
{
attrib *a = a_new(&at_key);
a->data.i = key;
return a;
}
attrib *find_key(attrib * alist, int key)
{
attrib *a = a_find(alist, &at_key);
while (a && a->type == &at_key && a->data.i != key) {
a = a->next;
} }
return (a && a->type == &at_key) ? a : NULL; keys = (int *)a->data.v;
if (keys) {
n = keys[0] + 1;
}
keys = realloc(keys, sizeof(int) *(n + 1));
// TODO: does insertion sort pay off here?
keys[0] = n;
keys[n] = key;
a->data.v = keys;
}
void key_unset(attrib ** alist, int key)
{
attrib *a;
assert(key != 0);
a = a_find(*alist, &at_keys);
if (a) {
int i, *keys = (int *)a->data.v;
if (keys) {
for (i = 1; i <= keys[0]; ++i) {
if (keys[i] == key) {
keys[i] = keys[keys[0]];
keys[0]--;
}
}
}
}
}
bool key_get(attrib *alist, int key) {
attrib *a;
assert(key != 0);
a = a_find(alist, &at_keys);
if (a) {
int i, *keys = (int *)a->data.v;
if (keys) {
for (i = 1; i <= keys[0]; ++i) {
if (keys[i] == key) {
return true;
}
}
}
}
return false;
} }

View File

@ -21,12 +21,14 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
struct attrib;
struct attrib_type;
extern struct attrib_type at_key; extern struct attrib_type at_key;
extern struct attrib_type at_keys;
struct attrib *make_key(int key); void key_set(struct attrib **alist, int key);
struct attrib *find_key(struct attrib *alist, int key); void key_unset(struct attrib **alist, int key);
struct attrib *add_key(struct attrib **alist, int key); bool key_get(struct attrib *alist, int key);
#ifdef __cplusplus #ifdef __cplusplus
} }

27
src/attributes/key.test.c Normal file
View File

@ -0,0 +1,27 @@
#include <platform.h>
#include "key.h"
#include <util/attrib.h>
#include <CuTest.h>
static void test_get_set_keys(CuTest *tc) {
attrib *a = 0;
key_set(&a, 42);
key_set(&a, 43);
key_set(&a, 44);
CuAssertTrue(tc, key_get(a, 42));
CuAssertTrue(tc, key_get(a, 43));
CuAssertTrue(tc, key_get(a, 44));
key_unset(&a, 42);
CuAssertTrue(tc, !key_get(a, 42));
CuAssertTrue(tc, key_get(a, 43));
CuAssertTrue(tc, key_get(a, 44));
a_removeall(&a, NULL);
}
CuSuite *get_key_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_get_set_keys);
return suite;
}

View File

@ -28,6 +28,7 @@ attrib_type at_matmod = {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
ATF_PRESERVE ATF_PRESERVE
}; };

View File

@ -28,7 +28,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
attrib_type at_orcification = { attrib_type at_orcification = {
"orcification", NULL, NULL, NULL, a_writeint, a_readint, ATF_UNIQUE "orcification", NULL, NULL, NULL, a_writeint, a_readint, NULL, ATF_UNIQUE
}; };
attrib *make_orcification(int orcification) attrib *make_orcification(int orcification)

View File

@ -53,7 +53,7 @@ int read_of(struct attrib *a, void *owner, struct storage *store)
} }
attrib_type at_otherfaction = { attrib_type at_otherfaction = {
"otherfaction", NULL, NULL, NULL, write_of, read_of, ATF_UNIQUE "otherfaction", NULL, NULL, NULL, write_of, read_of, NULL, ATF_UNIQUE
}; };
struct faction *get_otherfaction(const struct attrib *a) struct faction *get_otherfaction(const struct attrib *a)

View File

@ -28,7 +28,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <stdlib.h> #include <stdlib.h>
attrib_type at_raceprefix = { attrib_type at_raceprefix = {
"raceprefix", NULL, a_finalizestring, NULL, a_writestring, a_readstring, "raceprefix", NULL, a_finalizestring, NULL, a_writestring, a_readstring, NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -49,6 +49,7 @@ attrib_type at_reduceproduction = {
age_reduceproduction, age_reduceproduction,
a_writeshorts, a_writeshorts,
a_readshorts, a_readshorts,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -52,6 +52,7 @@ attrib_type at_targetregion = {
NULL, NULL,
write_targetregion, write_targetregion,
read_targetregion, read_targetregion,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -542,11 +542,9 @@ static int tolua_region_getkey(lua_State * L)
{ {
region *self = (region *)tolua_tousertype(L, 1, 0); region *self = (region *)tolua_tousertype(L, 1, 0);
const char *name = tolua_tostring(L, 2, 0); const char *name = tolua_tostring(L, 2, 0);
int flag = atoi36(name); int flag = atoi36(name);
attrib *a = find_key(self->attribs, flag);
lua_pushboolean(L, a != NULL);
lua_pushboolean(L, key_get(self->attribs, flag));
return 1; return 1;
} }
@ -555,14 +553,13 @@ static int tolua_region_setkey(lua_State * L)
region *self = (region *)tolua_tousertype(L, 1, 0); region *self = (region *)tolua_tousertype(L, 1, 0);
const char *name = tolua_tostring(L, 2, 0); const char *name = tolua_tostring(L, 2, 0);
int value = tolua_toboolean(L, 3, 0); int value = tolua_toboolean(L, 3, 0);
int flag = atoi36(name); int flag = atoi36(name);
attrib *a = find_key(self->attribs, flag);
if (a == NULL && value) { if (value) {
add_key(&self->attribs, flag); key_set(&self->attribs, flag);
} }
else if (a != NULL && !value) { else {
a_remove(&self->attribs, a); key_unset(&self->attribs, flag);
} }
return 0; return 0;
} }

View File

@ -807,8 +807,7 @@ static int tolua_unit_get_flag(lua_State * L)
unit *self = (unit *)tolua_tousertype(L, 1, 0); unit *self = (unit *)tolua_tousertype(L, 1, 0);
const char *name = tolua_tostring(L, 2, 0); const char *name = tolua_tostring(L, 2, 0);
int flag = atoi36(name); int flag = atoi36(name);
attrib *a = find_key(self->attribs, flag); lua_pushboolean(L, key_get(self->attribs, flag));
lua_pushboolean(L, (a != NULL));
return 1; return 1;
} }
@ -818,12 +817,11 @@ static int tolua_unit_set_flag(lua_State * L)
const char *name = tolua_tostring(L, 2, 0); const char *name = tolua_tostring(L, 2, 0);
int value = (int)tolua_tonumber(L, 3, 0); int value = (int)tolua_tonumber(L, 3, 0);
int flag = atoi36(name); int flag = atoi36(name);
attrib *a = find_key(self->attribs, flag); if (value) {
if (a == NULL && value) { key_set(&self->attribs, flag);
add_key(&self->attribs, flag);
} }
else if (a != NULL && !value) { else {
a_remove(&self->attribs, a); key_unset(&self->attribs, flag);
} }
return 0; return 0;
} }

View File

@ -96,7 +96,7 @@ TOLUA_PKG(game);
int log_lua_error(lua_State * L) int log_lua_error(lua_State * L)
{ {
const char *error = lua_tostring(L, -1); const char *error = lua_tostring(L, -1);
log_fatal("LUA call failed.\n%s\n", error); log_fatal("Lua call failed.\n%s\n", error);
lua_pop(L, 1); lua_pop(L, 1);
return 1; return 1;
} }
@ -186,8 +186,8 @@ static int tolua_getkey(lua_State * L)
{ {
const char *name = tolua_tostring(L, 1, 0); const char *name = tolua_tostring(L, 1, 0);
int flag = atoi36(name); int flag = atoi36(name);
attrib *a = find_key(global.attribs, flag);
lua_pushboolean(L, a != NULL); lua_pushboolean(L, key_get(global.attribs, flag));
return 1; return 1;
} }
@ -209,12 +209,11 @@ static int tolua_setkey(lua_State * L)
const char *name = tolua_tostring(L, 1, 0); const char *name = tolua_tostring(L, 1, 0);
int value = tolua_toboolean(L, 2, 0); int value = tolua_toboolean(L, 2, 0);
int flag = atoi36(name); int flag = atoi36(name);
attrib *a = find_key(global.attribs, flag); if (value) {
if (a == NULL && value) { key_set(&global.attribs, flag);
add_key(&global.attribs, flag);
} }
else if (a != NULL && !value) { else {
a_remove(&global.attribs, a); key_unset(&global.attribs, flag);
} }
return 0; return 0;
} }
@ -1184,6 +1183,7 @@ int eressea_run(lua_State *L, const char *luafile)
err = lua_pcall(L, 1, 1, -3); err = lua_pcall(L, 1, 1, -3);
if (err != 0) { if (err != 0) {
log_lua_error(L); log_lua_error(L);
assert(!"Lua syntax error? check log.");
} }
else { else {
if (lua_isnumber(L, -1)) { if (lua_isnumber(L, -1)) {

View File

@ -50,6 +50,7 @@ attrib_type at_chaoscount = {
DEFAULT_AGE, DEFAULT_AGE,
a_writeint, a_writeint,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -38,6 +38,7 @@ attrib_type at_guard = {
DEFAULT_AGE, DEFAULT_AGE,
a_writeint, a_writeint,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -480,7 +480,7 @@ int victorycondition(const alliance * al, const char *name)
for (qi = 0; flist; ql_advance(&flist, &qi, 1)) { for (qi = 0; flist; ql_advance(&flist, &qi, 1)) {
faction *f = (faction *)ql_get(flist, qi); faction *f = (faction *)ql_get(flist, qi);
if (find_key(f->attribs, atoi36("phnx"))) { if (key_get(f->attribs, atoi36("phnx"))) {
return 1; return 1;
} }
} }
@ -509,7 +509,7 @@ int victorycondition(const alliance * al, const char *name)
for (qi = 0; flist; ql_advance(&flist, &qi, 1)) { for (qi = 0; flist; ql_advance(&flist, &qi, 1)) {
faction *f = (faction *)ql_get(flist, qi); faction *f = (faction *)ql_get(flist, qi);
if (find_key(f->attribs, atoi36("pyra"))) { if (key_get(f->attribs, atoi36("pyra"))) {
return 1; return 1;
} }
} }

View File

@ -121,6 +121,7 @@ attrib_type at_npcfaction = {
NULL, NULL,
a_writeint, a_writeint,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -141,7 +141,7 @@ int buildingcapacity(const building * b)
} }
attrib_type at_building_generic_type = { attrib_type at_building_generic_type = {
"building_generic_type", NULL, NULL, NULL, a_writestring, a_readstring, "building_generic_type", NULL, NULL, NULL, a_writestring, a_readstring, NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -659,7 +659,7 @@ int read_borders(struct storage *store)
type->read(b, store); type->read(b, store);
if (global.data_version < NOBORDERATTRIBS_VERSION) { if (global.data_version < NOBORDERATTRIBS_VERSION) {
attrib *a = NULL; attrib *a = NULL;
int result = a_read(store, &a, b); int result = read_attribs(store, &a, b);
if (border_convert_cb) { if (border_convert_cb) {
border_convert_cb(b, a); border_convert_cb(b, a);
} }

View File

@ -285,6 +285,7 @@ attrib_type at_curse = {
curse_age, curse_age,
curse_write, curse_write,
curse_read, curse_read,
NULL,
ATF_CURSE ATF_CURSE
}; };

View File

@ -798,6 +798,7 @@ attrib_type at_maxmagicians = {
NULL, NULL,
a_writeint, a_writeint,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -119,7 +119,7 @@ write_group(const attrib * a, const void *owner, struct storage *store)
attrib_type at_group = { /* attribute for units assigned to a group */ attrib_type at_group = { /* attribute for units assigned to a group */
"grp", "grp",
DEFAULT_INIT, DEFAULT_INIT,
DEFAULT_FINALIZE, DEFAULT_AGE, write_group, read_group, ATF_UNIQUE }; DEFAULT_FINALIZE, DEFAULT_AGE, write_group, read_group, NULL, ATF_UNIQUE };
void free_group(group * g) void free_group(group * g)
{ {
@ -209,7 +209,7 @@ void write_groups(struct storage *store, const faction * f)
} }
} }
WRITE_INT(store, 0); WRITE_INT(store, 0);
a_write(store, g->attribs, g); write_attribs(store, g->attribs, g);
WRITE_SECTION(store); WRITE_SECTION(store);
} }
WRITE_INT(store, 0); WRITE_INT(store, 0);
@ -241,6 +241,6 @@ void read_groups(struct storage *store, faction * f)
if (!a->faction) if (!a->faction)
ur_add(fid, &a->faction, resolve_faction); ur_add(fid, &a->faction, resolve_faction);
} }
a_read(store, &g->attribs, g); read_attribs(store, &g->attribs, g);
} }
} }

View File

@ -926,14 +926,14 @@ struct order *ord)
"unit item region command", user, itype->rtype, user->region, ord)); "unit item region command", user, itype->rtype, user->region, ord));
return -1; return -1;
} }
if (!is_mage(user) || find_key(f->attribs, atoi36("mbst")) != NULL) { if (!is_mage(user) || key_get(f->attribs, atoi36("mbst"))) {
cmistake(user, user->thisorder, 214, MSG_EVENT); cmistake(user, user->thisorder, 214, MSG_EVENT);
return -1; return -1;
} }
use_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, use_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK,
user->number); user->number);
a_add(&f->attribs, make_key(atoi36("mbst"))); key_set(&f->attribs, atoi36("mbst"));
set_level(user, SK_MAGIC, 3); set_level(user, SK_MAGIC, 3);
ADDMSG(&user->faction->msgs, msg_message("use_item", ADDMSG(&user->faction->msgs, msg_message("use_item",

View File

@ -483,6 +483,7 @@ attrib_type at_horseluck = {
DEFAULT_AGE, DEFAULT_AGE,
NO_WRITE, NO_WRITE,
NO_READ, NO_READ,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -496,6 +497,7 @@ attrib_type at_peasantluck = {
DEFAULT_AGE, DEFAULT_AGE,
NO_WRITE, NO_WRITE,
NO_READ, NO_READ,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -509,6 +511,7 @@ attrib_type at_deathcount = {
DEFAULT_AGE, DEFAULT_AGE,
a_writeint, a_writeint,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -522,6 +525,7 @@ attrib_type at_woodcount = {
DEFAULT_AGE, DEFAULT_AGE,
NO_WRITE, NO_WRITE,
a_readint, a_readint,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -577,6 +577,24 @@ const struct locale *lang)
WRITE_STR(data->store, obuf); WRITE_STR(data->store, obuf);
} }
int read_attribs(storage *store, attrib **alist, void *owner) {
if (global.data_version < ATHASH_VERSION) {
return a_read_orig(store, alist, owner);
}
else {
return a_read(store, alist, owner);
}
}
void write_attribs(storage *store, attrib *alist, const void *owner)
{
#if RELEASE_VERSION < ATHASH_VERSION
a_write_orig(store, alist, owner);
#else
a_write(store, alist, owner);
#endif
}
unit *read_unit(struct gamedata *data) unit *read_unit(struct gamedata *data)
{ {
unit *u; unit *u;
@ -748,8 +766,7 @@ unit *read_unit(struct gamedata *data)
log_error("Einheit %s hat %u Personen, und %u Trefferpunkte\n", itoa36(u->no), u->number, u->hp); log_error("Einheit %s hat %u Personen, und %u Trefferpunkte\n", itoa36(u->no), u->number, u->hp);
u->hp = u->number; u->hp = u->number;
} }
read_attribs(data->store, &u->attribs, u);
a_read(data->store, &u->attribs, u);
return u; return u;
} }
@ -823,7 +840,7 @@ void write_unit(struct gamedata *data, const unit * u)
} }
WRITE_INT(data->store, u->hp); WRITE_INT(data->store, u->hp);
WRITE_SECTION(data->store); WRITE_SECTION(data->store);
a_write(data->store, u->attribs, u); write_attribs(data->store, u->attribs, u);
WRITE_SECTION(data->store); WRITE_SECTION(data->store);
} }
@ -982,7 +999,7 @@ static region *readregion(struct gamedata *data, int x, int y)
read_owner(data, &r->land->ownership); read_owner(data, &r->land->ownership);
} }
} }
a_read(data->store, &r->attribs, r); read_attribs(data->store, &r->attribs, r);
return r; return r;
} }
@ -1046,7 +1063,7 @@ void writeregion(struct gamedata *data, const region * r)
WRITE_SECTION(data->store); WRITE_SECTION(data->store);
#endif #endif
} }
a_write(data->store, r->attribs, r); write_attribs(data->store, r->attribs, r);
WRITE_SECTION(data->store); WRITE_SECTION(data->store);
} }
@ -1253,7 +1270,7 @@ faction *readfaction(struct gamedata * data)
} }
} }
a_read(data->store, &f->attribs, f); read_attribs(data->store, &f->attribs, f);
read_items(data->store, &f->items); read_items(data->store, &f->items);
for (;;) { for (;;) {
READ_TOK(data->store, name, sizeof(name)); READ_TOK(data->store, name, sizeof(name));
@ -1338,7 +1355,7 @@ void writefaction(struct gamedata *data, const faction * f)
WRITE_INT(data->store, f->magiegebiet); WRITE_INT(data->store, f->magiegebiet);
WRITE_INT(data->store, f->flags & FFL_SAVEMASK); WRITE_INT(data->store, f->flags & FFL_SAVEMASK);
a_write(data->store, f->attribs, f); write_attribs(data->store, f->attribs, f);
WRITE_SECTION(data->store); WRITE_SECTION(data->store);
write_items(data->store, f->items); write_items(data->store, f->items);
WRITE_SECTION(data->store); WRITE_SECTION(data->store);
@ -1451,7 +1468,7 @@ int readgame(const char *filename, bool backup)
else { else {
READ_STR(&store, NULL, 0); READ_STR(&store, NULL, 0);
} }
a_read(&store, &global.attribs, NULL); read_attribs(&store, &global.attribs, NULL);
READ_INT(&store, &turn); READ_INT(&store, &turn);
global.data_turn = turn; global.data_turn = turn;
log_debug(" - reading turn %d\n", turn); log_debug(" - reading turn %d\n", turn);
@ -1512,7 +1529,7 @@ int readgame(const char *filename, bool backup)
fno = read_faction_reference(&store); fno = read_faction_reference(&store);
} }
} }
a_read(&store, &pl->attribs, pl); read_attribs(&store, &pl->attribs, pl);
if (pl->id != 1094969858) { // Regatta if (pl->id != 1094969858) { // Regatta
addlist(&planes, pl); addlist(&planes, pl);
} }
@ -1580,7 +1597,7 @@ int readgame(const char *filename, bool backup)
READ_STR(&store, name, sizeof(name)); READ_STR(&store, name, sizeof(name));
b->type = bt_find(name); b->type = bt_find(name);
b->region = r; b->region = r;
a_read(&store, &b->attribs, b); read_attribs(&store, &b->attribs, b);
if (b->type == bt_lighthouse) { if (b->type == bt_lighthouse) {
r->flags |= RF_LIGHTHOUSE; r->flags |= RF_LIGHTHOUSE;
} }
@ -1627,7 +1644,7 @@ int readgame(const char *filename, bool backup)
if (sh->type->flags & SFL_NOCOAST) { if (sh->type->flags & SFL_NOCOAST) {
sh->coast = NODIRECTION; sh->coast = NODIRECTION;
} }
a_read(&store, &sh->attribs, sh); read_attribs(&store, &sh->attribs, sh);
} }
*shp = 0; *shp = 0;
@ -1777,7 +1794,7 @@ int writegame(const char *filename)
WRITE_INT(&store, game_id()); WRITE_INT(&store, game_id());
WRITE_SECTION(&store); WRITE_SECTION(&store);
a_write(&store, global.attribs, NULL); write_attribs(&store, global.attribs, NULL);
WRITE_SECTION(&store); WRITE_SECTION(&store);
WRITE_INT(&store, turn); WRITE_INT(&store, turn);
@ -1807,7 +1824,7 @@ int writegame(const char *filename)
w = w->next; w = w->next;
} }
write_faction_reference(NULL, &store); /* mark the end of the list */ write_faction_reference(NULL, &store); /* mark the end of the list */
a_write(&store, pl->attribs, pl); write_attribs(&store, pl->attribs, pl);
WRITE_SECTION(&store); WRITE_SECTION(&store);
} }
@ -1852,7 +1869,7 @@ int writegame(const char *filename)
WRITE_INT(&store, b->size); WRITE_INT(&store, b->size);
WRITE_TOK(&store, b->type->_name); WRITE_TOK(&store, b->type->_name);
WRITE_SECTION(&store); WRITE_SECTION(&store);
a_write(&store, b->attribs, b); write_attribs(&store, b->attribs, b);
WRITE_SECTION(&store); WRITE_SECTION(&store);
} }
@ -1870,7 +1887,7 @@ int writegame(const char *filename)
assert((sh->type->flags & SFL_NOCOAST) == 0 || sh->coast == NODIRECTION); assert((sh->type->flags & SFL_NOCOAST) == 0 || sh->coast == NODIRECTION);
WRITE_INT(&store, sh->coast); WRITE_INT(&store, sh->coast);
WRITE_SECTION(&store); WRITE_SECTION(&store);
a_write(&store, sh->attribs, sh); write_attribs(&store, sh->attribs, sh);
WRITE_SECTION(&store); WRITE_SECTION(&store);
} }

View File

@ -58,6 +58,9 @@ extern "C" {
void read_spellbook(struct spellbook **bookp, struct storage *store, int(*get_level)(const struct spell * sp, void *), void * cbdata); void read_spellbook(struct spellbook **bookp, struct storage *store, int(*get_level)(const struct spell * sp, void *), void * cbdata);
void write_spellbook(const struct spellbook *book, struct storage *store); void write_spellbook(const struct spellbook *book, struct storage *store);
void write_attribs(struct storage *store, struct attrib *alist, const void *owner);
int read_attribs(struct storage *store, struct attrib **alist, void *owner);
void write_unit(struct gamedata *data, const struct unit *u); void write_unit(struct gamedata *data, const struct unit *u);
struct unit *read_unit(struct gamedata *data); struct unit *read_unit(struct gamedata *data);

View File

@ -1,5 +1,7 @@
#include <platform.h> #include <platform.h>
#include <kernel/config.h> #include <kernel/config.h>
#include <util/attrib.h>
#include <attributes/key.h>
#include "save.h" #include "save.h"
#include "unit.h" #include "unit.h"
@ -61,9 +63,37 @@ static void test_readwrite_unit(CuTest * tc)
test_cleanup(); test_cleanup();
} }
static void test_readwrite_attrib(CuTest *tc) {
gamedata *data;
attrib *a = NULL;
const char *path = "attrib.dat";
test_cleanup();
global.data_version = RELEASE_VERSION; // FIXME: hack!
data = gamedata_open(path, "wb");
CuAssertPtrNotNull(tc, data);
key_set(&a, 41);
key_set(&a, 42);
write_attribs(data->store, a, NULL);
gamedata_close(data);
a_removeall(&a, NULL);
CuAssertPtrEquals(tc, 0, a);
data = gamedata_open(path, "rb");
CuAssertPtrNotNull(tc, data);
read_attribs(data->store, &a, NULL);
gamedata_close(data);
CuAssertTrue(tc, key_get(a, 41));
CuAssertTrue(tc, key_get(a, 42));
a_removeall(&a, NULL);
CuAssertIntEquals(tc, 0, remove(path));
test_cleanup();
}
CuSuite *get_save_suite(void) CuSuite *get_save_suite(void)
{ {
CuSuite *suite = CuSuiteNew(); CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_readwrite_attrib);
SUITE_ADD_TEST(suite, test_readwrite_data); SUITE_ADD_TEST(suite, test_readwrite_data);
SUITE_ADD_TEST(suite, test_readwrite_unit); SUITE_ADD_TEST(suite, test_readwrite_unit);
return suite; return suite;

View File

@ -59,6 +59,7 @@ attrib_type at_skillmod = {
NULL, NULL,
NULL, /* can't write function pointers */ NULL, /* can't write function pointers */
NULL, /* can't read function pointers */ NULL, /* can't read function pointers */
NULL,
ATF_PRESERVE ATF_PRESERVE
}; };

View File

@ -34,7 +34,8 @@
#define OWNER_3_VERSION 349 /* regions store last owner, not last alliance */ #define OWNER_3_VERSION 349 /* regions store last owner, not last alliance */
#define ATTRIBOWNER_VERSION 350 /* all attrib_type functions know who owns the attribute */ #define ATTRIBOWNER_VERSION 350 /* all attrib_type functions know who owns the attribute */
#define CRYPT_VERSION 351 /* passwords are encrypted */ #define CRYPT_VERSION 351 /* passwords are encrypted */
#define RELEASE_VERSION CRYPT_VERSION /* current datafile */ #define ATHASH_VERSION 352 /* attribute-type hash, not name */
#define RELEASE_VERSION ATHASH_VERSION /* current datafile */
#define MIN_VERSION INTPAK_VERSION /* minimal datafile we support */ #define MIN_VERSION INTPAK_VERSION /* minimal datafile we support */
#define MAX_VERSION RELEASE_VERSION /* change this if we can need to read the future datafile, and we can do so */ #define MAX_VERSION RELEASE_VERSION /* change this if we can need to read the future datafile, and we can do so */

View File

@ -486,6 +486,7 @@ attrib_type at_germs = {
DEFAULT_AGE, DEFAULT_AGE,
a_writeshorts, a_writeshorts,
a_readshorts, a_readshorts,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -2749,7 +2750,7 @@ void sinkships(struct region * r)
static attrib_type at_number = { static attrib_type at_number = {
"faction_renum", "faction_renum",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -335,6 +335,7 @@ attrib_type at_mage = {
NULL, NULL,
write_mage, write_mage,
read_mage, read_mage,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -2416,6 +2417,7 @@ attrib_type at_familiarmage = {
age_unit, age_unit,
a_write_unit, a_write_unit,
read_magician, read_magician,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -2426,6 +2428,7 @@ attrib_type at_familiar = {
age_unit, age_unit,
a_write_unit, a_write_unit,
read_familiar, read_familiar,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -2436,6 +2439,7 @@ attrib_type at_clonemage = {
age_unit, age_unit,
a_write_unit, a_write_unit,
read_magician, read_magician,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };
@ -2446,6 +2450,7 @@ attrib_type at_clone = {
age_unit, age_unit,
a_write_unit, a_write_unit,
read_clone, read_clone,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -63,7 +63,7 @@ static void free_market(attrib * a)
attrib_type at_market = { attrib_type at_market = {
"script", "script",
NULL, free_market, NULL, NULL, free_market, NULL,
NULL, NULL, ATF_UNIQUE NULL, NULL, NULL, ATF_UNIQUE
}; };
static int rc_luxury_trade(const struct race *rc) static int rc_luxury_trade(const struct race *rc)

View File

@ -194,7 +194,7 @@ void warden_add_give(unit * src, unit * u, const item_type * itype, int n)
void create_museum(void) void create_museum(void)
{ {
#if 0 /* TODO: move this to LUA. It should be possible. */ #if 0 /* TODO: move this to Lua. It should be possible. */
unsigned int museum_id = hashstring("museum"); unsigned int museum_id = hashstring("museum");
plane *museum = getplanebyid(museum_id); plane *museum = getplanebyid(museum_id);
region *r; region *r;

View File

@ -96,6 +96,7 @@ static attrib_type at_alp = {
alp_verify, alp_verify,
alp_write, alp_write,
alp_read, alp_read,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -112,6 +112,7 @@ static attrib_type at_cursewall = {
curse_age, curse_age,
cw_write, cw_write,
cw_read, cw_read,
NULL,
ATF_CURSE ATF_CURSE
}; };

View File

@ -157,7 +157,7 @@ static void done_learning(struct attrib *a)
const attrib_type at_learning = { const attrib_type at_learning = {
"learning", "learning",
init_learning, done_learning, NULL, NULL, NULL, init_learning, done_learning, NULL, NULL, NULL, NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -118,6 +118,7 @@ int RunAllTests(int argc, char *argv[])
ADD_SUITE(monsters); ADD_SUITE(monsters);
ADD_SUITE(move); ADD_SUITE(move);
ADD_SUITE(piracy); ADD_SUITE(piracy);
ADD_SUITE(key);
ADD_SUITE(stealth); ADD_SUITE(stealth);
ADD_SUITE(otherfaction); ADD_SUITE(otherfaction);
ADD_SUITE(upkeep); ADD_SUITE(upkeep);

View File

@ -209,28 +209,37 @@ int a_remove(attrib ** pa, attrib * a)
void a_removeall(attrib ** pa, const attrib_type * at) void a_removeall(attrib ** pa, const attrib_type * at)
{ {
attrib **pnexttype = pa; attrib **pnexttype = pa;
attrib **pnext = NULL;
while (*pnexttype) { if (!at) {
attrib *next = *pnexttype; while (*pnexttype) {
if (next->type == at) attrib *a = *pnexttype;
break; *pnexttype = a->next;
pnexttype = &next->nexttype; a_free(a);
pnext = &next->next;
}
if (*pnexttype && (*pnexttype)->type == at) {
attrib *a = *pnexttype;
*pnexttype = a->nexttype;
if (pnext) {
while (*pnext && (*pnext)->type != at)
pnext = &(*pnext)->next;
*pnext = a->nexttype;
} }
while (a && a->type == at) { }
attrib *ra = a; else {
a = a->next; attrib **pnext = NULL;
a_free(ra); while (*pnexttype) {
attrib *a = *pnexttype;
if (a->type == at)
break;
pnexttype = &a->nexttype;
pnext = &a->next;
}
if (*pnexttype && (*pnexttype)->type == at) {
attrib *a = *pnexttype;
*pnexttype = a->nexttype;
if (pnext) {
while (*pnext && (*pnext)->type != at)
pnext = &(*pnext)->next;
*pnext = a->nexttype;
}
while (a && a->type == at) {
attrib *ra = a;
a = a->next;
a_free(ra);
}
} }
} }
} }
@ -267,15 +276,92 @@ int a_age(attrib ** p, void *owner)
static critbit_tree cb_deprecated = { 0 }; static critbit_tree cb_deprecated = { 0 };
typedef struct deprecated_s {
unsigned int hash;
int(*reader)(attrib *, void *, struct storage *);
} deprecated_t;
void at_deprecate(const char * name, int(*reader)(attrib *, void *, struct storage *)) void at_deprecate(const char * name, int(*reader)(attrib *, void *, struct storage *))
{ {
char buffer[64]; deprecated_t value;
size_t len = strlen(name);
len = cb_new_kv(name, len, &reader, sizeof(reader), buffer); value.hash = __at_hashkey(name);
cb_insert(&cb_deprecated, buffer, len); value.reader = reader;
cb_insert(&cb_deprecated, &value, sizeof(value));
} }
int a_read(struct storage *store, attrib ** attribs, void *owner) static int a_read_i(struct storage *store, attrib ** attribs, void *owner, unsigned int key) {
int retval = AT_READ_OK;
int(*reader)(attrib *, void *, struct storage *) = 0;
attrib_type *at = at_find(key);
attrib * na = 0;
if (at) {
reader = at->read;
na = a_new(at);
}
else {
void *match;
if (cb_find_prefix(&cb_deprecated, &key, sizeof(key), &match, 1, 0)>0) {
deprecated_t *value = (deprecated_t *)match;
reader = value->reader;
}
else {
log_error("unknown attribute hash: %u\n", key);
assert(at || !"attribute not registered");
}
}
if (reader) {
int ret = reader(na, owner, store);
if (na) {
switch (ret) {
case AT_READ_DEPR:
case AT_READ_OK:
a_add(attribs, na);
retval = ret;
break;
case AT_READ_FAIL:
a_free(na);
break;
default:
assert(!"invalid return value");
break;
}
}
}
else {
assert(!"error: no registered callback can read attribute");
}
return retval;
}
int a_read(struct storage *store, attrib ** attribs, void *owner) {
int key, retval = AT_READ_OK;
key = -1;
READ_INT(store, &key);
while (key > 0) {
int ret = a_read_i(store, attribs, owner, key);
if (ret == AT_READ_DEPR) {
retval = AT_READ_DEPR;
}
READ_INT(store, &key);
}
if (retval == AT_READ_DEPR) {
/* handle deprecated attributes */
attrib *a = *attribs;
while (a) {
if (a->type->upgrade) {
a->type->upgrade(attribs, a);
}
a = a->nexttype;
}
}
return AT_READ_OK;
}
int a_read_orig(struct storage *store, attrib ** attribs, void *owner)
{ {
int key, retval = AT_READ_OK; int key, retval = AT_READ_OK;
char zText[128]; char zText[128];
@ -283,52 +369,14 @@ int a_read(struct storage *store, attrib ** attribs, void *owner)
zText[0] = 0; zText[0] = 0;
key = -1; key = -1;
READ_TOK(store, zText, sizeof(zText)); READ_TOK(store, zText, sizeof(zText));
if (strcmp(zText, "end") == 0) if (strcmp(zText, "end") == 0) {
return retval; return retval;
else }
else {
key = __at_hashkey(zText); key = __at_hashkey(zText);
}
while (key != -1) { while (key > 0) {
int(*reader)(attrib *, void *, struct storage *) = 0; retval = a_read_i(store, attribs, owner, key);
attrib_type *at = at_find(key);
attrib * na = 0;
if (at) {
reader = at->read;
na = a_new(at);
}
else {
void * kv = 0;
cb_find_prefix(&cb_deprecated, zText, strlen(zText) + 1, &kv, 1, 0);
if (kv) {
cb_get_kv(kv, &reader, sizeof(reader));
}
else {
fprintf(stderr, "attribute hash: %d (%s)\n", key, zText);
assert(at || !"attribute not registered");
}
}
if (reader) {
int i = reader(na, owner, store);
if (na) {
switch (i) {
case AT_READ_OK:
a_add(attribs, na);
break;
case AT_READ_FAIL:
retval = AT_READ_FAIL;
a_free(na);
break;
default:
assert(!"invalid return value");
break;
}
}
}
else {
assert(!"error: no registered callback can read attribute");
}
READ_TOK(store, zText, sizeof(zText)); READ_TOK(store, zText, sizeof(zText));
if (!strcmp(zText, "end")) if (!strcmp(zText, "end"))
break; break;
@ -337,7 +385,24 @@ int a_read(struct storage *store, attrib ** attribs, void *owner)
return retval; return retval;
} }
void a_write(struct storage *store, const attrib * attribs, const void *owner) void a_write(struct storage *store, const attrib * attribs, const void *owner) {
const attrib *na = attribs;
while (na) {
if (na->type->write) {
assert(na->type->hashkey || !"attribute not registered");
WRITE_INT(store, na->type->hashkey);
na->type->write(na, owner, store);
na = na->next;
}
else {
na = na->nexttype;
}
}
WRITE_INT(store, 0);
}
void a_write_orig(struct storage *store, const attrib * attribs, const void *owner)
{ {
const attrib *na = attribs; const attrib *na = attribs;

View File

@ -56,6 +56,7 @@ extern "C" {
/* age returns 0 if the attribute needs to be removed, !=0 otherwise */ /* age returns 0 if the attribute needs to be removed, !=0 otherwise */
void(*write) (const struct attrib *, const void *owner, struct storage *); void(*write) (const struct attrib *, const void *owner, struct storage *);
int(*read) (struct attrib *, void *owner, struct storage *); /* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ int(*read) (struct attrib *, void *owner, struct storage *); /* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */
void(*upgrade) (struct attrib **alist, struct attrib *a);
unsigned int flags; unsigned int flags;
/* ---- internal data, do not modify: ---- */ /* ---- internal data, do not modify: ---- */
struct attrib_type *nexthash; struct attrib_type *nexthash;
@ -72,11 +73,13 @@ extern "C" {
extern int a_remove(attrib ** pa, attrib * at); extern int a_remove(attrib ** pa, attrib * at);
extern void a_removeall(attrib ** a, const attrib_type * at); extern void a_removeall(attrib ** a, const attrib_type * at);
extern attrib *a_new(const attrib_type * at); extern attrib *a_new(const attrib_type * at);
int a_age(attrib ** attribs, void *owner);
extern int a_age(attrib ** attribs, void *owner); int a_read_orig(struct storage *store, attrib ** attribs, void *owner);
extern int a_read(struct storage *store, attrib ** attribs, void *owner); void a_write_orig(struct storage *store, const attrib * attribs, const void *owner);
extern void a_write(struct storage *store, const attrib * attribs,
const void *owner); int a_read(struct storage *store, attrib ** attribs, void *owner);
void a_write(struct storage *store, const attrib * attribs, const void *owner);
void free_attribs(void); void free_attribs(void);
@ -88,6 +91,7 @@ extern "C" {
#define AT_READ_OK 0 #define AT_READ_OK 0
#define AT_READ_FAIL -1 #define AT_READ_FAIL -1
#define AT_READ_DEPR 1 /* a deprecated attribute was read, should run a_upgrade */
#define AT_AGE_REMOVE 0 /* remove the attribute after calling age() */ #define AT_AGE_REMOVE 0 /* remove the attribute after calling age() */
#define AT_AGE_KEEP 1 /* keep the attribute for another turn */ #define AT_AGE_KEEP 1 /* keep the attribute for another turn */

View File

@ -51,6 +51,23 @@ static void test_attrib_remove_self(CuTest * tc) {
CuAssertPtrEquals(tc, a, alist); CuAssertPtrEquals(tc, a, alist);
} }
static void test_attrib_removeall(CuTest * tc) {
const attrib_type at_foo = { "foo" };
const attrib_type at_bar = { "bar" };
attrib *alist = 0, *a;
a_add(&alist, a_new(&at_foo));
a = a_add(&alist, a_new(&at_bar));
a_add(&alist, a_new(&at_foo));
a_removeall(&alist, &at_foo);
CuAssertPtrEquals(tc, a, alist);
CuAssertPtrEquals(tc, 0, alist->next);
a_add(&alist, a_new(&at_bar));
a_add(&alist, a_new(&at_foo));
a_removeall(&alist, NULL);
CuAssertPtrEquals(tc, 0, alist);
}
static void test_attrib_remove(CuTest * tc) static void test_attrib_remove(CuTest * tc)
{ {
attrib_type at_foo = { "foo" }; attrib_type at_foo = { "foo" };
@ -98,6 +115,7 @@ CuSuite *get_attrib_suite(void)
SUITE_ADD_TEST(suite, test_attrib_new); SUITE_ADD_TEST(suite, test_attrib_new);
SUITE_ADD_TEST(suite, test_attrib_add); SUITE_ADD_TEST(suite, test_attrib_add);
SUITE_ADD_TEST(suite, test_attrib_remove); SUITE_ADD_TEST(suite, test_attrib_remove);
SUITE_ADD_TEST(suite, test_attrib_removeall);
SUITE_ADD_TEST(suite, test_attrib_remove_self); SUITE_ADD_TEST(suite, test_attrib_remove_self);
SUITE_ADD_TEST(suite, test_attrib_nexttype); SUITE_ADD_TEST(suite, test_attrib_nexttype);
return suite; return suite;

View File

@ -134,6 +134,7 @@ static attrib_type at_wormhole = {
wormhole_age, wormhole_age,
wormhole_write, wormhole_write,
wormhole_read, wormhole_read,
NULL,
ATF_UNIQUE ATF_UNIQUE
}; };

View File

@ -1,4 +1,4 @@
NEWFILES="data/185.dat datum parteien parteien.full passwd score turn" NEWFILES="data/185.dat datum parteien parteien.full htpasswd score turn"
cleanup () { cleanup () {
rm -rf reports $NEWFILES rm -rf reports $NEWFILES
} }
@ -19,6 +19,7 @@ expr=$2
expect=$3 expect=$3
count=`grep -cE $expr $file` count=`grep -cE $expr $file`
[ $count -eq $expect ] || quit 1 "expected $expect counts of $expr in $file, got $count" [ $count -eq $expect ] || quit 1 "expected $expect counts of $expr in $file, got $count"
echo "PASS: $expr is $expect"
} }
ROOT=`pwd` ROOT=`pwd`
@ -34,10 +35,11 @@ VALGRIND=`which valgrind`
SERVER=../Debug/eressea/eressea SERVER=../Debug/eressea/eressea
if [ -n "$VALGRIND" ]; then if [ -n "$VALGRIND" ]; then
SUPP=../share/ubuntu-12_04.supp SUPP=../share/ubuntu-12_04.supp
SERVER="$VALGRIND --suppressions=$SUPP --error-exitcode=1 --leak-check=no $SERVER" SERVER="$VALGRIND --track-origins=yes --gen-suppressions=all --suppressions=$SUPP --error-exitcode=1 --leak-check=no $SERVER"
fi fi
echo "running $SERVER" echo "running $SERVER"
$SERVER -t 184 ../scripts/run-turn.lua $SERVER -t 184 ../scripts/run-turn.lua
echo "integration tests"
[ -d reports ] || quit 4 "no reports directory created" [ -d reports ] || quit 4 "no reports directory created"
CRFILE=185-zvto.cr CRFILE=185-zvto.cr
for file in $NEWFILES reports/$CRFILE ; do for file in $NEWFILES reports/$CRFILE ; do

View File

@ -1 +0,0 @@


View File

@ -1,3 +0,0 @@
# alliance:factions:persons:score
1248287:1:0:0
1490214:1:0:2105