Skip to content

Commit eda227f

Browse files
committed
implementing Custom Zone
1 parent 95c0330 commit eda227f

File tree

5 files changed

+427
-13
lines changed

5 files changed

+427
-13
lines changed

python/CustomZoneSample.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from bzfsItf import *
2+
3+
# The custom zone we'll be making for this plug-in and we'll be extending a
4+
# class provided by the API.
5+
# The class provided in the API handles all of the logic handling rectangular
6+
# and circular zones.
7+
class MsgZone (bz_CustomZoneObject):
8+
# Our custom constructor will call the parent constructor so we can setup
9+
# default positions for the zone
10+
flag = 'US'
11+
12+
# Custom fields that are unique to our zone so we can build on top of the
13+
# class we're extending
14+
message = ''
15+
16+
class CustomZoneSample (bz_Plugin, bz_CustomMapObjectHandler):
17+
msgZones = []
18+
19+
def Name (self):
20+
return 'Custom Zone Sample'
21+
22+
def Init (self, _):
23+
self.Register(bz_ePlayerUpdateEvent)
24+
25+
# Whenever a player enters a zone and is carrying a specified flag,
26+
# they will receive the specified message
27+
bz_registerCustomMapObject('msgzone', self)
28+
29+
30+
def Event (self, updateData):
31+
if updateData.eventType != bz_ePlayerUpdateEvent:
32+
return
33+
34+
# This event is called each time a player sends an update to the server
35+
# Loop through all of our custom zones
36+
for msgZone in self.msgZones:
37+
# Use the pointInZone(float pos[3]) function provided by the
38+
# bz_CustomZoneObject to check if the position
39+
# of the player is inside of the zone. This function will
40+
# automatically handle the logic if the zone is a
41+
# rectangle (even if it's rotated) or a circle
42+
if msgZone.pointInZone(updateData.state.pos) and \
43+
bz_getPlayerFlagID(updateData.playerID) >= 0:
44+
# If the player has the flag specified in the zone, send them
45+
# a message and remove their flag
46+
if bz_getPlayerFlag(updateData.playerID) == msgZone.flag:
47+
bz_sendTextMessage(updateData.playerID, msgZone.message)
48+
bz_removePlayerFlag(updateData.playerID);
49+
50+
def Cleanup (self):
51+
self.Flush();
52+
53+
bz_removeCustomMapObject('msgzone');
54+
55+
def MapObject (self, object, data):
56+
if object != 'MSGZONE' or not data:
57+
return False
58+
59+
# The new zone we just found and we'll be storing in our vector of
60+
# zones
61+
newZone = MsgZone()
62+
63+
# This function will parse the attributes that are handled by
64+
# bz_CustomZoneObject which
65+
# handles rectangular and circular zones
66+
#
67+
# For rectangular zones:
68+
# - position
69+
# - size
70+
# - rotation
71+
#
72+
# For circular zones:
73+
# - position
74+
# - height
75+
# - radius
76+
#
77+
# This also handles BBOX and CYLINDER fields but they have been
78+
# deprecated and will be removed in the future
79+
newZone.handleDefaultOptions(data)
80+
81+
# Loop through the object data
82+
for line in data:
83+
nubs = bz_tokenize(line)
84+
85+
if len(nubs) <= 1:
86+
continue
87+
88+
if len(nubs[1]) >=2 and nubs[1][0] == '"':
89+
nubs[1] = nubs[1][1:-1]
90+
91+
key = nubs[0].upper()
92+
93+
# These are our custom fields in the MsgZone class
94+
if key == 'MESSAGE':
95+
newZone.message = nubs[1]
96+
elif key == 'FLAG':
97+
newZone.flag = nubs[1]
98+
print(newZone.__dict__)
99+
100+
self.msgZones.append(newZone)
101+
102+
return True
103+
104+
plugin = CustomZoneSample()

python/bzfsItf.py

Lines changed: 170 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import time
22
from enum import Enum
3+
import math
4+
import re
35

46
class Team(Enum):
57
eNoTeam = -1
@@ -73,6 +75,138 @@ def bz_getLocaltime():
7375
myTime.dow = (now.tm_wday + 1) % 7
7476
return myTime
7577

