-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFight_StreamlabsSystem.py
More file actions
506 lines (424 loc) · 21.1 KB
/
Fight_StreamlabsSystem.py
File metadata and controls
506 lines (424 loc) · 21.1 KB
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
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Parent = Parent # pylint: disable=invalid-name
"""Minigames script to add a collection of games for viwers to play"""
# ---------------------------------------
# Libraries and references
# ---------------------------------------
import codecs
import json
import os
import re
import ctypes
import winsound
from time import time
# ---------------------------------------
# Script information
# ---------------------------------------
ScriptName = "Fight"
Website = "https://www.twitch.tv/Xailran"
Creator = "Xailran"
Version = "1.3.3"
Description = "Let viewers fight each other with a variety of weapons"
# ---------------------------------------
# Versions
# ---------------------------------------
"""
1.3.3 - Fixed all messages to be customizable
1.3.2 - Made changes to file handling, so that weapons and description files are no longer reset on an update.
1.3.1 - Added YouTube compatibility
1.3.0 - Added Response Reset button.
1.2.2 - Changed when cooldown starts
1.2.1 - Mixer-specific and general bug fixes
1.2.0 - Added $addweapon() parameter.
1.1.1 - Fixed script to properly work for Mixer and YT. Also fixed bug with using the "accept" settings
1.1.0 - Added UI options for viewers to select amount of points to fight for, and option for users to "accept" a fight
before it actually happens. (Funded by twitch.tv/GodOfRanch)
1.0.0 - Initial Release
"""
# ---------------------------------------
# Variables
# ---------------------------------------
settingsFile = os.path.join(os.path.dirname(__file__), "settings.json")
MessageBox = ctypes.windll.user32.MessageBoxW
Weapons = os.path.join(os.path.dirname(__file__), "Weapons.txt")
FightDescriptions = os.path.join(os.path.dirname(__file__), "FightResponses.txt")
MB_YES = 6
fightDict = {}
MySet = None # type: Settings
# ---------------------------------------
# Settings functions
# ---------------------------------------
def SetDefaults():
"""Set default settings function"""
winsound.MessageBeep()
returnValue = MessageBox(0, u"You are about to reset the settings, are you sure you want to continue?",
u"Reset settings file?", 4)
if returnValue == MB_YES:
Settings(None, None).Save(settingsFile)
MessageBox(0, u"Settings successfully restored to default values!\r\n"
u"Make sure to reload script to load new values into UI",
u"Reset complete!", 0)
def ReloadSettings(jsonData):
"""Reload settings on Save"""
global MySet
MySet.Reload(jsonData)
# ---------------------------------------
# UI functions
# ---------------------------------------
def OpenReadMe():
"""Open the readme.txt in the scripts folder"""
location = os.path.join(os.path.dirname(__file__), "README.txt")
os.startfile(location)
def OpenFolder():
"""Open Minigames Script Folder"""
location = (os.path.dirname(os.path.realpath(__file__)))
os.startfile(location)
def WeaponsFile():
"""Open file with weapons data"""
location = os.path.join(os.path.dirname(__file__), "Weapons.txt")
os.startfile(location)
def FightDescriptionsFile():
"""Open file with weapons data"""
location = os.path.join(os.path.dirname(__file__), "FightResponses.txt")
os.startfile(location)
def ResetWeaponsFile():
"""Resets weapons file back to defaults"""
winsound.MessageBeep()
returnValue = MessageBox(0, u"You are about to reset the weapons file, are you sure you want to continue?",
u"Reset weapons file?", 4)
if returnValue == MB_YES:
CreateWeapons()
MessageBox(0, u"Weapons successfully restored to default values!",
u"Reset complete!", 0)
def ResetResponsesFile():
"""Resets responses file back to defaults"""
winsound.MessageBeep()
returnValue = MessageBox(0, u"You are about to reset the responses file, are you sure you want to continue?",
u"Reset responses file?", 4)
if returnValue == MB_YES:
CreateResponses()
MessageBox(0, u"Responses successfully restored to default values!",
u"Reset complete!", 0)
# ---------------------------------------
# Optional functions
# ---------------------------------------
def IsOnCooldown(data, command):
"""Handle cooldowns"""
cooldown = Parent.IsOnCooldown(ScriptName, command)
usercooldown = Parent.IsOnUserCooldown(ScriptName, command, data.User)
caster = (Parent.HasPermission(data.User, "Caster", "") and MySet.castercd)
if (cooldown or usercooldown) and caster is False:
if MySet.usecd:
cooldownDuration = Parent.GetCooldownDuration(ScriptName, command)
userCDD = Parent.GetUserCooldownDuration(ScriptName, command, data.User)
if cooldownDuration > userCDD:
SendResp(data, MySet.oncooldown.format(data.UserName, cooldownDuration))
else:
SendResp(data, MySet.onusercooldown.format(data.UserName, userCDD))
return False
return True
def SendResp(data, message):
"""Sends message to Stream or discord chat depending on settings"""
message = message.replace("$user", data.UserName)
message = message.replace("$currencyname", Parent.GetCurrencyName())
message = message.replace("$target", data.GetParam(1))
if not data.IsFromDiscord() and not data.IsWhisper():
Parent.SendStreamMessage(message)
if not data.IsFromDiscord() and data.IsWhisper():
Parent.SendStreamWhisper(data.User, message)
if data.IsFromDiscord() and not data.IsWhisper():
Parent.SendDiscordmessage(message)
if data.IsFromDiscord() and data.IsWhisper():
Parent.SendDiscordDM(data.User, message)
def HasPermission(data, permission, permissioninfo):
"""Return true or false dending on if the user has permission.
Also sends permission response if user doesn't"""
if not Parent.HasPermission(data.User, permission, permissioninfo):
message = MySet.notperm.format(data.UserName, permission, permissioninfo)
SendResp(data, message)
return False
return True
# ---------------------------------------
# [Required] functions
# ---------------------------------------
def Init():
"""data on Load, required function"""
global MySet
MySet = Settings(Parent, settingsFile)
global parent
parent = Parent
global fightDict
fightDict = {}
def Execute(data):
"""Required Execute data function"""
if not data.IsWhisper() and not data.IsFromDiscord() and data.GetParam(0).lower() == MySet.command.lower():
# Path to run fight itself, or sets up fight acknowledgment as determined by settings
if not HasPermission(data, MySet.FightPermission, MySet.FightPermissionInfo):
return
if MySet.onlylive and not Parent.IsLive():
message = MySet.respNotLive.format(data.UserName)
SendResp(data, message)
return
if IsOnCooldown(data, "fight"):
Fight(data)
if not data.IsWhisper() and not data.IsFromDiscord() and MySet.NeedApproval and \
data.GetParam(0).lower() == MySet.acceptcommand.lower():
# Path for user to accept a challenge that has been issued
global fightDict
for user, userdict in fightDict.items():
# Removes expired challenges
if userdict["timestamp"] + MySet.accepttime < int(time()):
del fightDict[user]
# Checks if user is a current target for a challenge
else:
if userdict["opponentname"].lower() == data.UserName.lower():
if data.IsFromYoutube():
userdict["opponentid"] = data.User
Attack(userdict["data"], userdict)
del fightDict[user]
break
# Triggers if a valid user was not found
else:
message = MySet.nochallenge.format(data.UserName)
SendResp(data, message)
def Tick():
"""Required tick function"""
return
def Parse(parseString, userid, username, targetid, targetname, message):
"""Adds parameters to be used in commands"""
# Add Weapon parameter
# Group 1 = whole parameter, group 2 = weapon to be added, group 3 = success message, group 4 = failure message
aw = re.match(r'^.*(\$addweapon\("?([^"]+)"?,\s?"?([^"]*)"?,\s?"?([^"]+)"?\)).*', parseString)
if aw:
with codecs.open(Weapons, encoding="utf-8-sig", mode="r") as text:
Item = [line.strip().lower() for line in text]
if aw.group(2).lower() in Item or aw.group(2) == "":
# Weapon already exists
parseString = parseString.replace(aw.group(1), aw.group(4))
else:
# Weapon needs to be added
Item.append(aw.group(2))
Item.sort()
with codecs.open(Weapons, "w", "utf-8") as f:
filedata = Item.pop(0)
for x in Item:
filedata += "\r\n" + x
f.write(filedata)
parseString = parseString.replace(aw.group(1), aw.group(3))
return parseString
# ---------------------------------------
# [Optional] Fight functions
# ---------------------------------------
def Fight(data):
"""Full setup for attack function"""
# Establishes opponent
global MySet
userfightdict = {"data": data, "opponentname": data.GetParam(1).replace("@", "")}
viewerlist = [x for x in Parent.GetViewerList()]
if data.IsFromTwitch():
viewerlist.append(Parent.GetChannelName())
if userfightdict["opponentname"] == "":
message = MySet.needinfo.format(data.UserName, MySet.command)
SendResp(data, message)
return
if userfightdict["opponentname"].lower() == data.UserName.lower():
message = MySet.attackself.format(data.UserName)
SendResp(data, message)
return
check = False
if data.IsFromYoutube():
check = True
MySet.NeedApproval = True
userfightdict["opponentname"] = Parent.GetDisplayName(userfightdict["opponentname"].lower())
userfightdict["opponentid"] = userfightdict["opponentname"].lower()
else:
for x in viewerlist:
if userfightdict["opponentname"].lower() == Parent.GetDisplayName(x.lower()).lower():
userfightdict["opponentname"] = Parent.GetDisplayName(x)
userfightdict["opponentid"] = x.lower()
check = True
break
if not check:
message = MySet.targetoffline.format(data.UserName, userfightdict["opponentname"])
SendResp(data, message)
return
# Sets parameters
userfightdict["UPoints"] = Parent.GetPoints(data.User)
userfightdict["TPoints"] = Parent.GetPoints(userfightdict["opponentid"])
# Sets command cost
if MySet.UserChoiceSetting == "Standard" or MySet.UserChoiceSetting == "Default" and data.GetParam(2):
try:
userfightdict["fightpoints"] = int(data.GetParam(2))
except ValueError:
message = MySet.outsiderange.format(data.UserName, str(MySet.FightMinValue), str(MySet.FightMaxValue),
MySet.command)
SendResp(data, message)
return
else:
if not MySet.FightMinValue <= userfightdict["fightpoints"] <= MySet.FightMaxValue:
message = MySet.outsiderange.format(data.UserName, str(MySet.FightMinValue), str(MySet.FightMaxValue),
MySet.command)
SendResp(data, message)
return
else:
if MySet.FightSetting == "Constant":
userfightdict["fightpoints"] = MySet.FightCValue
else:
if MySet.FightMinValue < MySet.FightMaxValue:
userfightdict["fightpoints"] = Parent.GetRandom(MySet.FightMinValue, MySet.FightMaxValue + 1)
else:
userfightdict["fightpoints"] = Parent.GetRandom(MySet.FightMaxValue, MySet.FightMinValue + 1)
# Checks that all parties have enough points
if Parent.GetPoints(data.User) < userfightdict["fightpoints"]:
message = MySet.notenough.format(data.UserName, str(userfightdict["fightpoints"]), Parent.GetCurrencyName(),
str(userfightdict["UPoints"]))
SendResp(data, message)
return
if userfightdict["TPoints"] < userfightdict["fightpoints"]:
message = MySet.opponentnotenough.format(userfightdict["opponentname"], str(userfightdict["fightpoints"]),
Parent.GetCurrencyName(), str(userfightdict["TPoints"]))
SendResp(data, message)
return
# Approval system
Parent.AddCooldown(ScriptName, "fight", MySet.timerCooldown)
Parent.AddUserCooldown(ScriptName, "fight", userfightdict["data"].User, MySet.timerUserCooldown)
if MySet.NeedApproval:
global fightDict
userfightdict["timestamp"] = int(time())
fightDict[data.UserName] = userfightdict
message = MySet.challengeissued.format(data.UserName, userfightdict["opponentname"], MySet.acceptcommand,
str(MySet.accepttime))
SendResp(data, message)
else:
Attack(data, userfightdict)
def Attack(data, userfightdict):
"""Handles the actual fight component"""
# Battle setup
UWeapon, OWeapon = GrabWeapons(userfightdict)
if not UWeapon or not OWeapon:
return
victory_message = GetVictoryMessage()
# Determines winner. Point and response management follows
if MySet.FightAttWinChance >= Parent.GetRandom(1, 101):
SendResult(data, data.User, data.UserName, UWeapon, userfightdict["opponentid"],
userfightdict["opponentname"], OWeapon, userfightdict["fightpoints"], victory_message)
message = MySet.userwon.format(userfightdict["data"].UserName, userfightdict["opponentname"],
userfightdict["fightpoints"], Parent.GetCurrencyName())
SendResp(data, message)
else:
SendResult(data, userfightdict["opponentid"], userfightdict["opponentname"], OWeapon,
data.User, data.UserName, UWeapon, userfightdict["fightpoints"], victory_message)
message = MySet.targetwon.format(data.UserName, userfightdict["opponentname"],
userfightdict["fightpoints"], Parent.GetCurrencyName())
SendResp(data, message)
def GrabWeapons(userfightdict):
"""Gets a unique weapon for each opponent"""
if not os.path.exists(Weapons):
CreateWeapons()
with codecs.open(Weapons, encoding="utf-8-sig", mode="r") as text:
Item = [line.strip() for line in text]
UWeapon = Item[Parent.GetRandom(0, len(Item))]
OWeapon = Item[Parent.GetRandom(0, len(Item))]
count = 0
while UWeapon == OWeapon and MySet.force_unique:
OWeapon = Item[Parent.GetRandom(0, len(Item))]
count += 1
if count == 3:
message = MySet.identicalweapons
SendResp(userfightdict["data"], message)
return "", ""
return UWeapon, OWeapon
def GetVictoryMessage():
if not os.path.exists(FightDescriptions):
CreateResponses()
with codecs.open(FightDescriptions, encoding="utf-8-sig", mode="r") as text:
Item = [line.strip() for line in text]
return Item[Parent.GetRandom(1, len(Item))]
def SendResult(data, winner_id, winner_name, winner_weapon, loser_id, loser_name, loser_weapon, points,
victory_message):
Parent.RemovePoints(loser_id, loser_name, points)
Parent.AddPoints(winner_id, winner_name, points)
message = victory_message.format(winner_name, winner_weapon, loser_name, loser_weapon)
SendResp(data, message)
def CreateWeapons():
"""Replaces/creates weapons file with default weapons"""
with codecs.open(Weapons, "w", "utf-8") as f:
textline = "apple\r\naxe\r\nboomerang\r\nchainsaw\r\ngolf club\r\npistol\r\nspear\r\nspoon\r\ntaco\r\ntrain"
f.write(textline)
def CreateResponses():
"""Replaces/creates responses file with default responses"""
with codecs.open(FightDescriptions, "w", "utf-8") as f:
textline = "Formatting: {0} = winner, {1} = winner's weapon, {2} = loser, {3} = loser's weapon\r\n" \
"{0} used a(n) {1} to fight {2}, and crushed in their skull!\r\n" \
"Failure! Not for you though {0}. Your glorious {1} pulverised {2}'s {3}.\r\n" \
"Ahhh, victory! {0}'s fantastic {1} smashed {2} with their {3}.\r\n" \
"With a {1}, {0} attacked {2}. Even with their {3}. {2} could do nothing to win this fight!\r\n" \
"{2} used a(n) {3} with horrible results! {0} with their {1} obliterated them!\r\n" \
"Ahhh, victory! Not for you though {2}. Your puny {3} lost against {0}'s {1}.\r\n" \
"{0} used a(n) {1} to fight {2} who used a(n) {3}, and {0} won!"
f.write(textline)
# ---------------------------------------
# Classes
# ---------------------------------------
class Settings:
"""" Loads settings from file if file is found if not uses default values"""
# The 'default' variable names need to match UI_Config
def __init__(self, parent, settingsFile=None):
if settingsFile and os.path.isfile(settingsFile):
with codecs.open(settingsFile, encoding='utf-8-sig', mode='r') as f:
self.__dict__ = json.load(f, encoding='utf-8-sig')
else: # set variables if no custom settings file is found
self.onlylive = False
self.permission = "Everyone"
self.PermissionInfo = ""
self.castercd = True
self.usecd = True
self.timerCooldown = 5
self.oncooldown = "{0} the command is still on cooldown for {1} seconds!"
self.timerUserCooldown = 30
self.onusercooldown = "{0} the command is still on user cooldown for {1} seconds!"
self.command = "!fight"
self.FightPermission = "Everyone"
self.FightPermissionInfo = ""
self.FightAttWinChance = 50
self.UserChoiceSetting = "Default"
self.FightSetting = "Constant"
self.FightCValue = 250
self.FightMinValue = 100
self.FightMaxValue = 500
self.NeedApproval = False
self.acceptcommand = "accept"
self.accepttime = 180
self.force_unique = True
self.challengeissued = "{0} has issued a challenge against {1}; " \
"Type {2} in chat within the next {3} seconds to accept!"
self.nochallenge = "Sorry {0}, but there are no challenges for you to confirm currently!"
self.targetoffline = "Hey, that viewer isn't online right now, fighting them would be unfair!"
self.needinfo = "You must choose a target when using the {0} command!"
self.attackself = "Trying to fight yourself? That's a little angsty"
self.respNotLive = "Sorry {0}, but the stream must be live in order to use that command."
self.notenough = "{0} -> you don't have the {1} {2} required to use this command."
self.outsiderange = "{0} -> you need to enter a number between {1} and {2} for the amount to fight for! " \
"Format: {3} (opponent name) (amount)"
self.opponentnotenough = "{0} doesn't have enough {2}!"
self.userwon = "{0}, you WON {2} {3}. {1}, you LOST {2} {3}"
self.targetwon = "{0}, you LOST {2} {3}. {1}, you WON {2} {3}"
self.identicalweapons = "Despite my best efforts, we only seem to have one weapon available!"
self.notperm = "{0} -> you don't have permission to use this command. permission is: [{1} / {2}]"
self.parent = parent
# Reload settings on save through UI
def Reload(self, data):
"""Reload settings on save through UI"""
parent = self.parent
self.__dict__ = json.loads(data, encoding='utf-8-sig')
self.parent = parent
def Save(self, settingsfile):
""" Save settings contained within the .json and .js settings files. """
try:
with codecs.open(settingsfile, encoding="utf-8-sig", mode="w+") as f:
json.dump(self.__dict__, f, encoding="utf-8", ensure_ascii=False)
with codecs.open(settingsfile.replace("json", "js"), encoding="utf-8-sig", mode="w+") as f:
f.write("var settings = {0};".format(json.dumps(self.__dict__, encoding='utf-8', ensure_ascii=False)))
except ValueError:
MessageBox(0, u"Settings failed to save to file", u"Saving failed", 0)