Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Broadcasting Messages - rozkazy dla legionu

Witaj, główny zwiadowco legionu! Konsul Caesar.js potrzebuje sprawnego systemu przekazywania rozkazów i informacji do wszystkich jednostek legionu. Broadcasting to jak potężny ogień sygnałowy na wieży strażniczej - musi dotrzeć do każdego fortu, niezależnie od odległości czy warunków!

Strategie Broadcasting w Socket.IO

Wyobraź sobie, że jesteś głównym zwiadowcą sztabu dowodzenia. Musisz umieć:

  • Wysyłać rozkazy do całego legionu jednocześnie
  • Kierować wiadomości do konkretnych grup kohort
  • Organizować komunikację w pokojach tematycznych
  • Zarządzać priorytetami wiadomości
  • Filtrować odbiorców według kryteriów

Centralny Serwis Broadcasting

1// services/legion-broadcast.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { LegionCommandGateway } from '../gateways/legion-command.gateway';
4import { EventEmitter2 } from '@nestjs/event-emitter';
5
6export enum BroadcastType {
7 LEGION_WIDE = 'legion-wide',
8 ROOM_BASED = 'room-based',
9 TARGETED = 'targeted',
10 PROXIMITY = 'proximity',
11 CONDITIONAL = 'conditional',
12}
13
14export enum MessagePriority {
15 ROUTINE = 'ROUTINE',
16 PRIORITY = 'PRIORITY',
17 IMMEDIATE = 'IMMEDIATE',
18 FLASH = 'FLASH',
19 EMERGENCY = 'EMERGENCY',
20}
21
22export interface BroadcastMessage {
23 id: string;
24 from: string;
25 content: string;
26 priority: MessagePriority;
27 type: BroadcastType;
28 timestamp: Date;
29 expiresAt?: Date;
30 acknowledgmentRequired?: boolean;
31 targetCriteria?: any;
32}
33
34@Injectable()
35export class LegionBroadcastService {
36 private readonly logger = new Logger(LegionBroadcastService.name);
37 private messageHistory = new Map<string, BroadcastMessage>();
38 private acknowledgments = new Map<string, Set<string>>();
39 
40 constructor(
41 private legionGateway: LegionCommandGateway,
42 private eventEmitter: EventEmitter2,
43 ) {}
44
45 // Broadcast do całego legionu
46 async broadcastToLegion(
47 message: string,
48 priority: MessagePriority = MessagePriority.ROUTINE,
49 options?: {
50 from?: string;
51 acknowledgmentRequired?: boolean;
52 expiresIn?: number; // w sekundach
53 excludeLegions?: string[];
54 }
55 ): Promise<BroadcastResult> {
56 const broadcastId = this.generateBroadcastId();
57 const timestamp = new Date();
58 
59 const broadcastMessage: BroadcastMessage = {
60 id: broadcastId,
61 from: options?.from || 'Legion Command',
62 content: message,
63 priority,
64 type: BroadcastType.LEGION_WIDE,
65 timestamp,
66 expiresAt: options?.expiresIn ? 
67 new Date(timestamp.getTime() + options.expiresIn * 1000) : undefined,
68 acknowledgmentRequired: options?.acknowledgmentRequired || false,
69 };
70 
71 // Zapisz w historii
72 this.messageHistory.set(broadcastId, broadcastMessage);
73 
74 // Przygotuj dane do wysłania
75 const broadcastData = {
76 id: broadcastId,
77 message,
78 priority,
79 from: broadcastMessage.from,
80 timestamp: timestamp.toISOString(),
81 requiresAck: broadcastMessage.acknowledgmentRequired,
82 broadcastType: 'LEGION_WIDE',
83 };
84 
85 // Wyślij do wszystkich kohort
86 const legions = this.legionGateway.getLegions();
87 let deliveredCount = 0;
88 
89 for (const cohort of legions) {
90 // Sprawdź czy fort nie jest wykluczony
91 if (options?.excludeLegions?.includes(cohort.id)) {
92 continue;
93 }
94 
95 try {
96 this.legionGateway.broadcastToLegion(cohort.id, 'legion:broadcast', broadcastData);
97 deliveredCount++;
98 } catch (error) {
99 this.logger.error(`Błąd wysyłania do ${cohort.name}:`, error);
100 }
101 }
102 
103 // Inicjalizuj śledzenie potwierdzeń
104 if (broadcastMessage.acknowledgmentRequired) {
105 this.acknowledgments.set(broadcastId, new Set());
106 
107 // Timeout dla potwierdzeń
108 setTimeout(() => {
109 this.checkAcknowledgments(broadcastId);
110 }, 60000); // 1 minuta
111 }
112 
113 this.logger.log(`Legion broadcast [${priority}] delivered to ${deliveredCount} cohorts`);
114 
115 return {
116 broadcastId,
117 deliveredTo: deliveredCount,
118 totalLegions: legions.length,
119 excluded: options?.excludeLegions?.length || 0,
120 };
121 }
122
123 // Broadcast do pokoju
124 async broadcastToRoom(
125 roomName: string,
126 message: string,
127 priority: MessagePriority = MessagePriority.ROUTINE,
128 options?: {
129 from?: string;
130 acknowledgmentRequired?: boolean;
131 }
132 ): Promise<BroadcastResult> {
133 const broadcastId = this.generateBroadcastId();
134 
135 const broadcastData = {
136 id: broadcastId,
137 message,
138 priority,
139 from: options?.from || 'Room Command',
140 timestamp: new Date().toISOString(),
141 requiresAck: options?.acknowledgmentRequired || false,
142 broadcastType: 'ROOM',
143 roomName,
144 };
145 
146 // Wyślij do pokoju
147 this.legionGateway.broadcastToRoom(roomName, 'room:broadcast', broadcastData);
148 
149 const roomMembers = this.legionGateway.getRoomMembers(roomName);
150 
151 this.logger.log(`Room broadcast [${priority}] to ${roomName} (${roomMembers.length} members)`);
152 
153 return {
154 broadcastId,
155 deliveredTo: roomMembers.length,
156 totalLegions: roomMembers.length,
157 roomName,
158 };
159 }
160
161 // Broadcast oparty na proximity (odległości)
162 async broadcastToProximity(
163 centerLocation: { lat: number; lng: number },
164 radiusRomanMiles: number,
165 message: string,
166 priority: MessagePriority = MessagePriority.ROUTINE,
167 options?: {
168 from?: string;
169 excludeLegions?: string[];
170 }
171 ): Promise<BroadcastResult> {
172 const broadcastId = this.generateBroadcastId();
173 const nearbyLegions = this.findLegionsInRadius(centerLocation, radiusRomanMiles);
174 
175 const broadcastData = {
176 id: broadcastId,
177 message,
178 priority,
179 from: options?.from || 'Regional Command',
180 timestamp: new Date().toISOString(),
181 broadcastType: 'PROXIMITY',
182 centerLocation,
183 radius: radiusRomanMiles,
184 };
185 
186 let deliveredCount = 0;
187 
188 for (const cohort of nearbyLegions) {
189 if (options?.excludeLegions?.includes(cohort.id)) {
190 continue;
191 }
192 
193 try {
194 this.legionGateway.broadcastToLegion(cohort.id, 'proximity:broadcast', broadcastData);
195 deliveredCount++;
196 } catch (error) {
197 this.logger.error(`Błąd wysyłania do ${cohort.name}:`, error);
198 }
199 }
200 
201 this.logger.log(` Proximity broadcast [${priority}] to ${deliveredCount} cohorts within ${radiusRomanMiles}nm`);
202 
203 return {
204 broadcastId,
205 deliveredTo: deliveredCount,
206 totalLegions: nearbyLegions.length,
207 radius: radiusRomanMiles,
208 };
209 }
210
211 // Broadcast warunkowy (z filtrami)
212 async broadcastConditional(
213 message: string,
214 conditions: {
215 cohortTypes?: string[];
216 minRank?: string;
217 maxDistance?: number;
218 fromLocation?: { lat: number; lng: number };
219 status?: string[];
220 hasCapability?: string[];
221 },
222 priority: MessagePriority = MessagePriority.ROUTINE,
223 options?: {
224 from?: string;
225 acknowledgmentRequired?: boolean;
226 }
227 ): Promise<BroadcastResult> {
228 const broadcastId = this.generateBroadcastId();
229 const eligibleLegions = this.filterLegionsByConditions(conditions);
230 
231 const broadcastData = {
232 id: broadcastId,
233 message,
234 priority,
235 from: options?.from || 'Conditional Command',
236 timestamp: new Date().toISOString(),
237 requiresAck: options?.acknowledgmentRequired || false,
238 broadcastType: 'CONDITIONAL',
239 conditions,
240 };
241 
242 let deliveredCount = 0;
243 
244 for (const cohort of eligibleLegions) {
245 try {
246 this.legionGateway.broadcastToLegion(cohort.id, 'conditional:broadcast', broadcastData);
247 deliveredCount++;
248 } catch (error) {
249 this.logger.error(`Błąd wysyłania do ${cohort.name}:`, error);
250 }
251 }
252 
253 this.logger.log(` Conditional broadcast [${priority}] to ${deliveredCount} eligible cohorts`);
254 
255 return {
256 broadcastId,
257 deliveredTo: deliveredCount,
258 totalLegions: eligibleLegions.length,
259 conditions,
260 };
261 }
262
263 // Broadcast awaryjny z najwyższym priorytetem
264 async emergencyBroadcast(
265 emergency: {
266 type: 'SOS' | 'ATTACK' | 'STORM' | 'FIRE' | 'COLLISION' | 'MUTINY';
267 location: { lat: number; lng: number };
268 description: string;
269 severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
270 requiredAssistance?: string[];
271 },
272 fromLegion: string
273 ): Promise<BroadcastResult> {
274 const broadcastId = this.generateBroadcastId();
275 
276 const emergencyData = {
277 id: broadcastId,
278 alert: ' EMERGENCY ALERT ',
279 emergency,
280 from: fromLegion,
281 priority: MessagePriority.EMERGENCY,
282 timestamp: new Date().toISOString(),
283 requiresAck: true,
284 broadcastType: 'EMERGENCY',
285 };
286 
287 // Wyślij do wszystkich kohort natychmiast
288 this.legionGateway.broadcastToLegion('emergency:alert', emergencyData);
289 
290 // Znajdź forty ratunkowe w pobliżu
291 const nearbyRescueLegions = this.findRescueLegionsNearby(emergency.location, 100);
292 
293 // Wyślij specjalną wiadomość do kohort ratunkowych
294 for (const rescueLegion of nearbyRescueLegions) {
295 this.legionGateway.broadcastToLegion(rescueLegion.id, 'rescue:mission', {
296 ...emergencyData,
297 missionType: 'RESCUE_OPERATION',
298 distance: this.calculateDistance(rescueLegion.location, emergency.location),
299 estimatedArrival: this.calculateETA(rescueLegion, emergency.location),
300 });
301 }
302 
303 // Powiadom władze prowincji
304 this.notifyPortAuthorities(emergency);
305 
306 // Zapisz w systemie awaryjnym
307 this.logEmergency(broadcastId, emergency, fromLegion);
308 
309 this.logger.error(` EMERGENCY BROADCAST: ${emergency.type} from ${fromLegion}`);
310 
311 return {
312 broadcastId,
313 deliveredTo: this.legionGateway.getLegions().length,
314 totalLegions: this.legionGateway.getLegions().length,
315 rescueLegionsNotified: nearbyRescueLegions.length,
316 emergency: true,
317 };
318 }
319
320 // Potwierdzenie odbioru wiadomości
321 async acknowledgeBroadcast(broadcastId: string, cohortId: string): Promise<boolean> {
322 const acks = this.acknowledgments.get(broadcastId);
323 
324 if (!acks) {
325 this.logger.warn(`Próba potwierdzenia nieistniejącego broadcastu: ${broadcastId}`);
326 return false;
327 }
328 
329 acks.add(cohortId);
330 
331 const broadcast = this.messageHistory.get(broadcastId);
332 if (broadcast) {
333 this.logger.log(`✅ Legion ${cohortId} acknowledged broadcast: ${broadcast.content.substring(0, 50)}...`);
334 }
335 
336 return true;
337 }
338
339 // Sprawdzenie status potwierdzeń
340 getBroadcastAcknowledgments(broadcastId: string): {
341 total: number;
342 acknowledged: number;
343 pending: string[];
344 } {
345 const acks = this.acknowledgments.get(broadcastId);
346 const broadcast = this.messageHistory.get(broadcastId);
347 
348 if (!acks || !broadcast) {
349 return { total: 0, acknowledged: 0, pending: [] };
350 }
351 
352 const allLegions = this.legionGateway.getLegions();
353 const acknowledgedLegions = Array.from(acks);
354 const pendingLegions = allLegions
355 .filter(cohort => !acknowledgedLegions.includes(cohort.id))
356 .map(cohort => cohort.name);
357 
358 return {
359 total: allLegions.length,
360 acknowledged: acknowledgedLegions.length,
361 pending: pendingLegions,
362 };
363 }
364
365 // Metody pomocnicze
366 private generateBroadcastId(): string {
367 return `BCAST-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`
1 }
2
3 private findLegionsInRadius(
4 center: { lat: number; lng: number },
5 radiusRomanMiles: number
6 ): any[] {
7 return this.legionGateway.getLegions().filter(cohort => {
8 const distance = this.calculateDistance(center, cohort.location);
9 return distance <= radiusRomanMiles;
10 });
11 }
12
13 private filterLegionsByConditions(conditions: any): any[] {
14 return this.legionGateway.getLegions().filter(cohort => {
15 // Filtruj według typu fortu
16 if (conditions.cohortTypes && !conditions.cohortTypes.includes(cohort.type)) {
17 return false;
18 }
19 
20 // Filtruj według statusu
21 if (conditions.status && !conditions.status.includes(cohort.status)) {
22 return false;
23 }
24 
25 // Filtruj według odległości
26 if (conditions.maxDistance && conditions.fromLocation) {
27 const distance = this.calculateDistance(conditions.fromLocation, cohort.location);
28 if (distance > conditions.maxDistance) {
29 return false;
30 }
31 }
32 
33 return true;
34 });
35 }
36
37 private findRescueLegionsNearby(
38 location: { lat: number; lng: number },
39 maxDistance: number
40 ): any[] {
41 return this.legionGateway.getLegions().filter(cohort => {
42 const isRescueLegion = ['RESCUE', 'HOSPITAL', 'SUPPORT'].includes(cohort.type);
43 const distance = this.calculateDistance(location, cohort.location);
44 return isRescueLegion && distance <= maxDistance;
45 });
46 }
47
48 private calculateDistance(
49 pos1: { lat: number; lng: number },
50 pos2: { lat: number; lng: number }
51 ): number {
52 // Formuła Haversine w milach rzymskich
53 const R = 3440.065; // Promień Ziemi w milach rzymskich
54 const dLat = this.deg2rad(pos2.lat - pos1.lat);
55 const dLon = this.deg2rad(pos2.lng - pos1.lng);
56 const a = 
57 Math.sin(dLat/2) * Math.sin(dLat/2) +
58 Math.cos(this.deg2rad(pos1.lat)) * Math.cos(this.deg2rad(pos2.lat)) * 
59 Math.sin(dLon/2) * Math.sin(dLon/2);
60 const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
61 return R * c;
62 }
63
64 private deg2rad(deg: number): number {
65 return deg * (Math.PI/180);
66 }
67
68 private calculateETA(cohort: any, destination: { lat: number; lng: number }): string {
69 const distance = this.calculateDistance(cohort.location, destination);
70 const speed = cohort.speed || 10; // domyślna prędkość 10 mil
71 const hoursToArrival = distance / speed;
72 
73 const eta = new Date(Date.now() + hoursToArrival * 60 * 60 * 1000);
74 return eta.toISOString();
75 }
76
77 private async notifyPortAuthorities(emergency: any) {
78 // Implementacja powiadomienia władz prowincji
79 this.logger.log('Powiadamiam władze prowincji o sytuacji awaryjnej');
80 }
81
82 private logEmergency(broadcastId: string, emergency: any, fromLegion: string) {
83 // Implementacja logowania w systemie awaryjnym
84 this.logger.log(` Emergency logged: ${broadcastId}`);
85 }
86
87 private checkAcknowledgments(broadcastId: string) {
88 const status = this.getBroadcastAcknowledgments(broadcastId);
89 
90 if (status.pending.length > 0) {
91 this.logger.warn(
92 ` Broadcast ${broadcastId} - Pending acknowledgments from: ${status.pending.join(', ')}`
93 );
94 
95 // Wyślij przypomnienie
96 this.sendAcknowledgmentReminder(broadcastId, status.pending);
97 }
98 }
99
100 private sendAcknowledgmentReminder(broadcastId: string, pendingLegions: string[]) {
101 const broadcast = this.messageHistory.get(broadcastId);
102 if (!broadcast) return;
103 
104 const reminderData = {
105 originalBroadcastId: broadcastId,
106 reminder: true,
107 message: 'Potwierdzenie odbioru wymagane',
108 originalMessage: broadcast.content,
109 timestamp: new Date().toISOString(),
110 };
111 
112 // Wyślij przypomnienie do kohort, które nie potwierdziły
113 pendingLegions.forEach(cohortName => {
114 const cohort = this.legionGateway.getLegions().find(s => s.name === cohortName);
115 if (cohort) {
116 this.legionGateway.broadcastToLegion(cohort.id, 'acknowledgment:reminder', reminderData);
117 }
118 });
119 }
120}
121
122// Typy pomocnicze
123interface BroadcastResult {
124 broadcastId: string;
125 deliveredTo: number;
126 totalLegions: number;
127 excluded?: number;
128 roomName?: string;
129 radius?: number;
130 conditions?: any;
131 rescueLegionsNotified?: number;
132 emergency?: boolean;
133}

