bombsquad-plugin-manager/plugins/minigames/dodge_the_ball.py

766 lines
27 KiB
Python
Raw Normal View History

2023-05-15 15:56:45 +05:30
"""
DondgeTheBall minigame by EmperoR#4098
"""
# Feel free to edit.
# ba_meta require api 7
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
2023-05-15 10:58:00 +00:00
from random import choice
2023-05-15 15:56:45 +05:30
from enum import Enum
from bastd.actor.bomb import Blast
from bastd.actor.popuptext import PopupText
from bastd.actor.powerupbox import PowerupBox
from bastd.actor.onscreencountdown import OnScreenCountdown
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import NoReturn, Sequence, Any
# Type of ball in this game
class BallType(Enum):
""" Types of ball """
EASY = 0
# Decrease the next ball shooting speed(not ball speed).
# Ball color is yellow.
MEDIUM = 1
# increase the next ball shooting speed(not ball speed).
# target the head of player.
# Ball color is purple.
HARD = 2
# Target player according to player movement (not very accurate).
# Taget: player head.
# increase the next ball speed but less than MEDIUM.
# Ball color is crimson(purple+red = pinky color type).
2023-05-15 10:58:00 +00:00
# this dict decide the ball_type spawning rate like powerup box
2023-05-15 15:56:45 +05:30
ball_type_dict: dict[BallType, int] = {
BallType.EASY: 3,
BallType.MEDIUM: 2,
BallType.HARD: 1,
2023-05-15 10:58:00 +00:00
}
2023-05-15 15:56:45 +05:30
class Ball(ba.Actor):
""" Shooting Ball """
2023-05-15 10:58:00 +00:00
def __init__(self,
position: Sequence[float],
velocity: Sequence[float],
texture: ba.Texture,
body_scale: float = 1.0,
gravity_scale: float = 1.0,
) -> NoReturn:
super().__init__()
shared = SharedObjects.get()
ball_material = ba.Material()
2023-05-15 15:56:45 +05:30
ball_material.add_actions(
conditions=(
(
('we_are_younger_than', 100),
'or',
('they_are_younger_than', 100),
),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
self.node = ba.newnode(
'prop',
delegate=self,
attrs={
'body': 'sphere',
'position': position,
'velocity': velocity,
'body_scale': body_scale,
'model': ba.getmodel('frostyPelvis'),
'model_scale': body_scale,
'color_texture': texture,
'gravity_scale': gravity_scale,
2023-05-15 10:58:00 +00:00
'density': 4.0, # increase density of ball so ball collide with player with heavy force. # ammm very bad grammer
2023-05-15 15:56:45 +05:30
'materials': (ball_material,),
2023-05-15 10:58:00 +00:00
},
)
2023-05-15 15:56:45 +05:30
# die the ball manually incase the ball doesn't fall the outside of the map
2023-05-15 10:58:00 +00:00
ba.timer(2.5, ba.WeakCall(self.handlemessage, ba.DieMessage()))
2023-05-15 15:56:45 +05:30
# i am not handling anything in this ball Class(except for diemessage).
# all game things and logics going to be in the box class
def handlemessage(self, msg: Any) -> Any:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
if isinstance(msg, ba.DieMessage):
2023-05-15 10:58:00 +00:00
self.node.delete()
2023-05-15 15:56:45 +05:30
else:
2023-05-15 10:58:00 +00:00
super().handlemessage(msg)
2023-05-15 15:56:45 +05:30
class Box(ba.Actor):
""" A box that spawn midle of map as a decoration perpose """
2023-05-15 10:58:00 +00:00
def __init__(self,
position: Sequence[float],
velocity: Sequence[float],
) -> NoReturn:
super().__init__()
shared = SharedObjects.get()
2023-05-15 15:56:45 +05:30
# self.ball_jump = 0.0;
2023-05-15 10:58:00 +00:00
no_hit_material = ba.Material()
2023-05-15 15:56:45 +05:30
# we don't need that the box was move and collide with objects.
no_hit_material.add_actions(
conditions=(
('they_have_material', shared.pickup_material),
'or',
('they_have_material', shared.attack_material),
),
actions=('modify_part_collision', 'collide', False),
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
no_hit_material.add_actions(
conditions=(
('they_have_material', shared.object_material),
'or',
('they_dont_have_material', shared.footing_material),
),
actions=(
('modify_part_collision', 'collide', False),
('modify_part_collision', 'physical', False),
),
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
self.node = ba.newnode(
'prop',
delegate=self,
attrs={
'body': 'box',
'position': position,
'model': ba.getmodel('powerup'),
'light_model': ba.getmodel('powerupSimple'),
'shadow_size': 0.5,
'body_scale': 1.4,
'model_scale': 1.4,
'color_texture': ba.gettexture('landMineLit'),
'reflection': 'powerup',
'reflection_scale': [1.0],
'materials': (no_hit_material,),
2023-05-15 10:58:00 +00:00
},
)
2023-05-15 15:56:45 +05:30
# light
self.light = ba.newnode(
"light",
2023-05-15 10:58:00 +00:00
owner=self.node,
2023-05-15 15:56:45 +05:30
attrs={
2023-05-15 10:58:00 +00:00
'radius': 0.2,
'intensity': 0.8,
'color': (0.0, 1.0, 0.0),
2023-05-15 15:56:45 +05:30
}
2023-05-15 10:58:00 +00:00
)
self.node.connectattr("position", self.light, "position")
# Drawing circle and circleOutline in radius of 3,
# so player can see that how close he is to the box.
# If player is inside this circle the ball speed will increase.
2023-05-15 15:56:45 +05:30
circle = ba.newnode(
"locator",
2023-05-15 10:58:00 +00:00
owner=self.node,
attrs={
2023-05-15 15:56:45 +05:30
'shape': 'circle',
'color': (1.0, 0.0, 0.0),
'opacity': 0.1,
'size': (6.0, 0.0, 6.0),
'draw_beauty': False,
'additive': True,
},
)
2023-05-15 10:58:00 +00:00
self.node.connectattr("position", circle, "position")
2023-05-15 15:56:45 +05:30
# also adding a outline cause its look nice.
circle_outline = ba.newnode(
"locator",
2023-05-15 10:58:00 +00:00
owner=self.node,
attrs={
2023-05-15 15:56:45 +05:30
'shape': 'circleOutline',
'color': (1.0, 1.0, 0.0),
'opacity': 0.1,
'size': (6.0, 0.0, 6.0),
'draw_beauty': False,
'additive': True,
},
2023-05-15 10:58:00 +00:00
)
self.node.connectattr("position", circle_outline, "position")
2023-05-15 15:56:45 +05:30
# all ball attribute that we need.
2023-05-15 10:58:00 +00:00
self.ball_type: BallType = BallType.EASY
self.shoot_timer: ba.Timer | None = None
self.shoot_speed: float = 0.0
2023-05-15 15:56:45 +05:30
# this force the shoot if player is inside the red circle.
2023-05-15 10:58:00 +00:00
self.force_shoot_speed: float = 0.0
self.ball_mag = 3000
self.ball_gravity: float = 1.0
self.ball_tex: ba.Texture | None = None
# only for Hard ball_type
self.player_facing_direction: list[float, float] = [0.0, 0.0]
2023-05-15 15:56:45 +05:30
# ball shoot soound.
2023-05-15 10:58:00 +00:00
self.shoot_sound = ba.getsound('laserReverse')
# same as "powerupdist"
self.ball_type_dist: list[BallType] = []
2023-05-15 15:56:45 +05:30
for ball in ball_type_dict:
for _ in range(ball_type_dict[ball]):
2023-05-15 10:58:00 +00:00
self.ball_type_dist.append(ball)
2023-05-15 15:56:45 +05:30
# Here main logic of game goes here.
# like shoot balls, shoot speed, anything we want goes here(except for some thing).
def start_shoot(self) -> NoReturn:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# getting all allive players in a list.
2023-05-15 10:58:00 +00:00
alive_players_list = self.activity.get_alive_players()
2023-05-15 15:56:45 +05:30
# make sure that list is not Empty.
if len(alive_players_list) > 0:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# choosing a random player from list.
2023-05-15 10:58:00 +00:00
target_player = choice(alive_players_list)
# highlight the target player
self.highlight_target_player(target_player)
2023-05-15 15:56:45 +05:30
# to finding difference between player and box.
# we just need to subtract player pos and ball pos.
# Same logic as eric applied in Target Practice Gamemode.
2023-05-15 10:58:00 +00:00
difference = ba.Vec3(target_player.position) - ba.Vec3(self.node.position)
2023-05-15 15:56:45 +05:30
# discard Y position so ball shoot more straight.
difference[1] = 0.0
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# and now, this length method returns distance in float.
# we're gonna use this value for calculating player analog stick
2023-05-15 10:58:00 +00:00
distance = difference.length()
2023-05-15 15:56:45 +05:30
# shoot a random BallType
2023-05-15 10:58:00 +00:00
self.upgrade_ball_type(choice(self.ball_type_dist))
2023-05-15 15:56:45 +05:30
# and check the ball_type and upgrade it gravity_scale, texture, next ball speed.
2023-05-15 10:58:00 +00:00
self.check_ball_type(self.ball_type)
2023-05-15 15:56:45 +05:30
# For HARD ball i am just focusing on player analog stick facing direction.
# Not very accurate and that's we need.
if self.ball_type == BallType.HARD:
2023-05-15 10:58:00 +00:00
self.calculate_player_analog_stick(target_player, distance)
2023-05-15 15:56:45 +05:30
else:
2023-05-15 10:58:00 +00:00
self.player_facing_direction = [0.0, 0.0]
pos = self.node.position
2023-05-15 15:56:45 +05:30
if self.ball_type == BallType.MEDIUM or self.ball_type == BallType.HARD:
# Target head by increasing Y pos.
# How this work? cause ball gravity_scale is ......
2023-05-15 10:58:00 +00:00
pos = (pos[0], pos[1]+.25, pos[2])
2023-05-15 15:56:45 +05:30
# ball is generating..
ball = Ball(
2023-05-15 10:58:00 +00:00
position=pos,
velocity=(0.0, 0.0, 0.0),
texture=self.ball_tex,
gravity_scale=self.ball_gravity,
body_scale=1.0,
).autoretain()
2023-05-15 15:56:45 +05:30
# shoot Animation and sound.
2023-05-15 10:58:00 +00:00
self.shoot_animation()
2023-05-15 15:56:45 +05:30
# force the shoot speed if player try to go inside the red circle.
if self.force_shoot_speed != 0.0:
2023-05-15 10:58:00 +00:00
self.shoot_speed = self.force_shoot_speed
2023-05-15 15:56:45 +05:30
# push the ball to the player
ball.node.handlemessage(
2023-05-15 10:58:00 +00:00
'impulse',
2023-05-15 15:56:45 +05:30
self.node.position[0], # ball spawn position X
self.node.position[1], # Y
self.node.position[2], # Z
2023-05-15 10:58:00 +00:00
0, 0, 0, # velocity x,y,z
self.ball_mag, # magnetude
0.000, # magnetude velocity
2023-05-15 15:56:45 +05:30
0.000, # radius
0.000, # idk
2023-05-15 10:58:00 +00:00
difference[0] + self.player_facing_direction[0], # force direction X
difference[1], # force direction Y
difference[2] + self.player_facing_direction[1], # force direction Z
)
2023-05-15 15:56:45 +05:30
# creating our timer and shoot the ball again.(and we create a loop)
2023-05-15 10:58:00 +00:00
self.shoot_timer = ba.Timer(self.shoot_speed, self.start_shoot)
2023-05-15 15:56:45 +05:30
def upgrade_ball_type(self, ball_type: BallType) -> NoReturn:
2023-05-15 10:58:00 +00:00
self.ball_type = ball_type
2023-05-15 15:56:45 +05:30
def check_ball_type(self, ball_type: BallType) -> NoReturn:
2023-05-15 10:58:00 +00:00
if ball_type == BallType.EASY:
self.shoot_speed = 0.8
self.ball_gravity = 1.0
# next ball shoot speed
self.ball_mag = 3000
# box light color and ball tex
self.light.color = (1.0, 1.0, 0.0)
self.ball_tex = ba.gettexture('egg4')
elif ball_type == BallType.MEDIUM:
self.ball_mag = 3000
# decrease the gravity scale so, ball shoot without falling and straight.
self.ball_gravity = 0.0
# next ball shoot speed.
self.shoot_speed = 0.4
# box light color and ball tex.
self.light.color = (1.0, 0.0, 1.0)
self.ball_tex = ba.gettexture('egg3')
elif ball_type == BallType.HARD:
self.ball_mag = 2500
self.ball_gravity = 0.0
# next ball shoot speed.
self.shoot_speed = 0.6
# box light color and ball tex.
self.light.color = (1.0, 0.2, 1.0)
self.ball_tex = ba.gettexture('egg1')
2023-05-15 15:56:45 +05:30
def shoot_animation(self) -> NoReturn:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
ba.animate(
self.node,
2023-05-15 10:58:00 +00:00
"model_scale", {
2023-05-15 15:56:45 +05:30
0.00: 1.4,
0.05: 1.7,
0.10: 1.4,
}
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
# playing shoot sound.
2023-05-15 10:58:00 +00:00
ba.playsound(self.shoot_sound, position=self.node.position)
2023-05-15 15:56:45 +05:30
def highlight_target_player(self, player: ba.Player) -> NoReturn:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# adding light
light = ba.newnode(
"light",
2023-05-15 10:58:00 +00:00
owner=self.node,
2023-05-15 15:56:45 +05:30
attrs={
2023-05-15 10:58:00 +00:00
'radius': 0.0,
'intensity': 1.0,
'color': (1.0, 0.0, 0.0),
2023-05-15 15:56:45 +05:30
}
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
ba.animate(
2023-05-15 10:58:00 +00:00
light,
"radius", {
0.05: 0.02,
0.10: 0.07,
0.15: 0.15,
0.20: 0.13,
0.25: 0.10,
0.30: 0.05,
0.35: 0.02,
0.40: 0.00,
}
)
2023-05-15 15:56:45 +05:30
# And a circle outline with ugly animation.
circle_outline = ba.newnode(
"locator",
2023-05-15 10:58:00 +00:00
owner=player.actor.node,
2023-05-15 15:56:45 +05:30
attrs={
'shape': 'circleOutline',
'color': (1.0, 0.0, 0.0),
'opacity': 1.0,
'draw_beauty': False,
'additive': True,
},
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
ba.animate_array(
circle_outline,
2023-05-15 10:58:00 +00:00
'size',
2023-05-15 15:56:45 +05:30
1, {
2023-05-15 10:58:00 +00:00
0.05: [0.5],
0.10: [0.8],
0.15: [1.5],
0.20: [2.0],
0.25: [1.8],
0.30: [1.3],
0.35: [0.6],
0.40: [0.0],
}
)
2023-05-15 15:56:45 +05:30
# coonect it and...
2023-05-15 10:58:00 +00:00
player.actor.node.connectattr("position", light, "position")
player.actor.node.connectattr("position", circle_outline, "position")
2023-05-15 15:56:45 +05:30
# immediately delete the node after another player has been targeted.
2023-05-15 10:58:00 +00:00
self.shoot_speed = 0.5 if self.shoot_speed == 0.0 else self.shoot_speed
ba.timer(self.shoot_speed, light.delete)
ba.timer(self.shoot_speed, circle_outline.delete)
def calculate_player_analog_stick(self, player: ba.Player, distance: float) -> NoReturn:
2023-05-15 15:56:45 +05:30
# at first i was very confused how i can read the player analog stick \
# then i saw TheMikirog#1984 autorun plugin code.
# and i got it how analog stick values are works.
# just need to store analog stick facing direction and need some calculation according how far player pushed analog stick.
# Notice that how vertical direction is inverted, so we need to put a minus infront of veriable.(so ball isn't shoot at wrong direction).
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = player.actor.node.move_left_right
self.player_facing_direction[1] = -player.actor.node.move_up_down
2023-05-15 15:56:45 +05:30
# if player is too close and the player pushing his analog stick fully the ball shoot's too far away to player.
# so, we need to reduce the value of "self.player_facing_direction" to fix this problem.
if distance <= 3:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = 0.4 if self.player_facing_direction[0] > 0 else -0.4
self.player_facing_direction[1] = 0.4 if self.player_facing_direction[0] > 0 else -0.4
2023-05-15 15:56:45 +05:30
# same problem to long distance but in reverse, the ball can't reach to the player,
# its because player analog stick value is between 1 and -1,
# and this value is low to shoot ball forward to Player if player is too far from the box.
# so. let's increase to 1.5 if player pushed analog stick fully.
elif distance > 6.5:
# So many calculation according to how analog stick pushed by player.
2023-05-15 10:58:00 +00:00
# Horizontal(left-right) calculation
2023-05-15 15:56:45 +05:30
if self.player_facing_direction[0] > 0.4:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = 1.5
2023-05-15 15:56:45 +05:30
elif self.player_facing_direction[0] < -0.4:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = -1.5
2023-05-15 15:56:45 +05:30
else:
if self.player_facing_direction[0] > 0.0:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = 0.2
2023-05-15 15:56:45 +05:30
elif self.player_facing_direction[0] < 0.0:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = -0.2
2023-05-15 15:56:45 +05:30
else:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[0] = 0.0
2023-05-15 15:56:45 +05:30
# Vertical(up-down) calculation.
if self.player_facing_direction[1] > 0.4:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[1] = 1.5
2023-05-15 15:56:45 +05:30
elif self.player_facing_direction[1] < -0.4:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[1] = -1.5
2023-05-15 15:56:45 +05:30
else:
if self.player_facing_direction[1] > 0.0:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[1] = 0.2
2023-05-15 15:56:45 +05:30
elif self.player_facing_direction[1] < 0.0:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[1] = -0.2
2023-05-15 15:56:45 +05:30
else:
2023-05-15 10:58:00 +00:00
self.player_facing_direction[1] = -0.0
2023-05-15 15:56:45 +05:30
# if we want stop the ball shootes
def stop_shoot(self) -> NoReturn:
2023-05-15 10:58:00 +00:00
# Kill the timer.
self.shoot_timer = None
2023-05-15 15:56:45 +05:30
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
# almost 80 % for game we done in box class.
2023-05-15 10:58:00 +00:00
# now remain things, like name, seetings, scoring, cooldonw,
2023-05-15 15:56:45 +05:30
# and main thing don't allow player to camp inside of box are going in this class.
# ba_meta export game
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
class DodgeTheBall(ba.TeamGameActivity[Player, Team]):
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# defining name, description and settings..
2023-05-15 10:58:00 +00:00
name = 'Dodge the ball'
description = 'Survive from shooting balls'
2023-05-15 15:56:45 +05:30
available_settings = [
ba.IntSetting(
'Cooldown',
2023-05-15 10:58:00 +00:00
min_value=20,
default=45,
increment=5,
2023-05-15 15:56:45 +05:30
),
ba.BoolSetting('Epic Mode', default=False)
]
# Don't allow joining after we start.
2023-05-15 10:58:00 +00:00
allow_mid_activity_joins = False
2023-05-15 15:56:45 +05:30
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
# We support team and ffa sessions.
return issubclass(sessiontype, ba.FreeForAllSession) or issubclass(
sessiontype, ba.DualTeamSession,
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
# This Game mode need a flat and perfect shape map where can player fall outside map.
# bombsquad have "Doom Shroom" map.
# Not perfect map for this game mode but its fine for this gamemode.
# the problem is that Doom Shroom is not a perfect circle and not flat also.
2023-05-15 10:58:00 +00:00
return ['Doom Shroom']
2023-05-15 15:56:45 +05:30
def __init__(self, settings: dict):
2023-05-15 10:58:00 +00:00
super().__init__(settings)
self._epic_mode = bool(settings['Epic Mode'])
self.countdown_time = int(settings['Cooldown'])
self.check_player_pos_timer: ba.Timer | None = None
self.shield_drop_timer: ba.Timer | None = None
2023-05-15 15:56:45 +05:30
# cooldown and Box
2023-05-15 10:58:00 +00:00
self._countdown: OnScreenCountdown | None = None
self.box: Box | None = None
2023-05-15 15:56:45 +05:30
# this lists for scoring.
2023-05-15 10:58:00 +00:00
self.joined_player_list: list[ba.Player] = []
self.dead_player_list: list[ba.Player] = []
2023-05-15 15:56:45 +05:30
# normally play RUN AWAY music cause is match with our gamemode at.. my point,
# but in epic switch to EPIC.
2023-05-15 10:58:00 +00:00
self.slow_motion = self._epic_mode
2023-05-15 15:56:45 +05:30
self.default_music = (
ba.MusicType.EPIC if self._epic_mode else ba.MusicType.RUN_AWAY
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
def get_instance_description(self) -> str | Sequence:
2023-05-15 10:58:00 +00:00
return 'Keep away as possible as you can'
2023-05-15 15:56:45 +05:30
# add a tiny text under our game name.
def get_instance_description_short(self) -> str | Sequence:
2023-05-15 10:58:00 +00:00
return 'Dodge the shooting balls'
2023-05-15 15:56:45 +05:30
def on_begin(self) -> NoReturn:
2023-05-15 10:58:00 +00:00
super().on_begin()
2023-05-15 15:56:45 +05:30
# spawn our box at middle of the map
self.box = Box(
position=(0.5, 2.7, -3.9),
velocity=(0.0, 0.0, 0.0),
2023-05-15 10:58:00 +00:00
).autoretain()
# create our cooldown
2023-05-15 15:56:45 +05:30
self._countdown = OnScreenCountdown(
2023-05-15 10:58:00 +00:00
duration=self.countdown_time,
endcall=self.play_victory_sound_and_end,
)
2023-05-15 15:56:45 +05:30
# and starts the cooldown and shootes.
2023-05-15 10:58:00 +00:00
ba.timer(5.0, self._countdown.start)
ba.timer(5.0, self.box.start_shoot)
2023-05-15 15:56:45 +05:30
# start checking all player pos.
2023-05-15 10:58:00 +00:00
ba.timer(5.0, self.check_player_pos)
2023-05-15 15:56:45 +05:30
# drop shield every ten Seconds
# need five seconds delay Because shootes start after 5 seconds.
2023-05-15 10:58:00 +00:00
ba.timer(15.0, self.drop_shield)
2023-05-15 15:56:45 +05:30
# This function returns all alive players in game.
# i thinck you see this function in Box class.
def get_alive_players(self) -> Sequence[ba.Player]:
2023-05-15 10:58:00 +00:00
alive_players = []
2023-05-15 15:56:45 +05:30
for team in self.teams:
for player in team.players:
if player.is_alive():
2023-05-15 10:58:00 +00:00
alive_players.append(player)
return alive_players
2023-05-15 15:56:45 +05:30
# let's disallowed camping inside of box by doing a blast and increasing ball shoot speed.
def check_player_pos(self):
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
for player in self.get_alive_players():
2023-05-15 10:58:00 +00:00
# same logic as applied for the ball
difference = ba.Vec3(player.position) - ba.Vec3(self.box.node.position)
distance = difference.length()
if distance < 3:
self.box.force_shoot_speed = 0.2
else:
self.box.force_shoot_speed = 0.0
if distance < 0.5:
Blast(
position=self.box.node.position,
velocity=self.box.node.velocity,
blast_type='normal',
blast_radius=1.0,
).autoretain()
PopupText(
position=self.box.node.position,
text='Keep away from me',
random_offset=0.0,
scale=2.0,
color=self.box.light.color,
).autoretain()
2023-05-15 15:56:45 +05:30
# create our timer and start looping it
2023-05-15 10:58:00 +00:00
self.check_player_pos_timer = ba.Timer(0.1, self.check_player_pos)
2023-05-15 15:56:45 +05:30
# drop useless shield's too give player temptation.
def drop_shield(self) -> NoReturn:
2023-05-15 10:58:00 +00:00
pos = self.box.node.position
PowerupBox(
position=(pos[0] + 4.0, pos[1] + 3.0, pos[2]),
poweruptype='shield',
).autoretain()
PowerupBox(
position=(pos[0] - 4.0, pos[1] + 3.0, pos[2]),
poweruptype='shield',
).autoretain()
self.shield_drop_timer = ba.Timer(10.0, self.drop_shield)
2023-05-15 15:56:45 +05:30
# when cooldown time up i don't want that the game end immediately.
def play_victory_sound_and_end(self) -> NoReturn:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# kill timers
2023-05-15 10:58:00 +00:00
self.box.stop_shoot()
2023-05-15 15:56:45 +05:30
self.check_player_pos_timer = None
self.shield_drop_timer = None
2023-05-15 10:58:00 +00:00
ba.timer(2.0, self.end_game)
2023-05-15 15:56:45 +05:30
# this function runs when A player spawn in map
def spawn_player(self, player: Player) -> NoReturn:
2023-05-15 10:58:00 +00:00
spaz = self.spawn_player_spaz(player)
2023-05-15 15:56:45 +05:30
# reconnect this player's controls.
# without bomb, punch and pickup.
spaz.connect_controls_to_player(
enable_punch=False, enable_bomb=False, enable_pickup=False,
2023-05-15 10:58:00 +00:00
)
2023-05-15 15:56:45 +05:30
# storing all players for ScorinG.
2023-05-15 10:58:00 +00:00
self.joined_player_list.append(player)
2023-05-15 15:56:45 +05:30
# Also lets have them make some noise when they die.
2023-05-15 10:58:00 +00:00
spaz.play_big_death_sound = True
2023-05-15 15:56:45 +05:30
# very helpful function to check end game when player dead or leav.
def _check_end_game(self) -> bool:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
if living_team_count <= 0:
# kill the coutdown timer incase the all players dead before game is about to going to be end.
# so, countdown won't call the function.
# FIXE ME: it's that ok to kill this timer?
# self._countdown._timer = None;
self.end_game()
# this function called when player leave.
def on_player_leave(self, player: Player) -> NoReturn:
# Augment default behavior.
2023-05-15 10:58:00 +00:00
super().on_player_leave(player)
2023-05-15 15:56:45 +05:30
# checking end game.
2023-05-15 10:58:00 +00:00
self._check_end_game()
2023-05-15 15:56:45 +05:30
# this gamemode needs to handle only one msg "PlayerDiedMessage".
def handlemessage(self, msg: Any) -> Any:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
2023-05-15 10:58:00 +00:00
super().handlemessage(msg)
2023-05-15 15:56:45 +05:30
# and storing the dead player records in our dead_player_list.
2023-05-15 10:58:00 +00:00
self.dead_player_list.append(msg.getplayer(Player))
2023-05-15 15:56:45 +05:30
# check the end game.
2023-05-15 10:58:00 +00:00
ba.timer(1.0, self._check_end_game)
2023-05-15 15:56:45 +05:30
def end_game(self):
# kill timers
self.box.stop_shoot()
self.check_player_pos_timer = None
self.shield_drop_timer = None
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# here the player_dead_list and joined_player_list gonna be very helpful.
for team in self.teams:
for player in team.players:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# for scoring i am just following the index of the player_dead_list.
# for dead list...
# 0th index player dead first.
# 1st index player dead second.
# and so on...
2023-05-15 10:58:00 +00:00
# i think you got it... maybe
2023-05-15 15:56:45 +05:30
# sometime we also got a empty list
# if we got a empty list that means all players are survived or maybe only one player playing and he/she survived.
if len(self.dead_player_list) > 0:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
for index, dead_player in enumerate(self.dead_player_list):
# if this condition is true we find the dead player \
# and his index with enumerate function.
if player == dead_player:
# updating with one, because i don't want to give 0 score to first dead player.
2023-05-15 10:58:00 +00:00
index += 1
2023-05-15 15:56:45 +05:30
break
# and if this statement is true we just find a survived player.
# for survived player i am giving the highest score according to how many players are joined.
elif index == len(self.dead_player_list) - 1:
index = len(self.joined_player_list)
# for survived player i am giving the highest score according to how many players are joined.
else:
index = len(self.joined_player_list)
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
# and here i am following Table of 10 for scoring.
# very lazY.
score = int(10 * index)
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
self.stats.player_scored(player, score, screenmessage=False)
# Ok now calc game results: set a score for each team and then tell \
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form \
# of 'teams' mode where each player gets their own team, so we can \
# just always deal in teams and have all cases covered.
# hmmm... some eric comments might be helpful to you.
for team in self.teams:
2023-05-15 10:58:00 +00:00
2023-05-15 15:56:45 +05:30
max_index = 0
for player in team.players:
2023-05-15 10:58:00 +00:00
# for the team, we choose only one player who survived longest.
# same logic..
if len(self.dead_player_list) > 0:
2023-05-15 15:56:45 +05:30
for index, dead_player in enumerate(self.dead_player_list):
if player == dead_player:
index += 1
break
elif index == len(self.dead_player_list) - 1:
index = len(self.joined_player_list)
2023-05-15 10:58:00 +00:00
else:
2023-05-15 15:56:45 +05:30
index = len(self.joined_player_list)
2023-05-15 10:58:00 +00:00
max_index = max(max_index, index)
2023-05-15 15:56:45 +05:30
# set the team score
2023-05-15 10:58:00 +00:00
results.set_team_score(team, int(10 * max_index))
2023-05-15 15:56:45 +05:30
# and end the game
self.end(results=results)