diff --git a/python/CFReputation/__init__.py b/python/CFReputation/__init__.py new file mode 100644 index 000000000..39b0c6e01 --- /dev/null +++ b/python/CFReputation/__init__.py @@ -0,0 +1,66 @@ +import os.path +import sqlite3 + +import Crossfire + +_dict_name = 'CFReputationConnection' + +def _init_schema(con, version, *init_files): + con.execute("PRAGMA journal_mode=WAL;"); + con.execute("PRAGMA synchronous=NORMAL;"); + con.execute("CREATE TABLE IF NOT EXISTS schema(version INT);"); + result = con.execute("SELECT version FROM schema").fetchall(); + curr = len(result) + if curr < version: + Crossfire.Log(Crossfire.LogInfo, + "Initializing factions schema %d->%d" % (curr, version)) + for f in init_files: + with open(f) as initfile: + con.executescript(initfile.read()) + con.commit() + +def _get_sql_path(f): + return os.path.join(Crossfire.DataDirectory(), Crossfire.MapDirectory(), + "python/CFReputation/sql", f) + +def _init_db(): + init_files = map(_get_sql_path, ["init.sql", "gods.sql"]) + db_path = os.path.join(Crossfire.LocalDirectory(), "factions.db") + con = sqlite3.connect(':memory:') + _init_schema(con, 1, *init_files) + Crossfire.GetSharedDictionary()[_dict_name] = con + +def _get_db(): + d = Crossfire.GetSharedDictionary() + if _dict_name not in d: + _init_db() + return d[_dict_name] + +def reputation(player): + con = _get_db() + query=""" +SELECT faction, CAST(ROUND(reputation*100) as integer) as rep +FROM reputations +WHERE name=? AND ABS(rep) > 0; + """ + result = con.execute(query, (player,)).fetchall() + return result + +def record_kill(race, region, player, fraction=0.0005, limit=0.4): + con = _get_db() + query = """ +WITH updates AS ( + SELECT faction, -attitude*? AS change + FROM regions + NATURAL JOIN relations + WHERE race=? AND (region=? OR region='ALL')) +REPLACE INTO reputations +SELECT ? AS player, updates.faction, + COALESCE(reputation, 0) + change AS new_rep +FROM updates +LEFT JOIN reputations + ON updates.faction=reputations.faction AND player=reputations.name +WHERE ABS(new_rep) <= ?; + """ + con.execute(query, (fraction, race, region, player, limit)) + con.commit() diff --git a/python/CFReputation/sql/gods.sql b/python/CFReputation/sql/gods.sql new file mode 100644 index 000000000..0263dc327 --- /dev/null +++ b/python/CFReputation/sql/gods.sql @@ -0,0 +1,51 @@ +insert into regions values ('Valriel', 'ALL', 0.25); +insert into relations values ('Valriel', 'angel', 1); +insert into relations values ('Valriel', 'demon', -1); +insert into regions values ('Gorokh', 'ALL', 0.25); +insert into relations values ('Gorokh', 'demon', 1); +insert into relations values ('Gorokh', 'angel', -1); +insert into regions values ('Devourers', 'ALL', 0.25); +insert into relations values ('Devourers', 'undead', 1); +insert into relations values ('Devourers', 'none', -1); +insert into regions values ('Sorig', 'ALL', 0.25); +insert into relations values ('Sorig', 'air_elemental', 1); +insert into relations values ('Sorig', 'none', -1); +insert into regions values ('Ruggilli', 'ALL', 0.25); +insert into relations values ('Ruggilli', 'consuming_fire_creatures', 1); +insert into relations values ('Ruggilli', 'chaotic_water_creatures', -1); +insert into regions values ('Ixalovh', 'ALL', 0.25); +insert into relations values ('Ixalovh', 'chaotic_water_creatures', 1); +insert into relations values ('Ixalovh', 'consuming_fire_creatures', -1); +insert into regions values ('Gaea', 'ALL', 0.25); +insert into relations values ('Gaea', 'animal', 1); +insert into relations values ('Gaea', 'bird', 1); +insert into relations values ('Gaea', 'slime', 1); +insert into relations values ('Gaea', 'insect', 1); +insert into relations values ('Gaea', 'reptile', 1); +insert into relations values ('Gaea', 'water_elemental', 1); +insert into relations values ('Gaea', 'earth_elemental', 1); +insert into relations values ('Gaea', 'air_elemental', 1); +insert into relations values ('Gaea', 'fire_elemental', 1); +insert into relations values ('Gaea', 'undead', -1); +insert into relations values ('Gaea', 'unnatural', -1); +insert into regions values ('Valkyrie', 'ALL', 0.25); +insert into relations values ('Valkyrie', 'human', 1); +insert into relations values ('Valkyrie', 'troll', 1); +insert into relations values ('Valkyrie', 'unnatural', -1); +insert into relations values ('Valkyrie', 'angel', -1); +insert into relations values ('Valkyrie', 'demon', -1); +insert into relations values ('Valkyrie', 'undead', -1); +insert into regions values ('Mostrai', 'ALL', 0.25); +insert into relations values ('Mostrai', 'dwarf', 1); +insert into relations values ('Mostrai', 'goblin', -1); +insert into relations values ('Mostrai', 'giant', -1); +insert into regions values ('Lythander', 'ALL', 0.25); +insert into relations values ('Lythander', 'faerie', 1); +insert into relations values ('Lythander', 'goblin', -1); +insert into relations values ('Lythander', 'troll', -1); +insert into regions values ('Gnarg', 'ALL', 0.25); +insert into relations values ('Gnarg', 'goblin', 1); +insert into relations values ('Gnarg', 'giant', 1); +insert into relations values ('Gnarg', 'troll', 1); +insert into relations values ('Gnarg', 'faerie', -1); +insert into relations values ('Gnarg', 'dwarf', -1); diff --git a/python/CFReputation/sql/init.sql b/python/CFReputation/sql/init.sql new file mode 100644 index 000000000..8f39d6533 --- /dev/null +++ b/python/CFReputation/sql/init.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS schema(version INT); + +CREATE TABLE regions( + faction TEXT, + region TEXT, -- region name (or 'ALL') this faction controls + influence NUMERIC, + CONSTRAINT influence_range CHECK(influence BETWEEN 0 AND 1) +); + +CREATE TABLE relations( + faction TEXT, + race TEXT, + attitude NUMERIC, + PRIMARY KEY (faction, race), + CONSTRAINT attitude_range CHECK(attitude BETWEEN -1 AND 1) +); + +CREATE TABLE reputations( + name TEXT, -- player name + faction TEXT, + reputation NUMERIC, + PRIMARY KEY (name, faction), + CONSTRAINT reputation_range CHECK(reputation BETWEEN -1 AND 1) +); + +INSERT INTO regions VALUES +('Dragons', 'ALL', 0.4), +('Scorn', 'scorn', 0.5), +('Scorn', 'scornarena', 0.5), +('Scorn', 'scorncounty', 0.5), +('Scorn', 'scornoldcity', 0.5); + +INSERT INTO relations VALUES +('Dragons', 'dragon', 1), +('Dragons', 'faerie', -1), +('Dragons', 'human', -1), +('Scorn', 'demon', -1), +('Scorn', 'dragon', -1), +('Scorn', 'giant', -1), +('Scorn', 'goblin', -1), +('Scorn', 'human', 1), +('Scorn', 'reptile', -1), +('Scorn', 'troll', -1), +('Scorn', 'undead', -1), +('Scorn', 'unnatural', -1); + +INSERT INTO schema VALUES(1); diff --git a/python/CFReputation/utils/gods2factions b/python/CFReputation/utils/gods2factions new file mode 100755 index 000000000..026710b95 --- /dev/null +++ b/python/CFReputation/utils/gods2factions @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# gods2factions -- convert `crossfire-server -m6` output to factions SQL +import sys + +def print_rels(faction, rel_str, rel): + rels = rel_str.split(',') + for r in rels: + print("insert into relations values ('%s', '%s', %d);" + % (faction, r, rel)) + +for l in sys.stdin.read().splitlines(): + xs = map(str.strip, l.split(':')) + if len(xs) < 1: + continue + if xs[0] == 'GOD': + curr_god = xs[1] + print("insert into regions values ('%s', 'ALL', 0.25);" % curr_god) + elif xs[0] == 'aligned_race(s)': + print_rels(curr_god, xs[1], 1) + elif xs[0] == 'enemy_race(s)': + print_rels(curr_god, xs[1], -1) diff --git a/python/commands/reputation.py b/python/commands/reputation.py new file mode 100644 index 000000000..5b5ffb956 --- /dev/null +++ b/python/commands/reputation.py @@ -0,0 +1,15 @@ +import CFReputation +import Crossfire + +def show_reputation(who): + reputations = CFReputation.reputation(who.Name) + lines = [] + if len(reputations) == 0: + lines.append("You don't have a reputation with anyone.") + else: + lines.append("You are known by the following factions:") + for p in reputations: + lines.append("\t%s....................%d" % (p[0], p[1])) + who.Message("\n".join(lines)) + +show_reputation(Crossfire.WhoAmI()) diff --git a/python/events/gkill/reputation.py b/python/events/gkill/reputation.py new file mode 100644 index 000000000..42e396325 --- /dev/null +++ b/python/events/gkill/reputation.py @@ -0,0 +1,17 @@ +import Crossfire +import CFReputation + +def get_killer(hitter): + owner = hitter.Owner + if owner is not None: + return owner + return hitter + +def is_player(op): + return op.Type == Crossfire.Type.PLAYER + +killer = get_killer(Crossfire.WhoIsActivator()) +victim = Crossfire.WhoAmI() + +if is_player(killer): + CFReputation.record_kill(victim.Race, victim.Map.Region.Name, killer.Name) diff --git a/python/events/init/reputation_command.py b/python/events/init/reputation_command.py new file mode 100644 index 000000000..64945e28b --- /dev/null +++ b/python/events/init/reputation_command.py @@ -0,0 +1,2 @@ +import Crossfire +Crossfire.RegisterCommand("reputation", "/python/commands/reputation", 0)