-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.py
220 lines (193 loc) · 7.35 KB
/
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# server.py
import asyncio
import sqlite3
from config import HOST, PORT, DATABASE_FILE, INITIAL_MONEY, STARTING_REGION, DEFAULT_STAMINA, DEFAULT_MANA
from utils.database import init_db
from characters.creation import create_new_character, load_character_to_player
from utils.helpers import register_player, unregister_player
from utils.commands import parse_command
from utils.colors import RESET
from world.area_generator import ensure_world_generated
class PlayerConnection:
"""
Rappresenta la connessione di un singolo giocatore.
Contiene attributi del personaggio e metodi per l'interazione con il server.
"""
def __init__(self, reader, writer):
self.reader = reader
self.writer = writer
self.client_address = writer.get_extra_info('peername')
# Identificativi e stato di autenticazione
self.name = None
self.character_loaded = False
self.authenticated = False
# Flag di morte
self.dead = False
# Attributi del personaggio di base
self.class_name = None
self.level = 1
self.exp = 0
# Punti ferita
self.hp = 100
self.max_hp = 100
# Risorse base
self.money = 0
self.inventory = []
# Posizione
self.x = 25
self.y = 25
self.region = STARTING_REGION
# Achievements, quests
self.achievements = []
self.quests = []
# Risorse di "energia"
self.stamina = DEFAULT_STAMINA
self.mana = DEFAULT_MANA
# NUOVI ATTRIBUTI: Forza, Intelligenza, Magia
self.strength = 0
self.intelligence = 0
self.magic = 0
# Se desideri implementare resistenze per il giocatore
self.resistances = {
"physical": 0, # Puoi modificare questi valori come preferisci
"magical": 0
}
async def send(self, message):
"""
Invia un messaggio testuale al client, terminandolo con \r\n.
Usa writer.drain() per assicurarsi che i dati siano inviati su socket.
"""
if self.dead:
return
try:
self.writer.write((message + "\r\n").encode('utf-8'))
await self.writer.drain()
except ConnectionResetError:
# Il client potrebbe essersi disconnesso bruscamente
print(f"Connessione persa con {self.name if self.name else self.client_address}.")
self.dead = True
async def prompt_name(self):
"""
Chiede il nome del giocatore all'avvio.
"""
await self.send("Benvenuto in Mid-World, pellegrino. Inserisci il tuo nome:")
try:
data = await self.reader.readline()
if not data:
return False
self.name = data.decode('utf-8').strip()
return True
except UnicodeDecodeError:
await self.send("Nome non valido. Riprova.")
return False
def character_exists(self, name):
"""
Controlla se esiste già un personaggio con questo nome nel database.
"""
conn = sqlite3.connect(DATABASE_FILE)
cur = conn.cursor()
cur.execute("SELECT name FROM characters WHERE name=?", (name,))
res = cur.fetchone()
conn.close()
return True if res else False
async def authenticate(self):
"""
Se il personaggio non esiste, lo crea;
altrimenti carica i suoi dati.
"""
if not self.character_exists(self.name):
await self.send("Creazione nuovo personaggio...")
await create_new_character(self)
else:
await self.send(f"Bentornato, {self.name}!")
load_character_to_player(self, self.name)
self.authenticated = True
self.character_loaded = True
async def main_loop(self):
"""
Ciclo principale di ricezione comandi dal giocatore.
"""
await self.send(f"Sei nel {self.region}. Un vento caldo soffia da ovest, portando con sé il ricordo della Torre Nera.")
while True:
try:
data = await self.reader.readline()
if not data:
break # Il client ha chiuso la connessione
try:
# Decodifica ignorando i byte non validi
command = data.decode('utf-8', errors='ignore').strip()
except UnicodeDecodeError:
await self.send("Comando non valido. Usa caratteri standard.")
continue
if command:
response = await parse_command(self, command)
if response:
await self.send(response)
except ConnectionResetError:
print(f"Connessione persa con {self.name if self.name else self.client_address}.")
break
except Exception as e:
# Log di qualsiasi altra eccezione per debug
print(f"Errore durante la gestione del comando da {self.name if self.name else self.client_address}: {e}")
await self.send("Si è verificato un errore interno. Riprova più tardi.")
def receive_damage(self, damage, damage_type):
"""
Riduce i punti ferita (hp) del giocatore in base al danno e al tipo di danno.
Considera le resistenze se definite.
:param damage: Danno in ingresso
:param damage_type: Tipo di danno ("physical", "magical", ecc.)
:return: Danno effettivamente inflitto dopo la resistenza
"""
resistance_value = self.resistances.get(damage_type, 0)
actual_damage = max(damage - resistance_value, 0)
self.hp -= actual_damage
if self.hp <= 0:
self.hp = 0
self.dead = True
return actual_damage
async def handle_client(reader, writer):
"""
Coroutine che gestisce un singolo client: crea un PlayerConnection,
esegue l'autenticazione e il loop principale, e poi chiude pulizia.
"""
conn = PlayerConnection(reader, writer)
# Prompt iniziale per il nome
if not await conn.prompt_name():
writer.close()
return
# Autenticazione/Caricamento personaggio
await conn.authenticate()
if conn.dead:
await conn.send("Sei morto. Non puoi continuare.")
writer.close()
return
# Registra il player nella lista dei connessi
register_player(conn)
try:
# Ciclo principale di comandi
await conn.main_loop()
except ConnectionResetError:
print(f"Connessione persa con {conn.name if conn.name else conn.client_address}.")
finally:
unregister_player(conn)
writer.close()
async def main():
"""
Punto di ingresso del server:
- Inizializza il DB
- Assicura la generazione della mappa
- Avvia il server su (HOST, PORT)
- Gestisce i client in arrivo con handle_client
"""
init_db() # Crea tabelle se non esistono
ensure_world_generated() # Genera la mappa se non esiste
server = await asyncio.start_server(handle_client, HOST, PORT)
addrs = ", ".join(str(sock.getsockname()) for sock in server.sockets)
print(f"Server avviato su {addrs}")
async with server:
await server.serve_forever()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nServer interrotto da tastiera.")