78+
class bz_CustomMapObjectHandler():
79+
pass
80+
81+
class bz_CustomZoneObject():
82+
box = False
83+
radius2 = 25
84+
cX = 0
85+
cY = 0
86+
cZ = 0
87+
hh = 0
88+
hw = 0
89+
height = 5
90+
sin_val = 0
91+
cos_val = 1
92+
93+
def pointInZone(self, pos):
94+
# Coordinates of player relative to the "fake" origin
95+
px = pos[0] - self.cX;
96+
py = pos[1] - self.cY;
97+
pz = pos[2] - self.cZ;
98+
99+
if pos[2] < 0:
100+
return False
101+
if pos[2] > self.height:
102+
return False
103+
104+
if self.box:
105+
# Instead of rotating the box against (0,0)
106+
# rotates the world in the opposite direction
107+
px, py = \
108+
( px * cos_val + py * sin_val), \
109+
(-px * sin_val + py * cos_val)
110+
# As the world is now simmetric remove the sign
111+
px = abs(px)
112+
py = abs(py)
113+
114+
# Check now it the point is within the box
115+
if px > hw:
116+
return False
117+
if py > hh:
118+
return False
119+
else:
120+
dist2 = px * px + py * py
121+
122+
if dist2 > self.radius2:
123+
return False
124+
return True
125+
126+
def handleDefaultOptions(self, data):
127+
# default values just in case
128+
self.radius2 = 25 # Default radius 5
129+
self.height = 5
130+
self.sin_val = 0
131+
self.cos_val = 1
132+
133+
# parse all the chunks
134+
for line in data:
135+
nubs = line.split()
136+
137+
if len(nubs) <= 1:
138+
continue
139+
140+
key = nubs[0].upper()
141+
142+
if key == 'BBOX' and len(nubs) > 6:
143+
self.box = True
144+
145+
xMin = float(nubs[1])
146+
xMax = float(nubs[2])
147+
yMin = float(nubs[3])
148+
yMax = float(nubs[4])
149+
150+
# Center of the rectangle, we can treat this as the "fake"
151+
# origin
152+
self.cX = (xMax + xMin) / 2
153+
self.cY = (yMax + yMin) / 2
154+
155+
self.hh = abs(yMax - yMin) / 2
156+
self.hw = abs(xMax - xMin) / 2
157+
158+
self.cZ = float(nubs[5])
159+
self.height = float(nubs[6]) - self.cZ
160+
161+
bz_debugMessage(0,
162+
'WARNING: The "BBOX" attribute has been deprecated.'\
163+
' Please use the `position` and `size` '\
164+
'attributes instead:')
165+
bz_debugMessage(0, ' position {f} {f} {f}'
166+
.format(self.cX, self.cY, self.cZ))
167+
bz_debugMessage(0, ' size {} {} {}'
168+
.format(self.hw, self.hh, self.height))
169+
elif key == 'CYLINDER' and len(nubs) > 5:
170+
self.box = False
171+
# Center of the cylinder
172+
self.cX = float(nubs[1])
173+
self.cY = float(nubs[2])
174+
self.cZ = float(nubs[3])
175+
self.height = float(nubs[4]) - self.cZ
176+
self.radius = float(nubs[5])
177+
178+
bz_debugMessage(0,
179+
'WARNING: The "CYLINDER" attribute has been '\
180+
'deprecated. Please use `radius` and '\
181+
'`height` instead:')
182+
bz_debugMessage(0, ' position {} {} {}'
183+
.format(self.cX, self.cY, self.cZ))
184+
bz_debugMessage(0, ' radius {}'.format(self.radius))
185+
bz_debugMessage(0, ' height {}'.format(self.height))
186+
elif (key == 'POSITION' or key == 'POS') and len(nubs) > 3:
187+
self.cX = float(nubs[1])
188+
self.cY = float(nubs[2])
189+
self.cZ = float(nubs[3])
190+
elif key == 'SIZE' and len(nubs) > 3:
191+
self.box = True
192+
# Half Width and Half Heigth
193+
self.hw = float(nubs[1])
194+
self.hh = float(nubs[2])
195+
self.height = float(nubs[3])
196+
elif (key == 'ROTATION') or (key == 'ROT'):
197+
rotation = float(nubs[1])
198+
if not 0 < rotation < 360:
199+
rotation = 0
200+
rotRad = rotation * math.pi / 180
201+
self.cos_val = math.cos(rotRad)
202+
self.sin_val = math.sin(rotRad)
203+
elif (key == 'RADIUS') or (key == 'RAD'):
204+
self.box = False
205+
self.radius = float(nubs[1])
206+
self.radius *= self.radius
207+
elif key == 'HEIGHT':
208+
self.height = float(nubs[1])
209+
76210
bz_eGetPlayerSpawnPosEvent = 1
77211
bz_eTickEvent = 2
78212
bz_eRawChatMessageEvent = 3
@@ -81,7 +215,7 @@ def bz_getLocaltime():
81215
bz_eFlagDroppedEvent = 6
82216
bz_eShotFiredEvent = 7
83217
bz_ePlayerDieEvent = 8
84-
218+
bz_ePlayerUpdateEvent = 9
85219

86220
eGoodFlag = 0
87221
eBadFlag = 1
@@ -170,12 +304,18 @@ def bzShotFiredEvent(plugin, playerID):
170304
event.playerID = playerID
171305
callEvents(event)
172306

