diff --git a/.editorconfig b/.editorconfig index 9cebe6d6d..cd3315bd8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ end_of_line = lf insert_final_newline = true # 4 space indentation -[*.{c,h,lua}] +[*.{c,h,lua,py}] indent_style = space indent_size = 4 diff --git a/process/CMakeLists.txt b/process/CMakeLists.txt index 174fae694..0ffb7b4a2 100644 --- a/process/CMakeLists.txt +++ b/process/CMakeLists.txt @@ -1,5 +1,6 @@ install(PROGRAMS create-orders backup-eressea run-turn send-zip-report send-bz2-report compress.py compress.sh epasswd.py orders-process +process-orders.py accept-orders.py checkpasswd.py sendreport.sh sendreports.sh orders-accept DESTINATION bin) install(DIRECTORY cron/ DESTINATION bin USE_SOURCE_PERMISSIONS diff --git a/process/accept-orders.py b/process/accept-orders.py new file mode 100755 index 000000000..3bf9ea965 --- /dev/null +++ b/process/accept-orders.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from email.Utils import parseaddr +from email.Parser import Parser +import os +import os.path +import ConfigParser +from re import compile, IGNORECASE +from stat import ST_MTIME +from string import upper, split, replace +import logging +import sys +import subprocess +from sys import stdin +from time import ctime, sleep, time +from socket import gethostname +from rfc822 import parsedate_tz, mktime_tz + +if 'ERESSEA' in os.environ: + dir = os.environ['ERESSEA'] +elif 'HOME' in os.environ: + dir = os.path.join(os.environ['HOME'], 'eressea') +else: # WTF? No HOME? + dir = "/home/eressea/eressea" +if not os.path.isdir(dir): + print "please set the ERESSEA environment variable to the install path" + sys.exit(1) +rootdir = dir + +game = int(sys.argv[1]) +gamedir = os.path.join(rootdir, "game-%d" % (game, )) +frommail = 'eressea-server@kn-bremen.de' +gamename = 'Eressea' +sender = '%s Server <%s>' % (gamename, frommail) + +inifile = os.path.join(gamedir, 'eressea.ini') +if not os.path.exists(inifile): + print "no such file: " . inifile +else: + config = ConfigParser.ConfigParser() + config.read(inifile) + if config.has_option('game', 'email'): + frommail = config.get('game', 'email') + if config.has_option('game', 'name'): + gamename = config.get('game', 'name') + if config.has_option('game', 'sender'): + sender = config.get('game', 'sender') + else: + sender = "%s Server <%s>" % (gamename, frommail) + config = None +prefix = 'turn-' +hostname = gethostname() +orderbase = "orders.dir" +sendmail = True +# maximum number of reports per sender: +maxfiles = 20 +# write headers to file? +writeheaders = True +# reject all html email? +rejecthtml = True + +def unlock_file(filename): + try: + os.unlink(filename+".lock") + except: + print "could not unlock %s.lock, file not found" % filename + +def lock_file(filename): + i = 0 + wait = 1 + if not os.path.exists(filename): + file=open(filename, "w") + file.close() + while True: + try: + os.symlink(filename, filename+".lock") + return + except: + i = i+1 + if i == 5: unlock_file(filename) + sleep(wait) + wait = wait*2 + +messages = { + "multipart-en" : + "ERROR: The orders you sent contain no plaintext. " \ + "The Eressea server cannot process orders containing HTML " \ + "or invalid attachments, which are the reasons why this " \ + "usually happens. Please change the settings of your mail " \ + "software and re-send the orders.", + + "multipart-de" : + "FEHLER: Die von dir eingeschickte Mail enthält keinen " \ + "Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \ + "ungültig formatierte Mail ingeschickt. Wir können ihn " \ + "deshalb nicht berücksichtigen. Schicke den Zug nochmals " \ + "als reinen Text ohne Formatierungen ein.", + + "maildate-de": + "Es erreichte uns bereits ein Zug mit einem späteren " \ + "Absendedatum (%s > %s). Entweder ist deine " \ + "Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \ + "dir auf dem Transportweg überholt. Entscheidend für die " \ + "Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \ + "deiner Mail.", + + "maildate-en": + "The server already received an order file that was sent at a later " \ + "date (%s > %s). Either your system clock is wrong, or two messages have " \ + "overtaken each other on the way to the server. The order of " \ + "execution on the server is always according to the Date: header in " \ + "your mail.", + + "nodate-en": + "Your message did not contain a valid Date: header in accordance with RFC2822.", + + "nodate-de": + "Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.", + + "error-de": + "Fehler", + + "error-en": + "Error", + + "warning-de": + "Warnung", + + "warning-en": + "Warning", + + "subject-de": + "Befehle angekommen", + + "subject-en": + "orders received" +} + +# return 1 if addr is a valid email address +def valid_email(addr): + rfc822_specials = '/()<>@,;:\\"[]' + # First we validate the name portion (name@domain) + c = 0 + while c < len(addr): + if addr[c] == '"' and (not c or addr[c - 1] == '.' or addr[c - 1] == '"'): + c = c + 1 + while c < len(addr): + if addr[c] == '"': break + if addr[c] == '\\' and addr[c + 1] == ' ': + c = c + 2 + continue + if ord(addr[c]) < 32 or ord(addr[c]) >= 127: return 0 + c = c + 1 + else: return 0 + if addr[c] == '@': break + if addr[c] != '.': return 0 + c = c + 1 + continue + if addr[c] == '@': break + if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0 + if addr[c] in rfc822_specials: return 0 + c = c + 1 + if not c or addr[c - 1] == '.': return 0 + + # Next we validate the domain portion (name@domain) + domain = c = c + 1 + if domain >= len(addr): return 0 + count = 0 + while c < len(addr): + if addr[c] == '.': + if c == domain or addr[c - 1] == '.': return 0 + count = count + 1 + if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0 + if addr[c] in rfc822_specials: return 0 + c = c + 1 + return count >= 1 + +# return the replyto or from address in the header +def get_sender(header): + replyto = header.get("Reply-To") + if replyto is None: + replyto = header.get("From") + if replyto is None: return None + x = parseaddr(replyto) + return x[1] + +# return first available filename basename,[0-9]+ +def available_file(dirname, basename): + ver = 0 + maxdate = 0 + filename = "%s/%s,%s,%d" % (dirname, basename, hostname, ver) + while os.path.exists(filename): + maxdate = max(os.stat(filename)[ST_MTIME], maxdate) + ver = ver + 1 + filename = "%s/%s,%s,%d" % (dirname, basename, hostname, ver) + if ver >= maxfiles: + return None, None + return maxdate, filename + +def formatpar(string, l=76, indent=2): + words = split(string) + res = "" + ll = 0 + first = 1 + + for word in words: + if first == 1: + res = word + first = 0 + ll = len(word) + else: + if ll + len(word) > l: + res = res + "\n"+" "*indent+word + ll = len(word) + indent + else: + res = res+" "+word + ll = ll + len(word) + 1 + + return res+"\n" + +def store_message(message, filename): + outfile = open(filename, "w") + outfile.write(message.as_string()) + outfile.close() + return + +def write_part(outfile, part): + charset = part.get_content_charset() + payload = part.get_payload(decode=True) + + if charset is None: + charset = "latin1" + try: + msg = payload.decode(charset, "ignore") + except: + msg = payload + charset = None + try: + utf8 = msg.encode("utf-8", "ignore") + outfile.write(utf8) + except: + outfile.write(msg) + return False + outfile.write("\n"); + return True + +def copy_orders(message, filename, sender): + # print the header first + if writeheaders: + from os.path import split + dirname, basename = split(filename) + dirname = dirname + '/headers' + if not os.path.exists(dirname): os.mkdir(dirname) + outfile = open(dirname + '/' + basename, "w") + for name, value in message.items(): + outfile.write(name + ": " + value + "\n") + outfile.close() + + found = False + outfile = open(filename, "w") + if message.is_multipart(): + for part in message.get_payload(): + content_type = part.get_content_type() + logger.debug("found content type %s for %s" % (content_type, sender)) + if content_type=="text/plain": + if write_part(outfile, part): + found = True + else: + charset = part.get_content_charset() + logger.error("could not write text/plain part (charset=%s) for %s" % (charset, sender)) + + else: + if write_part(outfile, message): + found = True + else: + charset = message.get_content_charset() + logger.error("could not write text/plain message (charset=%s) for %s" % (charset, sender)) + outfile.close() + return found + +# create a file, containing: +# game=0 locale=de file=/path/to/filename email=rcpt@domain.to +def accept(game, locale, stream, extend=None): + global rootdir, orderbase, gamedir, gamename, sender + if extend is not None: + orderbase = orderbase + ".pre-" + extend + savedir = os.path.join(gamedir, orderbase) + # check if it's one of the pre-sent orders. + # create the save-directories if they don't exist + if not os.path.exists(gamedir): os.mkdir(gamedir) + if not os.path.exists(savedir): os.mkdir(savedir) + # parse message + message = Parser().parse(stream) + email = get_sender(message) + logger = logging.getLogger(email) + # write syslog + if email is None or valid_email(email)==0: + logger.warning("invalid email address: " + str(email)) + return -1 + logger.info("received orders from " + email) + # get an available filename + lock_file(gamedir + "/orders.queue") + maxdate, filename = available_file(savedir, prefix + email) + if filename is None: + logger.warning("more than " + str(maxfiles) + " orders from " + email) + return -1 + # copy the orders to the file + text_ok = copy_orders(message, filename, email) + unlock_file(gamedir + "/orders.queue") + + warning, msg, fail = None, "", False + maildate = message.get("Date") + if maildate != None: + turndate = mktime_tz(parsedate_tz(maildate)) + os.utime(filename, (turndate, turndate)) + logger.debug("mail date is '%s' (%d)" % (maildate, turndate)) + if False and turndate < maxdate: + logger.warning("inconsistent message date " + email) + warning = " (" + messages["warning-" + locale] + ")" + msg = msg + formatpar(messages["maildate-" + locale] % (ctime(maxdate),ctime(turndate)), 76, 2) + "\n" + else: + logger.warning("missing message date " + email) + warning = " (" + messages["warning-" + locale] + ")" + msg = msg + formatpar(messages["nodate-" + locale], 76, 2) + "\n" + + if not text_ok: + warning = " (" + messages["error-" + locale] + ")" + msg = msg + formatpar(messages["multipart-" + locale], 76, 2) + "\n" + logger.warning("rejected - no text/plain in orders from " + email) + os.unlink(filename) + savedir = savedir + "/rejected" + if not os.path.exists(savedir): os.mkdir(savedir) + lock_file(gamedir + "/orders.queue") + maxdate, filename = available_file(savedir, prefix + email) + store_message(message, filename) + unlock_file(gamedir + "/orders.queue") + fail = True + + if sendmail and warning is not None: + subject = gamename + " " + messages["subject-"+locale] + warning + print("mail " + subject) + ps = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE) + ps.communicate(msg) + + if not sendmail: + print text_ok, fail, email + print filename + + if not fail: + lock_file(gamedir + "/orders.queue") + queue = open(gamedir + "/orders.queue", "a") + queue.write("email=%s file=%s locale=%s game=%s\n" % (email, filename, locale, game)) + queue.close() + unlock_file(gamedir + "/orders.queue") + + logger.info("done - accepted orders from " + email) + + return 0 + +# the main body of the script: +try: + os.mkdir(os.path.join(rootdir, 'log')) +except: + pass # already exists? +LOG_FILENAME=os.path.join(rootdir, 'log/orders.log') +logging.basicConfig(level=logging.DEBUG, filename=LOG_FILENAME) +logger = logging +delay = None # TODO: parse the turn delay +locale = sys.argv[2] +infile = stdin +if len(sys.argv)>3: + infile = open(sys.argv[3], "r") +retval = accept(game, locale, infile, delay) +if infile!=stdin: + infile.close() +sys.exit(retval) diff --git a/process/orders-accept b/process/orders-accept index 2f8f0bd29..266424a41 100755 --- a/process/orders-accept +++ b/process/orders-accept @@ -1,378 +1,5 @@ -#!/usr/bin/env python -# -*- coding: iso-8859-1 -*- +#!/bin/sh +SCRIPT=$(readlink -f $0) +cd $(dirname $SCRIPT) +python accept-orders.py "$@" -from email.Utils import parseaddr -from email.Parser import Parser -import os -import os.path -import ConfigParser -from re import compile, IGNORECASE -from stat import ST_MTIME -from string import upper, split, replace -import logging -import sys -from sys import stdin -from time import ctime, sleep, time -from socket import gethostname -from rfc822 import parsedate_tz, mktime_tz - -if 'ERESSEA' in os.environ: - dir = os.environ['ERESSEA'] -elif 'HOME' in os.environ: - dir = os.path.join(os.environ['HOME'], '/eressea') -else: # WTF? No HOME? - dir = "/home/eressea/eressea" -if not os.path.isdir(dir): - print "please set the ERESSEA environment variable to the install path" - sys.exit(1) -rootdir = dir - -game = int(sys.argv[1]) -gamedir = os.path.join(rootdir, "game-%d" % (game, )) -frommail = 'eressea-server@kn-bremen.de' -gamename = 'Eressea' -sender = '%s Server <%s>' % (gamename, frommail) - -inifile = os.path.join(gamedir, 'eressea.ini') -if not os.path.exists(inifile): - print "no such file: " . inifile -else: - config = ConfigParser.ConfigParser() - config.read(inifile) - if config.has_option('game', 'email'): - frommail = config.get('game', 'email') - if config.has_option('game', 'name'): - gamename = config.get('game', 'name') - if config.has_option('game', 'sender'): - sender = config.get('game', 'sender') - else: - sender = "%s Server <%s>" % (gamename, frommail) - config = None -prefix = 'turn-' -hostname = gethostname() -orderbase = "orders.dir" -sendmail = True -# maximum number of reports per sender: -maxfiles = 20 -# write headers to file? -writeheaders = True -# reject all html email? -rejecthtml = True - -def unlock_file(filename): - try: - os.unlink(filename+".lock") - except: - print "could not unlock %s.lock, file not found" % filename - -def lock_file(filename): - i = 0 - wait = 1 - if not os.path.exists(filename): - file=open(filename, "w") - file.close() - while True: - try: - os.symlink(filename, filename+".lock") - return - except: - i = i+1 - if i == 5: unlock_file(filename) - sleep(wait) - wait = wait*2 - -messages = { - "multipart-en" : - "ERROR: The orders you sent contain no plaintext. " \ - "The Eressea server cannot process orders containing HTML " \ - "or invalid attachments, which are the reasons why this " \ - "usually happens. Please change the settings of your mail " \ - "software and re-send the orders.", - - "multipart-de" : - "FEHLER: Die von dir eingeschickte Mail enth�lt keinen " \ - "Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \ - "ung�ltig formatierte Mail ingeschickt. Wir k�nnen ihn " \ - "deshalb nicht ber�cksichtigen. Schicke den Zug nochmals " \ - "als reinen Text ohne Formatierungen ein.", - - "maildate-de": - "Es erreichte uns bereits ein Zug mit einem sp�teren " \ - "Absendedatum (%s > %s). Entweder ist deine " \ - "Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \ - "dir auf dem Transportweg �berholt. Entscheidend f�r die " \ - "Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \ - "deiner Mail.", - - "maildate-en": - "The server already received an order file that was sent at a later " \ - "date (%s > %s). Either your system clock is wrong, or two messages have " \ - "overtaken each other on the way to the server. The order of " \ - "execution on the server is always according to the Date: header in " \ - "your mail.", - - "nodate-en": - "Your message did not contain a valid Date: header in accordance with RFC2822.", - - "nodate-de": - "Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.", - - "error-de": - "Fehler", - - "error-en": - "Error", - - "warning-de": - "Warnung", - - "warning-en": - "Warning", - - "subject-de": - "Befehle angekommen", - - "subject-en": - "orders received" -} - -# return 1 if addr is a valid email address -def valid_email(addr): - rfc822_specials = '/()<>@,;:\\"[]' - # First we validate the name portion (name@domain) - c = 0 - while c < len(addr): - if addr[c] == '"' and (not c or addr[c - 1] == '.' or addr[c - 1] == '"'): - c = c + 1 - while c < len(addr): - if addr[c] == '"': break - if addr[c] == '\\' and addr[c + 1] == ' ': - c = c + 2 - continue - if ord(addr[c]) < 32 or ord(addr[c]) >= 127: return 0 - c = c + 1 - else: return 0 - if addr[c] == '@': break - if addr[c] != '.': return 0 - c = c + 1 - continue - if addr[c] == '@': break - if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0 - if addr[c] in rfc822_specials: return 0 - c = c + 1 - if not c or addr[c - 1] == '.': return 0 - - # Next we validate the domain portion (name@domain) - domain = c = c + 1 - if domain >= len(addr): return 0 - count = 0 - while c < len(addr): - if addr[c] == '.': - if c == domain or addr[c - 1] == '.': return 0 - count = count + 1 - if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0 - if addr[c] in rfc822_specials: return 0 - c = c + 1 - return count >= 1 - -# return the replyto or from address in the header -def get_sender(header): - replyto = header.get("Reply-To") - if replyto is None: - replyto = header.get("From") - if replyto is None: return None - x = parseaddr(replyto) - return x[1] - -# return first available filename basename,[0-9]+ -def available_file(dirname, basename): - ver = 0 - maxdate = 0 - filename = "%s/%s,%s,%d" % (dirname, basename, hostname, ver) - while os.path.exists(filename): - maxdate = max(os.stat(filename)[ST_MTIME], maxdate) - ver = ver + 1 - filename = "%s/%s,%s,%d" % (dirname, basename, hostname, ver) - if ver >= maxfiles: - return None, None - return maxdate, filename - -def formatpar(string, l=76, indent=2): - words = split(string) - res = "" - ll = 0 - first = 1 - - for word in words: - if first == 1: - res = word - first = 0 - ll = len(word) - else: - if ll + len(word) > l: - res = res + "\n"+" "*indent+word - ll = len(word) + indent - else: - res = res+" "+word - ll = ll + len(word) + 1 - - return res+"\n" - -def store_message(message, filename): - outfile = open(filename, "w") - outfile.write(message.as_string()) - outfile.close() - return - -def write_part(outfile, part): - charset = part.get_content_charset() - payload = part.get_payload(decode=True) - - if charset is None: - charset = "latin1" - try: - msg = payload.decode(charset, "ignore") - except: - msg = payload - charset = None - try: - utf8 = msg.encode("utf-8", "ignore") - outfile.write(utf8) - except: - outfile.write(msg) - return False - outfile.write("\n"); - return True - -def copy_orders(message, filename, sender): - # print the header first - if writeheaders: - from os.path import split - dirname, basename = split(filename) - dirname = dirname + '/headers' - if not os.path.exists(dirname): os.mkdir(dirname) - outfile = open(dirname + '/' + basename, "w") - for name, value in message.items(): - outfile.write(name + ": " + value + "\n") - outfile.close() - - found = False - outfile = open(filename, "w") - if message.is_multipart(): - for part in message.get_payload(): - content_type = part.get_content_type() - logger.debug("found content type %s for %s" % (content_type, sender)) - if content_type=="text/plain": - if write_part(outfile, part): - found = True - else: - charset = part.get_content_charset() - logger.error("could not write text/plain part (charset=%s) for %s" % (charset, sender)) - - else: - if write_part(outfile, message): - found = True - else: - charset = message.get_content_charset() - logger.error("could not write text/plain message (charset=%s) for %s" % (charset, sender)) - outfile.close() - return found - -# create a file, containing: -# game=0 locale=de file=/path/to/filename email=rcpt@domain.to -def accept(game, locale, stream, extend=None): - global rootdir, orderbase, gamedir, gamename, sender - if extend is not None: - orderbase = orderbase + ".pre-" + extend - savedir = os.path.join(gamedir, orderbase) - # check if it's one of the pre-sent orders. - # create the save-directories if they don't exist - if not os.path.exists(gamedir): os.mkdir(gamedir) - if not os.path.exists(savedir): os.mkdir(savedir) - # parse message - message = Parser().parse(stream) - email = get_sender(message) - logger = logging.getLogger(email) - # write syslog - if email is None or valid_email(email)==0: - logger.warning("invalid email address: " + str(email)) - return -1 - logger.info("received orders from " + email) - # get an available filename - lock_file(gamedir + "/orders.queue") - maxdate, filename = available_file(savedir, prefix + email) - if filename is None: - logger.warning("more than " + str(maxfiles) + " orders from " + email) - return -1 - # copy the orders to the file - text_ok = copy_orders(message, filename, email) - unlock_file(gamedir + "/orders.queue") - - warning, msg, fail = None, "", False - maildate = message.get("Date") - if maildate != None: - turndate = mktime_tz(parsedate_tz(maildate)) - os.utime(filename, (turndate, turndate)) - logger.debug("mail date is '%s' (%d)" % (maildate, turndate)) - if turndate < maxdate: - logger.warning("inconsistent message date " + email) - warning = " (" + messages["warning-" + locale] + ")" - msg = msg + formatpar(messages["maildate-" + locale] % (ctime(maxdate),ctime(turndate)), 76, 2) + "\n" - else: - logger.warning("missing message date " + email) - warning = " (" + messages["warning-" + locale] + ")" - msg = msg + formatpar(messages["nodate-" + locale], 76, 2) + "\n" - - if not text_ok: - warning = " (" + messages["error-" + locale] + ")" - msg = msg + formatpar(messages["multipart-" + locale], 76, 2) + "\n" - logger.warning("rejected - no text/plain in orders from " + email) - os.unlink(filename) - savedir = savedir + "/rejected" - if not os.path.exists(savedir): os.mkdir(savedir) - lock_file(gamedir + "/orders.queue") - maxdate, filename = available_file(savedir, prefix + email) - store_message(message, filename) - unlock_file(gamedir + "/orders.queue") - fail = True - - if sendmail and warning is not None: - subject = gamename + " " + messages["subject-"+locale] + warning - mail = "Subject: %s\nFrom: %s\nTo: %s\n\n" % (subject, sender, email) + msg - from smtplib import SMTP - server = SMTP("localhost") - server.sendmail(sender, email, mail) - server.close() - - if not sendmail: - print text_ok, fail, email - print filename - - if not fail: - lock_file(gamedir + "/orders.queue") - queue = open(gamedir + "/orders.queue", "a") - queue.write("email=%s file=%s locale=%s game=%s\n" % (email, filename, locale, game)) - queue.close() - unlock_file(gamedir + "/orders.queue") - - logger.info("done - accepted orders from " + email) - - return 0 - -# the main body of the script: -try: - os.mkdir(os.path.join(rootdir, 'log')) -except: - pass # already exists? -LOG_FILENAME=os.path.join(rootdir, 'log/orders.log') -logging.basicConfig(level=logging.DEBUG, filename=LOG_FILENAME) -logger = logging -delay=None # TODO: parse the turn delay -locale = sys.argv[2] -infile = stdin -if len(sys.argv)>3: - infile = open(sys.argv[3], "r") -retval = accept(game, locale, infile, delay) -if infile!=stdin: - infile.close() -sys.exit(retval) diff --git a/process/orders-process b/process/orders-process index 79c7b3378..38b2d1115 100755 --- a/process/orders-process +++ b/process/orders-process @@ -1,242 +1,5 @@ -#!/usr/bin/env python -# -*- coding: iso-8859-1 -*- +#!/bin/sh +SCRIPT=$(readlink -f $0) +cd $(dirname $SCRIPT) +python process-orders.py "$@" -from os import unlink, symlink, rename, popen, tmpfile -import sys -import os -import os.path -import ConfigParser -from re import compile, IGNORECASE -from string import split, join, upper, strip -from sys import argv, exit -from time import sleep, time, ctime -from syslog import openlog, closelog, syslog - -from epasswd import EPasswd - -def pwd_get_email(faction, pwd, pwdfile=None): - return None - -def split_filename(filename): - return os.path.split(filename) - -def unlock_file(filename): - try: - unlink(filename+".lock") - except: - print "could not unlock %s.lock, file not found" % filename - raise - -def lock_file(filename): - i = 0 - wait = 1 - if not os.path.exists(filename): - file=open(filename, "w") - file.close() - while True: - try: - symlink(filename, filename+".lock") - return - except: - i = i+1 - if i == 5: - raise - sleep(wait) - wait = wait*2 - -messages = { -"subject-de": "Befehle angekommen", -"subject-en": "orders received", - -"validate-en": "Validating", -"validate-de": "Verarbeite", - -"faction-en": "Faction", -"faction-de": "Partei", - -"unknown-de": "WARNUNG: Die Partei ist nicht bekannt, oder das Passwort falsch!", -"unknown-en": "WARNING: This faction is unknown, or the password is incorrect!", - -"warning-de": "Warnung", -"warning-en": "Warning", - -"error-de": "Fehler", -"error-en": "Error", -} - -game = int(sys.argv[1]) -echeck_cmd = "/home/eressea/echeck/echeck.sh" -maxlines = 25 - -# base directory for all your games: -install_dir = "/home/eressea/eressea" -if 'ERESSEA' in os.environ: - install_dir = os.environ['ERESSEA'] -elif 'HOME' in os.environ: - install_dir = os.path.join(os.environ['HOME'], '/eressea') -if not os.path.isdir(install_dir): - print "please set the ERESSEA environment variable to the install path" - sys.exit(1) - -game_dir = os.path.join(install_dir, "game-%d" % (game, )) -frommail = 'eressea-server@kn-bremen.de' -gamename = 'Eressea' -sender = '%s Server <%s>' % (gamename, frommail) - -inifile = os.path.join(game_dir, 'eressea.ini') -if not os.path.exists(inifile): - print "no such file: " . inifile -else: - config = ConfigParser.ConfigParser() - config.read(inifile) - if config.has_option('game', 'email'): - frommail = config.get('game', 'email') - if config.has_option('game', 'name'): - gamename = config.get('game', 'name') - if config.has_option('game', 'sender'): - sender = config.get('game', 'sender') - else: - sender = "%s Server <%s>" % (gamename, frommail) - config = None - -queue_file = os.path.join(game_dir, "orders.queue") -if not os.path.exists(queue_file): - exit(0) - -# regular expression that finds the start of a faction -fact_re = compile("^\s*(eressea|partei|faction)\s+([a-zA-Z0-9]+)\s+\"?([^\"]*)\"?", IGNORECASE) - -def check_pwd(filename, email, pw_data): - results = [] - try: - file = open(filename, "r") - except: - print "could not open file", filename - return results - for line in file.readlines(): - mo = fact_re.search(strip(line)) - if mo != None: - fact_nr = str(mo.group(2)) - fact_pw = str(mo.group(3)) - if pw_data.fac_exists(fact_nr): - if not pw_data.check(fact_nr, fact_pw): - game_email = pw_data.get_email(fact_nr) - results = results + [ (fact_nr, game_email, False, fact_pw) ] - else: - game_email = pw_data.get_email(fact_nr) - results = results + [ (fact_nr, game_email, True, fact_pw) ] - else: - results = results + [ (fact_nr, None, False, fact_pw) ] - return results - -def echeck(filename, locale, rules): - dirname, filename = split_filename(filename) - stream = popen("%s %s %s %s %s" % (echeck_cmd, locale, filename, dirname, rules), 'r') - lines = stream.readlines() - if len(lines)==0: - stream.close() - return None - if len(lines)>maxlines: - mail = join(lines[:maxlines-3] + ["...", "\n"] + lines[-3:], '') - else: - mail = join(lines[:maxlines], '') - stream.close() - return mail - -# parse the queue file - -#print "connecting to SMTP..." -from smtplib import SMTP -try: - server = SMTP("localhost") -except: - print "could not connect to SMTP server" - exit(0) - -#print "reading password file..." -pw_data = EPasswd(os.path.join(game_dir,"passwd")) - -#print "reading orders.queue..." -# move the queue file to a save space while locking it: -try: - lock_file(queue_file) -except: - exit(0) -queuefile = open(queue_file, "r") -lines = queuefile.readlines() -queuefile.close() - -# copy to a temp file - -tname="/tmp/orders.queue.%s" % str(time()) -try: - lock_file(tname) -except: - exit(0) -tmpfile=open(tname, "w") -for line in lines: - tmpfile.write(line) -tmpfile.close() - -openlog("orders") - -unlink(queue_file) -try: - unlock_file(queue_file) -except: - pass - -for line in lines: - tokens = split(line[:-1], ' ') - dict = {} - for token in tokens: - name, value = split(token, '=') - dict[name] = value - - email = dict["email"] - locale = dict["locale"] - game = int(dict["game"]) - infile = dict["file"] - gamename='[E%d]' % game - rules='e%d' % game - warning = "" - failed = True - results = check_pwd(infile, email, pw_data) - logfile = open(os.path.join(game_dir, "zug.log"), "a") - dirname, filename = split_filename(infile) - msg = messages["validate-"+locale] + " " + infile + "\n\n" - for faction, game_email, success, pwd in results: - msg = msg + messages["faction-"+locale] + " " + faction + "\n" - if success: failed = False - else: msg = msg + messages["unknown-"+locale] + "\n" - msg = msg + "\n" - logfile.write("%s:%s:%s:%s:%s:%s\n" % (ctime(time()), email, game_email, faction, pwd, success)) - logfile.close() - - if failed: - warning = " (" + messages["warning-" + locale] + ")" - syslog("failed - no valid password in " + infile) - else: - result = None - if os.path.exists(echeck_cmd): - result = echeck(infile, locale, rules) - if result is None: - # echeck did not finish - msg = msg + "Echeck is broken. Your turn was accepted, but could not be verified.\n" - warning = " (" + messages["warning-" + locale] + ")" - syslog("process - echeck broken, " + infile) - else: - msg = msg + result - syslog("process - checked orders in " + infile) - - subject = gamename + " " + messages["subject-" + locale] + warning - msg = "Subject: %s\nFrom: %s\nTo: %s\nContent-Type: text/plain; charset=utf-8\n\n" % (subject, sender, email) + msg - try: - server.sendmail(sender, email, msg) - except: - syslog("failed - cannot send to " + email) - -server.close() - -closelog() -unlink(tname) -unlock_file(tname) diff --git a/process/process-orders.py b/process/process-orders.py new file mode 100755 index 000000000..8ed5e8625 --- /dev/null +++ b/process/process-orders.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from os import unlink, symlink, rename, popen, tmpfile +import sys +import os +import os.path +import ConfigParser +import subprocess +from re import compile, IGNORECASE +from string import split, join, upper, strip +from sys import argv, exit +from time import sleep, time, ctime +from syslog import openlog, closelog, syslog + +from epasswd import EPasswd + +def pwd_get_email(faction, pwd, pwdfile=None): + return None + +def split_filename(filename): + return os.path.split(filename) + +def unlock_file(filename): + try: + unlink(filename+".lock") + except: + print "could not unlock %s.lock, file not found" % filename + raise + +def lock_file(filename): + i = 0 + wait = 1 + if not os.path.exists(filename): + file=open(filename, "w") + file.close() + while True: + try: + symlink(filename, filename+".lock") + return + except: + i = i+1 + if i == 5: + raise + sleep(wait) + wait = wait*2 + +messages = { +"subject-de": "Befehle angekommen", +"subject-en": "orders received", + +"validate-en": "Validating", +"validate-de": "Verarbeite", + +"faction-en": "Faction", +"faction-de": "Partei", + +"unknown-de": "WARNUNG: Die Partei ist nicht bekannt, oder das Passwort falsch!", +"unknown-en": "WARNING: This faction is unknown, or the password is incorrect!", + +"warning-de": "Warnung", +"warning-en": "Warning", + +"error-de": "Fehler", +"error-en": "Error", +} + +game = int(sys.argv[1]) +echeck_cmd = "/home/eressea/echeck/echeck.sh" +maxlines = 25 + +# base directory for all your games: +install_dir = "/home/eressea/eressea" +if 'ERESSEA' in os.environ: + install_dir = os.environ['ERESSEA'] +elif 'HOME' in os.environ: + install_dir = os.path.join(os.environ['HOME'], 'eressea') +if not os.path.isdir(install_dir): + print "please set the ERESSEA environment variable to the install path" + sys.exit(1) + +game_dir = os.path.join(install_dir, "game-%d" % (game, )) +gamename = 'Eressea' + +inifile = os.path.join(game_dir, 'eressea.ini') +queue_file = os.path.join(game_dir, "orders.queue") +if not os.path.exists(queue_file): + exit(0) + +# regular expression that finds the start of a faction +fact_re = compile("^\s*(eressea|partei|faction)\s+([a-zA-Z0-9]+)\s+\"?([^\"]*)\"?", IGNORECASE) + +def check_pwd(filename, email, pw_data): + results = [] + try: + file = open(filename, "r") + except: + print "could not open file", filename + return results + for line in file.readlines(): + mo = fact_re.search(strip(line)) + if mo != None: + fact_nr = str(mo.group(2)) + fact_pw = str(mo.group(3)) + if pw_data.fac_exists(fact_nr): + if not pw_data.check(fact_nr, fact_pw): + game_email = pw_data.get_email(fact_nr) + results = results + [ (fact_nr, game_email, False, fact_pw) ] + else: + game_email = pw_data.get_email(fact_nr) + results = results + [ (fact_nr, game_email, True, fact_pw) ] + else: + results = results + [ (fact_nr, None, False, fact_pw) ] + return results + +def echeck(filename, locale, rules): + dirname, filename = split_filename(filename) + stream = popen("%s %s %s %s %s" % (echeck_cmd, locale, filename, dirname, rules), 'r') + lines = stream.readlines() + if len(lines)==0: + stream.close() + return None + if len(lines)>maxlines: + mail = join(lines[:maxlines-3] + ["...", "\n"] + lines[-3:], '') + else: + mail = join(lines[:maxlines], '') + stream.close() + return mail + +#print "reading password file..." +pw_data = EPasswd(os.path.join(game_dir,"passwd")) + +#print "reading orders.queue..." +# move the queue file to a save space while locking it: +try: + lock_file(queue_file) +except: + exit(0) +queuefile = open(queue_file, "r") +lines = queuefile.readlines() +queuefile.close() + +# copy to a temp file + +tname="/tmp/orders.queue.%s" % str(time()) +try: + lock_file(tname) +except: + exit(0) +tmpfile=open(tname, "w") +for line in lines: + tmpfile.write(line) +tmpfile.close() + +openlog("orders") + +unlink(queue_file) +try: + unlock_file(queue_file) +except: + pass + +for line in lines: + tokens = split(line[:-1], ' ') + dict = {} + for token in tokens: + name, value = split(token, '=') + dict[name] = value + + email = dict["email"] + locale = dict["locale"] + game = int(dict["game"]) + infile = dict["file"] + gamename='[E%d]' % game + rules='e%d' % game + warning = "" + failed = True + results = check_pwd(infile, email, pw_data) + logfile = open(os.path.join(game_dir, "zug.log"), "a") + dirname, filename = split_filename(infile) + msg = messages["validate-"+locale] + " " + infile + "\n\n" + for faction, game_email, success, pwd in results: + msg = msg + messages["faction-"+locale] + " " + faction + "\n" + if success: failed = False + else: msg = msg + messages["unknown-"+locale] + "\n" + msg = msg + "\n" + logfile.write("%s:%s:%s:%s:%s:%s\n" % (ctime(time()), email, game_email, faction, pwd, success)) + logfile.close() + + if failed: + warning = " (" + messages["warning-" + locale] + ")" + syslog("failed - no valid password in " + infile) + else: + result = None + if os.path.exists(echeck_cmd): + result = echeck(infile, locale, rules) + if result is None: + # echeck did not finish + msg = msg + "Echeck is broken. Your turn was accepted, but could not be verified.\n" + warning = " (" + messages["warning-" + locale] + ")" + syslog("process - echeck broken, " + infile) + else: + msg = msg + result + syslog("process - checked orders in " + infile) + + subject = gamename + " " + messages["subject-" + locale] + warning + try: + sp = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE) + sp.communicate(msg) + except: + syslog("failed - cannot send to " + email) + +closelog() +unlink(tname) +unlock_file(tname) diff --git a/res/core/messages.xml b/res/core/messages.xml index 757243d9f..d566c82e6 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -698,10 +698,16 @@ - + - + + + + + + + @@ -5494,12 +5500,12 @@ - + - + diff --git a/res/translations/messages.de.po b/res/translations/messages.de.po index 13fd10c96..3413ffc2c 100644 --- a/res/translations/messages.de.po +++ b/res/translations/messages.de.po @@ -1355,8 +1355,11 @@ msgstr "\"Es herrscht eine fröhliche und ausgelassene Stimmung. ($int36($id))\" msgid "buildroad" msgstr "\"$unit($unit) erweitert in $region($region) das Straßennetz um $int($size).\"" -msgid "nr_borderlist_postfix" -msgstr "\"$if($transparent,\" befindet sich\",\" versperrt\") ${object}$if($transparent,\"\",\" die Sicht\").\"" +msgid "nr_border_opaque" +msgstr "Im $direction($dir) verperrt ${object} die Sicht." + +msgid "nr_border_transparent" +msgstr "Im $direction($dir) befindet sich ${object}." msgid "effectstrength" msgstr "\"$unit($mage) erhöht die Körperkraft von $unit.dative($target) beträchtlich.\"" diff --git a/res/translations/messages.en.po b/res/translations/messages.en.po index fa795d0f6..6eaf6f258 100644 --- a/res/translations/messages.en.po +++ b/res/translations/messages.en.po @@ -1355,8 +1355,11 @@ msgstr "\"Everyone in this region seems to be having a very good time. ($int36($ msgid "buildroad" msgstr "\"$unit($unit) extends the road network in $region($region) by $int($size).\"" -msgid "nr_borderlist_postfix" -msgstr "\"$if($transparent,\" there is\",\" sight is blocked by \") ${object}.\"" +msgid "nr_border_opaque" +msgstr "To the $direction($dir), ${object} is blocking the view." + +msgid "nr_border_transparent" +msgstr "To the $direction($dir) is ${object}." msgid "effectstrength" msgstr "\"$unit($mage) increases the strength of $unit($target) dramatically.\"" diff --git a/res/translations/strings.de.po b/res/translations/strings.de.po index e56e36f55..afcceb94b 100644 --- a/res/translations/strings.de.po +++ b/res/translations/strings.de.po @@ -1906,9 +1906,6 @@ msgstr "Mallorn" msgid "castle" msgstr "Burg" -msgid "nr_borderlist_infix" -msgstr ", im" - msgctxt "race" msgid "shadowbat_p" msgstr "Todesflattern" @@ -3706,9 +3703,6 @@ msgctxt "spell" msgid "analyse_object" msgstr "Lied des Ortes analysieren" -msgid "nr_borderlist_lastfix" -msgstr "und im" - msgctxt "race" msgid "shadowknight_d" msgstr "Schattenrittern" @@ -5573,7 +5567,7 @@ msgid "swamp_trail" msgstr "der Sumpf von %s" msgid "nr_nb_final" -msgstr "und im" +msgstr "und im " msgid "aoc" msgstr "Katzenamulett" diff --git a/res/translations/strings.en.po b/res/translations/strings.en.po index 8629d5f5e..c8bd637db 100644 --- a/res/translations/strings.en.po +++ b/res/translations/strings.en.po @@ -1642,9 +1642,6 @@ msgstr "mallorn" msgid "castle" msgstr "castle" -msgid "nr_borderlist_infix" -msgstr ", to the " - msgctxt "race" msgid "shadowbat_p" msgstr "darkbats" @@ -3261,9 +3258,6 @@ msgctxt "spell" msgid "analyse_object" msgstr "Analysis" -msgid "nr_borderlist_lastfix" -msgstr ", and to the " - msgctxt "race" msgid "shadowknight_d" msgstr "shadow knights" diff --git a/scripts/tests/e2/e2features.lua b/scripts/tests/e2/e2features.lua index 4758fce39..be09ec448 100644 --- a/scripts/tests/e2/e2features.lua +++ b/scripts/tests/e2/e2features.lua @@ -491,8 +491,29 @@ function test_dwarf_mining() local f = faction.create('dwarf') local r = region.create(0, 0, 'plain') local u = unit.create(f, r) - u.name = 'Xolgrim' u:set_skill('mining', 2) assert_equal(2, u:get_skill('mining')) assert_equal(4, u:eff_skill('mining')) end + +function test_buy_sell() + local f = faction.create('human') + local r = region.create(0, 0, 'plain') + local u = unit.create(f, r) + local lux = r.luxury + local b = building.create(r, 'castle') + b.size = 10 + u:set_skill('trade', 1) + item = 'silk' + name = 'Seide' + if lux == 'silk' then + item = 'balm' + name = 'Balsam' + end + u:add_item(item, 5) + u:add_order('VERKAUFE 1 ' .. name) + assert_equal(0, u:get_item('money')) + process_orders() + assert_equal(4, u:get_item(item)) + assert_not_equal(0, u:get_item('money')) +end diff --git a/src/exparse.c b/src/exparse.c index e6975fc17..07acb090e 100644 --- a/src/exparse.c +++ b/src/exparse.c @@ -818,7 +818,9 @@ static void start_resources(parseinfo *pi, const XML_Char *el, const XML_Char ** handle_requirement(pi, el, attr); } else if (xml_strcmp(el, "luxury") == 0) { - rtype->ltype = new_luxurytype(itype, 0); + int price = atoi(attr_get(attr, "price")); + assert(price > 0); + rtype->ltype = new_luxurytype(itype, price); } else if (xml_strcmp(el, "potion") == 0) { int i, level = 0; @@ -1090,9 +1092,13 @@ static void start_races(parseinfo *pi, const XML_Char *el, const XML_Char **attr name = attr_get(attr, "name"); if (name) { + int i; + assert(!rc); pi->object = rc = rc_get_or_create(name); - int i; + while (AT_NONE != rc->attack[nattacks].type) { + ++nattacks; + } for (i = 0; attr[i]; i += 2) { const XML_Char *key = attr[i], *val = attr[i + 1]; diff --git a/src/kernel/connection.c b/src/kernel/connection.c index 014fce9d8..213700fe1 100644 --- a/src/kernel/connection.c +++ b/src/kernel/connection.c @@ -188,7 +188,7 @@ border_type *find_bordertype(const char *name) { border_type *bt = bordertypes; - while (bt && strcmp(bt->__name, name)!=0) + while (bt && strcmp(bt->_name, name)!=0) bt = bt->next; return bt; } @@ -563,7 +563,7 @@ void write_borders(struct storage *store) for (b = bhash; b != NULL; b = b->next) { if (b->type->valid && !b->type->valid(b)) continue; - WRITE_TOK(store, b->type->__name); + WRITE_TOK(store, b->type->_name); WRITE_INT(store, b->id); WRITE_INT(store, b->from->uid); WRITE_INT(store, b->to->uid); @@ -614,7 +614,7 @@ int read_borders(gamedata *data) if (to == from && from) { direction_t dir = (direction_t)(rng_int() % MAXDIRECTIONS); region *r = rconnect(from, dir); - log_error("[read_borders] invalid %s in %s\n", type->__name, regionname(from, NULL)); + log_error("[read_borders] invalid %s in %s\n", type->_name, regionname(from, NULL)); if (r != NULL) to = r; } @@ -633,5 +633,5 @@ int read_borders(gamedata *data) } const char * border_name(const connection *co, const struct region * r, const struct faction * f, int flags) { - return (co->type->name) ? co->type->name(co, r, f, flags) : co->type->__name; + return (co->type->name) ? co->type->name(co, r, f, flags) : co->type->_name; } diff --git a/src/kernel/connection.h b/src/kernel/connection.h index c847f690f..af412b036 100644 --- a/src/kernel/connection.h +++ b/src/kernel/connection.h @@ -45,7 +45,7 @@ extern "C" { } connection; typedef struct border_type { - const char *__name; /* internal use only */ + const char *_name; /* internal use only */ variant_type datatype; bool(*transparent) (const connection *, const struct faction *); /* is it possible to see through this? */ diff --git a/src/move.c b/src/move.c index c1ded5faa..990d6fc14 100644 --- a/src/move.c +++ b/src/move.c @@ -73,7 +73,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include @@ -1047,15 +1046,17 @@ int movewhere(const unit * u, const char *token, region * r, region ** resultp) order * cycle_route(order * ord, const struct locale *lang, int gereist) { int cm = 0; - char tail[1024], *bufp = tail; - char neworder[2048], *obuf = neworder; + char tail[1024]; + char neworder[2048]; char token[128]; direction_t d = NODIRECTION; bool paused = false; - bool pause; order *norder; - size_t size = sizeof(tail) - 1; + sbstring sbtail; + sbstring sborder; + sbs_init(&sbtail, tail, sizeof(tail)); + sbs_init(&sborder, neworder, sizeof(neworder)); assert(getkeyword(ord) == K_ROUTE); tail[0] = '\0'; neworder[0] = '\0'; @@ -1063,14 +1064,10 @@ order * cycle_route(order * ord, const struct locale *lang, int gereist) for (cm = 0;; ++cm) { const char *s; - pause = false; s = gettoken(token, sizeof(token)); if (s && *s) { d = get_direction(s, lang); - if (d == D_PAUSE) { - pause = true; - } - else if (d == NODIRECTION) { + if (d == NODIRECTION) { break; } } @@ -1079,38 +1076,37 @@ order * cycle_route(order * ord, const struct locale *lang, int gereist) } if (cm < gereist) { /* TODO: hier sollte keine PAUSE auftreten */ - assert(!pause); - if (!pause) { + assert (d != D_PAUSE); + if (d != D_PAUSE) { const char *loc = LOC(lang, shortdirections[d]); assert(loc); - if (bufp != tail) { - bufp = STRLCPY_EX(bufp, " ", &size, "cycle_route"); + if (sbs_length(&sbtail) > 0) { + sbs_strcat(&sbtail, " "); } - bufp = STRLCPY_EX(bufp, loc, &size, "cycle_route"); + sbs_strcat(&sbtail, loc); } } else if (strlen(neworder) > sizeof(neworder) / 2) break; - else if (cm == gereist && !paused && pause) { + else if (cm == gereist && !paused && (d == D_PAUSE)) { const char *loc = LOC(lang, parameters[P_PAUSE]); - bufp = STRLCPY_EX(bufp, " ", &size, "cycle_route"); - bufp = STRLCPY_EX(bufp, loc, &size, "cycle_route"); + sbs_strcat(&sbtail, " "); + sbs_strcat(&sbtail, loc); paused = true; } - else if (pause) { - /* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist - * hier keine normale direction), muss jede PAUSE einzeln - * herausgefiltert und explizit gesetzt werden */ - if (neworder != obuf) { - obuf += str_strlcat(obuf, " ", sizeof(neworder) - (obuf - neworder)); - } - obuf += str_strlcat(obuf, LOC(lang, parameters[P_PAUSE]), sizeof(neworder) - (obuf - neworder)); - } else { - if (neworder != obuf) { - obuf += str_strlcat(obuf, " ", sizeof(neworder) - (obuf - neworder)); + if (sbs_length(&sbtail) > 0) { + sbs_strcat(&sborder, " "); + } + if (d == D_PAUSE) { + /* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist + * hier keine normale direction), muss jede PAUSE einzeln + * herausgefiltert und explizit gesetzt werden */ + sbs_strcat(&sborder, LOC(lang, parameters[P_PAUSE])); + } + else { + sbs_strcat(&sborder, LOC(lang, shortdirections[d])); } - obuf += str_strlcat(obuf, LOC(lang, shortdirections[d]), sizeof(neworder) - (obuf - neworder)); } } @@ -2250,10 +2246,9 @@ static direction_t hunted_dir(attrib * at, int id) int follow_ship(unit * u, order * ord) { region *rc = u->region; - size_t bytes; + sbstring sbcmd; int moves, id, speed; - char command[256], *bufp = command; - size_t size = sizeof(command); + char command[256]; direction_t dir; if (fval(u, UFL_NOTMOVING)) { @@ -2289,11 +2284,10 @@ int follow_ship(unit * u, order * ord) return 0; } - bufp = command; - bytes = slprintf(bufp, size, "%s %s", LOC(u->faction->locale, keyword(K_MOVE)), LOC(u->faction->locale, directions[dir])); - assert(bytes <= INT_MAX); - if (wrptr(&bufp, &size, (int)bytes) != 0) - WARN_STATIC_BUFFER(); + sbs_init(&sbcmd, command, sizeof(command)); + sbs_strcpy(&sbcmd, LOC(u->faction->locale, keyword(K_MOVE))); + sbs_strcat(&sbcmd, " "); + sbs_strcat(&sbcmd, LOC(u->faction->locale, directions[dir])); moves = 1; @@ -2309,8 +2303,8 @@ int follow_ship(unit * u, order * ord) rc = rconnect(rc, dir); while (rc && moves < speed && (dir = hunted_dir(rc->attribs, id)) != NODIRECTION) { const char *loc = LOC(u->faction->locale, directions[dir]); - bufp = STRLCPY_EX(bufp, " ", &size, "hunt"); - bufp = STRLCPY_EX(bufp, loc, &size, "hunt"); + sbs_strcat(&sbcmd, " "); + sbs_strcat(&sbcmd, loc); moves++; rc = rconnect(rc, dir); } diff --git a/src/move.test.c b/src/move.test.c index a2fa4e760..f8c11114f 100644 --- a/src/move.test.c +++ b/src/move.test.c @@ -495,6 +495,7 @@ static void test_follow_ship_msg(CuTest * tc) { follow_ship(u, ord); + CuAssertPtrEquals(tc, r, u->region); CuAssertPtrNotNull(tc, msg = test_find_messagetype(u->faction->msgs, "error18")); p = msg->parameters[2].v; CuAssertPtrNotNull(tc, p); diff --git a/src/report.c b/src/report.c index 3976bb05b..2a0e9eb3a 100644 --- a/src/report.c +++ b/src/report.c @@ -769,11 +769,11 @@ static void rp_battles(struct stream *out, faction * f) while (bm) { char buf[256]; RENDER(f, buf, sizeof(buf), ("header_battle", "region", bm->r)); - newline(out); centre(out, buf, true); newline(out); rp_messages(out, bm->msgs, f, 0, false); bm = bm->next; + newline(out); } } } @@ -1237,39 +1237,17 @@ void report_region(struct stream *out, const region * r, faction * f) if (edges) newline(out); for (e = edges; e; e = e->next) { - bool first = true; message *msg; - bufp = buf; - size = sizeof(buf) - 1; for (d = 0; d != MAXDIRECTIONS; ++d) { - if (!e->exist[d]) - continue; - /* this localization might not work for every language but is fine for de and en */ - if (first) - bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_borderlist_prefix"), size); - else if (e->lastd == d) - bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_borderlist_lastfix"), size); - else - bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_borderlist_infix"), size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - bytes = (int)str_strlcpy(bufp, LOC(f->locale, directions[d]), size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - first = false; + if (e->exist[d]) { + msg = msg_message(e->transparent ? "nr_border_transparent" : "nr_border_opaque", + "object dir", e->name, d); + nr_render(msg, f->locale, buf, sizeof(buf), f); + msg_release(msg); + paragraph(out, buf, 0, 0, 0); + } } - /* TODO name is localized? Works for roads anyway... */ - /* TODO: creating messages during reporting makes them not show up in CR? */ - msg = msg_message("nr_borderlist_postfix", "transparent object", - e->transparent, e->name); - bytes = (int)nr_render(msg, f->locale, bufp, size, f); - msg_release(msg); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - - *bufp = 0; - paragraph(out, buf, 0, 0, 0); } if (edges) { while (edges) { @@ -1299,7 +1277,6 @@ static void statistics(struct stream *out, const region * r, const faction * f) } } /* print */ - newline(out); m = msg_message("nr_stat_header", "region", r); nr_render(m, f->locale, buf, sizeof(buf), f); msg_release(m); @@ -2305,8 +2282,10 @@ report_plaintext(const char *filename, report_context * ctx, report_travelthru(out, r, f); } - if (wants_stats && r->seen.mode >= seen_unit) + if (wants_stats && r->seen.mode >= seen_unit) { statistics(out, r, f); + newline(out); + } /* Nachrichten an REGION in der Region */ if (r->seen.mode >= seen_travel) { diff --git a/src/util/strings.c b/src/util/strings.c index f4bbd54a5..ef351ab1f 100644 --- a/src/util/strings.c +++ b/src/util/strings.c @@ -275,6 +275,11 @@ void sbs_strcpy(struct sbstring *sbs, const char *str) sbs->end = sbs->begin + len; } +size_t sbs_length(const struct sbstring *sbs) +{ + return sbs->end - sbs->begin; +} + char *str_unescape(char *str) { char *read = str, *write = str; while (*read) { diff --git a/src/util/strings.h b/src/util/strings.h index 2d93b414e..bd6f0de58 100644 --- a/src/util/strings.h +++ b/src/util/strings.h @@ -50,6 +50,7 @@ extern "C" { void sbs_strcat(struct sbstring *sbs, const char *str); void sbs_strncat(struct sbstring *sbs, const char *str, size_t size); void sbs_strcpy(struct sbstring *sbs, const char *str); + size_t sbs_length(const struct sbstring *sbs); /* benchmark for units: * JENKINS_HASH: 5.25 misses/hit (with good cache behavior)