home · contact · privacy
Add basic plugin infrastructure.
[plomrogue] / roguelike-server
index bd3ab104da977ddadc359214b8e190def2d6cb23..2f3480a5d6fc1ea45671c483c0194018e8bde875 100755 (executable)
@@ -648,10 +648,10 @@ def actor_move(t):
             if t == world_db["Things"][0]:
                 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
                 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
-                log("You wound " + hitted_name + ".")
+                log("You WOUND" + hitted_name + ".")
             elif 0 == hit_id:
                 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
-                log(hitter_name +" wounds you.")
+                log(hitter_name +" WOUNDS you.")
             decrement_lifepoints(world_db["Things"][hit_id])
             return
         passable = "." == chr(world_db["MAP"][pos])
@@ -665,9 +665,7 @@ def actor_move(t):
             world_db["Things"][id]["T_POSX"] = move_result[2]
         build_fov_map(t)
         if t == world_db["Things"][0]:
-            log("You move " + dir + ".")
-    elif t == world_db["Things"][0]:
-        log("You fail to move " + dir + ".")
+            log("You MOVE " + dir + ".")
 
 
 def actor_pick_up(t):
@@ -689,9 +687,7 @@ def actor_pick_up(t):
         world_db["Things"][id]["carried"] = True
         t["T_CARRIES"].append(id)
         if t == world_db["Things"][0]:
-                log("You pick up an object.")
-    elif t == world_db["Things"][0]:
-            log("You try to pick up an object, but there is none.")
+                log("You PICK UP an object.")
 
 
 def actor_drop(t):
@@ -702,9 +698,7 @@ def actor_drop(t):
         t["T_CARRIES"].remove(id)
         world_db["Things"][id]["carried"] = False
         if t == world_db["Things"][0]:
-            log("You drop an object.")
-    elif t == world_db["Things"][0]:
-       log("You try to drop an object, but you own none.")
+            log("You DROP an object.")
 
 
 def actor_use(t):
@@ -718,11 +712,9 @@ def actor_use(t):
             del world_db["Things"][id]
             t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_TOOLPOWER"]
             if t == world_db["Things"][0]:
-                log("You consume this object.")
+                log("You CONSUME this object.")
         elif t == world_db["Things"][0]:
-            log("You try to use this object, but fail.")
-    elif t == world_db["Things"][0]:
-        log("You try to use an object, but you own none.")
+            log("You try to use this object, but FAIL.")
 
 
 def thingproliferation(t, prol_map):
@@ -759,7 +751,7 @@ def try_healing(t):
         if (testval <= 1 or 1 == (rand.next() % testval)):
             t["T_LIFEPOINTS"] += 1
             if t == world_db["Things"][0]:
-                log("You heal.")
+                log("You HEAL.")
 
 
 def hunger_per_turn(type_id):
@@ -774,9 +766,9 @@ def hunger(t):
     if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
         if t == world_db["Things"][0]:
             if t["T_SATIATION"] < 0:
-                log("You suffer from hunger.")
+                log("You SUFFER from hunger.")
             else:
-                log("You suffer from over-eating.")
+                log("You SUFFER from over-eating.")
         decrement_lifepoints(t)
 
 
@@ -786,16 +778,15 @@ def get_dir_to_target(t, filter):
     The path-wise nearest target is chosen, via the shortest available path.
     Target must not be t. On succcess, return positive value, else False.
     Filters:
