-
Notifications
You must be signed in to change notification settings - Fork 69
/
xControl.py
818 lines (774 loc) · 25 KB
/
xControl.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
from phBot import *
from threading import Timer
import phBotChat
import QtBind
import struct
import random
import json
import os
import sqlite3
pName = 'xControl'
pVersion = '1.9.1'
pUrl = 'https://raw.githubusercontent.com/JellyBitz/phBot-xPlugins/master/xControl.py'
# ______________________________ Initializing ______________________________ #
# Globals
inGame = None
followActivated = False
followPlayer = ''
followDistance = 0
# Graphic user interface
gui = QtBind.init(__name__,pName)
QtBind.createLabel(gui,'Control your party using in-game chat. Leader writes commands and your character will follow it.',11,11)
QtBind.createLabel(gui,'< COMMAND (uppercased) #Variable (required) #Variable? (optional) >',11,30)
QtBind.createLabel(gui,'- START : Start bot\n- STOP : Stop bot\n- TRACE #Player? : Start trace to leader or another player\n- NOTRACE : Stop trace\n- RETURN : Use some "Return Scroll" from your inventory\n- TP #A #B : Use teleport from location A to B\n- RECALL #Town : Set recall on city portal\n- ZERK : Use berserker mode if is available\n- GETOUT : Left party\n- MOVEON #Radius? : Set a random movement\n- MOUNT #PetType? : Mount horse by default\n- DISMOUNT #PetType? : Dismount horse by default\n- SETPOS #PosX? #PosY? #Region? #PosZ? : Set training position\n- SETRADIUS #Radius? : Set training radius\n- SETSCRIPT #Path? : Change script path for training area\n- SETAREA #Name : Changes training area by config name\n- PROFILE #Name? : Loads a profile by his name\n- DC : Disconnect from game',15,45)
QtBind.createLabel(gui,'- INJECT #Opcode #Encrypted? #Data? : Inject packet\n- CHAT #Type #Message : Send any message type\n- FOLLOW #Player? #Distance? : Trace a party player using distance\n- NOFOLLOW : Stop following\n- JUMP : Generate knockback visual effect\n- SIT : Sit or Stand up, depends\n- CAPE #Type? : Use PVP Cape\n- EQUIP #ItemName : Equips an item from inventory\n- UNEQUIP #ItemName : Unequips item from character\n- REVERSE #Type #Name?\n- GETPOS : Gets current position\n- USE #ItemName : Use item from inventory',345,80)
tbxLeaders = QtBind.createLineEdit(gui,"",525,11,110,20)
lstLeaders = QtBind.createList(gui,525,32,110,38)
btnAddLeader = QtBind.createButton(gui,'btnAddLeader_clicked'," Add ",635,10)
btnRemLeader = QtBind.createButton(gui,'btnRemLeader_clicked'," Remove ",635,32)
# ______________________________ Methods ______________________________ #
# Return xControl folder path
def getPath():
return get_config_dir()+pName+"\\"
# Return character configs path (JSON)
def getConfig():
return getPath()+inGame['server'] + "_" + inGame['name'] + ".json"
# Check if character is ingame
def isJoined():
global inGame
inGame = get_character_data()
if not (inGame and "name" in inGame and inGame["name"]):
inGame = None
return inGame
# Load default configs
def loadDefaultConfig():
# Clear data
QtBind.clear(gui,lstLeaders)
# Loads all config previously saved
def loadConfigs():
loadDefaultConfig()
if isJoined():
# Check config exists to load
if os.path.exists(getConfig()):
data = {}
with open(getConfig(),"r") as f:
data = json.load(f)
if "Leaders" in data:
for nickname in data["Leaders"]:
QtBind.append(gui,lstLeaders,nickname)
# Add leader to the list
def btnAddLeader_clicked():
if inGame:
player = QtBind.text(gui,tbxLeaders)
# Player nickname it's not empty
if player and not lstLeaders_exist(player):
# Init dictionary
data = {}
# Load config if exist
if os.path.exists(getConfig()):
with open(getConfig(), 'r') as f:
data = json.load(f)
# Add new leader
if not "Leaders" in data:
data['Leaders'] = []
data['Leaders'].append(player)
# Replace configs
with open(getConfig(),"w") as f:
f.write(json.dumps(data, indent=4, sort_keys=True))
QtBind.append(gui,lstLeaders,player)
QtBind.setText(gui, tbxLeaders,"")
log('Plugin: Leader added ['+player+']')
# Remove leader selected from list
def btnRemLeader_clicked():
if inGame:
selectedItem = QtBind.text(gui,lstLeaders)
if selectedItem:
if os.path.exists(getConfig()):
data = {"Leaders":[]}
with open(getConfig(), 'r') as f:
data = json.load(f)
try:
# remove leader nickname from file if exists
data["Leaders"].remove(selectedItem)
with open(getConfig(),"w") as f:
f.write(json.dumps(data, indent=4, sort_keys=True))
except:
pass # just ignore file if doesn't exist
QtBind.remove(gui,lstLeaders,selectedItem)
log('Plugin: Leader removed ['+selectedItem+']')
# Return True if nickname exist at the leader list
def lstLeaders_exist(nickname):
nickname = nickname.lower()
players = QtBind.getItems(gui,lstLeaders)
for i in range(len(players)):
if players[i].lower() == nickname:
return True
return False
# Inject teleport packet, using the source and destination name
def inject_teleport(source,destination):
t = get_teleport_data(source, destination)
if t:
npcs = get_npcs()
for key, npc in npcs.items():
if npc['name'] == source or npc['servername'] == source:
log("Plugin: Selecting teleporter ["+source+"]")
# Teleport found, select it
inject_joymax(0x7045, struct.pack('<I', key), False)
# Start a timer to teleport in 2.0 seconds
Timer(2.0, inject_joymax, (0x705A,struct.pack('<IBI', key, 2, t[1]),False)).start()
Timer(2.0, log, ("Plugin: Teleporting to ["+destination+"]")).start()
return
log('Plugin: NPC not found. Wrong NPC name or servername')
else:
log('Plugin: Teleport data not found. Wrong teleport name or servername')
# Send message, Ex. "All Hello World!" or "private JellyBitz Hi!"
def handleChatCommand(msg):
# Try to split message
args = msg.split(' ',1)
# Check if the format is correct and is not empty
if len(args) != 2 or not args[0] or not args[1]:
return
# Split correctly the message
t = args[0].lower()
if t == 'private' or t == 'note':
# then check message is not empty
argsExtra = args[1].split(' ',1)
if len(argsExtra) != 2 or not argsExtra[0] or not argsExtra[1]:
return
args.pop(1)
args += argsExtra
# Check message type
sent = False
if t == "all":
sent = phBotChat.All(args[1])
elif t == "private":
sent = phBotChat.Private(args[1],args[2])
elif t == "party":
sent = phBotChat.Party(args[1])
elif t == "guild":
sent = phBotChat.Guild(args[1])
elif t == "union":
sent = phBotChat.Union(args[1])
elif t == "note":
sent = phBotChat.Note(args[1],args[2])
elif t == "stall":
sent = phBotChat.Stall(args[1])
elif t == "global":
sent = phBotChat.Global(args[1])
if sent:
log('Plugin: Message "'+t+'" sent successfully!')
# Move to a random position from the actual position using a maximum radius
def randomMovement(radiusMax=10):
# Generating a random new point
pX = random.uniform(-radiusMax,radiusMax)
pY = random.uniform(-radiusMax,radiusMax)
# Mixing with the actual position
p = get_position()
pX = pX + p["x"]
pY = pY + p["y"]
# Moving to new position
move_to(pX,pY,p["z"])
log("Plugin: Random movement to (X:%.1f,Y:%.1f)"%(pX,pY))
# Follow a player using distance. Return success
def start_follow(player,distance):
if party_player(player):
global followActivated,followPlayer,followDistance
followPlayer = player
followDistance = distance
followActivated = True
return True
return False
# Return True if the player is in the party
def party_player(player):
players = get_party()
if players:
for p in players:
if players[p]['name'] == player:
return True
return False
# Return point [X,Y] if player is in the party and near, otherwise return None
def near_party_player(player):
players = get_party()
if players:
for p in players:
if players[p]['name'] == player and players[p]['player_id'] > 0:
return players[p]
return None
# Calc the distance from point A to B
def GetDistance(ax,ay,bx,by):
return ((bx-ax)**2 + (by-ay)**2)**0.5
# Stop follow player
def stop_follow():
global followActivated,followPlayer,followDistance
result = followActivated
# stop
followActivated = False
followPlayer = ""
followDistance = 0
return result
# Try to summon a vehicle
def MountHorse():
# search item with similar name or exact server name
item = GetItemByExpression(lambda n,s: s.startswith('ITEM_COS_C_'),13)
if item:
UseItem(item)
return True
log('Plugin: Horse not found at your inventory')
return False
# Try to mount pet by type, return success
def MountPet(petType):
# just in case
if petType == 'pick':
return False
elif petType == 'horse':
return MountHorse()
# get all summoned pets
pets = get_pets()
if pets:
for uid,pet in pets.items():
if pet['type'] == petType:
p = b'\x01' # mount flag
p += struct.pack('I',uid)
inject_joymax(0x70CB,p, False)
return True
return False
# Try to dismount pet by type, return success
def DismountPet(petType):
petType = petType.lower()
# just in case
if petType == 'pick':
return False
# get all summoned pets
pets = get_pets()
if pets:
for uid,pet in pets.items():
if pet['type'] == petType:
p = b'\x00'
p += struct.pack('I',uid)
inject_joymax(0x70CB,p, False)
return True
return False
# Gets the NPC unique ID if the specified name is found near
def GetNPCUniqueID(name):
NPCs = get_npcs()
if NPCs:
name = name.lower()
for UniqueID, NPC in NPCs.items():
NPCName = NPC['name'].lower()
if name == NPCName:
return UniqueID
return 0
# Search an item by name or servername through lambda expression and return his information
def GetItemByExpression(_lambda,start=0,end=0):
inventory = get_inventory()
items = inventory['items']
if end == 0:
end = inventory['size']
# check items between intervals
for slot, item in enumerate(items):
if start <= slot and slot <= end:
if item:
# Search by lambda
if _lambda(item['name'],item['servername']):
# Save slot location
item['slot'] = slot
return item
return None
# Finds an empty slot, returns -1 if inventory is full
def GetEmptySlot():
items = get_inventory()['items']
# check the first empty
for slot, item in enumerate(items):
if slot >= 13:
if not item:
return slot
return -1
# Injects item movement on inventory
def Inject_InventoryMovement(movementType,slotInitial,slotFinal,logItemName,quantity=0):
p = struct.pack('<B',movementType)
p += struct.pack('<B',slotInitial)
p += struct.pack('<B',slotFinal)
p += struct.pack('<H',quantity)
log('Plugin: Moving item "'+logItemName+'"...')
# CLIENT_INVENTORY_ITEM_MOVEMENT
inject_joymax(0x7034,p,False)
# Try to equip item
def EquipItem(item):
itemData = get_item(item['model'])
# Check equipables only
if itemData['tid1'] != 1:
log('Plugin: '+item['name']+' cannot be equiped!')
return
# Check equipable type
t = itemData['tid2']
# garment, protector, armor, robe, light, heavy
if t == 1 or t == 2 or t == 3 or t == 9 or t == 10 or t == 11:
t = itemData['tid3']
# head
if t == 1:
Inject_InventoryMovement(0,item['slot'],0,item['name'])
# shoulders
elif t == 2:
Inject_InventoryMovement(0,item['slot'],2,item['name'])
# chest
elif t == 3:
Inject_InventoryMovement(0,item['slot'],1,item['name'])
# pants
elif t == 4:
Inject_InventoryMovement(0,item['slot'],4,item['name'])
# gloves
elif t == 5:
Inject_InventoryMovement(0,item['slot'],3,item['name'])
# boots
elif t == 6:
Inject_InventoryMovement(0,item['slot'],5,item['name'])
# shields
elif t == 4:
Inject_InventoryMovement(0,item['slot'],7,item['name'])
# accesories ch/eu
elif t == 5 or t == 12:
t = itemData['tid3']
# earring
if t == 1:
Inject_InventoryMovement(0,item['slot'],9,item['name'])
# necklace
elif t == 2:
Inject_InventoryMovement(0,item['slot'],10,item['name'])
# ring
elif t == 3:
# Check if second ring slot is empty
if not GetItemByExpression(lambda s,n: True,11):
Inject_InventoryMovement(0,item['slot'],12,item['name'])
else:
Inject_InventoryMovement(0,item['slot'],11,item['name'])
# weapon ch/eu
elif t == 6:
Inject_InventoryMovement(0,item['slot'],6,item['name'])
# job
elif t == 7:
Inject_InventoryMovement(0,item['slot'],8,item['name'])
# avatar
elif t == 13:
t = itemData['tid3']
# hat
if t == 1:
Inject_InventoryMovement(36,item['slot'],0,item['name'])
# dress
elif t == 2:
Inject_InventoryMovement(36,item['slot'],1,item['name'])
# accesory
elif t == 3:
Inject_InventoryMovement(36,item['slot'],2,item['name'])
# flag
elif t == 4:
Inject_InventoryMovement(36,item['slot'],3,item['name'])
# devil spirit
elif t == 14:
Inject_InventoryMovement(36,item['slot'],4,item['name'])
# Try to unequip item
def UnequipItem(item):
# find an empty slot
slot = GetEmptySlot()
if slot != -1:
Inject_InventoryMovement(0,item['slot'],slot,item['name'])
# Try to use the item specified
def UseItem(item):
# Create packet and inject it
p = struct.pack('<B',item['slot'])
loc = get_locale()
tid = GetTIDFromItem(item['model'])
if loc == 22: # vsro
p += struct.pack('<H',tid)
else:
p += struct.pack('<I',tid)
log('Plugin: Using item "'+item['name']+'"...')
# CLIENT_INVENTORY_ITEM_USE
inject_joymax(0x704C,p,True)
# Get Type ID from item
def GetTIDFromItem(itemId):
conn = GetDatabaseConnection()
c = conn.cursor()
c.execute('SELECT cash_item, tid1, tid2, tid3 FROM items WHERE id=?',(itemId,))
result = c.fetchone()
# calculate TID
result = result[0] + (3 * 4) + (result[1] * 32) + (result[2] * 128) + (result[3] * 2048)
conn.close()
return result
# Create a connection to database
def GetDatabaseConnection():
bot_path = os.getcwd()
# Load the server info
data = {}
locale = get_locale()
# vSRO
if locale == 22:
with open(bot_path+"/vSRO.json","r") as f:
data = json.load(f)
# Match data with the current server name
server = character_data['server']
for k in data:
servers = data[k]['servers']
# Check if servers is in list
if server in servers:
# Scan data folder
for path in os.scandir(bot_path+"/Data"):
# Check databases only
if path.is_file() and path.name.endswith(".db3"):
# Connect to check if the data matches
conn = sqlite3.connect(bot_path+"/Data/"+path.name)
c = conn.cursor()
c.execute('SELECT * FROM data WHERE k="path" AND v=?',(data[k]['path'],))
if c.fetchone():
# match found
return conn
else:
conn.close()
# iSRO
elif locale == 18:
return sqlite3.connect(bot_path+"/Data/iSRO.db3")
# TrSRO
elif locale == 56:
return sqlite3.connect(bot_path+"/Data/TRSRO.db3")
return None
# ______________________________ Events ______________________________ #
# Called when the bot successfully connects to the game server
def connected():
global inGame
inGame = None
# Called when the character enters the game world
def joined_game():
loadConfigs()
# All chat messages received are sent to this function
def handle_chat(t,player,msg):
# Remove guild name from union chat messages
if t == 11:
msg = msg.split(': ',1)[1]
# Check player at leader list or a Discord message
if player and lstLeaders_exist(player) or t == 100:
# Parsing message command
if msg == "START":
start_bot()
log("Plugin: Bot started")
elif msg == "STOP":
stop_bot()
log("Plugin: Bot stopped")
elif msg.startswith("TRACE"):
# deletes empty spaces on right
msg = msg.rstrip()
if msg == "TRACE":
if start_trace(player):
log("Plugin: Starting trace to ["+player+"]")
else:
msg = msg[5:].split()[0]
if start_trace(msg):
log("Plugin: Starting trace to ["+msg+"]")
elif msg == "NOTRACE":
stop_trace()
log("Plugin: Trace stopped")
elif msg.startswith("SETPOS"):
# deletes empty spaces on right
msg = msg.rstrip()
if msg == "SETPOS":
p = get_position()
set_training_position(p['region'], p['x'], p['y'],p['z'])
log("Plugin: Training area set to current position (X:%.1f,Y:%.1f)"%(p['x'],p['y']))
else:
try:
# check arguments
p = msg[6:].split()
x = float(p[0])
y = float(p[1])
# auto calculated if is not specified
region = int(p[2]) if len(p) >= 3 else 0
z = float(p[3]) if len(p) >= 4 else 0
set_training_position(region,x,y,z)
log("Plugin: Training area set to (X:%.1f,Y:%.1f)"%(x,y))
except:
log("Plugin: Wrong training area coordinates!")
elif msg == 'GETPOS':
# Check current position
pos = get_position()
phBotChat.Private(player,'My position is (X:%.1f,Y:%.1f,Z:%1f,Region:%d)'%(pos['x'],pos['y'],pos['z'],pos['region']))
elif msg.startswith("SETRADIUS"):
# deletes empty spaces on right
msg = msg.rstrip()
if msg == "SETRADIUS":
# set default radius
radius = 35
set_training_radius(radius)
log("Plugin: Training radius reseted to "+str(radius)+" m.")
else:
try:
# split and parse movement radius
radius = int(float(msg[9:].split()[0]))
# to absolute
radius = (radius if radius > 0 else radius*-1)
set_training_radius(radius)
log("Plugin: Training radius set to "+str(radius)+" m.")
except:
log("Plugin: Wrong training radius value!")
elif msg.startswith('SETSCRIPT'):
# deletes empty spaces on right
msg = msg.rstrip()
if msg == 'SETSCRIPT':
# reset script
set_training_script('')
log('Plugin: Training script path has been reseted')
else:
# change script to the path specified
set_training_script(msg[9:])
log('Plugin: Training script path has been changed')
elif msg.startswith('SETAREA '):
# deletes empty spaces on right
msg = msg[8:]
if msg:
# try to change to specified area name
if set_training_area(msg):
log('Plugin: Training area has been changed to ['+msg+']')
else:
log('Plugin: Training area ['+msg+'] not found in the list')
elif msg == "SIT":
log("Plugin: Sit/Stand")
inject_joymax(0x704F,b'\x04',False)
elif msg == "JUMP":
# Just a funny emote lol
log("Plugin: Jumping!")
inject_joymax(0x3091,b'\x0c',False)
elif msg.startswith("CAPE"):
# deletes empty spaces on right
msg = msg.rstrip()
if msg == "CAPE":
log("Plugin: Using PVP Cape by default (Yellow)")
inject_joymax(0x7516,b'\x05',False)
else:
# get cape type normalized
cape = msg[4:].split()[0].lower()
if cape == "off":
log("Plugin: Removing PVP Cape")
inject_joymax(0x7516,b'\x00',False)
elif cape == "red":
log("Plugin: Using PVP Cape (Red)")
inject_joymax(0x7516,b'\x01',False)
elif cape == "gray":
log("Plugin: Using PVP Cape (Gray)")
inject_joymax(0x7516,b'\x02',False)
elif cape == "blue":
log("Plugin: Using PVP Cape (Blue)")
inject_joymax(0x7516,b'\x03',False)
elif cape == "white":
log("Plugin: Using PVP Cape (White)")
inject_joymax(0x7516,b'\x04',False)
elif cape == "yellow":
log("Plugin: Using PVP Cape (Yellow)")
inject_joymax(0x7516,b'\x05',False)
else:
log("Plugin: Wrong PVP Cape color!")
elif msg == "ZERK":
log("Plugin: Using Berserker mode")
inject_joymax(0x70A7,b'\x01',False)
elif msg == "RETURN":
# Quickly check if is dead
character = get_character_data()
if character['hp'] == 0:
# RIP
log('Plugin: Resurrecting at town...')
inject_joymax(0x3053,b'\x01',False)
else:
log('Plugin: Trying to use return scroll...')
# Avoid high CPU usage with too many chars at the same time
Timer(random.uniform(0.5,2),use_return_scroll).start()
elif msg.startswith("TP"):
# deletes command header and whatever used as separator
msg = msg[3:]
if not msg:
return
# select split char
split = ',' if ',' in msg else ' '
# extract arguments
source_dest = msg.split(split)
# needs to be at least two name points to try teleporting
if len(source_dest) >= 2:
inject_teleport(source_dest[0].strip(),source_dest[1].strip())
elif msg.startswith("INJECT "):
msgPacket = msg[7:].split()
msgPacketLen = len(msgPacket)
if msgPacketLen == 0:
log("Plugin: Incorrect structure to inject packet")
return
# Check packet structure
opcode = int(msgPacket[0],16)
data = bytearray()
encrypted = False
dataIndex = 1
if msgPacketLen >= 2:
enc = msgPacket[1].lower()
if enc == 'true' or enc == 'false':
encrypted = enc == "true"
dataIndex +=1
# Create packet data and inject it
for i in range(dataIndex, msgPacketLen):
data.append(int(msgPacket[i],16))
inject_joymax(opcode,data,encrypted)
# Log the info
log("Plugin: Injecting packet...\nOpcode: 0x"+'{:02X}'.format(opcode)+" - Encrypted: "+("Yes" if encrypted else "No")+"\nData: "+(' '.join('{:02X}'.format(int(msgPacket[x],16)) for x in range(dataIndex, msgPacketLen)) if len(data) else 'None'))
elif msg.startswith("CHAT "):
handleChatCommand(msg[5:])
elif msg.startswith("MOVEON"):
if msg == "MOVEON":
randomMovement()
else:
try:
# split and parse movement radius
radius = int(float(msg[6:].split()[0]))
# to positive
radius = (radius if radius > 0 else radius*-1)
randomMovement(radius)
except:
log("Plugin: Movement maximum radius incorrect")
elif msg.startswith("FOLLOW"):
# default values
charName = player
distance = 10
if msg != "FOLLOW":
# Check params
msg = msg[6:].split()
try:
if len(msg) >= 1:
charName = msg[0]
if len(msg) >= 2:
distance = float(msg[1])
except:
log("Plugin: Follow distance incorrect")
return
# Start following
if start_follow(charName,distance):
log("Plugin: Starting to follow to ["+charName+"] using ["+str(distance)+"] as distance")
elif msg == "NOFOLLOW":
if stop_follow():
log("Plugin: Following stopped")
elif msg.startswith("PROFILE"):
if msg == "PROFILE":
if set_profile('Default'):
log("Plugin: Setting Default profile")
else:
msg = msg[7:]
if set_profile(msg):
log("Plugin: Setting "+msg+" profile")
elif msg == "DC":
log("Plugin: Disconnecting...")
disconnect()
elif msg.startswith("MOUNT"):
# default value
pet = "horse"
if msg != "MOUNT":
msg = msg[5:].split()
if msg:
pet = msg[0]
# Try mount pet
if MountPet(pet):
log("Plugin: Mounting pet ["+pet+"]")
elif msg.startswith("DISMOUNT"):
# default value
pet = "horse"
if msg != "DISMOUNT":
msg = msg[8:].split()
if msg:
pet = msg[0]
# Try dismount pet
if DismountPet(pet):
log("Plugin: Dismounting pet ["+pet+"]")
elif msg == "GETOUT":
# Check if has party
if get_party():
# Left it
log("Plugin: Leaving the party..")
inject_joymax(0x7061,b'',False)
elif msg.startswith("RECALL "):
msg = msg[7:]
if msg:
npcUID = GetNPCUniqueID(msg)
if npcUID > 0:
log("Plugin: Designating recall to \""+msg.title()+"\"...")
inject_joymax(0x7059, struct.pack('I',npcUID), False)
elif msg.startswith("EQUIP "):
msg = msg[6:]
if msg:
# search item with similar name or exact server name
item = GetItemByExpression(lambda n,s: msg in n or msg == s,13)
if item:
EquipItem(item)
elif msg.startswith("UNEQUIP "):
msg = msg[8:]
if msg:
# search item with similar name or exact server name
item = GetItemByExpression(lambda n,s: msg in n or msg == s,0,12)
if item:
UnequipItem(item)
elif msg.startswith("REVERSE "):
# remove command
msg = msg[8:]
if msg:
# check params
msg = msg.split(' ',1)
# param type
if msg[0] == 'return':
# try to use it
if reverse_return(0,''):
log('Plugin: Using reverse to the last return scroll location')
elif msg[0] == 'death':
# try to use it
if reverse_return(1,''):
log('Plugin: Using reverse to the last death location')
elif msg[0] == 'player':
# Check existing name
if len(msg) >= 2:
# try to use it
if reverse_return(2,msg[1]):
log('Plugin: Using reverse to player "'+msg[1]+'" location')
elif msg[0] == 'zone':
# Check existing zone
if len(msg) >= 2:
# try to use it
if reverse_return(3,msg[1]):
log('Plugin: Using reverse to zone "'+msg[1]+'" location')
elif msg.startswith("USE "):
# remove command
msg = msg[4:]
if msg:
# search item with similar name or exact server name
item = GetItemByExpression(lambda n,s: msg in n or msg == s,13)
if item:
UseItem(item)
# Called every 500ms
def event_loop():
if inGame and followActivated:
player = near_party_player(followPlayer)
# check if is near
if not player:
return
# check distance to the player
if followDistance > 0:
p = get_position()
playerDistance = round(GetDistance(p['x'],p['y'],player['x'],player['y']),2)
# check if has to move
if followDistance < playerDistance:
# generate vector unit
x_unit = (player['x'] - p['x']) / playerDistance
y_unit = (player['y'] - p['y']) / playerDistance
# distance to move
movementDistance = playerDistance-followDistance
log("Following "+followPlayer+"...")
move_to(movementDistance * x_unit + p['x'],movementDistance * y_unit + p['y'],0)
else:
# Avoid negative numbers
log("Following "+followPlayer+"...")
move_to(player['x'],player['y'],0)
# Plugin loaded
log("Plugin: "+pName+" v"+pVersion+" successfully loaded")
if os.path.exists(getPath()):
# Adding RELOAD plugin support
loadConfigs()
else:
# Creating configs folder
os.makedirs(getPath())
log('Plugin: '+pName+' folder has been created')