Skip to content

Commit 1baacc1

Browse files
committed
ENHANCEMENTS:
- CreditSystem: Players can have badges now
1 parent e63400b commit 1baacc1

File tree

5 files changed

+73
-25
lines changed

5 files changed

+73
-25
lines changed

plugins/creditsystem/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ DEFAULT: # valid for all servers
4040
achievements: # OPTIONAL: you can give players Discord roles according to their achievements
4141
- credits: 0
4242
playtime: 0 # Playtime is in hours
43-
role: Rookie # Initially, with 0 credits and 0 playtime, you get the role "Rookie" (has to be in Discord)
43+
badge: # Optional: give the player a badge
44+
name: Rookie
45+
img: https://example.com/rookie_badge.png
46+
role: Rookie # Optional: Initially, with 0 credits and 0 playtime, you get the role "Rookie" (has to be in Discord)
4447
- credits: 50
4548
playtime: 25
4649
role: Veteran # to get the Veteran role, you have to have EITHER 50 credit points OR a playtime of 25 hrs

plugins/creditsystem/listener.py

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import discord
33

44
from core import EventListener, Server, Status, utils, event, chat_command, get_translation, DataObjectFactory
5-
from typing import cast, TYPE_CHECKING
5+
from typing import cast, TYPE_CHECKING, Literal
66