-    "a": Thing in FOV is below a certain distance, animate, but of ThingType
-         that is not t's, and starts out weaker than t is; build path as
-         avoiding things of t's ThingType
-    "f": neighbor cell (not inhabited by any animate Thing) further away from
-         animate Thing not further than x steps away and in FOV and of a
-         ThingType that is not t's, and starts out stronger or as strong as t
-         is currently; or (cornered), if no such flight cell, but Thing of
-         above criteria is too near,1 a cell closer to it, or, if less near,
-         just wait
-    "c": Thing in memorized map is consumable
+    "a": Thing in FOV is animate, but of ThingType, starts out weaker than t
+         is, and its corpse would be healthy food for t
+    "f": move away from an enemy – any visible actor whose thing type has more
+         TT_LIFEPOINTS than t LIFEPOINTS, and might find t's corpse healthy
+         food – if it is closer than n steps, where n will shrink as t's hunger
+         grows; if enemy is too close, move towards (attack) the enemy instead;
+         if no fleeing is possible, nor attacking useful, wait; don't tread on
+         non-enemies for fleeing
+    "c": Thing in memorized map is consumable of sufficient nutrition for t
     "s": memory map cell with greatest-reachable degree of unexploredness
     """
 
@@ -809,6 +800,11 @@ def get_dir_to_target(t, filter):
             raise RuntimeError("No score map allocated for "
                                "zero_score_map_where_char_on_memdepthmap().")
 
+    def set_map_score_at_thingpos(id, score):
+        pos = world_db["Things"][id]["T_POSY"] * world_db["MAP_LENGTH"] \
+                                     + world_db["Things"][id]["T_POSX"]
+        set_map_score(pos, score)
+
     def set_map_score(pos, score):
         test = libpr.set_map_score(pos, score)
         if test:
@@ -820,20 +816,44 @@ def get_dir_to_target(t, filter):
             raise RuntimeError("No score map allocated for get_map_score().")
         return result
 
+    def animate_in_fov(Thing):
+        if Thing["carried"] or Thing == t or not Thing["T_LIFEPOINTS"]:
+            return False
+        pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] + Thing["T_POSX"]
+        if ord("v") == t["fovmap"][pos]:
+            return True
+
+    def good_attack_target(v):
+        eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
+        type = world_db["ThingTypes"][v["T_TYPE"]]
+        type_corpse = world_db["ThingTypes"][type["TT_CORPSE_ID"]]
+        if t["T_LIFEPOINTS"] > type["TT_LIFEPOINTS"] \
+        and type_corpse["TT_TOOL"] == "food" \
+        and type_corpse["TT_TOOLPOWER"] > eat_cost:
+            return True
+        return False
+
+    def good_flee_target(m):
+        own_corpse_id = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
+        corpse_type = world_db["ThingTypes"][own_corpse_id]
+        targetness = 0 if corpse_type["TT_TOOL"] != "food" \
+                       else corpse_type["TT_TOOLPOWER"]
+        type = world_db["ThingTypes"][m["T_TYPE"]]
+        if t["T_LIFEPOINTS"] < type["TT_LIFEPOINTS"] \
+        and targetness > eat_vs_hunger_threshold(m["T_TYPE"]):
+            return True
+        return False
+
     def seeing_thing():
-        if t["fovmap"] and ("a" == filter or "f" == filter):
+        if t["fovmap"] and "a" == filter:
             for id in world_db["Things"]:
-                Thing = world_db["Things"][id]
-                if Thing != t and Thing["T_LIFEPOINTS"] and \
-                   t["T_TYPE"] != Thing["T_TYPE"] and \
-                   'v' == chr(t["fovmap"][(Thing["T_POSY"]
-                                          * world_db["MAP_LENGTH"])
-                                          + Thing["T_POSX"]]):
-                    ThingType = world_db["ThingTypes"][Thing["T_TYPE"]]
-                    if ("f" == filter and ThingType["TT_LIFEPOINTS"] >=
-                        t["T_LIFEPOINTS"]) \
-                       or ("a" == filter and ThingType["TT_LIFEPOINTS"] <
-                            t["T_LIFEPOINTS"]):
+                if animate_in_fov(world_db["Things"][id]):
+                    if good_attack_target(world_db["Things"][id]):
+                        return True
+        elif t["fovmap"] and "f" == filter:
+            for id in world_db["Things"]:
+                if animate_in_fov(world_db["Things"][id]):
+                    if good_flee_target(world_db["Things"][id]):
                         return True
         elif t["T_MEMMAP"] and "c" == filter:
             eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
@@ -867,28 +887,14 @@ def get_dir_to_target(t, filter):
         set_cells_passable_on_memmap_to_65534_on_scoremap()
         if "a" == filter:
             for id in world_db["Things"]:
-                Thing = world_db["Things"][id]
-                pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
-                    + Thing["T_POSX"]
-                if t != Thing and Thing["T_LIFEPOINTS"] and \
-                   t["T_TYPE"] != Thing["T_TYPE"] and \
-                   ord_v == t["fovmap"][pos] and \
-                   t["T_LIFEPOINTS"] > \
-                   world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
-                    set_map_score(pos, 0)
-                elif t["T_TYPE"] == Thing["T_TYPE"]:
-                    set_map_score(pos, 65535)
+                if animate_in_fov(world_db["Things"][id]) \
+                and good_attack_target(world_db["Things"][id]):
+                    set_map_score_at_thingpos(id, 0)
         elif "f" == filter:
-            for id in [id for id in world_db["Things"]
-                       if world_db["Things"][id]["T_LIFEPOINTS"]]:
-                Thing = world_db["Things"][id]
-                pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
-                    + Thing["T_POSX"]
-                if t["T_TYPE"] != Thing["T_TYPE"] and \
-                   ord_v == t["fovmap"][pos] and \
-                   t["T_LIFEPOINTS"] <= \
-                   world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
-                    set_map_score(pos, 0)
+            for id in world_db["Things"]:
+                if animate_in_fov(world_db["Things"][id]) \
+                and good_flee_target(world_db["Things"][id]):
+                    set_map_score_at_thingpos(id, 0)
         elif "c" == filter:
             eat_cost = eat_vs_hunger_threshold(t["T_TYPE"])
             for mt in [mt for mt in t["T_MEMTHING"]
@@ -901,6 +907,16 @@ def get_dir_to_target(t, filter):
                 set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0)
         elif "s" == filter:
             zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
+        if "a" != filter:
+            for id in world_db["Things"]:
+                if animate_in_fov(world_db["Things"][id]):
+                    if "f" == filter:
+                        pos = world_db["Things"][id]["T_POSY"] \
+                              * world_db["MAP_LENGTH"] \
+                              + world_db["Things"][id]["T_POSX"]
+                        if 0 == get_map_score(pos):
+                            continue
+                    set_map_score_at_thingpos(id, 65535)
 
     def rand_target_dir(neighbors, cmp, dirs):
         candidates = []
@@ -925,19 +941,6 @@ def get_dir_to_target(t, filter):
         dirs = "edcxsw"
         eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
         neighbors = get_neighbor_scores(dirs, eye_pos)
-        if "f" == filter:
-            inhabited = [world_db["Things"][id]["T_POSY"]
-                         * world_db["MAP_LENGTH"]
-                         + world_db["Things"][id]["T_POSX"]
-                         for id in world_db["Things"]
-                         if world_db["Things"][id]["T_LIFEPOINTS"]]
-            for i in range(len(dirs)):
-                mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"])
-                pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \
-                    + libpr.result_x()
-                for pos in [pos for pos in inhabited if pos == pos_cmp]:
-                    neighbors[i] = 65535
-                    break
         minmax_start = 0 if "f" == filter else 65535 - 1
         minmax_neighbor = minmax_start
         for i in range(len(dirs)):
@@ -948,19 +951,23 @@ def get_dir_to_target(t, filter):
         if minmax_neighbor != minmax_start:
             dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
         if "f" == filter:
+            distance = get_map_score(eye_pos)
+            fear_distance = world_db["MAP_LENGTH"]
+            if t["T_SATIATION"] < 0 and math.sqrt(-t["T_SATIATION"]) > 0:
+                fear_distance = fear_distance / math.sqrt(-t["T_SATIATION"])
+            attack_distance = 1
             if not dir_to_target:
-                if 1 == get_map_score(eye_pos):
-                    dir_to_target = rand_target_dir(neighbors, 0, dirs)
-                elif 3 >= get_map_score(eye_pos):
+                if attack_distance >= distance:
+                    dir_to_target = rand_target_dir(neighbors,
+                                                    distance - 1, dirs)
+                elif fear_distance >= distance:
                     t["T_COMMAND"] = [id for id in world_db["ThingActions"]
                                       if
                                       world_db["ThingActions"][id]["TA_NAME"]
                                       == "wait"][0]
                     return 1
-            elif dir_to_target and 3 < get_map_score(eye_pos):
+            elif dir_to_target and fear_distance < distance:
                 dir_to_target = 0
-        elif "a" == filter and 10 <= get_map_score(eye_pos):
-            dir_to_target = 0
         return dir_to_target
 
     dir_to_target = False
@@ -1019,32 +1026,27 @@ def get_inventory_slot_to_consume(t):
 
 
 def ai(t):
-    """Determine next command/argment for actor t via AI algorithms.
-
-    AI will look for, and move towards, enemies (animate Things not of their
-    own ThingType); if they see none, they will consume consumables in their
-    inventory; if there are none, they will pick up what they stand on if they
-    stand on consumables; if they stand on none, they will move towards the
-    next consumable they see or remember on the map; if they see or remember
-    none, they will explore parts of the map unseen since ever or for at least
-    one turn; if there is nothing to explore, they will simply wait.
-    """
+    """Determine next command/argment for actor t via AI algorithms."""
     t["T_COMMAND"] = [id for id in world_db["ThingActions"]
                       if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
-    if not get_dir_to_target(t, "f"):
-        sel = get_inventory_slot_to_consume(t)
-        if -1 != sel:
-            t["T_COMMAND"] = [id for id in world_db["ThingActions"]
-                              if world_db["ThingActions"][id]["TA_NAME"]
-                              == "use"][0]
-            t["T_ARGUMENT"] = sel
-        elif standing_on_food(t):
+    if get_dir_to_target(t, "f"):
+        return
+    sel = get_inventory_slot_to_consume(t)
+    if -1 != sel:
+        t["T_COMMAND"] = [id for id in world_db["ThingActions"]
+                          if world_db["ThingActions"][id]["TA_NAME"]
+                             == "use"][0]
+        t["T_ARGUMENT"] = sel
+    elif standing_on_food(t):
             t["T_COMMAND"] = [id for id in world_db["ThingActions"]
                               if world_db["ThingActions"][id]["TA_NAME"]
                               == "pick_up"][0]
-        elif (not get_dir_to_target(t, "c")) and \
-             (not get_dir_to_target(t, "a")):
-            get_dir_to_target(t, "s")
+    else:
+        going_to_known_food_spot = get_dir_to_target(t, "c")
+        if not going_to_known_food_spot:
+            aiming_for_walking_food = get_dir_to_target(t, "a")
+            if not aiming_for_walking_food:
+                get_dir_to_target(t, "s")
 
 
 def turn_over():
@@ -1138,6 +1140,15 @@ def id_setter(id, category, id_store=False, start_at_1=False):
     return id
 
 
+def command_plugin(str_plugin):
+    """Run code in plugins/[str_plugin]."""
+    if (str_plugin.replace("_", "").isalnum()
+        and os.access("plugins/" + str_plugin, os.F_OK)):
+        exec(open("plugins/" + str_plugin).read())
+        return
+    print("Bad plugin name:", str_plugin)
+
+
 def command_ping():
     """Send PONG line to server output file."""
     strong_write(io_db["file_out"], "PONG\n")
@@ -1184,37 +1195,80 @@ def command_thingshere(str_y, str_x):
         print("Ignoring: Command only works on existing worlds.")
 
 
-def play_commander(action, args=False):
-    """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
+def set_command(action):
+    """Set player's T_COMMAND, then call turn_over()."""
+    id = [x for x in world_db["ThingActions"]
+          if world_db["ThingActions"][x]["TA_NAME"] == action][0]
+    world_db["Things"][0]["T_COMMAND"] = id
+    turn_over()
 
