github / src / game.py @ 0ce7e35f
1 |
# Copyright (c) 2016, LE GOFF Vincent
|
---|---|
2 |
# All rights reserved.
|
3 |
|
4 |
# Redistribution and use in source and binary forms, with or without
|
5 |
# modification, are permitted provided that the following conditions are met:
|
6 |
|
7 |
# * Redistributions of source code must retain the above copyright notice, this
|
8 |
# list of conditions and the following disclaimer.
|
9 |
|
10 |
# * Redistributions in binary form must reproduce the above copyright notice,
|
11 |
# this list of conditions and the following disclaimer in the documentation
|
12 |
# and/or other materials provided with the distribution.
|
13 |
|
14 |
# * Neither the name of ytranslate nor the names of its
|
15 |
# contributors may be used to endorse or promote products derived from
|
16 |
# this software without specific prior written permission.
|
17 |
|
18 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19 |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21 |
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24 |
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25 |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26 |
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27 |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28 |
|
29 |
"""This file contains the GameEngine class."""
|
30 |
|
31 |
import os |
32 |
|
33 |
from enum import Enum |
34 |
from twisted.internet import ssl, reactor |
35 |
|
36 |
from client import CocoFactory |
37 |
from config import Settings |
38 |
from log import logger, begin |
39 |
from sharp.engine import SharpScript |
40 |
from world import World, MergingMethod |
41 |
|
42 |
class Level(Enum): |
43 |
|
44 |
"""Enumeration for a feature level.
|
45 |
|
46 |
Features at the top level have the value "engine". They will be
|
47 |
common across all worlds and characters. Features are often defined
|
48 |
at the world level (common across characters) or at the character
|
49 |
level (specific to this character).
|
50 |
|
51 |
For instance, look at the macros, triggers and aliases.
|
52 |
|
53 |
"""
|
54 |
|
55 |
engine = 1
|
56 |
world = 2
|
57 |
character = 3
|
58 |
category = 4
|
59 |
|
60 |
|
61 |
class GameEngine: |
62 |
|
63 |
"""A class representing the game engine.
|
64 |
|
65 |
An instance of this class is to be created each time the program
|
66 |
runs. It doesn't handle thegraphical user interface, but centralizes
|
67 |
about anything else: the main configuration, world configuration
|
68 |
of different games, aliases, macros, triggers and so on. The
|
69 |
GUI has a direct access to the engine and can therefore access it.
|
70 |
|
71 |
"""
|
72 |
|
73 |
def __init__(self, config_dir="."): |
74 |
self.logger = logger("") |
75 |
begin() |
76 |
self.config_dir = config_dir
|
77 |
if config_dir != ".": |
78 |
self.logger.info(f"Using an alternative config directory: {config_dir}") |
79 |
|
80 |
self.settings = Settings(self, config_dir) |
81 |
self.sounds = True |
82 |
self.worlds = {}
|
83 |
self.default_world = None |
84 |
self.level = Level.engine
|
85 |
self.logger.info("CocoMUD engine started") |
86 |
|
87 |
def load(self): |
88 |
"""Load the configuration."""
|
89 |
self.logger.info("Loading the user's configuration...") |
90 |
self.settings.load()
|
91 |
self.TTS_on = self.settings["options.TTS.on"] |
92 |
self.TTS_outside = self.settings["options.TTS.outside"] |
93 |
|
94 |
# For each world, set the game engine
|
95 |
for world in self.worlds.values(): |
96 |
world.engine = self
|
97 |
|
98 |
def open(self, host, port, world, panel=None): |
99 |
"""Connect to the specified host and port.
|
100 |
|
101 |
This method creates and returns a 'Factory' class initialized
|
102 |
with the specified information. It also tries to connect a
|
103 |
client to this factory.
|
104 |
|
105 |
"""
|
106 |
self.logger.info("Creating a client for {host}:{port}".format( |
107 |
host=host, port=port)) |
108 |
|
109 |
self.prepare_world(world)
|
110 |
factory = CocoFactory(world, panel) |
111 |
|
112 |
if world.protocol.lower() == "ssl": |
113 |
reactor.connectSSL(host, port, factory, |
114 |
ssl.ClientContextFactory()) |
115 |
else:
|
116 |
reactor.connectTCP(host, port, factory) |
117 |
|
118 |
return factory
|
119 |
|
120 |
def open_help(self, name): |
121 |
"""Open the selected help file in HTML format.
|
122 |
|
123 |
This method open the browser with the appropriate file.
|
124 |
The file is the one in the user's language, unless it cannot
|
125 |
be found.
|
126 |
|
127 |
"""
|
128 |
lang = self.settings.get_language()
|
129 |
filename = name + ".html"
|
130 |
path = os.path.join(self.config_dir, "doc", lang, filename) |
131 |
if os.path.exists(path):
|
132 |
self.logger.debug("Open the help file for {} (lang={})".format( |
133 |
name, lang)) |
134 |
os.startfile(path) |
135 |
return
|
136 |
|
137 |
# Try English
|
138 |
path = os.path.join(self.config_dir, "doc", "en", filename) |
139 |
if os.path.exists(path):
|
140 |
self.logger.debug("Open the help file for {} (lang=en)".format( |
141 |
name)) |
142 |
os.startfile(path) |
143 |
return
|
144 |
|
145 |
# Neither worked
|
146 |
self.logger.warning("The documentation for the {} help file " \ |
147 |
"cannot be found, either using lang={} or lang=en".format(
|
148 |
name, lang)) |
149 |
|
150 |
def get_world(self, name): |
151 |
"""Return the selected world either by its name or location."""
|
152 |
name = name.lower() |
153 |
for world in self.worlds.values(): |
154 |
if world.name.lower() == name:
|
155 |
return world
|
156 |
elif world.location == name:
|
157 |
return world
|
158 |
|
159 |
return None |
160 |
|
161 |
def create_world(self, name): |
162 |
"""Create a world."""
|
163 |
world = World(name.lower()) |
164 |
world.engine = self
|
165 |
return world
|
166 |
|
167 |
def prepare_world(self, world, merge=None): |
168 |
"""Prepare the world, creating appropriate values."""
|
169 |
if not world.sharp_engine: |
170 |
sharp_engine = SharpScript(self, None, world) |
171 |
world.sharp_engine = sharp_engine |
172 |
|
173 |
if merge is not None: |
174 |
if merge == "ignore": |
175 |
world.merging = MergingMethod.ignore |
176 |
elif merge == "replace": |
177 |
world.merging = MergingMethod.replace |
178 |
else:
|
179 |
raise ValueError("unkwno merging method: {}".format( |
180 |
merge)) |
181 |
|
182 |
def stop(self): |
183 |
"""Stop the game engine and close the sessions."""
|
184 |
reactor.stop() |