Crossfire Server, Trunk
dragon.py
Go to the documentation of this file.
1 """
2 dragon.py -- talking dragon that flies you places
3 Kevin Zheng 2024
4 
5 This replaces the old dragon hangers, where every location that wanted a dragon
6 hanger needed to copy/paste a template map.
7 
8 Usage: Put an event_say and event_apply handler that calls this script on a
9 dragon_exit.
10 """
11 
12 import math
13 import re
14 
15 import Crossfire
16 
17 world_map_path_matcher = r"/world/world_(\d\d\d)_(\d\d\d)";
18 
19 activator = Crossfire.WhoIsActivator()
20 event = Crossfire.WhatIsEvent()
21 whoami = Crossfire.WhoAmI()
22 
23 price_per_worldmap_tile = 5*50 # price per world map tile traveled, in money units (silver)
24 max_fare = 150*50 # maximum fare per trip, in money units (silver)
25 
26 # All the fun places we can go! Tuple of map path and X, Y coordinate.
27 destinations = {
28  'Port Joseph': ('/world/world_101_114', 16, 39),
29  'Red Town': ('/pup_land/terminal', 10, 12),
30  'Wolfsburg': ('/world/world_128_109', 35, 13),
31  'Brest': ('/world/world_107_123', 32, 30),
32  'Navar': ('/world/world_121_116', 37, 46),
33  'Darcap': ('/world/world_116_102', 29, 37),
34  'Stoneville': ('/world/world_103_127', 5, 15),
35  'Scorn': ('/world/world_105_115', 5, 37),
36  'Lake Country': ('/world/world_109_126', 16, 20),
37  'Santo Dominion': ('/world/world_102_108', 17, 12),
38 # 'Nurnberg': ('/pup_land/nurnberg/city', 25, 15), # needs a passport check
39 }
40 
41 dest_searchable = {} # searchable index of destinations along with a canonical name
42 
43 for key, val in destinations.items():
44  dest_searchable[key.upper()] = (key, val)
45 
46 # Some maps aren't on the world map, so give them coordinate overrides so that
47 # they don't charge max price but still respect distance-based fares.
48 coord_override = {
49  '/pup_land/terminal': (94, 115), # 50 platinum from Scorn
50 }
51 
53  name = name.upper()
54  if name in dest_searchable:
55  return dest_searchable[name]
56  else:
57  return None
58 
59 def world_map_coord(path):
60  """Try to extract the coordinates from a world map path."""
61  if path in coord_override:
62  return coord_override[path]
63  groups = re.match(world_map_path_matcher, path)
64  if groups is not None:
65  coords = groups.group(1, 2)
66  cx, cy = int(coords[0]), int(coords[1])
67  return cx, cy
68  return None
69 
70 def fare(dest):
71  curr_coord = world_map_coord(whoami.Map.Path)
72  dest_coord = world_map_coord(dest[0])
73  if curr_coord is None or dest_coord is None:
74  return max_fare
75  else:
76  return min(max_fare, dist_fare(curr_coord, dest_coord))
77 
78 def dist_fare(start, end):
79  dist = math.hypot(end[0] - start[0], end[1] - start[1])
80  return math.ceil(dist * price_per_worldmap_tile)
81 
82 # State for each player. Dict (player_name: str, state) where state is
83 # (destination: str, price: int).
84 state = Crossfire.GetPrivateDictionary()
85 
86 def handle_say():
87  msg = Crossfire.WhatIsMessage()
88  text = msg.split()
89  if text[0] == "what":
90  whoami.Say("Dragon Express can whisk you to one of %d locations for a small fee. Travel faster today!" % len(destinations))
91  return
92  elif text[0] == "where":
93  whoami.Say("We have %d exciting destinations: %s. Where would you like to go?" % (len(destinations), ", ".join(destinations.keys())))
94  return
95  elif text[0] == "yes" and activator.Name in state:
96  dest_name = state[activator.Name][0]
97  price = state[activator.Name][1]
98  dest = destinations[dest_name]
99  m = Crossfire.ReadyMap(dest[0])
100  if not m:
101  whoami.Say("Oops, it looks like the landing site there is not clear. Let's try to go somewhere else.")
102  elif activator.PayAmount(price):
103  activator.Message("You pay the %s %s" % (whoami.Name, Crossfire.CostStringFromValue(price)))
104  activator.Message("You hop on the %s and it takes off. You enjoy a pleasant ride above the clouds before arriving at %s." % (whoami.Name, dest_name))
105  activator.Teleport(m, dest[1], dest[2])
106  else:
107  whoami.Say("It doesn't look like you can afford this trip. Please come back when you can.")
108  del(state[activator.Name])
109  return
110 
111  dest = search_destination(msg)
112  if dest is not None:
113  dest_name = dest[0]
114  dest = dest[1]
115  price = fare(dest)
116  whoami.Say("Alright, let's go to %s. That will cost %s. Is that okay?" % (dest_name, Crossfire.CostStringFromValue(price)))
117  Crossfire.AddReply("yes", "Okay, let's go.")
118  Crossfire.AddReply("no", "No thanks.")
119  state[activator.Name] = (dest_name, price)
120  else:
121  whoami.Say("Welcome to Dragon Express. Where can I take you today?")
122  Crossfire.AddReply("what", "What is Dragon Express?")
123  Crossfire.AddReply("where", "Where can you take me?")
124 
125 Crossfire.SetReturnValue(1)
126 if event.Subtype == Crossfire.EventType.SAY:
127  handle_say()
128 else:
129  whoami.Say("Hey, you can't get on without paying!")
dragon.dist_fare
def dist_fare(start, end)
Definition: dragon.py:78
dragon.search_destination
def search_destination(name)
Definition: dragon.py:52
dragon.fare
def fare(dest)
Definition: dragon.py:70
dragon.handle_say
def handle_say()
Definition: dragon.py:86
make_face_from_files.int
int
Definition: make_face_from_files.py:32
dragon.world_map_coord
def world_map_coord(path)
Definition: dragon.py:59