mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'BottledByte/buff_system' into 'master'
The Buff system Closes #413 See merge request veloren/veloren!1285
This commit is contained in:
commit
19e210672a
@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Gave the axe a third attack
|
||||
- A new secondary charged melee attack for the hammer
|
||||
- Added Dutch translations
|
||||
- Buff system
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
ItemDef(
|
||||
name: "Apple Stick",
|
||||
description: "Restores 20 Health",
|
||||
description: "Restores 25 Health",
|
||||
kind: Consumable(
|
||||
kind: "AppleStick",
|
||||
effect: Health((
|
||||
|
12
assets/common/items/npc_armor/back/backpack_0.ron
Normal file
12
assets/common/items/npc_armor/back/backpack_0.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Rugged Backpack",
|
||||
description: "Keeps all your stuff together.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Back("Backpack0"),
|
||||
stats: (
|
||||
protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Moderate,
|
||||
)
|
12
assets/common/items/npc_armor/back/backpack_blue_0.ron
Normal file
12
assets/common/items/npc_armor/back/backpack_blue_0.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Rugged Backpack",
|
||||
description: "Keeps all your stuff together.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Back("BackpackBlue0"),
|
||||
stats: (
|
||||
protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Moderate,
|
||||
)
|
BIN
assets/voxygen/element/animation/buff_frame/1.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/2.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/3.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/3.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/4.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/4.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/5.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/5.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/6.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/6.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/7.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/7.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/animation/buff_frame/8.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/animation/buff_frame/8.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/de_buffs/buff_plus_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/de_buffs/buff_plus_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/de_buffs/debuff_bleed_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/de_buffs/debuff_bleed_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/de_buffs/debuff_skull_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/de_buffs/debuff_skull_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/m1.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/m1.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/m2.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/m2.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/bar_content.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/bar_content.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/skillbar/bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/skillbar/energybar_bg.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/energybar_bg.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/frame.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/skillbar/frame.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/skillbar/healthbar_bg.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/healthbar_bg.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot_active.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot_active.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot_big.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot_big.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot_l.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot_l.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot_l_active.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot_l_active.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot_r.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot_r.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/skillbar_slot_r_active.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/skillbar_slot_r_active.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/xp_bar_content.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/xp_bar_content.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/xp_bar_left.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/xp_bar_left.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/xp_bar_mid.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/xp_bar_mid.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/skillbar/xp_bar_right.png
(Stored with Git LFS)
BIN
assets/voxygen/element/skillbar/xp_bar_right.png
(Stored with Git LFS)
Binary file not shown.
@ -183,7 +183,8 @@ https://account.veloren.net."#,
|
||||
"hud.chat.pvp_melee_kill_msg": "[{attacker}] defeated [{victim}]",
|
||||
"hud.chat.pvp_ranged_kill_msg": "[{attacker}] shot [{victim}]",
|
||||
"hud.chat.pvp_explosion_kill_msg": "[{attacker}] blew up [{victim}]",
|
||||
"hud.chat.pvp_energy_kill_msg": "[{attacker}] used magic to kill [{victim}]",
|
||||
"hud.chat.pvp_energy_kill_msg": "[{attacker}] killed [{victim}] with magic",
|
||||
"hud.chat.pvp_buff_kill_msg": "[{attacker}] killed [{victim}]",
|
||||
|
||||
"hud.chat.npc_melee_kill_msg": "{attacker} killed [{victim}]",
|
||||
"hud.chat.npc_ranged_kill_msg": "{attacker} shot [{victim}]",
|
||||
@ -290,6 +291,8 @@ magically infused items?"#,
|
||||
"hud.settings.transparency": "Transparency",
|
||||
"hud.settings.hotbar": "Hotbar",
|
||||
"hud.settings.toggle_shortcuts": "Toggle Shortcuts",
|
||||
"hud.settings.buffs_skillbar": "Buffs at Skillbar",
|
||||
"hud.settings.buffs_mmap": "Buffs at Minimap",
|
||||
"hud.settings.toggle_bar_experience": "Toggle Experience Bar",
|
||||
"hud.settings.scrolling_combat_text": "Scrolling Combat Text",
|
||||
"hud.settings.single_damage_number": "Single Damage Numbers",
|
||||
@ -342,9 +345,9 @@ magically infused items?"#,
|
||||
"hud.settings.refresh_rate": "Refresh Rate",
|
||||
"hud.settings.save_window_size": "Save window size",
|
||||
"hud.settings.lighting_rendering_mode": "Lighting Rendering Mode",
|
||||
"hud.settings.lighting_rendering_mode.ashikhmin": "Type A",
|
||||
"hud.settings.lighting_rendering_mode.blinnphong": "Type B",
|
||||
"hud.settings.lighting_rendering_mode.lambertian": "Type L",
|
||||
"hud.settings.lighting_rendering_mode.ashikhmin": "Type A - High ",
|
||||
"hud.settings.lighting_rendering_mode.blinnphong": "Type B - Medium",
|
||||
"hud.settings.lighting_rendering_mode.lambertian": "Type L - Cheap",
|
||||
"hud.settings.shadow_rendering_mode": "Shadow Rendering Mode",
|
||||
"hud.settings.shadow_rendering_mode.none": "None",
|
||||
"hud.settings.shadow_rendering_mode.cheap": "Cheap",
|
||||
@ -508,6 +511,16 @@ Protection
|
||||
"esc_menu.quit_game": "Quit Game",
|
||||
/// End Escape Menu Section
|
||||
|
||||
/// Buffs and Debuffs
|
||||
"buff.remove": "Click to remove",
|
||||
"buff.title.missing": "Missing Title",
|
||||
"buff.desc.missing": "Missing Description",
|
||||
// Buffs
|
||||
"buff.title.heal_test": "Heal Test",
|
||||
"buff.desc.heal_test": "This is a test buff to test healing.",
|
||||
// Debuffs
|
||||
"debuff.title.bleed_test": "Bleed Test",
|
||||
"debuff.desc.bleed_test": "This is a test debuff to test bleeding.",
|
||||
},
|
||||
|
||||
|
||||
|
@ -65,6 +65,7 @@ VoxygenLocalization(
|
||||
"common.create": "Crear",
|
||||
"common.okay": "Ok",
|
||||
"common.accept": "Aceptar",
|
||||
"common.decline": "Rechazar",
|
||||
"common.disclaimer": "Cuidado",
|
||||
"common.cancel": "Cancelar",
|
||||
"common.none": "Ninguno",
|
||||
@ -73,6 +74,13 @@ VoxygenLocalization(
|
||||
"common.you": "Tu",
|
||||
"common.automatic": "Automatico",
|
||||
"common.random": "Aleatorio",
|
||||
// Settings Window title
|
||||
"common.interface_settings": "Ajustes de Interfaz",
|
||||
"common.gameplay_settings": "Ajustes de Jugabilidad",
|
||||
"common.controls_settings": "Ajustes de Controles",
|
||||
"common.video_settings": "Ajustes de Graficos",
|
||||
"common.sound_settings": "Ajustes de Sonido",
|
||||
"common.language_settings": "Ajustes de Idiomas",
|
||||
|
||||
// Message when connection to the server is lost
|
||||
"common.connection_lost": r#"Conexión perdida!
|
||||
@ -89,9 +97,11 @@ El cliente está actualizado?"#,
|
||||
|
||||
"common.weapons.axe": "Hacha",
|
||||
"common.weapons.sword": "Espada",
|
||||
"common.weapons.staff": "Báculo",
|
||||
"common.weapons.staff": "Vara Mágica",
|
||||
"common.weapons.bow": "Arco",
|
||||
"common.weapons.hammer": "Martillo",
|
||||
"common.weapons.sceptre": "Cetro curativo",
|
||||
"common.rand_appearance": "Nombre y Apariencia Aleatoria",
|
||||
/// End Common section
|
||||
|
||||
|
||||
@ -141,6 +151,9 @@ https://account.veloren.net."#,
|
||||
"main.login.invalid_character": "El personaje seleccionado no es válido",
|
||||
"main.login.client_crashed": "El cliente crasheó",
|
||||
"main.login.not_on_whitelist": "No estas en la lista. Contacta al Dueño del Servidor si quieres unirte.",
|
||||
"main.login.banned": "Usted ha sido baneado por la siguiente razón",
|
||||
"main.login.kicked": "Te han echado por la siguiente razón",
|
||||
|
||||
|
||||
/// End Main screen section
|
||||
|
||||
@ -153,6 +166,7 @@ https://account.veloren.net."#,
|
||||
"hud.waypoint_saved": "Marcador Guardado",
|
||||
|
||||
"hud.press_key_to_show_keybindings_fmt": "Presiona {key} para mostrar los controles del teclado",
|
||||
"hud.press_key_to_toggle_lantern_fmt": "[{key}] Encender Linterna",
|
||||
"hud.press_key_to_show_debug_info_fmt": "Presiona {key} para mostrar información de depuración",
|
||||
"hud.press_key_to_toggle_keybindings_fmt": "Presiona {key} para alternar los controles del teclado",
|
||||
"hud.press_key_to_toggle_debug_info_fmt": "Presiona {key} para alternar la información de depuración",
|
||||
@ -160,6 +174,21 @@ https://account.veloren.net."#,
|
||||
// Chat outputs
|
||||
"hud.chat.online_msg": "[{name}] se ha conectado.",
|
||||
"hud.chat.offline_msg": "[{name}] se ha desconectado.",
|
||||
|
||||
"hud.chat.default_death_msg": "[{name}] Murió",
|
||||
"hud.chat.environmental_kill_msg": "[{name}] Murió en {environment}",
|
||||
"hud.chat.fall_kill_msg": "[{name}] Murió por el daño de la caída",
|
||||
"hud.chat.suicide_msg": "[{name}] Murió por heridas autoinfligidas",
|
||||
|
||||
"hud.chat.pvp_melee_kill_msg": "[{attacker}] Derroto a [{victim}]",
|
||||
"hud.chat.pvp_ranged_kill_msg": "[{attacker}] Le Disparo a [{victim}]",
|
||||
"hud.chat.pvp_explosion_kill_msg": "[{attacker}] Hizo explotar a [{victim}]",
|
||||
"hud.chat.pvp_energy_kill_msg": "[{attacker}] usó magia para matar [{victim}]",
|
||||
|
||||
"hud.chat.npc_melee_kill_msg": "{attacker} Mató a [{victim}]",
|
||||
"hud.chat.npc_ranged_kill_msg": "{attacker} Le Disparo a [{victim}]",
|
||||
"hud.chat.npc_explosion_kill_msg": "{attacker} Hizo explotar a [{victim}]",
|
||||
|
||||
"hud.chat.loot_msg": "Recogiste [{item}]",
|
||||
"hud.chat.loot_fail": "Tu inventario está lleno!",
|
||||
"hud.chat.goodbye": "Adiós!",
|
||||
@ -197,7 +226,7 @@ Deshazte de ellos haciendo click en ellos y luego arrastralos fuera de la bolsa.
|
||||
|
||||
Las noches pueden volverse bastante oscuras en Veloren.
|
||||
|
||||
Enciende tu farol escribiendo /lantern en el chat o presionando la G.
|
||||
Enciende tu Linterna escribiendo /lantern en el chat o presionando la G.
|
||||
|
||||
|
||||
Quieres liberar tu cursor para cerrar esta ventana? Presiona TAB!
|
||||
@ -232,6 +261,7 @@ objetos infundidos con magia?"#,
|
||||
"hud.bag.chest": "Torso",
|
||||
"hud.bag.hands": "Manos",
|
||||
"hud.bag.lantern": "Linterna",
|
||||
"hud.bag.glider": "Planeador",
|
||||
"hud.bag.belt": "Cinturón",
|
||||
"hud.bag.ring": "Anillo",
|
||||
"hud.bag.back": "Espalda",
|
||||
@ -282,8 +312,8 @@ objetos infundidos con magia?"#,
|
||||
"hud.settings.invert_scroll_zoom": "Invertir Desplazamiento de Zoom",
|
||||
"hud.settings.invert_mouse_y_axis": "Invertir eje Y del Ratón",
|
||||
"hud.settings.enable_mouse_smoothing": "Suavizado de la Cámara",
|
||||
"hud.settings.free_look_behavior": "Comportamiento de vista libre",
|
||||
"hud.settings.auto_walk_behavior": "Comportamiento al caminar automaticamente",
|
||||
"hud.settings.free_look_behavior": "Modo de vista libre",
|
||||
"hud.settings.auto_walk_behavior": "Modo de caminata automática",
|
||||
"hud.settings.stop_auto_walk_on_input": "Frenar caminata automática",
|
||||
|
||||
"hud.settings.view_distance": "Distancia de Visión",
|
||||
@ -292,22 +322,43 @@ objetos infundidos con magia?"#,
|
||||
"hud.settings.maximum_fps": "FPS Máximos",
|
||||
"hud.settings.fov": "Campo de Visión (grados)",
|
||||
"hud.settings.gamma": "Gama",
|
||||
"hud.settings.ambiance": "Brillo del Ambiente",
|
||||
"hud.settings.antialiasing_mode": "Modo Anti-Aliasing",
|
||||
"hud.settings.cloud_rendering_mode": "Modo de Renderizado de Nubes",
|
||||
"hud.settings.fluid_rendering_mode": "Modo de Renderizado de Fluidos",
|
||||
"hud.settings.fluid_rendering_mode.cheap": "Barato",
|
||||
"hud.settings.fluid_rendering_mode.shiny": "Brillante",
|
||||
"hud.settings.cloud_rendering_mode.regular": "Regular",
|
||||
"hud.settings.fluid_rendering_mode": "Modo de Renderizado del Agua",
|
||||
"hud.settings.fluid_rendering_mode.cheap": "Bajo",
|
||||
"hud.settings.fluid_rendering_mode.shiny": "Alto",
|
||||
"hud.settings.cloud_rendering_mode.regular": "Normal",
|
||||
"hud.settings.fullscreen": "Pantalla Completa",
|
||||
"hud.settings.save_window_size": "Guardar tamaño de la ventana",
|
||||
|
||||
"hud.settings.fullscreen_mode": "Modo de Pantalla Completa",
|
||||
"hud.settings.fullscreen_mode.exclusive": "Completo",
|
||||
"hud.settings.fullscreen_mode.borderless": "Con Bordes",
|
||||
"hud.settings.particles": "Particulas",
|
||||
"hud.settings.resolution": "Resolución",
|
||||
"hud.settings.bit_depth": "Profundidad de Bits",
|
||||
"hud.settings.refresh_rate": "Taza de Refresco",
|
||||
"hud.settings.save_window_size": " Guardar tamaño de ventana",
|
||||
"hud.settings.shadow_rendering_mode": "Renderizado de Sombras",
|
||||
"hud.settings.shadow_rendering_mode.none": "Ninguno",
|
||||
"hud.settings.shadow_rendering_mode.cheap": "Bajo",
|
||||
"hud.settings.shadow_rendering_mode.map": "Alto",
|
||||
"hud.settings.lighting_rendering_mode": "Renderizado de la luz de la Linterna",
|
||||
"hud.settings.lighting_rendering_mode.ashikhmin": "Tipo A",
|
||||
"hud.settings.lighting_rendering_mode.blinnphong": "Tipo B",
|
||||
"hud.settings.lighting_rendering_mode.lambertian": "Tipo L",
|
||||
"hud.settings.shadow_rendering_mode": "Renderizado de Sombras",
|
||||
"hud.settings.shadow_rendering_mode.none": "Ninguno",
|
||||
"hud.settings.shadow_rendering_mode.cheap": "Bajo",
|
||||
"hud.settings.shadow_rendering_mode.map": "Alto",
|
||||
"hud.settings.shadow_rendering_mode.map.resolution": "Resolución",
|
||||
"hud.settings.lod_detail": "Detalle de LoD",
|
||||
"hud.settings.music_volume": "Volumen de Musica",
|
||||
"hud.settings.sound_effect_volume": "Volumen de Efectos de Sonido",
|
||||
"hud.settings.audio_device": "Dispositivo de Audio",
|
||||
|
||||
"hud.settings.awaitingkey": "Presiona una tecla...",
|
||||
"hud.settings.unbound": "Ninguno",
|
||||
"hud.settings.reset_keybinds": "Reestablecer a los valores predeterminados",
|
||||
"hud.settings.reset_keybinds": "Reestablecer Controles",
|
||||
|
||||
"hud.social": "Lista de jugadores",
|
||||
"hud.social.online": "En Línea",
|
||||
@ -315,6 +366,10 @@ objetos infundidos con magia?"#,
|
||||
"hud.social.not_yet_available": "Aún no esta disponible",
|
||||
"hud.social.faction": "Facción",
|
||||
"hud.social.play_online_fmt": "{nb_player} jugador(es) en línea",
|
||||
"hud.social.name": "Nombre",
|
||||
"hud.social.level": "Nivel",
|
||||
"hud.social.zone": "Zona",
|
||||
"hud.social.account": "Cuenta",
|
||||
|
||||
"hud.crafting": "Crafteo",
|
||||
"hud.crafting.recipes": "Recetas",
|
||||
@ -322,6 +377,19 @@ objetos infundidos con magia?"#,
|
||||
"hud.crafting.craft": "Fabricar",
|
||||
"hud.crafting.tool_cata": "Requisitos:",
|
||||
|
||||
"hud.group": "Grupo",
|
||||
"hud.group.invite_to_join": "{name} Te invito a su Grupo!",
|
||||
"hud.group.invite": "Invitar",
|
||||
"hud.group.kick": "Echar",
|
||||
"hud.group.assign_leader": "Asignar Lider",
|
||||
"hud.group.leave": "Salir del Grupo",
|
||||
"hud.group.dead" : "Muerto",
|
||||
"hud.group.out_of_range": "Fuera de Alcance",
|
||||
"hud.group.add_friend": "Agregar a Amigos",
|
||||
"hud.group.link_group": "Conectar Grupos",
|
||||
"hud.group.in_menu": "Eligiendo Personaje",
|
||||
"hud.group.members": "Miembros del Grupo",
|
||||
|
||||
"hud.spell": "Hechizos",
|
||||
|
||||
"hud.free_look_indicator": "Vista libre activa",
|
||||
@ -381,6 +449,9 @@ objetos infundidos con magia?"#,
|
||||
"gameinput.freelook": "Vista Libre",
|
||||
"gameinput.autowalk": "Caminata Automática",
|
||||
"gameinput.dance": "Bailar",
|
||||
"gameinput.select": "Seleccione la Entidad",
|
||||
"gameinput.acceptgroupinvite": "Aceptar invitación al grupo",
|
||||
"gameinput.declinegroupinvite": "Rechazar invitación al grupo",
|
||||
"gameinput.crafting": "Craftear",
|
||||
"gameinput.sneak": "Agacharse",
|
||||
"gameinput.swimdown": "Sumergirse",
|
||||
@ -423,7 +494,7 @@ objetos infundidos con magia?"#,
|
||||
|
||||
Estado Físico
|
||||
|
||||
Fuerza de Voluntad
|
||||
Valentía
|
||||
|
||||
Protección
|
||||
"#,
|
||||
|
@ -1099,6 +1099,14 @@
|
||||
"voxel.armor.back.short-2",
|
||||
(0.0, -2.0, 0.0), (-90.0, 180.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Back("Backpack0")): VoxTrans(
|
||||
"voxel.armor.back.backpack-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 0.0, 0.0), 1.0,
|
||||
),
|
||||
Armor(Back("BackpackBlue0")): VoxTrans(
|
||||
"voxel.armor.back.backpack-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 0.0, 0.0), 1.0,
|
||||
),
|
||||
// Rings
|
||||
Armor(Ring("Ring0")): Png(
|
||||
"element.icons.ring-0",
|
||||
|
BIN
assets/voxygen/voxel/armor/back/backpack-0.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/armor/back/backpack-0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/armor/back/backpack-grey.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/armor/back/backpack-grey.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -24,5 +24,13 @@
|
||||
vox_spec: ("armor.back.short-2", (-5.0, -1.0, -11.0)),
|
||||
color: None
|
||||
),
|
||||
"Backpack0": (
|
||||
vox_spec: ("armor.back.backpack-0", (-7.0, -5.0, -10.0)),
|
||||
color: None
|
||||
),
|
||||
"BackpackBlue0": (
|
||||
vox_spec: ("armor.back.backpack-grey", (-7.0, -5.0, -10.0)),
|
||||
color: Some((76, 72, 178))
|
||||
),
|
||||
},
|
||||
))
|
||||
|
@ -37,6 +37,7 @@ use common::{
|
||||
terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use comp::BuffKind;
|
||||
use futures_executor::block_on;
|
||||
use futures_timer::Delay;
|
||||
use futures_util::{select, FutureExt};
|
||||
@ -644,6 +645,12 @@ impl Client {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern));
|
||||
}
|
||||
|
||||
pub fn remove_buff(&mut self, buff_id: BuffKind) {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff(
|
||||
buff_id,
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn max_group_size(&self) -> u32 { self.max_group_size }
|
||||
|
||||
pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration)> {
|
||||
@ -977,6 +984,11 @@ impl Client {
|
||||
|
||||
// 4) Tick the client's LocalState
|
||||
self.state.tick(dt, add_foreign_systems, true);
|
||||
// TODO: avoid emitting these in the first place
|
||||
self.state
|
||||
.ecs()
|
||||
.fetch::<EventBus<common::event::ServerEvent>>()
|
||||
.recv_all();
|
||||
|
||||
// 5) Terrain
|
||||
let pos = self
|
||||
@ -1728,6 +1740,11 @@ impl Client {
|
||||
alias_of_uid(attacker_uid),
|
||||
alias_of_uid(victim)
|
||||
),
|
||||
KillSource::Player(attacker_uid, KillType::Buff) => format!(
|
||||
"[{}] killed [{}]",
|
||||
alias_of_uid(attacker_uid),
|
||||
alias_of_uid(victim)
|
||||
),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Melee) => {
|
||||
format!("{} killed [{}]", attacker_name, alias_of_uid(victim))
|
||||
},
|
||||
@ -1742,6 +1759,9 @@ impl Client {
|
||||
attacker_name,
|
||||
alias_of_uid(victim)
|
||||
),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Buff) => {
|
||||
format!("{} killed [{}]", attacker_name, alias_of_uid(victim))
|
||||
},
|
||||
KillSource::Environment(environment) => {
|
||||
format!("[{}] died in {}", alias_of_uid(victim), environment)
|
||||
},
|
||||
@ -1767,6 +1787,9 @@ impl Client {
|
||||
KillSource::Player(attacker_uid, KillType::Energy) => message
|
||||
.replace("{attacker}", &alias_of_uid(attacker_uid))
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::Player(attacker_uid, KillType::Buff) => message
|
||||
.replace("{attacker}", &alias_of_uid(attacker_uid))
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Melee) => message
|
||||
.replace("{attacker}", attacker_name)
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
@ -1779,6 +1802,9 @@ impl Client {
|
||||
KillSource::NonPlayer(attacker_name, KillType::Energy) => message
|
||||
.replace("{attacker}", attacker_name)
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Buff) => message
|
||||
.replace("{attacker}", attacker_name)
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::Environment(environment) => message
|
||||
.replace("{name}", &alias_of_uid(victim))
|
||||
.replace("{environment}", environment),
|
||||
|
286
common/src/comp/buff.rs
Normal file
286
common/src/comp/buff.rs
Normal file
@ -0,0 +1,286 @@
|
||||
use crate::sync::Uid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{cmp::Ordering, collections::HashMap, time::Duration};
|
||||
|
||||
/// De/buff Kind.
|
||||
/// This is used to determine what effects a buff will have
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffKind {
|
||||
/// Restores health/time for some period
|
||||
Regeneration,
|
||||
/// Lowers health over time for some duration
|
||||
Bleeding,
|
||||
/// Lower a creature's max health
|
||||
/// Currently placeholder buff to show other stuff is possible
|
||||
Cursed,
|
||||
}
|
||||
|
||||
impl BuffKind {
|
||||
// Checks if buff is buff or debuff
|
||||
pub fn is_buff(self) -> bool {
|
||||
match self {
|
||||
BuffKind::Regeneration { .. } => true,
|
||||
BuffKind::Bleeding { .. } => false,
|
||||
BuffKind::Cursed { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Struct used to store data relevant to a buff
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct BuffData {
|
||||
pub strength: f32,
|
||||
pub duration: Option<Duration>,
|
||||
}
|
||||
|
||||
/// De/buff category ID.
|
||||
/// Similar to `BuffKind`, but to mark a category (for more generic usage, like
|
||||
/// positive/negative buffs).
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffCategory {
|
||||
Natural,
|
||||
Physical,
|
||||
Magical,
|
||||
Divine,
|
||||
PersistOnDeath,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ModifierKind {
|
||||
Additive,
|
||||
Multiplicative,
|
||||
}
|
||||
|
||||
/// Data indicating and configuring behaviour of a de/buff.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffEffect {
|
||||
/// Periodically damages or heals entity
|
||||
HealthChangeOverTime { rate: f32, accumulated: f32 },
|
||||
/// Changes maximum health by a certain amount
|
||||
MaxHealthModifier { value: f32, kind: ModifierKind },
|
||||
}
|
||||
|
||||
/// Actual de/buff.
|
||||
/// Buff can timeout after some time if `time` is Some. If `time` is None,
|
||||
/// Buff will last indefinitely, until removed manually (by some action, like
|
||||
/// uncursing).
|
||||
///
|
||||
/// Buff has a kind, which is used to determine the effects in a builder
|
||||
/// function.
|
||||
///
|
||||
/// To provide more classification info when needed,
|
||||
/// buff can be in one or more buff category.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Buff {
|
||||
pub kind: BuffKind,
|
||||
pub data: BuffData,
|
||||
pub cat_ids: Vec<BuffCategory>,
|
||||
pub time: Option<Duration>,
|
||||
pub effects: Vec<BuffEffect>,
|
||||
pub source: BuffSource,
|
||||
}
|
||||
|
||||
/// Information about whether buff addition or removal was requested.
|
||||
/// This to implement "on_add" and "on_remove" hooks for constant buffs.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BuffChange {
|
||||
/// Adds this buff.
|
||||
Add(Buff),
|
||||
/// Removes all buffs with this ID.
|
||||
RemoveByKind(BuffKind),
|
||||
/// Removes all buffs with this ID, but not debuffs.
|
||||
RemoveFromController(BuffKind),
|
||||
/// Removes buffs of these indices (first vec is for active buffs, second is
|
||||
/// for inactive buffs), should only be called when buffs expire
|
||||
RemoveById(Vec<BuffId>),
|
||||
/// Removes buffs of these categories (first vec is of categories of which
|
||||
/// all are required, second vec is of categories of which at least one is
|
||||
/// required, third vec is of categories that will not be removed)
|
||||
RemoveByCategory {
|
||||
all_required: Vec<BuffCategory>,
|
||||
any_required: Vec<BuffCategory>,
|
||||
none_required: Vec<BuffCategory>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Buff {
|
||||
/// Builder function for buffs
|
||||
pub fn new(
|
||||
kind: BuffKind,
|
||||
data: BuffData,
|
||||
cat_ids: Vec<BuffCategory>,
|
||||
source: BuffSource,
|
||||
) -> Self {
|
||||
let (effects, time) = match kind {
|
||||
BuffKind::Bleeding => (
|
||||
vec![BuffEffect::HealthChangeOverTime {
|
||||
rate: -data.strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
BuffKind::Regeneration => (
|
||||
vec![BuffEffect::HealthChangeOverTime {
|
||||
rate: data.strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
BuffKind::Cursed => (
|
||||
vec![BuffEffect::MaxHealthModifier {
|
||||
value: -100. * data.strength,
|
||||
kind: ModifierKind::Additive,
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
};
|
||||
Buff {
|
||||
kind,
|
||||
data,
|
||||
cat_ids,
|
||||
time,
|
||||
effects,
|
||||
source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Buff {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
if self == other {
|
||||
Some(Ordering::Equal)
|
||||
} else if self.data.strength > other.data.strength {
|
||||
Some(Ordering::Greater)
|
||||
} else if self.data.strength < other.data.strength {
|
||||
Some(Ordering::Less)
|
||||
} else if compare_duration(self.time, other.time) {
|
||||
Some(Ordering::Greater)
|
||||
} else if compare_duration(other.time, self.time) {
|
||||
Some(Ordering::Less)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_duration(a: Option<Duration>, b: Option<Duration>) -> bool {
|
||||
a.map_or(true, |dur_a| b.map_or(false, |dur_b| dur_a > dur_b))
|
||||
}
|
||||
|
||||
impl PartialEq for Buff {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.data.strength == other.data.strength && self.time == other.time
|
||||
}
|
||||
}
|
||||
|
||||
/// Source of the de/buff
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffSource {
|
||||
/// Applied by a character
|
||||
Character { by: Uid },
|
||||
/// Applied by world, like a poisonous fumes from a swamp
|
||||
World,
|
||||
/// Applied by command
|
||||
Command,
|
||||
/// Applied by an item
|
||||
Item,
|
||||
/// Applied by another buff (like an after-effect)
|
||||
Buff,
|
||||
/// Some other source
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Component holding all de/buffs that gets resolved each tick.
|
||||
/// On each tick, remaining time of buffs get lowered and
|
||||
/// buff effect of each buff is applied or not, depending on the `BuffEffect`
|
||||
/// (specs system will decide based on `BuffEffect`, to simplify
|
||||
/// implementation). TODO: Something like `once` flag for `Buff` to remove the
|
||||
/// dependence on `BuffEffect` enum?
|
||||
///
|
||||
/// In case of one-time buffs, buff effects will be applied on addition
|
||||
/// and undone on removal of the buff (by the specs system).
|
||||
/// Example could be decreasing max health, which, if repeated each tick,
|
||||
/// would be probably an undesired effect).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Buffs {
|
||||
/// Uid used for synchronization
|
||||
id_counter: u64,
|
||||
/// Maps Kinds of buff to Id's of currently applied buffs of that kind
|
||||
pub kinds: HashMap<BuffKind, Vec<BuffId>>,
|
||||
// All currently applied buffs stored by Id
|
||||
pub buffs: HashMap<BuffId, Buff>,
|
||||
}
|
||||
|
||||
impl Buffs {
|
||||
fn sort_kind(&mut self, kind: BuffKind) {
|
||||
if let Some(buff_order) = self.kinds.get_mut(&kind) {
|
||||
if buff_order.is_empty() {
|
||||
self.kinds.remove(&kind);
|
||||
} else {
|
||||
let buffs = &self.buffs;
|
||||
// Intentionally sorted in reverse so that the strongest buffs are earlier in
|
||||
// the vector
|
||||
buff_order.sort_by(|a, b| buffs[&b].partial_cmp(&buffs[&a]).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_kind(&mut self, kind: BuffKind) {
|
||||
if let Some(buff_ids) = self.kinds.get_mut(&kind) {
|
||||
for id in buff_ids {
|
||||
self.buffs.remove(id);
|
||||
}
|
||||
self.kinds.remove(&kind);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_insert(&mut self, id: BuffId, buff: Buff) -> BuffId {
|
||||
let kind = buff.kind;
|
||||
self.kinds.entry(kind).or_default().push(id);
|
||||
self.buffs.insert(id, buff);
|
||||
self.sort_kind(kind);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, buff: Buff) -> BuffId {
|
||||
self.id_counter += 1;
|
||||
self.force_insert(self.id_counter, buff)
|
||||
}
|
||||
|
||||
// Iterate through buffs of a given kind in effect order (most powerful first)
|
||||
pub fn iter_kind(&self, kind: BuffKind) -> impl Iterator<Item = (BuffId, &Buff)> + '_ {
|
||||
self.kinds
|
||||
.get(&kind)
|
||||
.map(|ids| ids.iter())
|
||||
.unwrap_or_else(|| (&[]).iter())
|
||||
.map(move |id| (*id, &self.buffs[id]))
|
||||
}
|
||||
|
||||
// Iterates through all active buffs (the most powerful buff of each kind)
|
||||
pub fn iter_active(&self) -> impl Iterator<Item = &Buff> + '_ {
|
||||
self.kinds
|
||||
.values()
|
||||
.map(move |ids| self.buffs.get(&ids[0]))
|
||||
.filter(|buff| buff.is_some())
|
||||
.map(|buff| buff.unwrap())
|
||||
}
|
||||
|
||||
// Gets most powerful buff of a given kind
|
||||
// pub fn get_active_kind(&self, kind: BuffKind) -> Buff
|
||||
|
||||
pub fn remove(&mut self, buff_id: BuffId) {
|
||||
let kind = self.buffs.remove(&buff_id).unwrap().kind;
|
||||
self.kinds
|
||||
.get_mut(&kind)
|
||||
.map(|ids| ids.retain(|id| *id != buff_id));
|
||||
self.sort_kind(kind);
|
||||
}
|
||||
}
|
||||
|
||||
pub type BuffId = u64;
|
||||
|
||||
impl Component for Buffs {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
@ -51,6 +51,7 @@ pub enum KillType {
|
||||
Projectile,
|
||||
Explosion,
|
||||
Energy,
|
||||
Buff,
|
||||
// Projectile(String), TODO: add projectile name when available
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
use crate::{comp::inventory::slot::Slot, sync::Uid, util::Dir};
|
||||
use crate::{
|
||||
comp::{inventory::slot::Slot, BuffKind},
|
||||
sync::Uid,
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -37,6 +41,7 @@ pub enum ControlEvent {
|
||||
Unmount,
|
||||
InventoryManip(InventoryManip),
|
||||
GroupManip(GroupManip),
|
||||
RemoveBuff(BuffKind),
|
||||
Respawn,
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ mod admin;
|
||||
pub mod agent;
|
||||
pub mod beam;
|
||||
pub mod body;
|
||||
pub mod buff;
|
||||
mod character_state;
|
||||
pub mod chat;
|
||||
mod controller;
|
||||
@ -31,6 +32,10 @@ pub use body::{
|
||||
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object,
|
||||
quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData,
|
||||
};
|
||||
pub use buff::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||
ModifierKind,
|
||||
};
|
||||
pub use character_state::{Attacking, CharacterState, StateUpdate};
|
||||
pub use chat::{
|
||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||
|
@ -8,6 +8,7 @@ use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
/// Specifies what and how much changed current health
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HealthChange {
|
||||
pub amount: i32,
|
||||
@ -20,6 +21,7 @@ pub enum HealthSource {
|
||||
Projectile { owner: Option<Uid> },
|
||||
Explosion { owner: Option<Uid> },
|
||||
Energy { owner: Option<Uid> },
|
||||
Buff { owner: Option<Uid> },
|
||||
Suicide,
|
||||
World,
|
||||
Revive,
|
||||
@ -32,6 +34,7 @@ pub enum HealthSource {
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct Health {
|
||||
base_max: u32,
|
||||
current: u32,
|
||||
maximum: u32,
|
||||
pub last_change: (f64, HealthChange),
|
||||
@ -67,11 +70,21 @@ impl Health {
|
||||
self.last_change = (0.0, change);
|
||||
}
|
||||
|
||||
// This is private because max hp is based on the level
|
||||
fn set_maximum(&mut self, amount: u32) {
|
||||
// This function changes the modified max health value, not the base health
|
||||
// value. The modified health value takes into account buffs and other temporary
|
||||
// changes to max health.
|
||||
pub fn set_maximum(&mut self, amount: u32) {
|
||||
self.maximum = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
// This is private because max hp is based on the level
|
||||
fn set_base_max(&mut self, amount: u32) {
|
||||
self.base_max = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
pub fn reset_max(&mut self) { self.maximum = self.base_max; }
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum StatChangeError {
|
||||
@ -148,6 +161,8 @@ impl Stats {
|
||||
|
||||
// TODO: Delete this once stat points will be a thing
|
||||
pub fn update_max_hp(&mut self, body: Body) {
|
||||
self.health
|
||||
.set_base_max(body.base_health() + body.base_health_increase() * self.level.amount);
|
||||
self.health
|
||||
.set_maximum(body.base_health() + body.base_health_increase() * self.level.amount);
|
||||
}
|
||||
@ -179,6 +194,7 @@ impl Stats {
|
||||
health: Health {
|
||||
current: 0,
|
||||
maximum: 0,
|
||||
base_max: 0,
|
||||
last_change: (0.0, HealthChange {
|
||||
amount: 0,
|
||||
cause: HealthSource::Revive,
|
||||
@ -198,6 +214,7 @@ impl Stats {
|
||||
};
|
||||
|
||||
stats.update_max_hp(body);
|
||||
|
||||
stats
|
||||
.health
|
||||
.set_to(stats.health.maximum(), HealthSource::Revive);
|
||||
@ -213,6 +230,7 @@ impl Stats {
|
||||
health: Health {
|
||||
current: 0,
|
||||
maximum: 0,
|
||||
base_max: 0,
|
||||
last_change: (0.0, HealthChange {
|
||||
amount: 0,
|
||||
cause: HealthSource::Revive,
|
||||
|
@ -106,6 +106,10 @@ pub enum ServerEvent {
|
||||
ChatCmd(EcsEntity, String),
|
||||
/// Send a chat message to the player from an npc or other player
|
||||
Chat(comp::UnresolvedChatMsg),
|
||||
Buff {
|
||||
entity: EcsEntity,
|
||||
buff_change: comp::BuffChange,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EventBus<E> {
|
||||
|
@ -13,6 +13,7 @@ sum_type! {
|
||||
Player(comp::Player),
|
||||
CanBuild(comp::CanBuild),
|
||||
Stats(comp::Stats),
|
||||
Buffs(comp::Buffs),
|
||||
Energy(comp::Energy),
|
||||
LightEmitter(comp::LightEmitter),
|
||||
Item(comp::Item),
|
||||
@ -42,6 +43,7 @@ sum_type! {
|
||||
Player(PhantomData<comp::Player>),
|
||||
CanBuild(PhantomData<comp::CanBuild>),
|
||||
Stats(PhantomData<comp::Stats>),
|
||||
Buffs(PhantomData<comp::Buffs>),
|
||||
Energy(PhantomData<comp::Energy>),
|
||||
LightEmitter(PhantomData<comp::LightEmitter>),
|
||||
Item(PhantomData<comp::Item>),
|
||||
@ -71,6 +73,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -98,6 +101,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -125,6 +129,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world),
|
||||
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),
|
||||
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
|
||||
EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world),
|
||||
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),
|
||||
EcsCompPhantom::LightEmitter(_) => {
|
||||
sync::handle_remove::<comp::LightEmitter>(entity, world)
|
||||
|
@ -112,6 +112,7 @@ impl State {
|
||||
ecs.register::<comp::Body>();
|
||||
ecs.register::<comp::Player>();
|
||||
ecs.register::<comp::Stats>();
|
||||
ecs.register::<comp::Buffs>();
|
||||
ecs.register::<comp::Energy>();
|
||||
ecs.register::<comp::CanBuild>();
|
||||
ecs.register::<comp::LightEmitter>();
|
||||
|
@ -601,6 +601,7 @@ impl<'a> System<'a> for Sys {
|
||||
if let comp::HealthSource::Attack { by }
|
||||
| comp::HealthSource::Projectile { owner: Some(by) }
|
||||
| comp::HealthSource::Energy { owner: Some(by) }
|
||||
| comp::HealthSource::Buff { owner: Some(by) }
|
||||
| comp::HealthSource::Explosion { owner: Some(by) } =
|
||||
my_stats.health.last_change.1.cause
|
||||
{
|
||||
|
146
common/src/sys/buff.rs
Normal file
146
common/src/sys/buff.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange,
|
||||
HealthSource, Loadout, ModifierKind, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, DeltaTime>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
WriteStorage<'a, Stats>,
|
||||
WriteStorage<'a, Buffs>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, dt, server_bus, uids, loadouts, mut stats, mut buffs): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
// Set to false to avoid spamming server
|
||||
buffs.set_event_emission(false);
|
||||
stats.set_event_emission(false);
|
||||
for (entity, buff_comp, uid, stat) in (&entities, &mut buffs, &uids, &mut stats).join() {
|
||||
let mut expired_buffs = Vec::<BuffId>::new();
|
||||
for (id, buff) in buff_comp.buffs.iter_mut() {
|
||||
// Tick the buff and subtract delta from it
|
||||
if let Some(remaining_time) = &mut buff.time {
|
||||
if let Some(new_duration) =
|
||||
remaining_time.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
{
|
||||
// The buff still continues.
|
||||
*remaining_time = new_duration;
|
||||
} else {
|
||||
// checked_sub returns None when remaining time
|
||||
// went below 0, so set to 0
|
||||
*remaining_time = Duration::default();
|
||||
// The buff has expired.
|
||||
// Remove it.
|
||||
expired_buffs.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(loadout) = loadouts.get(entity) {
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
if (damage_reduction - 1.0).abs() < f32::EPSILON {
|
||||
for (id, buff) in buff_comp.buffs.iter() {
|
||||
if !buff.kind.is_buff() {
|
||||
expired_buffs.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call to reset stats to base values
|
||||
stat.health.reset_max();
|
||||
|
||||
// Iterator over the lists of buffs by kind
|
||||
for buff_ids in buff_comp.kinds.values() {
|
||||
// Get the strongest of this buff kind
|
||||
if let Some(buff) = buff_comp.buffs.get_mut(&buff_ids[0]) {
|
||||
// Get buff owner?
|
||||
let buff_owner = if let BuffSource::Character { by: owner } = buff.source {
|
||||
Some(owner)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Now, execute the buff, based on it's delta
|
||||
for effect in &mut buff.effects {
|
||||
match effect {
|
||||
BuffEffect::HealthChangeOverTime { rate, accumulated } => {
|
||||
*accumulated += *rate * dt.0;
|
||||
// Apply damage only once a second (with a minimum of 1 damage), or
|
||||
// when a buff is removed
|
||||
if accumulated.abs() > rate.abs().max(10.0)
|
||||
|| buff.time.map_or(false, |dur| dur == Duration::default())
|
||||
{
|
||||
let cause = if *accumulated > 0.0 {
|
||||
HealthSource::Healing { by: buff_owner }
|
||||
} else {
|
||||
HealthSource::Buff { owner: buff_owner }
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid,
|
||||
change: HealthChange {
|
||||
amount: *accumulated as i32,
|
||||
cause,
|
||||
},
|
||||
});
|
||||
*accumulated = 0.0;
|
||||
};
|
||||
},
|
||||
BuffEffect::MaxHealthModifier { value, kind } => match kind {
|
||||
ModifierKind::Multiplicative => {
|
||||
stat.health.set_maximum(
|
||||
(stat.health.maximum() as f32 * *value) as u32,
|
||||
);
|
||||
},
|
||||
ModifierKind::Additive => {
|
||||
stat.health.set_maximum(
|
||||
(stat.health.maximum() as f32 + *value) as u32,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove buffs that expire
|
||||
if !expired_buffs.is_empty() {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity,
|
||||
buff_change: BuffChange::RemoveById(expired_buffs),
|
||||
});
|
||||
}
|
||||
|
||||
// Remove stats that don't persist on death
|
||||
if stat.is_dead {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity,
|
||||
buff_change: BuffChange::RemoveByCategory {
|
||||
all_required: vec![],
|
||||
any_required: vec![],
|
||||
none_required: vec![BuffCategory::PersistOnDeath],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// Turned back to true
|
||||
buffs.set_event_emission(true);
|
||||
stats.set_event_emission(true);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource,
|
||||
Loadout, Ori, Pos, Scale, Stats,
|
||||
buff, group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange,
|
||||
HealthSource, Loadout, Ori, Pos, Scale, Stats,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
@ -9,7 +9,9 @@ use crate::{
|
||||
sync::Uid,
|
||||
util::Dir,
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||
@ -150,6 +152,24 @@ impl<'a> System<'a> for Sys {
|
||||
cause,
|
||||
},
|
||||
});
|
||||
|
||||
// Apply bleeding buff on melee hits with 10% chance
|
||||
// TODO: Don't have buff uniformly applied on all melee attacks
|
||||
if thread_rng().gen::<f32>() < 0.1 {
|
||||
use buff::*;
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity: b,
|
||||
buff_change: BuffChange::Add(Buff::new(
|
||||
BuffKind::Bleeding,
|
||||
BuffData {
|
||||
strength: attack.base_damage as f32 / 10.0,
|
||||
duration: Some(Duration::from_secs(10)),
|
||||
},
|
||||
vec![BuffCategory::Physical],
|
||||
BuffSource::Character { by: *uid },
|
||||
)),
|
||||
});
|
||||
}
|
||||
attack.hit_count += 1;
|
||||
}
|
||||
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
slot::{EquipSlot, Slot},
|
||||
CharacterState, ControlEvent, Controller, InventoryManip,
|
||||
BuffChange, CharacterState, ControlEvent, Controller, InventoryManip,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
@ -83,6 +83,12 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
|
||||
}
|
||||
},
|
||||
ControlEvent::RemoveBuff(buff_id) => {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity,
|
||||
buff_change: BuffChange::RemoveFromController(buff_id),
|
||||
});
|
||||
},
|
||||
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),
|
||||
ControlEvent::EnableLantern => {
|
||||
server_emitter.emit(ServerEvent::EnableLantern(entity))
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod agent;
|
||||
mod beam;
|
||||
mod buff;
|
||||
pub mod character_behavior;
|
||||
pub mod combat;
|
||||
pub mod controller;
|
||||
@ -23,6 +24,7 @@ pub const PHYS_SYS: &str = "phys_sys";
|
||||
pub const PROJECTILE_SYS: &str = "projectile_sys";
|
||||
pub const SHOCKWAVE_SYS: &str = "shockwave_sys";
|
||||
pub const STATS_SYS: &str = "stats_sys";
|
||||
pub const BUFFS_SYS: &str = "buffs_sys";
|
||||
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
|
||||
@ -32,6 +34,7 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
CONTROLLER_SYS,
|
||||
]);
|
||||
dispatch_builder.add(stats::Sys, STATS_SYS, &[]);
|
||||
dispatch_builder.add(buff::Sys, BUFFS_SYS, &[]);
|
||||
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, MOUNT_SYS, STATS_SYS]);
|
||||
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]);
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
use common::{
|
||||
assets::Asset,
|
||||
comp::{
|
||||
self,
|
||||
self, buff,
|
||||
chat::{KillSource, KillType},
|
||||
object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource, Item,
|
||||
Player, Pos, Stats,
|
||||
@ -165,11 +165,34 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Energy)
|
||||
}
|
||||
},
|
||||
HealthSource::Buff { owner: Some(by) } => {
|
||||
// Get energy owner entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
// Check if attacker is another player or entity with stats (npc)
|
||||
if state
|
||||
.ecs()
|
||||
.read_storage::<Player>()
|
||||
.get(char_entity)
|
||||
.is_some()
|
||||
{
|
||||
KillSource::Player(by, KillType::Buff)
|
||||
} else if let Some(stats) =
|
||||
state.ecs().read_storage::<Stats>().get(char_entity)
|
||||
{
|
||||
KillSource::NonPlayer(stats.name.clone(), KillType::Buff)
|
||||
} else {
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Buff)
|
||||
}
|
||||
} else {
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Buff)
|
||||
}
|
||||
},
|
||||
HealthSource::World => KillSource::FallDamage,
|
||||
HealthSource::Suicide => KillSource::Suicide,
|
||||
HealthSource::Projectile { owner: None }
|
||||
| HealthSource::Explosion { owner: None }
|
||||
| HealthSource::Energy { owner: None }
|
||||
| HealthSource::Buff { owner: None }
|
||||
| HealthSource::Revive
|
||||
| HealthSource::Command
|
||||
| HealthSource::LevelUp
|
||||
@ -189,6 +212,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
let by = if let HealthSource::Attack { by }
|
||||
| HealthSource::Projectile { owner: Some(by) }
|
||||
| HealthSource::Energy { owner: Some(by) }
|
||||
| HealthSource::Buff { owner: Some(by) }
|
||||
| HealthSource::Explosion { owner: Some(by) } = cause
|
||||
{
|
||||
by
|
||||
@ -674,3 +698,65 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
|
||||
PlayerListUpdate::LevelChange(*uid, new_level),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: buff::BuffChange) {
|
||||
let ecs = &server.state.ecs();
|
||||
let mut buffs_all = ecs.write_storage::<comp::Buffs>();
|
||||
if let Some(buffs) = buffs_all.get_mut(entity) {
|
||||
use buff::BuffChange;
|
||||
match buff_change {
|
||||
BuffChange::Add(new_buff) => {
|
||||
buffs.insert(new_buff);
|
||||
},
|
||||
BuffChange::RemoveById(ids) => {
|
||||
for id in ids {
|
||||
buffs.remove(id);
|
||||
}
|
||||
},
|
||||
BuffChange::RemoveByKind(kind) => {
|
||||
buffs.remove_kind(kind);
|
||||
},
|
||||
BuffChange::RemoveFromController(kind) => {
|
||||
if kind.is_buff() {
|
||||
buffs.remove_kind(kind);
|
||||
}
|
||||
},
|
||||
BuffChange::RemoveByCategory {
|
||||
all_required,
|
||||
any_required,
|
||||
none_required,
|
||||
} => {
|
||||
let mut ids_to_remove = Vec::new();
|
||||
for (id, buff) in buffs.buffs.iter() {
|
||||
let mut required_met = true;
|
||||
for required in &all_required {
|
||||
if !buff.cat_ids.iter().any(|cat| cat == required) {
|
||||
required_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut any_met = any_required.is_empty();
|
||||
for any in &any_required {
|
||||
if buff.cat_ids.iter().any(|cat| cat == any) {
|
||||
any_met = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut none_met = true;
|
||||
for none in &none_required {
|
||||
if buff.cat_ids.iter().any(|cat| cat == none) {
|
||||
none_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if required_met && any_met && none_met {
|
||||
ids_to_remove.push(*id);
|
||||
}
|
||||
}
|
||||
for id in ids_to_remove {
|
||||
buffs.remove(id);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ use entity_creation::{
|
||||
handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
};
|
||||
use entity_manipulation::{
|
||||
handle_damage, handle_destroy, handle_explosion, handle_knockback, handle_land_on_ground,
|
||||
handle_level_up, handle_respawn,
|
||||
handle_buff, handle_damage, handle_destroy, handle_explosion, handle_knockback,
|
||||
handle_land_on_ground, handle_level_up, handle_respawn,
|
||||
};
|
||||
use group_manip::handle_group;
|
||||
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
||||
@ -133,6 +133,10 @@ impl Server {
|
||||
ServerEvent::Chat(msg) => {
|
||||
chat_messages.push(msg);
|
||||
},
|
||||
ServerEvent::Buff {
|
||||
entity,
|
||||
buff_change,
|
||||
} => handle_buff(self, entity, buff_change),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,6 +111,7 @@ impl StateExt for State {
|
||||
.with(comp::Gravity(1.0))
|
||||
.with(comp::CharacterState::default())
|
||||
.with(loadout)
|
||||
.with(comp::Buffs::default())
|
||||
}
|
||||
|
||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {
|
||||
@ -202,6 +203,7 @@ impl StateExt for State {
|
||||
entity,
|
||||
comp::Alignment::Owned(self.read_component_copied(entity).unwrap()),
|
||||
);
|
||||
self.write_component(entity, comp::Buffs::default());
|
||||
|
||||
// Make sure physics components are updated
|
||||
self.write_component(entity, comp::ForceUpdate);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::SysTimer;
|
||||
use common::{
|
||||
comp::{
|
||||
BeamSegment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item,
|
||||
BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item,
|
||||
LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave,
|
||||
Stats, Sticky, Vel,
|
||||
},
|
||||
@ -44,6 +44,7 @@ pub struct TrackedComps<'a> {
|
||||
pub body: ReadStorage<'a, Body>,
|
||||
pub player: ReadStorage<'a, Player>,
|
||||
pub stats: ReadStorage<'a, Stats>,
|
||||
pub buffs: ReadStorage<'a, Buffs>,
|
||||
pub energy: ReadStorage<'a, Energy>,
|
||||
pub can_build: ReadStorage<'a, CanBuild>,
|
||||
pub light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
@ -85,6 +86,10 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.buffs
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.energy
|
||||
.get(entity)
|
||||
.cloned()
|
||||
@ -157,6 +162,7 @@ pub struct ReadTrackers<'a> {
|
||||
pub body: ReadExpect<'a, UpdateTracker<Body>>,
|
||||
pub player: ReadExpect<'a, UpdateTracker<Player>>,
|
||||
pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
|
||||
pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>,
|
||||
pub energy: ReadExpect<'a, UpdateTracker<Energy>>,
|
||||
pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
|
||||
pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
@ -187,6 +193,7 @@ impl<'a> ReadTrackers<'a> {
|
||||
.with_component(&comps.uid, &*self.body, &comps.body, filter)
|
||||
.with_component(&comps.uid, &*self.player, &comps.player, filter)
|
||||
.with_component(&comps.uid, &*self.stats, &comps.stats, filter)
|
||||
.with_component(&comps.uid, &*self.buffs, &comps.buffs, filter)
|
||||
.with_component(&comps.uid, &*self.energy, &comps.energy, filter)
|
||||
.with_component(&comps.uid, &*self.can_build, &comps.can_build, filter)
|
||||
.with_component(
|
||||
@ -224,6 +231,7 @@ pub struct WriteTrackers<'a> {
|
||||
body: WriteExpect<'a, UpdateTracker<Body>>,
|
||||
player: WriteExpect<'a, UpdateTracker<Player>>,
|
||||
stats: WriteExpect<'a, UpdateTracker<Stats>>,
|
||||
buffs: WriteExpect<'a, UpdateTracker<Buffs>>,
|
||||
energy: WriteExpect<'a, UpdateTracker<Energy>>,
|
||||
can_build: WriteExpect<'a, UpdateTracker<CanBuild>>,
|
||||
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
@ -248,6 +256,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
trackers.body.record_changes(&comps.body);
|
||||
trackers.player.record_changes(&comps.player);
|
||||
trackers.stats.record_changes(&comps.stats);
|
||||
trackers.buffs.record_changes(&comps.buffs);
|
||||
trackers.energy.record_changes(&comps.energy);
|
||||
trackers.can_build.record_changes(&comps.can_build);
|
||||
trackers.light_emitter.record_changes(&comps.light_emitter);
|
||||
@ -283,6 +292,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
};
|
||||
log_counts!(uid, "Uids");
|
||||
log_counts!(body, "Bodies");
|
||||
log_counts!(buffs, "Buffs");
|
||||
log_counts!(player, "Players");
|
||||
log_counts!(stats, "Stats");
|
||||
log_counts!(energy, "Energies");
|
||||
@ -307,6 +317,7 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<Body>();
|
||||
world.register_tracker::<Player>();
|
||||
world.register_tracker::<Stats>();
|
||||
world.register_tracker::<Buffs>();
|
||||
world.register_tracker::<Energy>();
|
||||
world.register_tracker::<CanBuild>();
|
||||
world.register_tracker::<LightEmitter>();
|
||||
|
@ -76,6 +76,7 @@ impl<'a> System<'a> for Sys {
|
||||
| HealthSource::Projectile { owner: Some(by) }
|
||||
| HealthSource::Energy { owner: Some(by) }
|
||||
| HealthSource::Explosion { owner: Some(by) }
|
||||
| HealthSource::Buff { owner: Some(by) }
|
||||
| HealthSource::Healing { by: Some(by) } => {
|
||||
let by_me = my_uid.map_or(false, |&uid| by == uid);
|
||||
// If the attack was by me also reset this timer
|
||||
|
474
voxygen/src/hud/buffs.rs
Normal file
474
voxygen/src/hud/buffs.rs
Normal file
@ -0,0 +1,474 @@
|
||||
use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR,
|
||||
};
|
||||
use crate::{
|
||||
hud::{get_buff_info, BuffPosition},
|
||||
i18n::VoxygenLocalization,
|
||||
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
|
||||
GlobalState,
|
||||
};
|
||||
|
||||
use common::comp::{BuffKind, Buffs};
|
||||
use conrod_core::{
|
||||
color,
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
align,
|
||||
buffs_align,
|
||||
debuffs_align,
|
||||
buff_test,
|
||||
debuff_test,
|
||||
buffs[],
|
||||
buff_timers[],
|
||||
debuffs[],
|
||||
debuff_timers[],
|
||||
buff_txts[],
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct BuffsBar<'a> {
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
|
||||
buffs: &'a Buffs,
|
||||
pulse: f32,
|
||||
global_state: &'a GlobalState,
|
||||
}
|
||||
|
||||
impl<'a> BuffsBar<'a> {
|
||||
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
|
||||
pub fn new(
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
|
||||
buffs: &'a Buffs,
|
||||
pulse: f32,
|
||||
global_state: &'a GlobalState,
|
||||
) -> Self {
|
||||
Self {
|
||||
imgs,
|
||||
fonts,
|
||||
common: widget::CommonBuilder::default(),
|
||||
rot_imgs,
|
||||
tooltip_manager,
|
||||
localized_strings,
|
||||
buffs,
|
||||
pulse,
|
||||
global_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
ids: Ids,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
RemoveBuff(BuffKind),
|
||||
}
|
||||
|
||||
impl<'a> Widget for BuffsBar<'a> {
|
||||
type Event = Vec<Event>;
|
||||
type State = State;
|
||||
type Style = ();
|
||||
|
||||
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||
State {
|
||||
ids: Ids::new(id_gen),
|
||||
}
|
||||
}
|
||||
|
||||
fn style(&self) -> Self::Style {}
|
||||
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
let mut event = Vec::new();
|
||||
let localized_strings = self.localized_strings;
|
||||
let buffs = self.buffs;
|
||||
let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer
|
||||
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
|
||||
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||
let buff_position = self.global_state.settings.gameplay.buff_position;
|
||||
let buffs_tooltip = Tooltip::new({
|
||||
// Edge images [t, b, r, l]
|
||||
// Corner images [tr, tl, br, bl]
|
||||
let edge = &self.rot_imgs.tt_side;
|
||||
let corner = &self.rot_imgs.tt_corner;
|
||||
ImageFrame::new(
|
||||
[edge.cw180, edge.none, edge.cw270, edge.cw90],
|
||||
[corner.none, corner.cw270, corner.cw90, corner.cw180],
|
||||
Color::Rgba(0.08, 0.07, 0.04, 1.0),
|
||||
5.0,
|
||||
)
|
||||
})
|
||||
.title_font_size(self.fonts.cyri.scale(15))
|
||||
.parent(ui.window)
|
||||
.desc_font_size(self.fonts.cyri.scale(12))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.desc_text_color(TEXT_COLOR);
|
||||
if let BuffPosition::Bar = buff_position {
|
||||
// Alignment
|
||||
Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT)
|
||||
.mid_bottom_with_margin_on(ui.window, 92.0)
|
||||
.set(state.ids.align, ui);
|
||||
Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT)
|
||||
.bottom_left_with_margins_on(state.ids.align, 0.0, 0.0)
|
||||
.set(state.ids.debuffs_align, ui);
|
||||
Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT)
|
||||
.bottom_right_with_margins_on(state.ids.align, 0.0, 0.0)
|
||||
.set(state.ids.buffs_align, ui);
|
||||
|
||||
// Buffs and Debuffs
|
||||
let (buff_count, debuff_count) = buffs.iter_active().map(get_buff_info).fold(
|
||||
(0, 0),
|
||||
|(buff_count, debuff_count), info| {
|
||||
if info.is_buff {
|
||||
(buff_count + 1, debuff_count)
|
||||
} else {
|
||||
(buff_count, debuff_count + 1)
|
||||
}
|
||||
},
|
||||
);
|
||||
// Limit displayed buffs
|
||||
let buff_count = buff_count.min(22);
|
||||
let debuff_count = debuff_count.min(22);
|
||||
|
||||
let gen = &mut ui.widget_id_generator();
|
||||
if state.ids.buffs.len() < buff_count {
|
||||
state.update(|state| state.ids.buffs.resize(buff_count, gen));
|
||||
};
|
||||
if state.ids.debuffs.len() < debuff_count {
|
||||
state.update(|state| state.ids.debuffs.resize(debuff_count, gen));
|
||||
};
|
||||
if state.ids.buff_timers.len() < buff_count {
|
||||
state.update(|state| state.ids.buff_timers.resize(buff_count, gen));
|
||||
};
|
||||
if state.ids.debuff_timers.len() < debuff_count {
|
||||
state.update(|state| state.ids.debuff_timers.resize(debuff_count, gen));
|
||||
};
|
||||
|
||||
// Create Buff Widgets
|
||||
state
|
||||
.ids
|
||||
.buffs
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(
|
||||
buffs
|
||||
.iter_active()
|
||||
.map(get_buff_info)
|
||||
.filter(|info| info.is_buff),
|
||||
)
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), buff))| {
|
||||
let max_duration = buff.data.duration;
|
||||
let current_duration = buff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration
|
||||
.map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
|
||||
}) as u32; // Percentage to determine which frame of the timer overlay is displayed
|
||||
let buff_img = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
|
||||
_ => self.imgs.missing_icon,
|
||||
};
|
||||
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
|
||||
// Sort buffs into rows of 11 slots
|
||||
let x = i % 11;
|
||||
let y = i / 11;
|
||||
let buff_widget = buff_widget.bottom_left_with_margins_on(
|
||||
state.ids.buffs_align,
|
||||
0.0 + y as f64 * (21.0),
|
||||
0.0 + x as f64 * (21.0),
|
||||
);
|
||||
buff_widget
|
||||
.color(
|
||||
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
|
||||
Some(pulsating_col)
|
||||
} else {
|
||||
Some(norm_col)
|
||||
},
|
||||
)
|
||||
.set(id, ui);
|
||||
// Create Buff tooltip
|
||||
let title = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => {
|
||||
localized_strings.get("buff.title.heal_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.title.missing"),
|
||||
};
|
||||
let remaining_time = if current_duration.is_none() {
|
||||
"Permanent".to_string()
|
||||
} else {
|
||||
format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32())
|
||||
};
|
||||
let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove"));
|
||||
let desc_txt = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => {
|
||||
localized_strings.get("buff.desc.heal_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.desc.missing"),
|
||||
};
|
||||
let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove);
|
||||
// Timer overlay
|
||||
if Button::image(match duration_percentage as u64 {
|
||||
875..=1000 => self.imgs.nothing, // 8/8
|
||||
750..=874 => self.imgs.buff_0, // 7/8
|
||||
625..=749 => self.imgs.buff_1, // 6/8
|
||||
500..=624 => self.imgs.buff_2, // 5/8
|
||||
375..=499 => self.imgs.buff_3, // 4/8
|
||||
250..=374 => self.imgs.buff_4, //3/8
|
||||
125..=249 => self.imgs.buff_5, // 2/8
|
||||
0..=124 => self.imgs.buff_6, // 1/8
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
.w_h(20.0, 20.0)
|
||||
.middle_of(id)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
title,
|
||||
&desc,
|
||||
&buffs_tooltip,
|
||||
BUFF_COLOR,
|
||||
)
|
||||
.set(timer_id, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
event.push(Event::RemoveBuff(buff.kind));
|
||||
};
|
||||
});
|
||||
// Create Debuff Widgets
|
||||
state
|
||||
.ids
|
||||
.debuffs
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.debuff_timers.iter().copied())
|
||||
.zip(
|
||||
buffs
|
||||
.iter_active()
|
||||
.map(get_buff_info)
|
||||
.filter(|info| !info.is_buff),
|
||||
)
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), debuff))| {
|
||||
let max_duration = debuff.data.duration;
|
||||
let current_duration = debuff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration
|
||||
.map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
|
||||
}) as u32; // Percentage to determine which frame of the timer overlay is displayed
|
||||
let debuff_img = match debuff.kind {
|
||||
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
|
||||
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
|
||||
_ => self.imgs.missing_icon,
|
||||
};
|
||||
let debuff_widget = Image::new(debuff_img).w_h(20.0, 20.0);
|
||||
// Sort buffs into rows of 11 slots
|
||||
let x = i % 11;
|
||||
let y = i / 11;
|
||||
let debuff_widget = debuff_widget.bottom_right_with_margins_on(
|
||||
state.ids.debuffs_align,
|
||||
0.0 + y as f64 * (21.0),
|
||||
0.0 + x as f64 * (21.0),
|
||||
);
|
||||
|
||||
debuff_widget
|
||||
.color(
|
||||
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
|
||||
Some(pulsating_col)
|
||||
} else {
|
||||
Some(norm_col)
|
||||
},
|
||||
)
|
||||
.set(id, ui);
|
||||
// Create Debuff tooltip
|
||||
let title = match debuff.kind {
|
||||
BuffKind::Bleeding { .. } => {
|
||||
localized_strings.get("debuff.title.bleed_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.title.missing"),
|
||||
};
|
||||
let remaining_time = if current_duration.is_none() {
|
||||
"Permanent".to_string()
|
||||
} else {
|
||||
format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32())
|
||||
};
|
||||
let desc_txt = match debuff.kind {
|
||||
BuffKind::Bleeding { .. } => {
|
||||
localized_strings.get("debuff.desc.bleed_test")
|
||||
},
|
||||
_ => localized_strings.get("debuff.desc.missing"),
|
||||
};
|
||||
let desc = format!("{}\n\n{}", desc_txt, remaining_time);
|
||||
Image::new(match duration_percentage as u64 {
|
||||
875..=1000 => self.imgs.nothing, // 8/8
|
||||
750..=874 => self.imgs.buff_0, // 7/8
|
||||
625..=749 => self.imgs.buff_1, // 6/8
|
||||
500..=624 => self.imgs.buff_2, // 5/8
|
||||
375..=499 => self.imgs.buff_3, // 4/8
|
||||
250..=374 => self.imgs.buff_4, //3/8
|
||||
125..=249 => self.imgs.buff_5, // 2/8
|
||||
0..=124 => self.imgs.buff_6, // 1/8
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
.w_h(20.0, 20.0)
|
||||
.middle_of(id)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
title,
|
||||
&desc,
|
||||
&buffs_tooltip,
|
||||
DEBUFF_COLOR,
|
||||
)
|
||||
.set(timer_id, ui);
|
||||
});
|
||||
}
|
||||
|
||||
if let BuffPosition::Map = buff_position {
|
||||
// Alignment
|
||||
Rectangle::fill_with([210.0, 210.0], color::TRANSPARENT)
|
||||
.top_right_with_margins_on(ui.window, 5.0, 270.0)
|
||||
.set(state.ids.align, ui);
|
||||
|
||||
// Buffs and Debuffs
|
||||
let buff_count = buffs.kinds.len().min(11);
|
||||
// Limit displayed buffs
|
||||
let buff_count = buff_count.min(20);
|
||||
|
||||
let gen = &mut ui.widget_id_generator();
|
||||
if state.ids.buffs.len() < buff_count {
|
||||
state.update(|state| state.ids.buffs.resize(buff_count, gen));
|
||||
};
|
||||
if state.ids.buff_timers.len() < buff_count {
|
||||
state.update(|state| state.ids.buff_timers.resize(buff_count, gen));
|
||||
};
|
||||
if state.ids.buff_txts.len() < buff_count {
|
||||
state.update(|state| state.ids.buff_txts.resize(buff_count, gen));
|
||||
};
|
||||
|
||||
// Create Buff Widgets
|
||||
state
|
||||
.ids
|
||||
.buffs
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(state.ids.buff_txts.iter().copied())
|
||||
.zip(buffs.iter_active().map(get_buff_info))
|
||||
.enumerate()
|
||||
.for_each(|(i, (((id, timer_id), txt_id), buff))| {
|
||||
let max_duration = buff.data.duration;
|
||||
let current_duration = buff.dur;
|
||||
// Percentage to determine which frame of the timer overlay is displayed
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration
|
||||
.map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
|
||||
}) as u32;
|
||||
let buff_img = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
|
||||
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
|
||||
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
|
||||
};
|
||||
let buff_widget = Image::new(buff_img).w_h(40.0, 40.0);
|
||||
// Sort buffs into rows of 6 slots
|
||||
let x = i % 6;
|
||||
let y = i / 6;
|
||||
let buff_widget = buff_widget.top_right_with_margins_on(
|
||||
state.ids.align,
|
||||
0.0 + y as f64 * (54.0),
|
||||
0.0 + x as f64 * (42.0),
|
||||
);
|
||||
buff_widget
|
||||
.color(
|
||||
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
|
||||
Some(pulsating_col)
|
||||
} else {
|
||||
Some(norm_col)
|
||||
},
|
||||
)
|
||||
.set(id, ui);
|
||||
// Create Buff tooltip
|
||||
let title = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => {
|
||||
localized_strings.get("buff.title.heal_test")
|
||||
},
|
||||
BuffKind::Bleeding { .. } => {
|
||||
localized_strings.get("debuff.title.bleed_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.title.missing"),
|
||||
};
|
||||
let remaining_time = if current_duration.is_none() {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{:.0}s", current_duration.unwrap().as_secs_f32())
|
||||
};
|
||||
let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove"));
|
||||
let desc_txt = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => {
|
||||
localized_strings.get("buff.desc.heal_test")
|
||||
},
|
||||
BuffKind::Bleeding { .. } => {
|
||||
localized_strings.get("debuff.desc.bleed_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.desc.missing"),
|
||||
};
|
||||
let desc = if buff.is_buff {
|
||||
format!("{}\n\n{}", desc_txt, click_to_remove)
|
||||
} else {
|
||||
desc_txt.to_string()
|
||||
};
|
||||
// Timer overlay
|
||||
if Button::image(match duration_percentage as u64 {
|
||||
875..=1000 => self.imgs.nothing, // 8/8
|
||||
750..=874 => self.imgs.buff_0, // 7/8
|
||||
625..=749 => self.imgs.buff_1, // 6/8
|
||||
500..=624 => self.imgs.buff_2, // 5/8
|
||||
375..=499 => self.imgs.buff_3, // 4/8
|
||||
250..=374 => self.imgs.buff_4, // 3/8
|
||||
125..=249 => self.imgs.buff_5, // 2/8
|
||||
0..=124 => self.imgs.buff_6, // 1/8
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
.w_h(40.0, 40.0)
|
||||
.middle_of(id)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
title,
|
||||
&desc,
|
||||
&buffs_tooltip,
|
||||
if buff.is_buff {
|
||||
BUFF_COLOR
|
||||
} else {
|
||||
DEBUFF_COLOR
|
||||
},
|
||||
)
|
||||
.set(timer_id, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
event.push(Event::RemoveBuff(buff.kind));
|
||||
}
|
||||
Text::new(&remaining_time)
|
||||
.down_from(timer_id, 1.0)
|
||||
.font_size(self.fonts.cyri.scale(10))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.graphics_for(timer_id)
|
||||
.color(TEXT_COLOR)
|
||||
.set(txt_id, ui);
|
||||
});
|
||||
}
|
||||
event
|
||||
}
|
||||
}
|
@ -373,6 +373,10 @@ impl<'a> Widget for Chat<'a> {
|
||||
.localized_strings
|
||||
.get("hud.chat.pvp_energy_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::Player(_, KillType::Buff) => self
|
||||
.localized_strings
|
||||
.get("hud.chat.pvp_buff_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::NonPlayer(_, KillType::Melee) => self
|
||||
.localized_strings
|
||||
.get("hud.chat.npc_melee_kill_msg")
|
||||
@ -389,6 +393,10 @@ impl<'a> Widget for Chat<'a> {
|
||||
.localized_strings
|
||||
.get("hud.chat.npc_energy_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::NonPlayer(_, KillType::Buff) => self
|
||||
.localized_strings
|
||||
.get("hud.chat.npc_buff_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::Environment(_) => self
|
||||
.localized_strings
|
||||
.get("hud.chat.environmental_kill_msg")
|
||||
|
@ -1,15 +1,20 @@
|
||||
use super::{
|
||||
img_ids::Imgs, Show, BLACK, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, LOW_HP_COLOR,
|
||||
MANA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
Show, BLACK, BUFF_COLOR, DEBUFF_COLOR, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR,
|
||||
LOW_HP_COLOR, STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
i18n::VoxygenLocalization, settings::Settings, ui::fonts::ConrodVoxygenFonts,
|
||||
window::GameInput, GlobalState,
|
||||
hud::get_buff_info,
|
||||
i18n::VoxygenLocalization,
|
||||
settings::Settings,
|
||||
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
|
||||
window::GameInput,
|
||||
GlobalState,
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::{
|
||||
comp::{group::Role, Stats},
|
||||
comp::{group::Role, BuffKind, Stats},
|
||||
sync::{Uid, WorldSyncExt},
|
||||
};
|
||||
use conrod_core::{
|
||||
@ -19,7 +24,6 @@ use conrod_core::{
|
||||
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, WorldExt};
|
||||
|
||||
widget_ids! {
|
||||
pub struct Ids {
|
||||
group_button,
|
||||
@ -44,6 +48,8 @@ widget_ids! {
|
||||
member_panels_txt[],
|
||||
member_health[],
|
||||
member_stam[],
|
||||
buffs[],
|
||||
buff_timers[],
|
||||
dead_txt[],
|
||||
health_txt[],
|
||||
timeout_bg,
|
||||
@ -63,10 +69,12 @@ pub struct Group<'a> {
|
||||
client: &'a Client,
|
||||
settings: &'a Settings,
|
||||
imgs: &'a Imgs,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
|
||||
pulse: f32,
|
||||
global_state: &'a GlobalState,
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
@ -79,20 +87,24 @@ impl<'a> Group<'a> {
|
||||
client: &'a Client,
|
||||
settings: &'a Settings,
|
||||
imgs: &'a Imgs,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
|
||||
pulse: f32,
|
||||
global_state: &'a GlobalState,
|
||||
tooltip_manager: &'a mut TooltipManager,
|
||||
) -> Self {
|
||||
Self {
|
||||
show,
|
||||
client,
|
||||
settings,
|
||||
imgs,
|
||||
rot_imgs,
|
||||
fonts,
|
||||
localized_strings,
|
||||
pulse,
|
||||
global_state,
|
||||
tooltip_manager,
|
||||
common: widget::CommonBuilder::default(),
|
||||
}
|
||||
}
|
||||
@ -127,8 +139,26 @@ impl<'a> Widget for Group<'a> {
|
||||
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let localized_strings = self.localized_strings;
|
||||
let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer
|
||||
let buffs_tooltip = Tooltip::new({
|
||||
// Edge images [t, b, r, l]
|
||||
// Corner images [tr, tl, br, bl]
|
||||
let edge = &self.rot_imgs.tt_side;
|
||||
let corner = &self.rot_imgs.tt_corner;
|
||||
ImageFrame::new(
|
||||
[edge.cw180, edge.none, edge.cw270, edge.cw90],
|
||||
[corner.none, corner.cw270, corner.cw90, corner.cw180],
|
||||
Color::Rgba(0.08, 0.07, 0.04, 1.0),
|
||||
5.0,
|
||||
)
|
||||
})
|
||||
.title_font_size(self.fonts.cyri.scale(15))
|
||||
.parent(ui.window)
|
||||
.desc_font_size(self.fonts.cyri.scale(12))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.desc_text_color(TEXT_COLOR);
|
||||
|
||||
// Don't show pets
|
||||
let group_members = self
|
||||
@ -293,31 +323,35 @@ impl<'a> Widget for Group<'a> {
|
||||
let client_state = self.client.state();
|
||||
let stats = client_state.ecs().read_storage::<common::comp::Stats>();
|
||||
let energy = client_state.ecs().read_storage::<common::comp::Energy>();
|
||||
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
|
||||
let uid_allocator = client_state
|
||||
.ecs()
|
||||
.read_resource::<common::sync::UidAllocator>();
|
||||
|
||||
let offset = if self.global_state.settings.gameplay.toggle_debug {
|
||||
320.0
|
||||
} else {
|
||||
110.0
|
||||
};
|
||||
// Keep track of the total number of widget ids we are using for buffs
|
||||
let mut total_buff_count = 0;
|
||||
for (i, &uid) in group_members.iter().copied().enumerate() {
|
||||
self.show.group = true;
|
||||
let entity = uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let stats = entity.and_then(|entity| stats.get(entity));
|
||||
let energy = entity.and_then(|entity| energy.get(entity));
|
||||
let buffs = entity.and_then(|entity| buffs.get(entity));
|
||||
|
||||
if let Some(stats) = stats {
|
||||
let char_name = stats.name.to_string();
|
||||
let health_perc = stats.health.current() as f64 / stats.health.maximum() as f64;
|
||||
|
||||
// change panel positions when debug info is shown
|
||||
let offset = if self.global_state.settings.gameplay.toggle_debug {
|
||||
290.0
|
||||
} else {
|
||||
110.0
|
||||
};
|
||||
let back = if i == 0 {
|
||||
Image::new(self.imgs.member_bg)
|
||||
.top_left_with_margins_on(ui.window, offset, 20.0)
|
||||
} else {
|
||||
Image::new(self.imgs.member_bg)
|
||||
.down_from(state.ids.member_panels_bg[i - 1], 40.0)
|
||||
.down_from(state.ids.member_panels_bg[i - 1], 45.0)
|
||||
};
|
||||
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer
|
||||
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
|
||||
@ -404,25 +438,132 @@ impl<'a> Widget for Group<'a> {
|
||||
// Stamina
|
||||
Image::new(self.imgs.bar_content)
|
||||
.w_h(100.0 * stam_perc, 8.0)
|
||||
.color(Some(MANA_COLOR))
|
||||
.color(Some(STAMINA_COLOR))
|
||||
.top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0)
|
||||
.set(state.ids.member_stam[i], ui);
|
||||
}
|
||||
if let Some(buffs) = buffs {
|
||||
// Limit displayed buffs to 11
|
||||
let buff_count = buffs.kinds.len().min(11);
|
||||
total_buff_count += buff_count;
|
||||
let gen = &mut ui.widget_id_generator();
|
||||
if state.ids.buffs.len() < total_buff_count {
|
||||
state.update(|state| state.ids.buffs.resize(total_buff_count, gen));
|
||||
}
|
||||
if state.ids.buff_timers.len() < total_buff_count {
|
||||
state.update(|state| {
|
||||
state.ids.buff_timers.resize(total_buff_count, gen)
|
||||
});
|
||||
}
|
||||
// Create Buff Widgets
|
||||
let mut prev_id = None;
|
||||
state
|
||||
.ids
|
||||
.buffs
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.skip(total_buff_count - buff_count)
|
||||
.zip(buffs.iter_active().map(get_buff_info))
|
||||
.for_each(|((id, timer_id), buff)| {
|
||||
let max_duration = buff.data.duration;
|
||||
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
|
||||
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||
let current_duration = buff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration.map_or(1000.0, |max| {
|
||||
cur.as_secs_f32() / max.as_secs_f32() * 1000.0
|
||||
})
|
||||
}) as u32; // Percentage to determine which frame of the timer overlay is displayed
|
||||
let buff_img = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
|
||||
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
|
||||
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
|
||||
};
|
||||
let buff_widget = Image::new(buff_img).w_h(15.0, 15.0);
|
||||
let buff_widget = if let Some(id) = prev_id {
|
||||
buff_widget.right_from(id, 1.0)
|
||||
} else {
|
||||
buff_widget.bottom_left_with_margins_on(
|
||||
state.ids.member_panels_frame[i],
|
||||
-16.0,
|
||||
1.0,
|
||||
)
|
||||
};
|
||||
prev_id = Some(id);
|
||||
buff_widget
|
||||
.color(
|
||||
if current_duration
|
||||
.map_or(false, |cur| cur.as_secs_f32() < 10.0)
|
||||
{
|
||||
Some(pulsating_col)
|
||||
} else {
|
||||
Some(norm_col)
|
||||
},
|
||||
)
|
||||
.set(id, ui);
|
||||
// Create Buff tooltip
|
||||
let title = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => {
|
||||
localized_strings.get("buff.title.heal_test")
|
||||
},
|
||||
BuffKind::Bleeding { .. } => {
|
||||
localized_strings.get("debuff.title.bleed_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.title.missing"),
|
||||
};
|
||||
let remaining_time = if current_duration.is_none() {
|
||||
"Permanent".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"Remaining: {:.0}s",
|
||||
current_duration.unwrap().as_secs_f32()
|
||||
)
|
||||
};
|
||||
let desc_txt = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => {
|
||||
localized_strings.get("buff.desc.heal_test")
|
||||
},
|
||||
BuffKind::Bleeding { .. } => {
|
||||
localized_strings.get("debuff.desc.bleed_test")
|
||||
},
|
||||
_ => localized_strings.get("buff.desc.missing"),
|
||||
};
|
||||
let desc = format!("{}\n\n{}", desc_txt, remaining_time);
|
||||
Image::new(match duration_percentage as u64 {
|
||||
875..=1000 => self.imgs.nothing, // 8/8
|
||||
750..=874 => self.imgs.buff_0, // 7/8
|
||||
625..=749 => self.imgs.buff_1, // 6/8
|
||||
500..=624 => self.imgs.buff_2, // 5/8
|
||||
375..=499 => self.imgs.buff_3, // 4/8
|
||||
250..=374 => self.imgs.buff_4, // 3/8
|
||||
125..=249 => self.imgs.buff_5, // 2/8
|
||||
0..=124 => self.imgs.buff_6, // 1/8
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
.w_h(15.0, 15.0)
|
||||
.middle_of(id)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
title,
|
||||
&desc,
|
||||
&buffs_tooltip,
|
||||
if buff.is_buff {
|
||||
BUFF_COLOR
|
||||
} else {
|
||||
DEBUFF_COLOR
|
||||
},
|
||||
)
|
||||
.set(timer_id, ui);
|
||||
});
|
||||
} else {
|
||||
// Values N.A.
|
||||
if let Some(stats) = stats {
|
||||
Text::new(&stats.name.to_string())
|
||||
.top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0)
|
||||
.font_size(20)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(GROUP_COLOR)
|
||||
.set(state.ids.member_panels_txt[i], ui);
|
||||
};
|
||||
let offset = if self.global_state.settings.gameplay.toggle_debug {
|
||||
210.0
|
||||
} else {
|
||||
110.0
|
||||
};
|
||||
let back = if i == 0 {
|
||||
Image::new(self.imgs.member_bg)
|
||||
.top_left_with_margins_on(ui.window, offset, 20.0)
|
||||
@ -448,6 +589,7 @@ impl<'a> Widget for Group<'a> {
|
||||
.set(state.ids.dead_txt[i], ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.show.group_menu {
|
||||
let selected = state.selected_member;
|
||||
|
@ -147,22 +147,12 @@ image_ids! {
|
||||
|
||||
// Skillbar
|
||||
level_up: "voxygen.element.misc_bg.level_up",
|
||||
level_down: "voxygen.element.misc_bg.level_down",
|
||||
xp_bar_mid: "voxygen.element.skillbar.xp_bar_mid",
|
||||
xp_bar_left: "voxygen.element.skillbar.xp_bar_left",
|
||||
xp_bar_right: "voxygen.element.skillbar.xp_bar_right",
|
||||
healthbar_bg: "voxygen.element.skillbar.healthbar_bg",
|
||||
energybar_bg: "voxygen.element.skillbar.energybar_bg",
|
||||
level_down:"voxygen.element.misc_bg.level_down",
|
||||
bar_content: "voxygen.element.skillbar.bar_content",
|
||||
skillbar_slot_big: "voxygen.element.skillbar.skillbar_slot_big",
|
||||
skillbar_slot_big_bg: "voxygen.element.skillbar.skillbar_slot_big",
|
||||
skillbar_slot_big_act: "voxygen.element.skillbar.skillbar_slot_big",
|
||||
skillbar_slot: "voxygen.element.skillbar.skillbar_slot",
|
||||
skillbar_slot_act: "voxygen.element.skillbar.skillbar_slot_active",
|
||||
skillbar_slot_l: "voxygen.element.skillbar.skillbar_slot_l",
|
||||
skillbar_slot_r: "voxygen.element.skillbar.skillbar_slot_r",
|
||||
skillbar_slot_l_act: "voxygen.element.skillbar.skillbar_slot_l_active",
|
||||
skillbar_slot_r_act: "voxygen.element.skillbar.skillbar_slot_r_active",
|
||||
skillbar_bg: "voxygen.element.skillbar.bg",
|
||||
skillbar_frame: "voxygen.element.skillbar.frame",
|
||||
m1_ico: "voxygen.element.icons.m1",
|
||||
m2_ico: "voxygen.element.icons.m2",
|
||||
|
||||
// Other Icons/Art
|
||||
skull: "voxygen.element.icons.skull",
|
||||
@ -282,6 +272,7 @@ image_ids! {
|
||||
hammerleap: "voxygen.element.icons.skill_hammerleap",
|
||||
skill_axe_leap_slash: "voxygen.element.icons.skill_axe_leap_slash",
|
||||
skill_bow_jump_burst: "voxygen.element.icons.skill_bow_jump_burst",
|
||||
missing_icon: "voxygen.element.icons.missing_icon_grey",
|
||||
|
||||
// Buttons
|
||||
button: "voxygen.element.buttons.button",
|
||||
@ -359,6 +350,24 @@ image_ids! {
|
||||
chat_tell: "voxygen.element.icons.chat.tell",
|
||||
chat_world: "voxygen.element.icons.chat.world",
|
||||
|
||||
// Buffs
|
||||
buff_plus_0: "voxygen.element.icons.de_buffs.buff_plus_0",
|
||||
|
||||
// Debuffs
|
||||
debuff_skull_0: "voxygen.element.icons.de_buffs.debuff_skull_0",
|
||||
debuff_bleed_0: "voxygen.element.icons.de_buffs.debuff_bleed_0",
|
||||
|
||||
// Animation Frames
|
||||
// Buff Frame
|
||||
buff_0: "voxygen.element.animation.buff_frame.1",
|
||||
buff_1: "voxygen.element.animation.buff_frame.2",
|
||||
buff_2: "voxygen.element.animation.buff_frame.3",
|
||||
buff_3: "voxygen.element.animation.buff_frame.4",
|
||||
buff_4: "voxygen.element.animation.buff_frame.5",
|
||||
buff_5: "voxygen.element.animation.buff_frame.6",
|
||||
buff_6: "voxygen.element.animation.buff_frame.7",
|
||||
buff_7: "voxygen.element.animation.buff_frame.8",
|
||||
|
||||
<BlankGraphic>
|
||||
nothing: (),
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
let zoom = state.zoom;
|
||||
const SCALE: f64 = 1.5;
|
||||
const SCALE: f64 = 1.5; // TODO Make this a setting
|
||||
if self.show.mini_map {
|
||||
Image::new(self.imgs.mmap_frame)
|
||||
.w_h(174.0 * SCALE, 190.0 * SCALE)
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod bag;
|
||||
mod buffs;
|
||||
mod buttons;
|
||||
mod chat;
|
||||
mod crafting;
|
||||
@ -24,6 +25,7 @@ pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState};
|
||||
pub use settings_window::ScaleChange;
|
||||
|
||||
use bag::Bag;
|
||||
use buffs::BuffsBar;
|
||||
use buttons::Buttons;
|
||||
use chat::Chat;
|
||||
use chrono::NaiveTime;
|
||||
@ -58,7 +60,10 @@ use client::Client;
|
||||
use common::{
|
||||
assets::Asset,
|
||||
comp,
|
||||
comp::item::{ItemDesc, Quality},
|
||||
comp::{
|
||||
item::{ItemDesc, Quality},
|
||||
BuffKind,
|
||||
},
|
||||
span,
|
||||
sync::Uid,
|
||||
terrain::TerrainChunk,
|
||||
@ -91,10 +96,12 @@ const BLACK: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
|
||||
const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0);
|
||||
const LOW_HP_COLOR: Color = Color::Rgba(0.93, 0.59, 0.03, 1.0);
|
||||
const CRITICAL_HP_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0);
|
||||
const MANA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9);
|
||||
const STAMINA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9);
|
||||
//const TRANSPARENT: Color = Color::Rgba(0.0, 0.0, 0.0, 0.0);
|
||||
//const FOCUS_COLOR: Color = Color::Rgba(1.0, 0.56, 0.04, 1.0);
|
||||
//const RAGE_COLOR: Color = Color::Rgba(0.5, 0.04, 0.13, 1.0);
|
||||
const BUFF_COLOR: Color = Color::Rgba(0.06, 0.69, 0.12, 1.0);
|
||||
const DEBUFF_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0);
|
||||
|
||||
// Item Quality Colors
|
||||
const QUALITY_LOW: Color = Color::Rgba(0.41, 0.41, 0.41, 1.0); // Grey - Trash, can be sold to vendors
|
||||
@ -239,6 +246,7 @@ widget_ids! {
|
||||
spell,
|
||||
skillbar,
|
||||
buttons,
|
||||
buffs,
|
||||
esc_menu,
|
||||
small_window,
|
||||
social_window,
|
||||
@ -264,6 +272,14 @@ widget_ids! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BuffInfo {
|
||||
kind: comp::BuffKind,
|
||||
data: comp::BuffData,
|
||||
is_buff: bool,
|
||||
dur: Option<Duration>,
|
||||
}
|
||||
|
||||
pub struct DebugInfo {
|
||||
pub tps: f64,
|
||||
pub frame_time: Duration,
|
||||
@ -315,6 +331,7 @@ pub enum Event {
|
||||
ChatTransp(f32),
|
||||
ChatCharName(bool),
|
||||
CrosshairType(CrosshairType),
|
||||
BuffPosition(BuffPosition),
|
||||
ToggleXpBar(XpBar),
|
||||
Intro(Intro),
|
||||
ToggleBarNumbers(BarNumbers),
|
||||
@ -348,6 +365,7 @@ pub enum Event {
|
||||
KickMember(common::sync::Uid),
|
||||
LeaveGroup,
|
||||
AssignLeader(common::sync::Uid),
|
||||
RemoveBuff(BuffKind),
|
||||
}
|
||||
|
||||
// TODO: Are these the possible layouts we want?
|
||||
@ -388,6 +406,13 @@ pub enum ShortcutNumbers {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffPosition {
|
||||
Bar,
|
||||
Map,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum PressBehavior {
|
||||
Toggle = 0,
|
||||
@ -722,6 +747,7 @@ impl Hud {
|
||||
let ecs = client.state().ecs();
|
||||
let pos = ecs.read_storage::<comp::Pos>();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let buffs = ecs.read_storage::<comp::Buffs>();
|
||||
let energy = ecs.read_storage::<comp::Energy>();
|
||||
let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>();
|
||||
let uids = ecs.read_storage::<common::sync::Uid>();
|
||||
@ -1120,11 +1146,12 @@ impl Hud {
|
||||
let speech_bubbles = &self.speech_bubbles;
|
||||
|
||||
// Render overhead name tags and health bars
|
||||
for (pos, info, bubble, stats, height_offset, hpfl, in_group) in (
|
||||
for (pos, info, bubble, stats, _, height_offset, hpfl, in_group) in (
|
||||
&entities,
|
||||
&pos,
|
||||
interpolated.maybe(),
|
||||
&stats,
|
||||
&buffs,
|
||||
energy.maybe(),
|
||||
scales.maybe(),
|
||||
&bodies,
|
||||
@ -1138,7 +1165,7 @@ impl Hud {
|
||||
entity != me && !stats.is_dead
|
||||
})
|
||||
.filter_map(
|
||||
|(entity, pos, interpolated, stats, energy, scale, body, hpfl, uid)| {
|
||||
|(entity, pos, interpolated, stats, buffs, energy, scale, body, hpfl, uid)| {
|
||||
// Use interpolated position if available
|
||||
let pos = interpolated.map_or(pos.0, |i| i.pos);
|
||||
let in_group = client.group_members().contains_key(uid);
|
||||
@ -1168,6 +1195,7 @@ impl Hud {
|
||||
let info = display_overhead_info.then(|| overhead::Info {
|
||||
name: &stats.name,
|
||||
stats,
|
||||
buffs,
|
||||
energy,
|
||||
});
|
||||
let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) {
|
||||
@ -1182,6 +1210,7 @@ impl Hud {
|
||||
info,
|
||||
bubble,
|
||||
stats,
|
||||
buffs,
|
||||
body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
|
||||
hpfl,
|
||||
in_group,
|
||||
@ -1730,6 +1759,7 @@ impl Hud {
|
||||
// Bag button and nearby icons
|
||||
let ecs = client.state().ecs();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let buffs = ecs.read_storage::<comp::Buffs>();
|
||||
if let Some(player_stats) = stats.get(client.entity()) {
|
||||
match Buttons::new(
|
||||
client,
|
||||
@ -1754,6 +1784,48 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
|
||||
// Buffs and Debuffs
|
||||
if let Some(player_buffs) = buffs.get(client.entity()) {
|
||||
for event in BuffsBar::new(
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
&self.voxygen_i18n,
|
||||
&player_buffs,
|
||||
self.pulse,
|
||||
&global_state,
|
||||
)
|
||||
.set(self.ids.buffs, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
buffs::Event::RemoveBuff(buff_id) => events.push(Event::RemoveBuff(buff_id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Group Window
|
||||
for event in Group::new(
|
||||
&mut self.show,
|
||||
client,
|
||||
&global_state.settings,
|
||||
&self.imgs,
|
||||
&self.rot_imgs,
|
||||
&self.fonts,
|
||||
&self.voxygen_i18n,
|
||||
self.pulse,
|
||||
&global_state,
|
||||
tooltip_manager,
|
||||
)
|
||||
.set(self.ids.group_window, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
group::Event::Accept => events.push(Event::AcceptInvite),
|
||||
group::Event::Decline => events.push(Event::DeclineInvite),
|
||||
group::Event::Kick(uid) => events.push(Event::KickMember(uid)),
|
||||
group::Event::LeaveGroup => events.push(Event::LeaveGroup),
|
||||
group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)),
|
||||
}
|
||||
}
|
||||
// Popup (waypoint saved and similar notifications)
|
||||
Popup::new(
|
||||
&self.voxygen_i18n,
|
||||
@ -1828,8 +1900,8 @@ impl Hud {
|
||||
Some(stats),
|
||||
Some(loadout),
|
||||
Some(energy),
|
||||
Some(character_state),
|
||||
Some(controller),
|
||||
Some(_character_state),
|
||||
Some(_controller),
|
||||
Some(inventory),
|
||||
) = (
|
||||
stats.get(entity),
|
||||
@ -1848,9 +1920,9 @@ impl Hud {
|
||||
&stats,
|
||||
&loadout,
|
||||
&energy,
|
||||
&character_state,
|
||||
//&character_state,
|
||||
self.pulse,
|
||||
&controller,
|
||||
//&controller,
|
||||
&inventory,
|
||||
&self.hotbar,
|
||||
tooltip_manager,
|
||||
@ -1996,6 +2068,9 @@ impl Hud {
|
||||
settings_window::Event::ToggleZoomInvert(zoom_inverted) => {
|
||||
events.push(Event::ToggleZoomInvert(zoom_inverted));
|
||||
},
|
||||
settings_window::Event::BuffPosition(buff_position) => {
|
||||
events.push(Event::BuffPosition(buff_position));
|
||||
},
|
||||
settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => {
|
||||
events.push(Event::ToggleMouseYInvert(mouse_y_inverted));
|
||||
},
|
||||
@ -2032,9 +2107,6 @@ impl Hud {
|
||||
settings_window::Event::CrosshairType(crosshair_type) => {
|
||||
events.push(Event::CrosshairType(crosshair_type));
|
||||
},
|
||||
settings_window::Event::ToggleXpBar(xp_bar) => {
|
||||
events.push(Event::ToggleXpBar(xp_bar));
|
||||
},
|
||||
settings_window::Event::ToggleBarNumbers(bar_numbers) => {
|
||||
events.push(Event::ToggleBarNumbers(bar_numbers));
|
||||
},
|
||||
@ -2123,27 +2195,6 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Group Window
|
||||
for event in Group::new(
|
||||
&mut self.show,
|
||||
client,
|
||||
&global_state.settings,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&self.voxygen_i18n,
|
||||
self.pulse,
|
||||
&global_state,
|
||||
)
|
||||
.set(self.ids.group_window, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
group::Event::Accept => events.push(Event::AcceptInvite),
|
||||
group::Event::Decline => events.push(Event::DeclineInvite),
|
||||
group::Event::Kick(uid) => events.push(Event::KickMember(uid)),
|
||||
group::Event::LeaveGroup => events.push(Event::LeaveGroup),
|
||||
group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)),
|
||||
}
|
||||
}
|
||||
|
||||
// Spellbook
|
||||
if self.show.spell {
|
||||
@ -2675,3 +2726,12 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color {
|
||||
Quality::Debug => QUALITY_DEBUG,
|
||||
}
|
||||
}
|
||||
// Get info about applied buffs
|
||||
fn get_buff_info(buff: &comp::Buff) -> BuffInfo {
|
||||
BuffInfo {
|
||||
kind: buff.kind,
|
||||
data: buff.data,
|
||||
is_buff: buff.kind.is_buff(),
|
||||
dur: buff.time,
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
use super::{
|
||||
img_ids::Imgs, DEFAULT_NPC, FACTION_COLOR, GROUP_COLOR, GROUP_MEMBER, HP_COLOR, LOW_HP_COLOR,
|
||||
MANA_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR,
|
||||
REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR,
|
||||
};
|
||||
use crate::{
|
||||
hud::get_buff_info,
|
||||
i18n::VoxygenLocalization,
|
||||
settings::GameplaySettings,
|
||||
ui::{fonts::ConrodVoxygenFonts, Ingameable},
|
||||
};
|
||||
use common::comp::{Energy, SpeechBubble, SpeechBubbleType, Stats};
|
||||
use common::comp::{BuffKind, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats};
|
||||
use conrod_core::{
|
||||
color,
|
||||
position::Align,
|
||||
widget::{self, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
@ -44,6 +46,11 @@ widget_ids! {
|
||||
health_txt,
|
||||
mana_bar,
|
||||
health_bar_fg,
|
||||
|
||||
// Buffs
|
||||
buffs_align,
|
||||
buffs[],
|
||||
buff_timers[],
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +58,7 @@ widget_ids! {
|
||||
pub struct Info<'a> {
|
||||
pub name: &'a str,
|
||||
pub stats: &'a Stats,
|
||||
pub buffs: &'a Buffs,
|
||||
pub energy: Option<&'a Energy>,
|
||||
}
|
||||
|
||||
@ -119,13 +127,21 @@ impl<'a> Ingameable for Overhead<'a> {
|
||||
// - 1 for HP text
|
||||
// - If there's mana
|
||||
// - 1 Rect::new for mana
|
||||
//
|
||||
// If there are Buffs
|
||||
// - 1 Alignment Rectangle
|
||||
// - 10 + 10 Buffs and Timer Overlays (only if there is no speech bubble)
|
||||
// If there's a speech bubble
|
||||
// - 2 Text::new for speech bubble
|
||||
// - 1 Image::new for icon
|
||||
// - 10 Image::new for speech bubble (9-slice + tail)
|
||||
self.info.map_or(0, |info| {
|
||||
2 + if show_healthbar(info.stats) {
|
||||
2 + 1
|
||||
+ if self.bubble.is_none() {
|
||||
info.buffs.kinds.len().min(10) * 2
|
||||
} else {
|
||||
0
|
||||
}
|
||||
+ if show_healthbar(info.stats) {
|
||||
5 + if info.energy.is_some() { 1 } else { 0 }
|
||||
} else {
|
||||
0
|
||||
@ -155,6 +171,7 @@ impl<'a> Widget for Overhead<'a> {
|
||||
if let Some(Info {
|
||||
name,
|
||||
stats,
|
||||
buffs,
|
||||
energy,
|
||||
}) = self.info
|
||||
{
|
||||
@ -185,6 +202,84 @@ impl<'a> Widget for Overhead<'a> {
|
||||
1000..=999999 => format!("{:.0}K", (health_max / 1000.0).max(1.0)),
|
||||
_ => format!("{:.0}M", (health_max as f64 / 1.0e6).max(1.0)),
|
||||
};
|
||||
// Buffs
|
||||
// Alignment
|
||||
let buff_count = buffs.kinds.len().min(11);
|
||||
Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
|
||||
.x_y(-1.0, name_y + 60.0)
|
||||
.parent(id)
|
||||
.set(state.ids.buffs_align, ui);
|
||||
|
||||
let gen = &mut ui.widget_id_generator();
|
||||
if state.ids.buffs.len() < buff_count {
|
||||
state.update(|state| state.ids.buffs.resize(buff_count, gen));
|
||||
};
|
||||
if state.ids.buff_timers.len() < buff_count {
|
||||
state.update(|state| state.ids.buff_timers.resize(buff_count, gen));
|
||||
};
|
||||
|
||||
let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; //Animation timer
|
||||
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
|
||||
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||
// Create Buff Widgets
|
||||
if self.bubble.is_none() {
|
||||
state
|
||||
.ids
|
||||
.buffs
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(buffs.iter_active().map(get_buff_info))
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), buff))| {
|
||||
// Limit displayed buffs
|
||||
let max_duration = buff.data.duration;
|
||||
let current_duration = buff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration.map_or(1000.0, |max| {
|
||||
cur.as_secs_f32() / max.as_secs_f32() * 1000.0
|
||||
})
|
||||
}) as u32; // Percentage to determine which frame of the timer overlay is displayed
|
||||
let buff_img = match buff.kind {
|
||||
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
|
||||
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
|
||||
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
|
||||
};
|
||||
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
|
||||
// Sort buffs into rows of 5 slots
|
||||
let x = i % 5;
|
||||
let y = i / 5;
|
||||
let buff_widget = buff_widget.bottom_left_with_margins_on(
|
||||
state.ids.buffs_align,
|
||||
0.0 + y as f64 * (21.0),
|
||||
0.0 + x as f64 * (21.0),
|
||||
);
|
||||
buff_widget
|
||||
.color(
|
||||
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
|
||||
Some(pulsating_col)
|
||||
} else {
|
||||
Some(norm_col)
|
||||
},
|
||||
)
|
||||
.set(id, ui);
|
||||
|
||||
Image::new(match duration_percentage as u64 {
|
||||
875..=1000 => self.imgs.nothing, // 8/8
|
||||
750..=874 => self.imgs.buff_0, // 7/8
|
||||
625..=749 => self.imgs.buff_1, // 6/8
|
||||
500..=624 => self.imgs.buff_2, // 5/8
|
||||
375..=499 => self.imgs.buff_3, // 4/8
|
||||
250..=374 => self.imgs.buff_4, // 3/8
|
||||
125..=249 => self.imgs.buff_5, // 2/8
|
||||
0..=124 => self.imgs.buff_6, // 1/8
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
.w_h(20.0, 20.0)
|
||||
.middle_of(id)
|
||||
.set(timer_id, ui);
|
||||
});
|
||||
}
|
||||
// Name
|
||||
Text::new(name)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
@ -254,7 +349,7 @@ impl<'a> Widget for Overhead<'a> {
|
||||
|
||||
Rectangle::fill_with(
|
||||
[72.0 * energy_factor * BARSIZE, MANA_BAR_HEIGHT],
|
||||
MANA_COLOR,
|
||||
STAMINA_COLOR,
|
||||
)
|
||||
.x_y(
|
||||
((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE,
|
||||
|
@ -1,9 +1,10 @@
|
||||
use super::{
|
||||
img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, XpBar,
|
||||
CRITICAL_HP_COLOR, ERROR_COLOR, HP_COLOR, LOW_HP_COLOR, MANA_COLOR, MENU_BG,
|
||||
img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show,
|
||||
CRITICAL_HP_COLOR, ERROR_COLOR, HP_COLOR, LOW_HP_COLOR, MENU_BG, STAMINA_COLOR,
|
||||
TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
use crate::{
|
||||
hud::BuffPosition,
|
||||
i18n::{list_localizations, LanguageMetadata, VoxygenLocalization},
|
||||
render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode},
|
||||
ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton},
|
||||
@ -19,7 +20,6 @@ use conrod_core::{
|
||||
};
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use inline_tweak::*;
|
||||
use itertools::Itertools;
|
||||
use std::iter::once;
|
||||
use winit::monitor::VideoMode;
|
||||
@ -159,6 +159,7 @@ widget_ids! {
|
||||
sfx_volume_text,
|
||||
audio_device_list,
|
||||
audio_device_text,
|
||||
//
|
||||
hotbar_title,
|
||||
bar_numbers_title,
|
||||
show_bar_numbers_none_button,
|
||||
@ -167,18 +168,20 @@ widget_ids! {
|
||||
show_bar_numbers_values_text,
|
||||
show_bar_numbers_percentage_button,
|
||||
show_bar_numbers_percentage_text,
|
||||
//
|
||||
show_shortcuts_button,
|
||||
show_shortcuts_text,
|
||||
show_xpbar_button,
|
||||
show_xpbar_text,
|
||||
show_bars_button,
|
||||
show_bars_text,
|
||||
placeholder,
|
||||
buff_pos_bar_button,
|
||||
buff_pos_bar_text,
|
||||
buff_pos_map_button,
|
||||
buff_pos_map_text,
|
||||
//
|
||||
chat_transp_title,
|
||||
chat_transp_text,
|
||||
chat_transp_slider,
|
||||
chat_char_name_text,
|
||||
chat_char_name_button,
|
||||
//
|
||||
sct_title,
|
||||
sct_show_text,
|
||||
sct_show_radio,
|
||||
@ -195,6 +198,7 @@ widget_ids! {
|
||||
sct_num_dur_text,
|
||||
sct_num_dur_slider,
|
||||
sct_num_dur_value,
|
||||
//
|
||||
speech_bubble_text,
|
||||
speech_bubble_dark_mode_text,
|
||||
speech_bubble_dark_mode_button,
|
||||
@ -259,9 +263,9 @@ pub enum Event {
|
||||
ToggleHelp,
|
||||
ToggleDebug,
|
||||
ToggleTips(bool),
|
||||
ToggleXpBar(XpBar),
|
||||
ToggleBarNumbers(BarNumbers),
|
||||
ToggleShortcutNumbers(ShortcutNumbers),
|
||||
BuffPosition(BuffPosition),
|
||||
ChangeTab(SettingsTab),
|
||||
Close,
|
||||
AdjustMousePan(u32),
|
||||
@ -796,40 +800,6 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.hotbar_title, ui);
|
||||
// Show xp bar
|
||||
if Button::image(match self.global_state.settings.gameplay.xp_bar {
|
||||
XpBar::Always => self.imgs.checkbox_checked,
|
||||
XpBar::OnGain => self.imgs.checkbox,
|
||||
})
|
||||
.w_h(18.0, 18.0)
|
||||
.hover_image(match self.global_state.settings.gameplay.xp_bar {
|
||||
XpBar::Always => self.imgs.checkbox_checked_mo,
|
||||
XpBar::OnGain => self.imgs.checkbox_mo,
|
||||
})
|
||||
.press_image(match self.global_state.settings.gameplay.xp_bar {
|
||||
XpBar::Always => self.imgs.checkbox_checked,
|
||||
XpBar::OnGain => self.imgs.checkbox_press,
|
||||
})
|
||||
.down_from(state.ids.hotbar_title, 8.0)
|
||||
.set(state.ids.show_xpbar_button, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
match self.global_state.settings.gameplay.xp_bar {
|
||||
XpBar::Always => events.push(Event::ToggleXpBar(XpBar::OnGain)),
|
||||
XpBar::OnGain => events.push(Event::ToggleXpBar(XpBar::Always)),
|
||||
}
|
||||
}
|
||||
Text::new(
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.settings.toggle_bar_experience"),
|
||||
)
|
||||
.right_from(state.ids.show_xpbar_button, 10.0)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.graphics_for(state.ids.show_xpbar_button)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_xpbar_text, ui);
|
||||
// Show Shortcut Numbers
|
||||
if Button::image(match self.global_state.settings.gameplay.shortcut_numbers {
|
||||
ShortcutNumbers::On => self.imgs.checkbox_checked,
|
||||
@ -844,7 +814,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
ShortcutNumbers::On => self.imgs.checkbox_checked,
|
||||
ShortcutNumbers::Off => self.imgs.checkbox_press,
|
||||
})
|
||||
.down_from(state.ids.show_xpbar_button, 8.0)
|
||||
.down_from(state.ids.hotbar_title, 8.0)
|
||||
.set(state.ids.show_shortcuts_button, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
@ -864,11 +834,61 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
.graphics_for(state.ids.show_shortcuts_button)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_shortcuts_text, ui);
|
||||
|
||||
Rectangle::fill_with([60.0 * 4.0, 1.0 * 4.0], color::TRANSPARENT)
|
||||
.down_from(state.ids.show_shortcuts_text, 30.0)
|
||||
.set(state.ids.placeholder, ui);
|
||||
|
||||
// Buff Position
|
||||
// Buffs above skills
|
||||
if Button::image(match self.global_state.settings.gameplay.buff_position {
|
||||
BuffPosition::Bar => self.imgs.checkbox_checked,
|
||||
BuffPosition::Map => self.imgs.checkbox,
|
||||
})
|
||||
.w_h(18.0, 18.0)
|
||||
.hover_image(match self.global_state.settings.gameplay.buff_position {
|
||||
BuffPosition::Bar => self.imgs.checkbox_checked_mo,
|
||||
BuffPosition::Map => self.imgs.checkbox_mo,
|
||||
})
|
||||
.press_image(match self.global_state.settings.gameplay.buff_position {
|
||||
BuffPosition::Bar => self.imgs.checkbox_checked,
|
||||
BuffPosition::Map => self.imgs.checkbox_press,
|
||||
})
|
||||
.down_from(state.ids.show_shortcuts_button, 8.0)
|
||||
.set(state.ids.buff_pos_bar_button, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::BuffPosition(BuffPosition::Bar))
|
||||
}
|
||||
Text::new(&self.localized_strings.get("hud.settings.buffs_skillbar"))
|
||||
.right_from(state.ids.buff_pos_bar_button, 10.0)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.graphics_for(state.ids.show_shortcuts_button)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.buff_pos_bar_text, ui);
|
||||
// Buffs left from minimap
|
||||
if Button::image(match self.global_state.settings.gameplay.buff_position {
|
||||
BuffPosition::Map => self.imgs.checkbox_checked,
|
||||
BuffPosition::Bar => self.imgs.checkbox,
|
||||
})
|
||||
.w_h(18.0, 18.0)
|
||||
.hover_image(match self.global_state.settings.gameplay.buff_position {
|
||||
BuffPosition::Map => self.imgs.checkbox_checked_mo,
|
||||
BuffPosition::Bar => self.imgs.checkbox_mo,
|
||||
})
|
||||
.press_image(match self.global_state.settings.gameplay.buff_position {
|
||||
BuffPosition::Map => self.imgs.checkbox_checked,
|
||||
BuffPosition::Bar => self.imgs.checkbox_press,
|
||||
})
|
||||
.down_from(state.ids.buff_pos_bar_button, 8.0)
|
||||
.set(state.ids.buff_pos_map_button, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::BuffPosition(BuffPosition::Map))
|
||||
}
|
||||
Text::new(&self.localized_strings.get("hud.settings.buffs_mmap"))
|
||||
.right_from(state.ids.buff_pos_map_button, 10.0)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.graphics_for(state.ids.show_shortcuts_button)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.buff_pos_map_text, ui);
|
||||
// Content Right Side
|
||||
|
||||
/*Scrolling Combat text
|
||||
@ -1692,7 +1712,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
0..=14 => CRITICAL_HP_COLOR,
|
||||
15..=29 => LOW_HP_COLOR,
|
||||
30..=50 => HP_COLOR,
|
||||
_ => MANA_COLOR,
|
||||
_ => STAMINA_COLOR,
|
||||
};
|
||||
Text::new(&format!("FPS: {:.0}", self.fps))
|
||||
.color(fps_col)
|
||||
@ -2688,8 +2708,8 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
});
|
||||
};
|
||||
for (i, language) in language_list.iter().enumerate() {
|
||||
let button_w = tweak!(400.0);
|
||||
let button_h = tweak!(50.0);
|
||||
let button_w = 400.0;
|
||||
let button_h = 50.0;
|
||||
let button = Button::image(if selected_language == &language.language_identifier {
|
||||
self.imgs.selection
|
||||
} else {
|
||||
@ -2706,7 +2726,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
.hover_image(self.imgs.selection_hover)
|
||||
.press_image(self.imgs.selection_press)
|
||||
.label_color(TEXT_COLOR)
|
||||
.label_font_size(self.fonts.cyri.scale(tweak!(22)))
|
||||
.label_font_size(self.fonts.cyri.scale(22))
|
||||
.label_font_id(self.fonts.cyri.conrod_id)
|
||||
.label_y(conrod_core::position::Relative::Scalar(2.0))
|
||||
.set(state.ids.language_list[i], ui)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,6 @@ use conrod_core::{
|
||||
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
|
||||
};
|
||||
use image::DynamicImage;
|
||||
//use inline_tweak::*;
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -894,6 +894,10 @@ impl PlayState for SessionState {
|
||||
global_state.settings.gameplay.shortcut_numbers = shortcut_numbers;
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
HudEvent::BuffPosition(buff_position) => {
|
||||
global_state.settings.gameplay.buff_position = buff_position;
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
HudEvent::UiScale(scale_change) => {
|
||||
global_state.settings.gameplay.ui_scale =
|
||||
self.hud.scale_change(scale_change);
|
||||
@ -921,6 +925,10 @@ impl PlayState for SessionState {
|
||||
global_state.settings.graphics.max_fps = fps;
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
HudEvent::RemoveBuff(buff_id) => {
|
||||
let mut client = self.client.borrow_mut();
|
||||
client.remove_buff(buff_id);
|
||||
},
|
||||
HudEvent::UseSlot(x) => self.client.borrow_mut().use_slot(x),
|
||||
HudEvent::SwapSlots(a, b) => self.client.borrow_mut().swap_slots(a, b),
|
||||
HudEvent::DropSlot(x) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
hud::{BarNumbers, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar},
|
||||
hud::{BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar},
|
||||
i18n,
|
||||
render::RenderMode,
|
||||
ui::ScaleMode,
|
||||
@ -507,6 +507,7 @@ pub struct GameplaySettings {
|
||||
pub intro_show: Intro,
|
||||
pub xp_bar: XpBar,
|
||||
pub shortcut_numbers: ShortcutNumbers,
|
||||
pub buff_position: BuffPosition,
|
||||
pub bar_numbers: BarNumbers,
|
||||
pub ui_scale: ScaleMode,
|
||||
pub free_look_behavior: PressBehavior,
|
||||
@ -537,6 +538,7 @@ impl Default for GameplaySettings {
|
||||
intro_show: Intro::Show,
|
||||
xp_bar: XpBar::Always,
|
||||
shortcut_numbers: ShortcutNumbers::On,
|
||||
buff_position: BuffPosition::Map,
|
||||
bar_numbers: BarNumbers::Values,
|
||||
ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()),
|
||||
free_look_behavior: PressBehavior::Toggle,
|
||||
|
Loading…
Reference in New Issue
Block a user