173-
def bzDiedEvent(plugin, playerID, flagKilledWith):
307+
def bzDiedEvent(plugin):
174308
event = bz_EventData(bz_ePlayerDieEvent)
175309
event.playerID = playerID
176310
event.flagKilledWith = flagKilledWith
177311
callEvents(event)
178312

313+
def bzUpdateEvent(plugin, playerID, pos):
314+
event = bz_EventData(bz_ePlayerUpdateEvent)
315+
event.playerID = playerID;
316+
event.pos = pos;
317+
callEvents(event)
318+
179319
# Command Handler
180320

181321
class bz_CustomSlashCommandHandler():
@@ -279,3 +419,31 @@ def bz_PollHelp(playerID):
279419
for k, v in customPollTypes.items():
280420
reply = ' or /poll ' + k + ' ' + v.pollParameters
281421
bz_sendTextMessage(playerID, reply)
422+
423+
424+
customObjectMap = {}
425+
426+
def bz_registerCustomMapObject (objectName, handler):
427+
if not objectName or not handler:
428+
return False
429+
430+
customObjectMap[objectName.upper()] = handler;
431+
return True
432+
433+
def bz_removeCustomMapObject (objectName):
434+
if not objectName:
435+
return False
436+
437+
obj = objectName.upper()
438+
if obj in customObjectMap:
439+
del customObjectMap[obj]
440+
return True
441+
442+
def bz_CheckIfCustomMap(objectName):
443+
return objectName in customObjectMap
444+
445+
def bz_customZoneMapObject(customObject, customLines):
446+
customObjectMap[customObject].MapObject(customObject, customLines);
447+
448+
def bz_tokenize(line):
449+
return re.findall(r'[^"\s]\S*|".+?"', line)

src/bzfs/BZWReader.cxx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
// bzfs specific headers
5555
#include "bzfs.h"
56+
#include "bzfsPython.h"
5657

5758

5859
BZWReader::BZWReader(std::string filename) : cURLManager(), location(filename),
@@ -203,6 +204,7 @@ bool BZWReader::readWorldStream(std::vector<WorldFileObject*>& wlist,
203204

204205
std::string customObject;
205206
std::vector<std::string> customLines;
207+
bool fromPython = false;
206208

207209
bool gotWorld = false;
208210

@@ -217,7 +219,7 @@ bool BZWReader::readWorldStream(std::vector<WorldFileObject*>& wlist,
217219
std::string("discarding incomplete object"), line);
218220
if (object != fakeObject)
219221
delete object;
220-
else if (customObject.size())
222+
else if (not customObject.empty())
221223
{
222224
customObject = "";
223225
customLines.clear();
@@ -259,14 +261,20 @@ bool BZWReader::readWorldStream(std::vector<WorldFileObject*>& wlist,
259261
else
260262
wlist.push_back(object);
261263
}
262-
else if (customObject.size())
264+
else if (not customObject.empty())
263265
{
264-
bz_CustomMapObjectInfo data;
265-
data.name = bz_ApiString(customObject);
266-
for (unsigned int i = 0; i < customLines.size(); i++)
267-
data.data.push_back(customLines[i]);
268-
customObjectMap[customObject]->MapObject(bz_ApiString(customObject),&data);
269-
object = NULL;
266+
if (fromPython)
267+
{
268+
bzPython_customZoneMapObject(customObject, customLines);
269+
}
270+
else
271+
{
272+
bz_CustomMapObjectInfo data;
273+
data.name = bz_ApiString(customObject);
274+
for (unsigned int i = 0; i < customLines.size(); i++)
275+
data.data.push_back(customLines[i]);
276+
customObjectMap[customObject]->MapObject(bz_ApiString(customObject),&data);
277+
}
270278
}
271279
object = NULL;
272280
}
@@ -411,7 +419,7 @@ bool BZWReader::readWorldStream(std::vector<WorldFileObject*>& wlist,
411419
// return false;
412420
}
413421
}
414-
else if (customObject.size())
422+
else if (not customObject.empty())
415423
{
416424
std::string thisline = buffer;
417425
thisline += " ";
@@ -430,12 +438,21 @@ bool BZWReader::readWorldStream(std::vector<WorldFileObject*>& wlist,
430438
}
431439
else // filling the current object
432440
{
441+
std::string currObject = TextUtils::toupper(std::string(buffer));
433442
// unknown token
434-
if (customObjectMap.find(TextUtils::toupper(std::string(buffer))) != customObjectMap.end())
443+
if (customObjectMap.find(currObject) != customObjectMap.end())
444+
{
445+
customObject = currObject;
446+
object = fakeObject;
447+
customLines.clear();
448+
fromPython = false;
449+
}
450+
else if (bzPython_CheckIfCustomMap(currObject))
435451
{
436-
customObject = TextUtils::toupper(std::string(buffer));
452+
customObject = currObject;
437453
object = fakeObject;
438454
customLines.clear();
455+
fromPython = true;
439456
}
440457
else
441458
{

0 commit comments

Comments
 (0)