Skip to content

Commit 91204d6

Browse files
committed
add 13.7.0 files, missing new plane icons
1 parent 8e62119 commit 91204d6

File tree

150 files changed

+10710
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+10710
-8
lines changed

generated/abilities.json

+1-1
Large diffs are not rendered by default.

generated/achievements.json

+1-1
Large diffs are not rendered by default.

generated/exteriors.json

+1-1
Large diffs are not rendered by default.

generated/modernizations.json

+1-1
Large diffs are not rendered by default.

generated/planes.json

+1-1
Large diffs are not rendered by default.

generated/projectiles.json

+1-1
Large diffs are not rendered by default.

generated/ships.json

+1-1
Large diffs are not rendered by default.

generated/units.json

+1-1
Large diffs are not rendered by default.

replays/137.wowsreplay

2.67 MB
Binary file not shown.

resources/GameParams.data

145 KB
Binary file not shown.

resources/global.mo

27.8 KB
Binary file not shown.
1.95 KB
2.09 KB
-179 Bytes
-113 Bytes
1.9 KB
1.28 KB
1.95 KB
1.69 KB
1.7 KB
1.47 KB

src/renderer/versions/13_7_0/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .capture import LayerCapture
2+
from .health import LayerHealthBase as LayerHealth
3+
from .markers import LayerMarkersBase as LayerMarkers
4+
from .ribbon import LayerRibbon
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from typing import Optional
2+
from PIL import Image, ImageDraw
3+
from renderer.base import LayerBase
4+
from renderer.render import Renderer
5+
from renderer.const import COLORS_NORMAL, RELATION_NORMAL_STR
6+
from renderer.utils import replace_color
7+
from renderer.data import ReplayData
8+
from functools import lru_cache
9+
10+
11+
class LayerCapture(LayerBase):
12+
"""A class for handling/drawing capture points.
13+
14+
Args:
15+
LayerBase (_type_): _description_
16+
"""
17+
18+
def __init__(
19+
self, renderer: Renderer, replay_data: Optional[ReplayData] = None
20+
):
21+
"""Initiates this class.
22+
23+
Args:
24+
renderer (Renderer): The renderer.
25+
"""
26+
self._renderer = renderer
27+
self._replay_data = (
28+
replay_data if replay_data else self._renderer.replay_data
29+
)
30+
self._owner = self._replay_data.player_info[self._replay_data.owner_id]
31+
self._generated_caps: dict[
32+
int, tuple[Image.Image, tuple[int, int], int]
33+
] = {}
34+
35+
def draw(self, game_time: int, image: Image.Image):
36+
"""Draws the capture area on the minimap image.
37+
38+
Args:
39+
game_time (int): Game time. Used to sync. events.
40+
image (Image.Image): Image where the capture are will be pasted on.
41+
"""
42+
events = self._replay_data.events
43+
cps = events[game_time].evt_control.values()
44+
45+
for count, cp in enumerate(cps):
46+
if not cp.is_visible:
47+
continue
48+
49+
if count in self._generated_caps:
50+
cap_image, cap_pos, cap_hash = self._generated_caps[count]
51+
if hash(cp) == cap_hash:
52+
image.alpha_composite(cap_image, cap_pos)
53+
continue
54+
55+
x, y = self._renderer.get_scaled(cp.position)
56+
radius = self._renderer.get_scaled_r(cp.radius)
57+
w = h = round(radius * 2)
58+
cp_area = self._get_capture_area(cp.relation, (w, h))
59+
60+
if cp.control_point_type == 5:
61+
icon_name = "flag.png"
62+
else:
63+
icon_name = f"lettered_{count}.png"
64+
65+
relation_str = RELATION_NORMAL_STR[cp.relation]
66+
icon = self._renderer.resman.load_image(
67+
icon_name, path=f"cap_icons.{relation_str}"
68+
)
69+
70+
if cp.has_invaders and cp.invader_team != -1:
71+
if cp.invader_team == self._owner.team_id:
72+
from_color = COLORS_NORMAL[cp.relation]
73+
to_color = COLORS_NORMAL[0]
74+
else:
75+
from_color = COLORS_NORMAL[cp.relation]
76+
to_color = COLORS_NORMAL[1]
77+
progress = self._get_progress(
78+
from_color, to_color, cp.progress
79+
)
80+
else:
81+
normal = self._renderer.resman.load_image("cap_normal.png")
82+
from_color = "#000000"
83+
to_color = COLORS_NORMAL[cp.relation]
84+
progress = replace_color(normal, from_color, to_color)
85+
86+
px = round(cp_area.width / 2 - progress.width / 2) + 1
87+
py = round(cp_area.height / 2 - progress.height / 2) + 1
88+
89+
cp_area.alpha_composite(progress, (px, py))
90+
91+
ix = round(cp_area.width / 2 - icon.width / 2) + 1
92+
iy = round(cp_area.height / 2 - icon.height / 2) + 1
93+
94+
cp_area.alpha_composite(icon, (ix, iy))
95+
96+
cx = round(x - cp_area.width / 2)
97+
cy = round(y - cp_area.height / 2)
98+
99+
self._generated_caps[count] = (
100+
cp_area.copy(),
101+
(cx, cy),
102+
hash(cp)
103+
)
104+
105+
image.alpha_composite(cp_area, (cx, cy))
106+
107+
def _get_capture_area(self, relation: int, size: tuple) -> Image.Image:
108+
"""Loads the proper capture area image from the resources.
109+
110+
Args:
111+
relation (int): relation
112+
size (tuple): size of the image.
113+
114+
Returns:
115+
Image.Image: Image of the capture area, resized.
116+
"""
117+
relation_to_str = {-1: "neutral", 0: "ally", 1: "enemy"}
118+
filename = f"cap_{relation_to_str[relation]}.png"
119+
return self._renderer.resman.load_image(filename, size=size)
120+
121+
@lru_cache
122+
def _get_progress(self, from_color: str, to_color: str, percent: float):
123+
"""Gets the diamond progress `bar` from the resources and properly
124+
color it depending from the colors and percentage provided.
125+
126+
Args:
127+
from_color (str): From color.
128+
to_color (str): To color.
129+
percent (float): Percentage of the progress. 0.0 to 1.0
130+
131+
Returns:
132+
Image.Image: Diamond progress `bar` image.
133+
"""
134+
pd = self._renderer.resman.load_image("cap_invaded.png")
135+
136+
bg_diamond = replace_color(pd, "#000000", from_color)
137+
fg_diamond = replace_color(pd, "#000000", to_color)
138+
mask = Image.new("RGBA", pd.size)
139+
mask_draw = ImageDraw.Draw(mask, "RGBA")
140+
mask_draw.pieslice(
141+
(
142+
(0, 0),
143+
(pd.width - 1, pd.height - 1),
144+
),
145+
start=-90,
146+
end=(-90 + 360 * percent),
147+
fill="black",
148+
)
149+
bg_diamond.paste(fg_diamond, mask=mask)
150+
return bg_diamond
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import numpy as np
2+
3+
from typing import Optional
4+
from renderer.data import ReplayData
5+
from renderer.render import Renderer
6+
from renderer.base import LayerBase
7+
from PIL import Image, ImageDraw, ImageColor
8+
from math import floor
9+
10+
from renderer.layers.counter import X_POS as COUNTERS_POS
11+
12+
13+
CENTER = (800 + COUNTERS_POS) // 2
14+
SKILLS_ORDER = [
15+
"AirCarrier",
16+
"Battleship",
17+
"Cruiser",
18+
"Destroyer",
19+
"Auxiliary",
20+
"Submarine",
21+
]
22+
FIRE_PREVENTION_ID = 14
23+
BURN_NODE_BITS = 4
24+
BURN_NODE_POSITIONS = [
25+
[None, None, None, None],
26+
[(0.5, 0.52), None, None, None], # subs
27+
[(0.125, 0.52), None, None, (0.875, 0.52)], # only auxiliary ships?
28+
[(0.125, 0.52), (0.5, 0.52), None, (0.875, 0.52)], # battleships w/ FP
29+
[
30+
(0.125, 0.52),
31+
(0.375, 0.52),
32+
(0.625, 0.52),
33+
(0.875, 0.52),
34+
], # everything else
35+
]
36+
FLOOD_NODE_BITS = 2
37+
FLOOD_NODE_POSITIONS = [
38+
[None, None],
39+
[(0.5, 0.86), None], # subs
40+
[(0.252, 0.86), (0.748, 0.86)], # everything else
41+
]
42+
43+
44+
class LayerHealthBase(LayerBase):
45+
"""The class for handling player's ship health bar.
46+
47+
Args:
48+
LayerBase (_type_): _description_
49+
"""
50+
51+
def __init__(
52+
self, renderer: Renderer, replay_data: Optional[ReplayData] = None
53+
):
54+
self._renderer = renderer
55+
self._replay_data = (
56+
replay_data if replay_data else self._renderer.replay_data
57+
)
58+
self._ships = renderer.resman.load_json("ships.json")
59+
self._player = self._replay_data.player_info[
60+
self._replay_data.owner_id
61+
]
62+
self._font = self._renderer.resman.load_font(
63+
filename="warhelios_bold.ttf", size=16
64+
)
65+
self._green = ImageColor.getrgb("#4ce8aaff")
66+
self._yellow = ImageColor.getrgb("#ffc400ff")
67+
self._red = ImageColor.getrgb("#fe4d2aff")
68+
self._color_gray = ImageColor.getrgb("#ffffffc3")
69+
self._abilities = renderer.resman.load_json("abilities.json")
70+
71+
self._burn_icon = self._renderer.resman.load_image(
72+
"fire.png", path="status_icons"
73+
)
74+
self._flood_icon = self._renderer.resman.load_image(
75+
"flooding.png", path="status_icons"
76+
)
77+
78+
def _add_padding(self, bar: Image.Image):
79+
padded = Image.new("RGBA", (bar.width, bar.height + 4), (0, 0, 0, 0))
80+
padded.alpha_composite(bar, (0, 0))
81+
return padded
82+
83+
def draw(self, game_time: int, image: Image.Image):
84+
"""Draws the health bar into the image.
85+
86+
Args:
87+
game_time (int): The game time.
88+
image (Image.Image): The minimap image.
89+
"""
90+
ships = self._replay_data.events[game_time].evt_vehicle
91+
ship = ships[self._player.ship_id]
92+
ability = self._abilities[self._player.ship_params_id]
93+
per = ship.health / self._player.max_health
94+
info = self._ships[self._player.ship_params_id]
95+
96+
suffix_fg = "_h"
97+
suffix_bg = "_h_bg" if ship.is_alive else "_h_bgdead"
98+
99+
bg_bar = self._renderer.resman.load_image(
100+
f"{info['index']}{suffix_bg}.png", nearest=False, path="ship_bars"
101+
)
102+
bg_bar = self._add_padding(bg_bar)
103+
104+
fg_bar = self._renderer.resman.load_image(
105+
f"{info['index']}{suffix_fg}.png", nearest=False, path="ship_bars"
106+
)
107+
fg_bar = self._add_padding(fg_bar)
108+
fg_bar = fg_bar.resize(bg_bar.size, Image.Resampling.LANCZOS)
109+
110+
if per > 0.8:
111+
bar_color = self._green
112+
elif 0.8 >= per > 0.3:
113+
bar_color = self._yellow
114+
else:
115+
bar_color = self._red
116+
117+
if ship.is_alive:
118+
alpha = 75
119+
hp_bar_arr = np.array(fg_bar)
120+
hp_bar_arr[hp_bar_arr[:, :, 3] > alpha] = bar_color
121+
hp_bar_img = Image.fromarray(hp_bar_arr)
122+
mask_hp_img = Image.new(fg_bar.mode, fg_bar.size)
123+
mask_hp_img_w = mask_hp_img.width * per
124+
mask_hp_draw = ImageDraw.Draw(mask_hp_img)
125+
mask_hp_draw.rectangle(
126+
((0, 0), (mask_hp_img_w, mask_hp_img.width)), fill="black"
127+
)
128+
129+
if regen := ship.consumables_state.get(8, None):
130+
_, count, _, _ = regen
131+
if count:
132+
subtype = ability["id_to_subtype"][8]
133+
index = ability["id_to_index"][8]
134+
name = f"{index}.{subtype}"
135+
wt = ability[name]["workTime"]
136+
rhs = ability[name]["regenerationHPSpeed"]
137+
maxHeal = floor(wt) * rhs * self._player.max_health
138+
canHeal = (
139+
ship.regeneration_health
140+
if ship.regeneration_health < maxHeal
141+
else maxHeal
142+
)
143+
144+
per_limit = (
145+
canHeal + ship.health
146+
) / self._player.max_health
147+
148+
regen_bar_arr = np.array(fg_bar)
149+
regen_bar_arr[
150+
regen_bar_arr[:, :, 3] > alpha
151+
] = self._color_gray
152+
regen_bar_img = Image.fromarray(regen_bar_arr)
153+
mask_regen_img = Image.new(fg_bar.mode, fg_bar.size)
154+
mask_regen_img_w = mask_regen_img.width * per_limit
155+
mask_regen_draw = ImageDraw.Draw(mask_regen_img)
156+
mask_regen_draw.rectangle(
157+
((0, 0), (mask_regen_img_w, mask_regen_img.width)),
158+
fill="black",
159+
)
160+
161+
bg_bar.paste(regen_bar_img, mask=mask_regen_img)
162+
bg_bar.paste(hp_bar_img, mask=mask_hp_img)
163+
164+
hp_current = "{:,}".format(round(ship.health)).replace(",", " ")
165+
hp_max = "{:,}".format(round(self._player.max_health)).replace(
166+
",", " "
167+
)
168+
hp_max_text = f"/{hp_max}"
169+
170+
hp_c_w, hp_c_h = self._font.getbbox(hp_current)[2:]
171+
hp_w, hp_h = self._font.getbbox(hp_max_text)[2:]
172+
n_w, n_h = self._font.getbbox(info["name"])[2:]
173+
174+
bg_bar = bg_bar.resize((235, 62), resample=Image.Resampling.LANCZOS)
175+
176+
px = CENTER - round(bg_bar.width / 2)
177+
178+
th = Image.new("RGBA", (bg_bar.width, max(hp_h, n_h, hp_c_h)))
179+
th_draw = ImageDraw.Draw(th)
180+
181+
th_draw.text((0, 0), info["name"], fill="white", font=self._font)
182+
th_draw.text(
183+
(th.width - (hp_w + hp_c_w), 0),
184+
hp_current,
185+
fill=bar_color,
186+
font=self._font,
187+
)
188+
th_draw.text(
189+
(th.width - hp_w, 0),
190+
hp_max_text,
191+
fill="#cdcdcd",
192+
font=self._font,
193+
)
194+
195+
if (flags := bin(ship.burn_flags)[2:][::-1]) != "0":
196+
burn_nodes, flood_nodes = info["hulls"][self._player.hull]
197+
active_skills = self._player.skills.by_species(info["species"])
198+
if FIRE_PREVENTION_ID in active_skills:
199+
burn_nodes -= 1
200+
201+
self._draw_nodes(
202+
bg_bar,
203+
self._burn_icon,
204+
flags[0:BURN_NODE_BITS],
205+
BURN_NODE_POSITIONS[burn_nodes],
206+
)
207+
total_bits = BURN_NODE_BITS + FLOOD_NODE_BITS
208+
self._draw_nodes(
209+
bg_bar,
210+
self._flood_icon,
211+
flags[BURN_NODE_BITS:total_bits],
212+
FLOOD_NODE_POSITIONS[flood_nodes],
213+
)
214+
215+
image.paste(th, (px, 205), th)
216+
image.paste(bg_bar, (px, 145), bg_bar)
217+
218+
def _draw_nodes(
219+
self,
220+
image: Image.Image,
221+
icon: Image.Image,
222+
flags: str,
223+
positions: list,
224+
):
225+
"""Draws the status nodes into the health bar.
226+
227+
Args:
228+
image (Image.Image): Base image.
229+
icon (Image.Image): Status icon.
230+
flags (str): ?
231+
positions (list): The position of the node.
232+
"""
233+
for index, bit in enumerate(flags):
234+
if bit == "1":
235+
x_per, y_per = positions[index]
236+
image.alpha_composite(
237+
icon,
238+
(
239+
round(image.width * x_per - icon.width / 2),
240+
round(image.height * y_per - icon.height / 2),
241+
),
242+
)

0 commit comments

Comments
 (0)