77
from .player import CreditPlayer
88
from .squadron import Squadron
@@ -115,17 +115,17 @@ async def get_flighttime(self, ucid: str, campaign_id: int) -> int:
115115
cursor = await conn.execute("""
116116
SELECT COALESCE(ROUND(SUM(EXTRACT(EPOCH FROM (s.hop_off - s.hop_on)))), 0) AS playtime
117117
FROM statistics s JOIN missions m ON m.id = s.mission_id
118-
JOIN campaigns c ON c.id = %s
118+
JOIN campaigns c ON c.id = %(campaign_id)s
119119
JOIN campaigns_servers cs ON cs.campaign_id = c.id
120-
WHERE s.player_ucid = %s
120+
WHERE s.player_ucid = %(ucid)s
121121
AND m.server_name = cs.server_name
122122
AND tsrange(s.hop_on, s.hop_off) && tsrange(c.start, c.stop)
123-
""", (ucid, campaign_id))
123+
""", {"campaign_id": campaign_id, "ucid": ucid})
124124
return int((await cursor.fetchone())[0])
125125

126126
async def process_achievements(self, server: Server, player: CreditPlayer):
127127

128-
async def manage_role(member: discord.Member, role: str | int, action: str):
128+
async def manage_role(role: str | int, action: Literal['add', 'remove']):
129129
_role = self.bot.get_role(role)
130130
if not _role:
131131
self.log.error(f"Role {role} not found in your Discord!")
@@ -141,46 +141,79 @@ async def manage_role(member: discord.Member, role: str | int, action: str):
141141
self.log.error(
142142
f'The bot needs the "Manage Roles" permission or needs to be placed higher than role {_role.name}!')
143143

144-
# only linked player can achieve roles
145-
member = player.member
146-
if not member:
147-
return
144+
async def manage_badge(badge: dict, action: Literal['add', 'remove']):
145+
if action == "add":
146+
async with self.apool.connection() as conn:
147+
async with conn.transaction():
148+
await conn.execute("""
149+
INSERT INTO players_badges (campaign_id, player_ucid, badge_name, badge_url)
150+
VALUES (%s, %s, %s, %s)
151+
ON CONFLICT (campaign_id, player_ucid) DO NOTHING
152+
""", (campaign_id, player.ucid, badge['name'], badge['img']))
153+
await self.bot.audit(f"achieved the badge {badge['name']}", user=player.ucid)
154+
elif action == "remove":
155+
async with self.apool.connection() as conn:
156+
async with conn.transaction():
157+
await conn.execute("""
158+
DELETE FROM players_badges WHERE campaign_id = %s AND player_ucid = %s
159+
""", (campaign_id, player.ucid))
160+
await self.bot.audit(f"lost the badge {badge['name']}", user=player.ucid)
161+
148162
config: dict = self.plugin.get_config(server)
149163
if 'achievements' not in config:
150164
return
151165

152166
campaign_id, _ = utils.get_running_campaign(self.node, server)
167+
# only members can get roles
168+
member = player.member
153169
playtime = (await self.get_flighttime(player.ucid, campaign_id)) / 3600.0
154170
sorted_achievements = sorted(config['achievements'],
155171
key=lambda x: x['credits'] if 'credits' in x else x['playtime'],
156172
reverse=True)
157-
given = False
173+
role_given = badge_given = False
158174
for achievement in sorted_achievements:
159-
if given:
160-
await manage_role(member, achievement['role'], 'remove')
175+
if role_given or badge_given:
176+
if role_given:
177+
await manage_role(achievement['role'], 'remove')
178+
if badge_given:
179+
await manage_badge(achievement['badge'], 'remove')
161180
continue
162181
if achievement.get('combined'):
163182
if ('credits' in achievement and player.points >= achievement['credits']) and \
164183
('playtime' in achievement and playtime >= achievement['playtime']):
165-
await manage_role(member, achievement['role'], 'add')
166-
given = True
184+
if 'role' in achievement and member:
185+
await manage_role(achievement['role'], 'add')
186+
role_given = True
187+
if 'badge' in achievement:
188+
await manage_badge(achievement['badge'], 'add')
189+
badge_given = True
167190
else:
168-
await manage_role(member, achievement['role'], 'remove')
191+
if 'role' in achievement and member:
192+
await manage_role(achievement['role'], 'remove')
193+
if 'badge' in achievement:
194+
await manage_badge(achievement['badge'], 'remove')
169195
else:
170196
if ('credits' in achievement and player.points >= achievement['credits']) or \
171197
('playtime' in achievement and playtime >= achievement['playtime']):
172-
await manage_role(member, achievement['role'], 'add')
173-
given = True
198+
if 'role' in achievement and member:
199+
await manage_role(achievement['role'], 'add')
200+
role_given = True
201+
if 'badge' in achievement:
202+
await manage_badge(achievement['badge'], 'add')
203+
badge_given = True
174204
else:
175-
await manage_role(member, achievement['role'], 'remove')
205+
if 'role' in achievement and member:
206+
await manage_role(achievement['role'], 'remove')
207+
if 'badge' in achievement:
208+
await manage_badge(achievement['badge'], 'remove')
176209

177210
@event(name="onGameEvent")
178211
async def onGameEvent(self, server: Server, data: dict) -> None:
179212
config = self.plugin.get_config(server)
180213
if not config or server.status != Status.RUNNING:
181214
return
182215
if data['eventName'] == 'kill':
183-
# players gain points only, if they don't kill themselves and no teamkills
216+
# players gain points only if they don't kill themselves and no teamkills
184217
if data['arg1'] != -1 and data['arg1'] != data['arg4'] and data['arg3'] != data['arg6']:
185218
# Multicrew - pilot and all crew members gain points
186219
for player in server.get_crew_members(server.get_player(id=data['arg1'])): # type: CreditPlayer

plugins/creditsystem/schemas/creditsystem_schema.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ schema;element_schema:
6666
playtime: {type: int, range: {min: 0}, nullable: false}
6767
combined: {type: bool, nullable: false}
6868
role: {type: text, nullable: false}
69+
badge:
70+
type: map
71+
nullable: true
72+
mapping:
73+
name: {type: str, nullable: false, range: {min: 1}}
74+
img: {type: url, nullable: false}
6975
leaderboard:
7076
type: map
7177
nullable: false

plugins/restapi/commands.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -822,11 +822,13 @@ async def credits(self, nick: str = Form(...), date: str | None = Form(None),
822822
else:
823823
where = "WHERE (now() AT TIME ZONE 'utc') BETWEEN c.start AND COALESCE(c.stop, now() AT TIME ZONE 'utc')"
824824
await cursor.execute(f"""
825-
SELECT c.id, c.name, COALESCE(SUM(s.points), 0) AS credits
826-
FROM campaigns c LEFT OUTER JOIN credits s
827-
ON (c.id = s.campaign_id AND s.player_ucid = %(ucid)s)
825+
SELECT c.id, c.name, b.badge_name AS rank, b.badge_url AS badge,
826+
COALESCE(SUM(s.points), 0) AS credits
827+
FROM campaigns c
828+
LEFT OUTER JOIN credits s ON (c.id = s.campaign_id AND s.player_ucid = %(ucid)s)
829+
LEFT OUTER JOIN players_badges b ON (c.id = b.campaign_id AND b.player_ucid = %(ucid)s)
828830
{where}
829-
GROUP BY 1, 2
831+
GROUP BY 1, 2, 3, 4
830832
""", {"ucid": ucid, "campaign": campaign})
831833
row = await cursor.fetchone()
832834
if not row:

plugins/restapi/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,17 @@ class CampaignCredits(BaseModel):
412412
id: int = Field(..., description="Campaign ID")
413413
name: str = Field(..., description="Campaign name")
414414
credits: float = Field(..., description="Player's credits in this campaign")
415+
rank: str | None = Field(..., description="Player's rank")
416+
badge: str | None = Field(..., description="Player's badge")
415417

416418
model_config = {
417419
"json_schema_extra": {
418420
"example": {
419421
"id": 1,
420422
"name": "Summer Campaign 2025",
421-
"credits": 1500.0
423+
"credits": 1500.0,
424+
"rank": "Rookie",
425+
"badge": "https://example.com/rookie_badge.png"
422426
}
423427
}
424428
}

0 commit comments

Comments
 (0)