-    T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
-    """
 
-    def set_command():
-        id = [x for x in world_db["ThingActions"]
-              if world_db["ThingActions"][x]["TA_NAME"] == action][0]
-        world_db["Things"][0]["T_COMMAND"] = id
-        turn_over()
+def play_wait():
+    """Try "wait" as player's T_COMMAND."""
+    set_command("wait")
+
+
+def play_pickup():
+    """Try "pick_up" as player's T_COMMAND"."""
+    t = world_db["Things"][0]
+    ids = [id for id in world_db["Things"] if id
+           if not world_db["Things"][id]["carried"]
+           if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
+           if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
+    if not len(ids):
+         log("NOTHING to pick up.")
+    else:
+        set_command("pick_up")
+
 
-    def set_command_and_argument_int(str_arg):
+def play_drop(str_arg):
+    """Try "drop" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
+    t = world_db["Things"][0]
+    if 0 == len(t["T_CARRIES"]):
+        log("You have NOTHING to drop in your inventory.")
+    else:
         val = integer_test(str_arg, 0, 255)
-        if None != val:
+        if None != val and val < len(t["T_CARRIES"]):
             world_db["Things"][0]["T_ARGUMENT"] = val
-            set_command()
-
-    def set_command_and_argument_movestring(str_arg):
-        if str_arg in directions_db:
-            world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
-            set_command()
+            set_command("drop")
         else:
-            print("Ignoring: Argument must be valid direction string.")
+            print("Illegal inventory index.")
 
-    if action == "move":
-        return set_command_and_argument_movestring
-    elif args:
-        return set_command_and_argument_int
+
+def play_use(str_arg):
+    """Try "use" as player's T_COMMAND, int(str_arg) as T_ARGUMENT / slot."""
+    t = world_db["Things"][0]
+    if 0 == len(t["T_CARRIES"]):
+        log("You have NOTHING to use in your inventory.")
     else:
-        return set_command
+        val = integer_test(str_arg, 0, 255)
+        if None != val and val < len(t["T_CARRIES"]):
+            id = t["T_CARRIES"][val]
+            type = world_db["Things"][id]["T_TYPE"]
+            if not world_db["ThingTypes"][type]["TT_TOOL"] == "food":
+                log("You CAN'T consume this thing.")
+                return
+            world_db["Things"][0]["T_ARGUMENT"] = val
+            set_command("use")
+        else:
+            print("Illegal inventory index.")
+
+
+def play_move(str_arg):
+    """Try "move" as player's T_COMMAND, str_arg as T_ARGUMENT / direction."""
+    t = world_db["Things"][0]
+    if not str_arg in directions_db:
+        print("Illegal move direction string.")
+        return
+    dir = ord(directions_db[str_arg])
+    move_result = mv_yx_in_dir_legal(chr(dir), t["T_POSY"], t["T_POSX"])
+    if 1 == move_result[0]:
+        pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
+        if ord(".") == world_db["MAP"][pos]:
+            world_db["Things"][0]["T_ARGUMENT"] = dir
+            set_command("move")
+            return
+    log("You CAN'T move there.")
 
 
 def command_seedrandomness(seed_string):
@@ -1619,6 +1673,7 @@ be ignored in replay mode if read from server input file), and ([2]) a function
 to be called on it.
 """
 commands_db = {
+    "PLUGIN": (1, True, command_plugin),
     "QUIT": (0, True, command_quit),
     "PING": (0, True, command_ping),
     "THINGS_HERE": (2, True, command_thingshere),
@@ -1656,11 +1711,11 @@ commands_db = {
     "T_MEMTHING": (3, False, command_tmemthing),
     "T_POSY": (1, False, setter_tpos("Y")),
     "T_POSX": (1, False, setter_tpos("X")),
-    "wait": (0, False, play_commander("wait")),
-    "move": (1, False, play_commander("move")),
-    "pick_up": (0, False, play_commander("pick_up")),
-    "drop": (1, False, play_commander("drop", True)),
-    "use": (1, False, play_commander("use", True)),
+    "wait": (0, False, play_wait),
+    "move": (1, False, play_move),
+    "pick_up": (0, False, play_pickup),
+    "drop": (1, False, play_drop),
+    "use": (1, False, play_use),
     "ai": (0, False, command_ai)
 }
 # TODO: Unhandled cases: (Un-)killing animates (esp. player!) with T_LIFEPOINTS.