Integracja z Gateway

1// W legion-command.gateway.ts - dodanie metod broadcasting
2
3export class LegionCommandGateway {
4 // ... existing code
5 // Potwierdzenie odbioru broadcastu
6 @SubscribeMessage('broadcast:acknowledge')
7 handleBroadcastAcknowledgment(
8 @MessageBody() data: { broadcastId: string },
9 @ConnectedSocket() client: Socket,
10 ) {
11 const cohort = this.legionRegistry.get(client.id);
12 
13 if (!cohort) {
14 return { error: 'Legion not authenticated' };
15 }
16 
17 const success = this.broadcastService.acknowledgeBroadcast(data.broadcastId, client.id);
18 
19 return {
20 success,
21 broadcastId: data.broadcastId,
22 acknowledgedBy: cohort.name,
23 timestamp: new Date().toISOString(),
24 };
25 }
26
27 // Publiczne metody dla broadcasting
28 broadcastToLegion(cohortId: string, event: string, data: any) {
29 const socket = this.server.sockets.sockets.get(cohortId);
30 if (socket) {
31 socket.emit(event, data);
32 }
33 }
34
35 getRoomMembers(roomName: string): any[] {
36 const memberIds = this.rooms.get(roomName);
37 if (!memberIds) return [];
38 
39 return Array.from(memberIds)
40 .map(id => this.legionRegistry.get(id))
41 .filter(cohort => cohort !== undefined);
42 }
43}

Broadcasting to podstawa skutecznej komunikacji w każdym rzymskim legionie. Dobry system pozwala na natychmiastowe reagowanie, koordynację działań i zapewnienie bezpieczeństwa wszystkich jednostek!

Przejdź do CodeWorlds