@@ -65,7 +65,8 @@ Le JAR produit se trouve dans `build/libs/hytale-mmorpg-0.1.0.jar` (nom selon ve
|
|||||||
| `/mmorpg help` | Affiche l'aide |
|
| `/mmorpg help` | Affiche l'aide |
|
||||||
| `/mmorpg profile` | Résumé court (niveau, XP) |
|
| `/mmorpg profile` | Résumé court (niveau, XP) |
|
||||||
| `/mmorpg info` | Toutes vos informations (chat) |
|
| `/mmorpg info` | Toutes vos informations (chat) |
|
||||||
| `/mmorpg menu` | Ouvre l'interface d'informations du joueur (page UI custom) |
|
| `/mmorpg menu` | Menu joueur MMORPG — onglets Personnage, Inventaire, Compétences |
|
||||||
|
| `/mmorpg inventory` | Même menu, ouvert directement sur l'onglet Inventaire |
|
||||||
| `/mmorpg class <choisir\|quitter\|info> [classe]` | Gérer votre classe |
|
| `/mmorpg class <choisir\|quitter\|info> [classe]` | Gérer votre classe |
|
||||||
| `/mmorpg power <recevoir\|retirer\|info> [pouvoir]` | Gérer vos pouvoirs |
|
| `/mmorpg power <recevoir\|retirer\|info> [pouvoir]` | Gérer vos pouvoirs |
|
||||||
| `/mmorpg job <rejoindre\|quitter\|info> [metier]` | Gérer vos métiers |
|
| `/mmorpg job <rejoindre\|quitter\|info> [metier]` | Gérer vos métiers |
|
||||||
@@ -135,7 +136,7 @@ src/main/java/com/disklexar/mmorpg/
|
|||||||
├── rpg/ # clazz (classes) · power · job · race · group
|
├── rpg/ # clazz (classes) · power · job · race · group
|
||||||
├── combat/ # Capacités, état de combat, effets passifs, planificateur
|
├── combat/ # Capacités, état de combat, effets passifs, planificateur
|
||||||
│ └── system/ # Systèmes ECS : dégâts, morts/kills, casse de bloc
|
│ └── system/ # Systèmes ECS : dégâts, morts/kills, casse de bloc
|
||||||
├── ui/ # AbilityBarHud, AbilityBarService, PlayerInfoPage
|
├── ui/ # AbilityBarHud, PlayerMenuPage, PlayerInfoPage
|
||||||
├── quest/ # Stub (M3)
|
├── quest/ # Stub (M3)
|
||||||
└── social/ # Stub (M4)
|
└── social/ # Stub (M4)
|
||||||
```
|
```
|
||||||
@@ -148,6 +149,15 @@ src/main/java/com/disklexar/mmorpg/
|
|||||||
- [Guide de développement](docs/DEVELOPMENT.md)
|
- [Guide de développement](docs/DEVELOPMENT.md)
|
||||||
- [Contribution](CONTRIBUTING.md)
|
- [Contribution](CONTRIBUTING.md)
|
||||||
|
|
||||||
|
### Custom UI (Hytale)
|
||||||
|
|
||||||
|
Documentation officielle et guides communautaires utiles pour les assets `.ui` et les pages serveur :
|
||||||
|
|
||||||
|
- [Custom UI — vue d'ensemble](https://hytalemodding.dev/en/docs/official-documentation/custom-ui) (architecture `UICommandBuilder`, HUD vs pages, sélecteurs `#Id.Propriété`)
|
||||||
|
- [Layout](https://hytalemodding.dev/en/docs/official-documentation/custom-ui/layout) (`Anchor`, `Padding`, `LayoutMode`, `FlexWeight`)
|
||||||
|
- [Common Styling](https://hytalemodding.dev/en/docs/official-documentation/custom-ui/common-styling) (`Common.ui`, styles partagés)
|
||||||
|
- [Guide plugin UI](https://hytalemodding.dev/en/docs/guides/plugin/ui) (emplacement des fichiers, `IncludesAssetPack`, diagnostic client)
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
MIT — voir [LICENSE](LICENSE).
|
MIT — voir [LICENSE](LICENSE).
|
||||||
|
|||||||
+52
-23
@@ -78,7 +78,8 @@ src/main/resources/Common/UI/Custom/
|
|||||||
├── Pages/
|
├── Pages/
|
||||||
│ ├── MmorpgPlayerShell.ui # Fenêtre à onglets (coquille)
|
│ ├── MmorpgPlayerShell.ui # Fenêtre à onglets (coquille)
|
||||||
│ ├── MmorpgCharacterTab.ui # Onglet Personnage
|
│ ├── MmorpgCharacterTab.ui # Onglet Personnage
|
||||||
│ ├── MmorpgInventoryTab.ui # Onglet Inventaire
|
│ ├── MmorpgInventoryPage.ui # 🟡 Début — inventaire MMORPG (coquille + grille)
|
||||||
|
│ ├── MmorpgInventoryTab.ui # Onglet Inventaire (futur, dans coquille à onglets)
|
||||||
│ └── MmorpgSkillTreeTab.ui # Onglet Arbre de compétences
|
│ └── MmorpgSkillTreeTab.ui # Onglet Arbre de compétences
|
||||||
└── Widgets/
|
└── Widgets/
|
||||||
├── MmorpgProgressBar.ui # Barre réutilisable (vie, mana, XP)
|
├── MmorpgProgressBar.ui # Barre réutilisable (vie, mana, XP)
|
||||||
@@ -91,8 +92,11 @@ src/main/resources/Common/UI/Custom/
|
|||||||
| Composant UI | Classe Java | Statut |
|
| Composant UI | Classe Java | Statut |
|
||||||
|--------------|-------------|--------|
|
|--------------|-------------|--------|
|
||||||
| Barre capacités | `AbilityBarHud` + `AbilityBarService` | ✅ Actif |
|
| Barre capacités | `AbilityBarHud` + `AbilityBarService` | ✅ Actif |
|
||||||
| Profil joueur (liste) | `PlayerInfoPage` | ✅ Actif (layout base-game) |
|
| Profil joueur (fallback) | `PlayerInfoPage` | ✅ Actif (`/mmorpg menucustom`) |
|
||||||
| Coquille à onglets | `PlayerMenuPage` | 🔲 M1 |
|
| Menu à onglets | `PlayerMenuPage` | ✅ Actif (`/mmorpg menu`) |
|
||||||
|
| Onglet Personnage | `MmorpgCharacterTab.ui` | ✅ Actif |
|
||||||
|
| Onglet Inventaire | `MmorpgInventoryTab.ui` + `PlayerInventoryReader` | ✅ Actif |
|
||||||
|
| Onglet Compétences | `MmorpgSkillsTab.ui` | ✅ Actif (capacités classe + branches M1) |
|
||||||
| Barres vitales HUD | `VitalsHud` + `VitalsHudService` | 🔲 M1 |
|
| Barres vitales HUD | `VitalsHud` + `VitalsHudService` | 🔲 M1 |
|
||||||
| Buff tray | `BuffTrayHud` | 🔲 M1 |
|
| Buff tray | `BuffTrayHud` | 🔲 M1 |
|
||||||
| Hotbar étendue | `HotbarHud` | 🔲 M2 |
|
| Hotbar étendue | `HotbarHud` | 🔲 M2 |
|
||||||
@@ -193,9 +197,12 @@ l'inventaire natif ou des interactions custom futures.
|
|||||||
|
|
||||||
| Slot | ID | Touche | Mise à jour |
|
| Slot | ID | Touche | Mise à jour |
|
||||||
|------|-----|--------|-------------|
|
|------|-----|--------|-------------|
|
||||||
| 1 | `#Slot1Name`, `#Slot1Key` | Ability 1 | Nom + cooldown (`Charge (3s)`) |
|
| 1 | `#Slot1Name`, `#Slot1Key` | Q (Use Ability 1) | Nom + cooldown (`Charge (3s)`) |
|
||||||
| 2 | `#Slot2Name`, `#Slot2Key` | Ability 2 | idem |
|
| 2 | `#Slot2Name`, `#Slot2Key` | E (Use Ability 2) | idem |
|
||||||
| 3 | `#Slot3Name`, `#Slot3Key` | Ability 3 | idem |
|
| 3 | `#Slot3Name`, `#Slot3Key` | R (Use Ability 3) | idem |
|
||||||
|
|
||||||
|
Les labels `#Slot*Key` / `#Ability*Key` sont remplis par `AbilitySlotKeys` avec les raccourcis
|
||||||
|
Hytale par défaut (`[Q]`, `[E]`, `[R]`). Le serveur ne connaît pas les rebinding client.
|
||||||
|
|
||||||
**Commande debug :** `/mmorpg hud` — force la synchronisation.
|
**Commande debug :** `/mmorpg hud` — force la synchronisation.
|
||||||
|
|
||||||
@@ -203,8 +210,8 @@ l'inventaire natif ou des interactions custom futures.
|
|||||||
|
|
||||||
## 2. Menus joueur
|
## 2. Menus joueur
|
||||||
|
|
||||||
Ouverture cible : **`C`** ou `/mmorpg menu` → coquille à onglets.
|
Ouverture : **`/mmorpg menu`** → coquille à onglets (`PlayerMenuPage`).
|
||||||
État actuel : `/mmorpg menu` ouvre `PlayerInfoPage` (liste verticale, layout `WarpListPage.ui`).
|
Fallback chat : `/mmorpg info` si la page UI échoue.
|
||||||
|
|
||||||
### 2.1 Coquille — `MmorpgPlayerShell.ui`
|
### 2.1 Coquille — `MmorpgPlayerShell.ui`
|
||||||
|
|
||||||
@@ -247,18 +254,24 @@ pouvoirs / métiers / social.
|
|||||||
|
|
||||||
### 2.3 Onglet Inventaire
|
### 2.3 Onglet Inventaire
|
||||||
|
|
||||||
**Fichier cible :** `Pages/MmorpgInventoryTab.ui`
|
**Fichier inventaire :** `Pages/MmorpgInventoryPage.ui` — document **racine** pour l'onglet Inventaire (`/mmorpg inventory`)
|
||||||
|
**Fichier coquille :** `Pages/MmorpgPlayerShell.ui` — Personnage / Compétences uniquement
|
||||||
|
|
||||||
> Un plugin serveur **ne peut pas modifier l'inventaire natif**. Cet onglet est une **vue MMORPG**
|
> Relié à l'inventaire **vanilla** via `ItemGrid.InventorySectionId` (sac, barre rapide, armure, utilitaire).
|
||||||
> complémentaire (items tagués, équipement RPG, filtres).
|
> **`InventorySectionId` ne fonctionne pas** dans une coquille imbriquée : l'inventaire doit être le document
|
||||||
|
> racine (`MmorpgInventoryPage.ui`). Changement d'onglet vers/depuis Inventaire → `rebuild()` complet.
|
||||||
|
> Ne pas appeler `#Grid.Slots` depuis le serveur — le client rejette cette commande et déconnecte.
|
||||||
|
> Glisser-déposer synchronisé ; panneau détails au clic.
|
||||||
|
|
||||||
| Zone | Description |
|
| Zone | ID | Section vanilla |
|
||||||
|------|-------------|
|
|------|-----|-----------------|
|
||||||
| `#EquipmentPaperDoll` | Silhouette : tête, torse, mains, jambes, arme principale / secondaire |
|
| Armure | `#ArmorGrid` | `ARMOR_SECTION_ID` (-3) |
|
||||||
| `#InventoryGrid` | Grille 8×5 (40 slots) — items avec rareté (bordure couleur) |
|
| Utilitaire | `#UtilityGrid` | `UTILITY_SECTION_ID` (-5) |
|
||||||
| `#ItemDetails` | Panneau droit : nom, stats, description, boutons Utiliser / Jeter |
|
| Sac | `#StorageGrid` | `STORAGE_SECTION_ID` (-2), 9×4 |
|
||||||
| `#CategoryTabs` | Filtres : Tout, Équipement, Consommables, Matériaux, Quête |
|
| Barre rapide | `#HotbarGrid` | `HOTBAR_SECTION_ID` (-1) |
|
||||||
| `#WeightBar` | Encumbrance (futur) |
|
| Détails | `#ItemDetails` | Clic / survol slot → `PlayerInventoryReader` |
|
||||||
|
|
||||||
|
**À venir :** filtres par catégorie, stats MMORPG sur items tagués, boutons Utiliser / Jeter.
|
||||||
|
|
||||||
**Ergonomie :**
|
**Ergonomie :**
|
||||||
|
|
||||||
@@ -378,7 +391,7 @@ sequenceDiagram
|
|||||||
|-------|--------------|----------------------|
|
|-------|--------------|----------------------|
|
||||||
| **M0** ✅ | `MmorpgAbilityBar.ui`, `PlayerInfoPage`, `/mmorpg hud` | Classes, capacités |
|
| **M0** ✅ | `MmorpgAbilityBar.ui`, `PlayerInfoPage`, `/mmorpg hud` | Classes, capacités |
|
||||||
| **M1** | `MmorpgVitalsHud`, `MmorpgBuffTray`, `MmorpgPlayerShell` + onglet Personnage | Stats vie/mana, buffs |
|
| **M1** | `MmorpgVitalsHud`, `MmorpgBuffTray`, `MmorpgPlayerShell` + onglet Personnage | Stats vie/mana, buffs |
|
||||||
| **M2** | Onglet Inventaire MMORPG, `MmorpgHotbarHud` | Items tagués, équipement |
|
| **M2** | Onglet Inventaire MMORPG, `MmorpgHotbarHud` | Items tagués, équipement — **début** : `MmorpgInventoryPage.ui` |
|
||||||
| **M3** | Onglet Arbre de compétences | Points de compétence, déblocages |
|
| **M3** | Onglet Arbre de compétences | Points de compétence, déblocages |
|
||||||
| **M4** | Quêtes dans HUD, tooltips riches | Module quêtes |
|
| **M4** | Quêtes dans HUD, tooltips riches | Module quêtes |
|
||||||
|
|
||||||
@@ -399,10 +412,26 @@ sequenceDiagram
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Références projet
|
## 8. Références
|
||||||
|
|
||||||
- Code HUD actif : `src/main/java/com/disklexar/mmorpg/ui/`
|
### Documentation Custom UI (officielle / guides)
|
||||||
- Asset barre capacités : `src/main/resources/Common/UI/Custom/MmorpgAbilityBar.ui`
|
|
||||||
- Scaffold pages : `assetpack/ui/Pages/MmorpgPlayerInfo.ui`
|
- [Custom UI — vue d'ensemble](https://hytalemodding.dev/en/docs/official-documentation/custom-ui) — HUD vs pages, `append` / `set`, sélecteurs
|
||||||
|
- [Layout](https://hytalemodding.dev/en/docs/official-documentation/custom-ui/layout) — `Anchor`, `LayoutMode`, `FlexWeight` (utilisé pour le panneau inventaire 3 colonnes)
|
||||||
|
- [Common Styling](https://hytalemodding.dev/en/docs/official-documentation/custom-ui/common-styling) — `Common.ui`, styles vanilla réutilisables
|
||||||
|
- [Guide plugin UI](https://hytalemodding.dev/en/docs/guides/plugin/ui) — chemin `Common/UI/Custom`, `IncludesAssetPack`, mode diagnostic client
|
||||||
|
|
||||||
|
**Rappels appris en production :**
|
||||||
|
|
||||||
|
- `CustomUIHud` : uniquement `append(document)` + `set()` — pas de `appendInline` / `clear` (crash client).
|
||||||
|
- Propriétés dynamiques : préférer `ProgressBar.Value` plutôt que `Group.Width` (rejeté par le client).
|
||||||
|
- Ancrage HUD : le groupe racine doit avoir `Anchor: (Left: …, Width: …)` sinon centrage écran.
|
||||||
|
|
||||||
|
### Fichiers projet
|
||||||
|
|
||||||
|
- Code UI : `src/main/java/com/disklexar/mmorpg/ui/`
|
||||||
|
- HUD vitals + capacités : `src/main/resources/Common/UI/Custom/MmorpgAbilityBar.ui`
|
||||||
|
- Inventaire (début) : `src/main/resources/Common/UI/Custom/Pages/MmorpgInventoryPage.ui`
|
||||||
|
- Profil (scaffold) : `src/main/resources/Common/UI/Custom/Pages/MmorpgPlayerInfo.ui`
|
||||||
- Guide asset pack : [assetpack/README.md](../assetpack/README.md)
|
- Guide asset pack : [assetpack/README.md](../assetpack/README.md)
|
||||||
- Architecture : [ARCHITECTURE.md](ARCHITECTURE.md)
|
- Architecture : [ARCHITECTURE.md](ARCHITECTURE.md)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.disklexar.mmorpg.rpg.clazz.AbilityDefinition;
|
|||||||
import com.disklexar.mmorpg.rpg.clazz.ClassService;
|
import com.disklexar.mmorpg.rpg.clazz.ClassService;
|
||||||
import com.disklexar.mmorpg.rpg.clazz.PlayerClass;
|
import com.disklexar.mmorpg.rpg.clazz.PlayerClass;
|
||||||
import com.hypixel.hytale.component.Ref;
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
import com.hypixel.hytale.protocol.InteractionType;
|
import com.hypixel.hytale.protocol.InteractionType;
|
||||||
import com.hypixel.hytale.server.core.Message;
|
import com.hypixel.hytale.server.core.Message;
|
||||||
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
@@ -16,6 +17,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central entry point for routing Ability 1/2/3 inputs to MMORPG class abilities.
|
* Central entry point for routing Ability 1/2/3 inputs to MMORPG class abilities.
|
||||||
@@ -24,9 +26,11 @@ import javax.annotation.Nullable;
|
|||||||
public final class ClassAbilityController {
|
public final class ClassAbilityController {
|
||||||
|
|
||||||
private final MmorpgPlugin plugin;
|
private final MmorpgPlugin plugin;
|
||||||
|
private final HytaleLogger logger;
|
||||||
|
|
||||||
public ClassAbilityController(@Nonnull MmorpgPlugin plugin) {
|
public ClassAbilityController(@Nonnull MmorpgPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.logger = plugin.getLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,11 +38,12 @@ public final class ClassAbilityController {
|
|||||||
* should be suppressed for this slot (even if the cast fails, e.g. wrong weapon).
|
* should be suppressed for this slot (even if the cast fails, e.g. wrong weapon).
|
||||||
*/
|
*/
|
||||||
public boolean shouldSuppressVanilla(@Nonnull PlayerRef playerRef, int slot) {
|
public boolean shouldSuppressVanilla(@Nonnull PlayerRef playerRef, int slot) {
|
||||||
return resolveAbility(playerRef, slot) != null;
|
return resolve(playerRef, slot).resolved();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to cast the class ability bound to {@code slot}.
|
* Attempts to cast the class ability bound to {@code slot} (triggered by in-game input:
|
||||||
|
* Ability 1/2/3). Sends chat feedback only for failures — cooldown is shown on the HUD bar.
|
||||||
*
|
*
|
||||||
* @return {@code true} if vanilla should be blocked (player has a class with this slot).
|
* @return {@code true} if vanilla should be blocked (player has a class with this slot).
|
||||||
*/
|
*/
|
||||||
@@ -47,25 +52,31 @@ public final class ClassAbilityController {
|
|||||||
@Nonnull World world,
|
@Nonnull World world,
|
||||||
int slot,
|
int slot,
|
||||||
@Nullable Ref<EntityStore> aimedTarget) {
|
@Nullable Ref<EntityStore> aimedTarget) {
|
||||||
ResolvedAbility resolved = resolveAbility(playerRef, slot);
|
return tryCast(playerRef, world, slot, aimedTarget, true);
|
||||||
if (resolved == null) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fromInput {@code true} when triggered by Ability 1/2/3 keys (errors only in chat);
|
||||||
|
* {@code false} for {@code /mmorpg ability} (also confirms a successful cast).
|
||||||
|
*/
|
||||||
|
public boolean tryCast(
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
@Nonnull World world,
|
||||||
|
int slot,
|
||||||
|
@Nullable Ref<EntityStore> aimedTarget,
|
||||||
|
boolean fromInput) {
|
||||||
|
ResolveOutcome outcome = resolve(playerRef, slot);
|
||||||
|
if (!outcome.resolved()) {
|
||||||
|
if (outcome.failure() != ResolveFailure.SERVER_NOT_READY) {
|
||||||
|
notifyResolveFailure(playerRef, slot, outcome.failure());
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResolvedAbility resolved = outcome.ability();
|
||||||
AbilityService.CastResult result = resolved.abilities().cast(
|
AbilityService.CastResult result = resolved.abilities().cast(
|
||||||
playerRef, world, resolved.playerClass(), resolved.ability(), aimedTarget);
|
playerRef, world, resolved.playerClass(), resolved.ability(), aimedTarget);
|
||||||
|
notifyCastResult(playerRef, resolved, result, fromInput);
|
||||||
switch (result) {
|
|
||||||
case OK -> playerRef.sendMessage(Message.raw("Capacité : " + resolved.ability().displayName()));
|
|
||||||
case ON_COOLDOWN -> {
|
|
||||||
long remaining = resolved.abilities().cooldownRemaining(
|
|
||||||
playerRef.getUuid(), resolved.ability().id());
|
|
||||||
playerRef.sendMessage(Message.raw("En recharge (" + (remaining / 1000 + 1) + "s)."));
|
|
||||||
}
|
|
||||||
case WRONG_WEAPON -> playerRef.sendMessage(Message.raw(
|
|
||||||
"Mauvaise arme. Équipez : " + String.join(", ", resolved.playerClass().weapons())));
|
|
||||||
case UNKNOWN -> playerRef.sendMessage(Message.raw("Capacité inconnue."));
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,11 +93,11 @@ public final class ClassAbilityController {
|
|||||||
return slotFor(type) != 0;
|
return slotFor(type) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
private ResolvedAbility resolveAbility(@Nonnull PlayerRef playerRef, int slot) {
|
private ResolveOutcome resolve(@Nonnull PlayerRef playerRef, int slot) {
|
||||||
Bootstrap bootstrap = plugin.getBootstrap();
|
Bootstrap bootstrap = plugin.getBootstrap();
|
||||||
if (bootstrap == null) {
|
if (bootstrap == null) {
|
||||||
return null;
|
return ResolveOutcome.failed(ResolveFailure.SERVER_NOT_READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerSessionService sessions = bootstrap.getRegistry().require(PlayerSessionService.class);
|
PlayerSessionService sessions = bootstrap.getRegistry().require(PlayerSessionService.class);
|
||||||
@@ -95,20 +106,98 @@ public final class ClassAbilityController {
|
|||||||
|
|
||||||
PlayerProfile profile = sessions.getSession(playerRef.getUuid());
|
PlayerProfile profile = sessions.getSession(playerRef.getUuid());
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
return null;
|
return ResolveOutcome.failed(ResolveFailure.NO_PROFILE);
|
||||||
}
|
}
|
||||||
PlayerClass playerClass = classService.getClass(profile);
|
PlayerClass playerClass = classService.getClass(profile);
|
||||||
if (playerClass == null) {
|
if (playerClass == null) {
|
||||||
return null;
|
return ResolveOutcome.failed(ResolveFailure.NO_CLASS);
|
||||||
}
|
}
|
||||||
AbilityDefinition ability = playerClass.abilities().stream()
|
AbilityDefinition ability = playerClass.abilities().stream()
|
||||||
.filter(a -> a.slot() == slot)
|
.filter(a -> a.slot() == slot)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (ability == null) {
|
if (ability == null) {
|
||||||
return null;
|
return ResolveOutcome.failed(ResolveFailure.NO_ABILITY_SLOT);
|
||||||
|
}
|
||||||
|
return ResolveOutcome.ok(new ResolvedAbility(playerClass, ability, abilities));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyResolveFailure(
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
int slot,
|
||||||
|
@Nonnull ResolveFailure failure) {
|
||||||
|
String message = switch (failure) {
|
||||||
|
case NO_PROFILE -> "[Capacité " + slot + "] Profil indisponible. Reconnectez-vous.";
|
||||||
|
case NO_CLASS -> "[Capacité " + slot + "] Aucune classe active. "
|
||||||
|
+ "Utilisez /mmorpg class choisir <classe>.";
|
||||||
|
case NO_ABILITY_SLOT -> "[Capacité " + slot + "] Aucune capacité dans ce slot.";
|
||||||
|
case SERVER_NOT_READY -> null;
|
||||||
|
};
|
||||||
|
if (message != null) {
|
||||||
|
playerRef.sendMessage(Message.raw(message));
|
||||||
|
logAbilityFeedback(playerRef, "resolve:" + failure, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyCastResult(
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
@Nonnull ResolvedAbility resolved,
|
||||||
|
@Nonnull AbilityService.CastResult result,
|
||||||
|
boolean fromInput) {
|
||||||
|
String message = formatCastFeedback(resolved, result, fromInput);
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
playerRef.sendMessage(Message.raw(message));
|
||||||
|
logAbilityFeedback(playerRef, result.name(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String formatCastFeedback(
|
||||||
|
@Nonnull ResolvedAbility resolved,
|
||||||
|
@Nonnull AbilityService.CastResult result,
|
||||||
|
boolean fromInput) {
|
||||||
|
String abilityName = resolved.ability().displayName();
|
||||||
|
return switch (result) {
|
||||||
|
case OK -> fromInput ? null : "Capacité utilisée : " + abilityName + ".";
|
||||||
|
// Cooldown is already shown on the ability-bar HUD.
|
||||||
|
case ON_COOLDOWN -> null;
|
||||||
|
case WRONG_WEAPON -> "[" + abilityName + "] Arme requise : "
|
||||||
|
+ String.join(", ", resolved.playerClass().weapons()) + ".";
|
||||||
|
case UNKNOWN -> "[" + abilityName + "] Capacité indisponible pour le moment.";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logAbilityFeedback(
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
@Nonnull String outcome,
|
||||||
|
@Nonnull String message) {
|
||||||
|
logger.at(Level.INFO).log(
|
||||||
|
"Ability input [%s] %s — %s",
|
||||||
|
outcome,
|
||||||
|
playerRef.getUsername(),
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ResolveFailure {
|
||||||
|
SERVER_NOT_READY,
|
||||||
|
NO_PROFILE,
|
||||||
|
NO_CLASS,
|
||||||
|
NO_ABILITY_SLOT
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ResolveOutcome(@Nullable ResolvedAbility ability, @Nullable ResolveFailure failure) {
|
||||||
|
static ResolveOutcome ok(@Nonnull ResolvedAbility ability) {
|
||||||
|
return new ResolveOutcome(ability, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ResolveOutcome failed(@Nonnull ResolveFailure failure) {
|
||||||
|
return new ResolveOutcome(null, failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean resolved() {
|
||||||
|
return ability != null;
|
||||||
}
|
}
|
||||||
return new ResolvedAbility(playerClass, ability, abilities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ResolvedAbility(
|
private record ResolvedAbility(
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.disklexar.mmorpg.command;
|
||||||
|
|
||||||
|
import com.disklexar.mmorpg.MmorpgPlugin;
|
||||||
|
import com.disklexar.mmorpg.player.PlayerInfoView;
|
||||||
|
import com.disklexar.mmorpg.player.model.PlayerProfile;
|
||||||
|
import com.disklexar.mmorpg.combat.AbilityService;
|
||||||
|
import com.disklexar.mmorpg.progression.ProgressionService;
|
||||||
|
import com.disklexar.mmorpg.ui.PlayerInventoryPage;
|
||||||
|
import com.disklexar.mmorpg.ui.PlayerMenuPage;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code /mmorpg inventory} — opens the MMORPG inventory shell (custom UI).
|
||||||
|
*/
|
||||||
|
public final class InventoryCommand extends AbstractPlayerCommand {
|
||||||
|
|
||||||
|
private final MmorpgPlugin plugin;
|
||||||
|
|
||||||
|
public InventoryCommand(@Nonnull MmorpgPlugin plugin) {
|
||||||
|
super("inventory", "Ouvrir le menu joueur sur l'onglet Inventaire");
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void execute(
|
||||||
|
@Nonnull CommandContext context,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
@Nonnull World world) {
|
||||||
|
PlayerProfile profile = CommandSupport.profile(plugin, store, ref);
|
||||||
|
if (profile == null) {
|
||||||
|
context.sendMessage(Message.raw("Aucun profil chargé. Reconnectez-vous."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player player = store.getComponent(ref, Player.getComponentType());
|
||||||
|
if (player == null) {
|
||||||
|
context.sendMessage(Message.raw(PlayerInfoView.asText(profile)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
world.execute(() -> {
|
||||||
|
try {
|
||||||
|
pageManager.openCustomPage(ref, store, new PlayerInventoryPage(
|
||||||
|
playerRef,
|
||||||
|
profile,
|
||||||
|
CommandSupport.service(plugin, ProgressionService.class),
|
||||||
|
CommandSupport.service(plugin, AbilityService.class)));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
playerRef.sendMessage(Message.raw(
|
||||||
|
"Impossible d'ouvrir l'inventaire MMORPG : " + t.getMessage()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ package com.disklexar.mmorpg.command;
|
|||||||
import com.disklexar.mmorpg.MmorpgPlugin;
|
import com.disklexar.mmorpg.MmorpgPlugin;
|
||||||
import com.disklexar.mmorpg.player.PlayerInfoView;
|
import com.disklexar.mmorpg.player.PlayerInfoView;
|
||||||
import com.disklexar.mmorpg.player.model.PlayerProfile;
|
import com.disklexar.mmorpg.player.model.PlayerProfile;
|
||||||
import com.disklexar.mmorpg.ui.PlayerInfoPage;
|
import com.disklexar.mmorpg.combat.AbilityService;
|
||||||
|
import com.disklexar.mmorpg.progression.ProgressionService;
|
||||||
|
import com.disklexar.mmorpg.ui.PlayerMenuPage;
|
||||||
import com.hypixel.hytale.component.Ref;
|
import com.hypixel.hytale.component.Ref;
|
||||||
import com.hypixel.hytale.component.Store;
|
import com.hypixel.hytale.component.Store;
|
||||||
import com.hypixel.hytale.server.core.Message;
|
import com.hypixel.hytale.server.core.Message;
|
||||||
@@ -26,7 +28,7 @@ public final class MenuCommand extends AbstractPlayerCommand {
|
|||||||
private final MmorpgPlugin plugin;
|
private final MmorpgPlugin plugin;
|
||||||
|
|
||||||
public MenuCommand(@Nonnull MmorpgPlugin plugin) {
|
public MenuCommand(@Nonnull MmorpgPlugin plugin) {
|
||||||
super("menu", "Ouvrir l'interface d'informations du joueur");
|
super("menu", "Ouvrir le menu joueur MMORPG (Personnage / Inventaire / Compétences)");
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +48,12 @@ public final class MenuCommand extends AbstractPlayerCommand {
|
|||||||
PageManager pageManager = player.getPageManager();
|
PageManager pageManager = player.getPageManager();
|
||||||
world.execute(() -> {
|
world.execute(() -> {
|
||||||
try {
|
try {
|
||||||
pageManager.openCustomPage(ref, store, new PlayerInfoPage(playerRef, profile, false));
|
pageManager.openCustomPage(ref, store, new PlayerMenuPage(
|
||||||
|
playerRef,
|
||||||
|
profile,
|
||||||
|
CommandSupport.service(plugin, ProgressionService.class),
|
||||||
|
CommandSupport.service(plugin, AbilityService.class),
|
||||||
|
PlayerMenuPage.Tab.CHARACTER));
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
playerRef.sendMessage(Message.raw(PlayerInfoView.asText(profile)));
|
playerRef.sendMessage(Message.raw(PlayerInfoView.asText(profile)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public final class MmorpgCommand extends AbstractCommandCollection {
|
|||||||
addSubCommand(new MmorpgProfileCommand(plugin));
|
addSubCommand(new MmorpgProfileCommand(plugin));
|
||||||
addSubCommand(new InfoCommand(plugin));
|
addSubCommand(new InfoCommand(plugin));
|
||||||
addSubCommand(new MenuCommand(plugin));
|
addSubCommand(new MenuCommand(plugin));
|
||||||
|
addSubCommand(new InventoryCommand(plugin));
|
||||||
addSubCommand(new HudCommand(plugin));
|
addSubCommand(new HudCommand(plugin));
|
||||||
addSubCommand(new ClassCommand(plugin));
|
addSubCommand(new ClassCommand(plugin));
|
||||||
addSubCommand(new PowerCommand(plugin));
|
addSubCommand(new PowerCommand(plugin));
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public final class MmorpgHelpCommand extends AbstractAsyncCommand {
|
|||||||
/mmorpg help — cette aide
|
/mmorpg help — cette aide
|
||||||
/mmorpg profile — résumé court de votre profil
|
/mmorpg profile — résumé court de votre profil
|
||||||
/mmorpg info — toutes vos informations (chat)
|
/mmorpg info — toutes vos informations (chat)
|
||||||
/mmorpg menu — interface des informations du joueur
|
/mmorpg menu — menu joueur (Personnage / Inventaire / Compétences)
|
||||||
|
/mmorpg inventory — menu joueur, onglet Inventaire
|
||||||
/mmorpg menucustom — interface personnalisée (asset pack, test)
|
/mmorpg menucustom — interface personnalisée (asset pack, test)
|
||||||
/mmorpg class <choisir|quitter|info> [classe]
|
/mmorpg class <choisir|quitter|info> [classe]
|
||||||
/mmorpg power <recevoir|retirer|info> [pouvoir]
|
/mmorpg power <recevoir|retirer|info> [pouvoir]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.hypixel.hytale.protocol.Packet;
|
|||||||
import com.hypixel.hytale.protocol.packets.interaction.CancelInteractionChain;
|
import com.hypixel.hytale.protocol.packets.interaction.CancelInteractionChain;
|
||||||
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain;
|
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain;
|
||||||
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains;
|
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
import com.hypixel.hytale.server.core.entity.entities.Player;
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
import com.hypixel.hytale.server.core.io.adapter.PlayerPacketFilter;
|
import com.hypixel.hytale.server.core.io.adapter.PlayerPacketFilter;
|
||||||
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
@@ -73,16 +74,20 @@ public final class AbilityPacketFilter implements PlayerPacketFilter {
|
|||||||
|
|
||||||
Ref<EntityStore> entityRef = playerRef.getReference();
|
Ref<EntityStore> entityRef = playerRef.getReference();
|
||||||
if (entityRef == null || !entityRef.isValid()) {
|
if (entityRef == null || !entityRef.isValid()) {
|
||||||
|
playerRef.sendMessage(Message.raw(
|
||||||
|
"[Capacité " + slot + "] Impossible d'utiliser la capacité pour le moment."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Store<EntityStore> store = entityRef.getStore();
|
Store<EntityStore> store = entityRef.getStore();
|
||||||
Player player = store.getComponent(entityRef, Player.getComponentType());
|
Player player = store.getComponent(entityRef, Player.getComponentType());
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
|
playerRef.sendMessage(Message.raw(
|
||||||
|
"[Capacité " + slot + "] Impossible d'utiliser la capacité pour le moment."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
World world = player.getWorld();
|
World world = player.getWorld();
|
||||||
world.execute(() -> controller.tryCast(playerRef, world, slot, null));
|
world.execute(() -> controller.tryCast(playerRef, world, slot, null, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public final class PlayerConnectionHandler {
|
|||||||
profile.getLevel(),
|
profile.getLevel(),
|
||||||
com.disklexar.mmorpg.rpg.race.RaceCatalog.byId(profile.getRaceId()).displayName())));
|
com.disklexar.mmorpg.rpg.race.RaceCatalog.byId(profile.getRaceId()).displayName())));
|
||||||
universePlayerRef.sendMessage(Message.raw(
|
universePlayerRef.sendMessage(Message.raw(
|
||||||
"Commandes : /mmorpg menu (profil) | /mmorpg class info"));
|
"Commandes : /mmorpg menu (profil) | /mmorpg inventory | /mmorpg class info"));
|
||||||
|
|
||||||
if (classService.hasClass(profile)) {
|
if (classService.hasClass(profile)) {
|
||||||
scheduler.runLater(() -> abilityBar.sync(uuid, profile), 5_000L);
|
scheduler.runLater(() -> abilityBar.sync(uuid, profile), 5_000L);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.hypixel.hytale.component.Ref;
|
|||||||
import com.hypixel.hytale.component.Store;
|
import com.hypixel.hytale.component.Store;
|
||||||
import com.hypixel.hytale.protocol.InteractionState;
|
import com.hypixel.hytale.protocol.InteractionState;
|
||||||
import com.hypixel.hytale.protocol.InteractionType;
|
import com.hypixel.hytale.protocol.InteractionType;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
import com.hypixel.hytale.server.core.entity.InteractionContext;
|
import com.hypixel.hytale.server.core.entity.InteractionContext;
|
||||||
import com.hypixel.hytale.server.core.entity.entities.Player;
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
|
||||||
@@ -55,10 +56,6 @@ public final class MmorpgCastAbilityInteraction extends SimpleInstantInteraction
|
|||||||
if (abilitySlot == 0) {
|
if (abilitySlot == 0) {
|
||||||
abilitySlot = ClassAbilityController.slotFor(interactionType);
|
abilitySlot = ClassAbilityController.slotFor(interactionType);
|
||||||
}
|
}
|
||||||
if (abilitySlot == 0) {
|
|
||||||
interactionContext.getState().state = InteractionState.Failed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref<EntityStore> entityRef = interactionContext.getEntity();
|
Ref<EntityStore> entityRef = interactionContext.getEntity();
|
||||||
if (entityRef == null || !entityRef.isValid()) {
|
if (entityRef == null || !entityRef.isValid()) {
|
||||||
@@ -74,8 +71,15 @@ public final class MmorpgCastAbilityInteraction extends SimpleInstantInteraction
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (abilitySlot == 0) {
|
||||||
|
playerRef.sendMessage(Message.raw("Capacité invalide (slot inconnu)."));
|
||||||
|
interactionContext.getState().state = InteractionState.Failed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MmorpgPlugin plugin = MmorpgPlugin.get();
|
MmorpgPlugin plugin = MmorpgPlugin.get();
|
||||||
if (plugin == null) {
|
if (plugin == null) {
|
||||||
|
playerRef.sendMessage(Message.raw("Plugin MMORPG indisponible."));
|
||||||
interactionContext.getState().state = InteractionState.Failed;
|
interactionContext.getState().state = InteractionState.Failed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -83,8 +87,7 @@ public final class MmorpgCastAbilityInteraction extends SimpleInstantInteraction
|
|||||||
World world = player.getWorld();
|
World world = player.getWorld();
|
||||||
Ref<EntityStore> target = interactionContext.getTargetEntity();
|
Ref<EntityStore> target = interactionContext.getTargetEntity();
|
||||||
ClassAbilityController controller = plugin.getClassAbilityController();
|
ClassAbilityController controller = plugin.getClassAbilityController();
|
||||||
if (!controller.tryCast(playerRef, world, abilitySlot, target)) {
|
controller.tryCast(playerRef, world, abilitySlot, target);
|
||||||
interactionContext.getState().state = InteractionState.Failed;
|
interactionContext.getState().state = InteractionState.Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ public final class AbilityBarHud extends CustomUIHud {
|
|||||||
applyBar(ui, "#XpBar", xpCurrent, xpMax);
|
applyBar(ui, "#XpBar", xpCurrent, xpMax);
|
||||||
ui.set("#XpValue.Text", escape(xpCurrent + " / " + xpMax));
|
ui.set("#XpValue.Text", escape(xpCurrent + " / " + xpMax));
|
||||||
|
|
||||||
|
for (int slot = 1; slot <= 3; slot++) {
|
||||||
|
ui.set("#Slot" + slot + "Key.Text", escape(AbilitySlotKeys.keyLabel(slot)));
|
||||||
|
}
|
||||||
|
|
||||||
for (AbilityDefinition ability : playerClass.abilities()) {
|
for (AbilityDefinition ability : playerClass.abilities()) {
|
||||||
int slot = ability.slot();
|
int slot = ability.slot();
|
||||||
long remaining = abilities.cooldownRemaining(playerId, ability.id());
|
long remaining = abilities.cooldownRemaining(playerId, ability.id());
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.disklexar.mmorpg.ui;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Hytale key labels for class ability slots (Use Ability 1/2/3).
|
||||||
|
* Server-side only — players may rebind these in client settings.
|
||||||
|
*/
|
||||||
|
public final class AbilitySlotKeys {
|
||||||
|
|
||||||
|
private static final String[] DEFAULT_KEYS = {"", "Q", "E", "R"};
|
||||||
|
|
||||||
|
private AbilitySlotKeys() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static String keyLabel(int slot) {
|
||||||
|
if (slot < 1 || slot > 3) {
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
return "[" + DEFAULT_KEYS[slot] + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
package com.disklexar.mmorpg.ui;
|
||||||
|
|
||||||
|
import com.disklexar.mmorpg.combat.AbilityService;
|
||||||
|
import com.disklexar.mmorpg.player.model.PlayerProfile;
|
||||||
|
import com.disklexar.mmorpg.progression.ProgressionService;
|
||||||
|
import com.disklexar.mmorpg.rpg.clazz.ClassCatalog;
|
||||||
|
import com.disklexar.mmorpg.rpg.clazz.PlayerClass;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.InventoryComponent;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.EventData;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standalone inventory page ({@code MmorpgInventoryPage.ui} root document).
|
||||||
|
* Does not reference {@code MmorpgPlayerShell.ui}.
|
||||||
|
*/
|
||||||
|
public final class PlayerInventoryPage extends InteractiveCustomUIPage<PlayerInventoryPage.UiEvent> {
|
||||||
|
|
||||||
|
private static final String DOCUMENT = "Pages/MmorpgInventoryPage.ui";
|
||||||
|
|
||||||
|
private final PlayerRef playerRef;
|
||||||
|
private final PlayerProfile profile;
|
||||||
|
private final ProgressionService progression;
|
||||||
|
private final AbilityService abilities;
|
||||||
|
|
||||||
|
public PlayerInventoryPage(
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
@Nonnull PlayerProfile profile,
|
||||||
|
@Nullable ProgressionService progression,
|
||||||
|
@Nullable AbilityService abilities) {
|
||||||
|
super(playerRef, CustomPageLifetime.CanDismiss, UiEvent.CODEC);
|
||||||
|
this.playerRef = playerRef;
|
||||||
|
this.profile = profile;
|
||||||
|
this.progression = progression;
|
||||||
|
this.abilities = abilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull UIEventBuilder events,
|
||||||
|
@Nonnull Store<EntityStore> store) {
|
||||||
|
ui.append(DOCUMENT);
|
||||||
|
applyHeader(ui);
|
||||||
|
applyInventorySummary(ui, PlayerInventoryReader.snapshot(store, ref));
|
||||||
|
applyEmptyItemDetails(ui);
|
||||||
|
applyTabHighlight(ui);
|
||||||
|
bindTabButtons(events);
|
||||||
|
bindInventoryGridEvents(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDataEvent(
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull String raw) {
|
||||||
|
PlayerMenuPage.Tab tab = parseTab(raw);
|
||||||
|
if (tab != null && tab != PlayerMenuPage.Tab.INVENTORY) {
|
||||||
|
Player player = store.getComponent(ref, Player.getComponentType());
|
||||||
|
if (player != null) {
|
||||||
|
player.getPageManager().openCustomPage(ref, store, new PlayerMenuPage(
|
||||||
|
playerRef,
|
||||||
|
profile,
|
||||||
|
progression,
|
||||||
|
abilities,
|
||||||
|
tab));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotSelection selection = parseSlotSelection(raw);
|
||||||
|
if (selection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UICommandBuilder ui = new UICommandBuilder();
|
||||||
|
PlayerInventoryReader.ItemView item = PlayerInventoryReader.itemAt(
|
||||||
|
store, ref, selection.sectionId(), selection.slotIndex());
|
||||||
|
if (item != null) {
|
||||||
|
applyItemDetails(ui, item);
|
||||||
|
} else {
|
||||||
|
applyEmptyItemDetails(ui);
|
||||||
|
}
|
||||||
|
applyInventorySummary(ui, PlayerInventoryReader.snapshot(store, ref));
|
||||||
|
sendUpdate(ui, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyHeader(@Nonnull UICommandBuilder ui) {
|
||||||
|
ui.set("#PlayerName.Text", escape(profile.getDisplayName()));
|
||||||
|
ui.set("#ClassLabel.Text", escape(classLabel()));
|
||||||
|
ui.set("#LevelLabel.Text", escape("Nv. " + profile.getLevel()));
|
||||||
|
ui.set("#MoneyLabel.Text", escape(profile.getMoney() + " pièces"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTabHighlight(@Nonnull UICommandBuilder ui) {
|
||||||
|
ui.set("#TabCharacterIndicator.Visible", false);
|
||||||
|
ui.set("#TabInventoryIndicator.Visible", true);
|
||||||
|
ui.set("#TabSkillsIndicator.Visible", false);
|
||||||
|
ui.set("#TabCharacterWrap.Background", "#00000000");
|
||||||
|
ui.set("#TabInventoryWrap.Background", "#3a3f4a");
|
||||||
|
ui.set("#TabSkillsWrap.Background", "#00000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyInventorySummary(
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull PlayerInventoryReader.Snapshot snapshot) {
|
||||||
|
ui.set("#StorageSummary.Text", escape(
|
||||||
|
"Sac : " + snapshot.storageUsed() + " / " + snapshot.storageCapacity()));
|
||||||
|
ui.set("#HotbarSummary.Text", escape(
|
||||||
|
"Barre rapide : " + snapshot.hotbarUsed() + " / " + snapshot.hotbarCapacity()));
|
||||||
|
ui.set("#ArmorSummary.Text", escape(
|
||||||
|
"Armure : " + snapshot.armorUsed() + " / " + snapshot.armorCapacity()));
|
||||||
|
ui.set("#UtilitySummary.Text", escape(
|
||||||
|
"Utilitaire : " + snapshot.utilityUsed() + " / " + snapshot.utilityCapacity()));
|
||||||
|
|
||||||
|
float fill = snapshot.storageCapacity() <= 0
|
||||||
|
? 0f
|
||||||
|
: Math.min(1f, (float) snapshot.storageUsed() / snapshot.storageCapacity());
|
||||||
|
ui.set("#WeightBar.Value", fill);
|
||||||
|
ui.set("#WeightValue.Text", escape(snapshot.totalItemCount() + " piles d'objets"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyEmptyItemDetails(@Nonnull UICommandBuilder ui) {
|
||||||
|
ui.set("#ItemName.Text", escape("Aucun objet sélectionné"));
|
||||||
|
ui.set("#ItemRarity.Text", escape("Cliquez un emplacement"));
|
||||||
|
ui.set("#ItemDescription.Text", escape(
|
||||||
|
"Les grilles sont reliées à votre inventaire vanilla."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyItemDetails(@Nonnull UICommandBuilder ui, @Nonnull PlayerInventoryReader.ItemView item) {
|
||||||
|
ui.set("#ItemName.Text", escape(item.name()));
|
||||||
|
ui.set("#ItemRarity.Text", escape(item.rarity()));
|
||||||
|
ui.set("#ItemDescription.Text", escape(item.description()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindTabButtons(@Nonnull UIEventBuilder events) {
|
||||||
|
events.addEventBinding(
|
||||||
|
CustomUIEventBindingType.Activating,
|
||||||
|
"#TabCharacter",
|
||||||
|
EventData.of("Tab", "character"),
|
||||||
|
false);
|
||||||
|
events.addEventBinding(
|
||||||
|
CustomUIEventBindingType.Activating,
|
||||||
|
"#TabInventory",
|
||||||
|
EventData.of("Tab", "inventory"),
|
||||||
|
false);
|
||||||
|
events.addEventBinding(
|
||||||
|
CustomUIEventBindingType.Activating,
|
||||||
|
"#TabSkills",
|
||||||
|
EventData.of("Tab", "skills"),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindInventoryGridEvents(@Nonnull UIEventBuilder events) {
|
||||||
|
bindGridEvents(events, "#ArmorGrid", InventoryComponent.ARMOR_SECTION_ID);
|
||||||
|
bindGridEvents(events, "#StorageGrid", InventoryComponent.STORAGE_SECTION_ID);
|
||||||
|
bindGridEvents(events, "#HotbarGrid", InventoryComponent.HOTBAR_SECTION_ID);
|
||||||
|
bindGridEvents(events, "#UtilityGrid", InventoryComponent.UTILITY_SECTION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void bindGridEvents(
|
||||||
|
@Nonnull UIEventBuilder events,
|
||||||
|
@Nonnull String selector,
|
||||||
|
int sectionId) {
|
||||||
|
EventData data = EventData.of("Section", String.valueOf(sectionId));
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.SlotClicking, selector, data, false);
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.SlotMouseEntered, selector, data, false);
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.Dropped, selector, data, false);
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.SlotMouseDragCompleted, selector, data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private String classLabel() {
|
||||||
|
PlayerClass playerClass = ClassCatalog.byId(profile.getClassId());
|
||||||
|
return playerClass == null ? "Sans classe" : playerClass.displayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static PlayerMenuPage.Tab parseTab(@Nonnull String raw) {
|
||||||
|
try {
|
||||||
|
JsonObject json = JsonParser.parseString(raw).getAsJsonObject();
|
||||||
|
if (!json.has("Tab")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String value = json.get("Tab").getAsString().toLowerCase();
|
||||||
|
return switch (value) {
|
||||||
|
case "character", "personnage" -> PlayerMenuPage.Tab.CHARACTER;
|
||||||
|
case "inventory", "inventaire" -> PlayerMenuPage.Tab.INVENTORY;
|
||||||
|
case "skills", "competences", "compétences" -> PlayerMenuPage.Tab.SKILLS;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static SlotSelection parseSlotSelection(@Nonnull String raw) {
|
||||||
|
try {
|
||||||
|
JsonObject json = JsonParser.parseString(raw).getAsJsonObject();
|
||||||
|
Integer slotIndex = readInt(json, "SlotIndex", "slotIndex", "Index");
|
||||||
|
if (slotIndex == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Integer sectionId = readInt(json, "Section", "InventorySectionId", "SourceInventorySectionId");
|
||||||
|
if (sectionId == null) {
|
||||||
|
sectionId = InventoryComponent.STORAGE_SECTION_ID;
|
||||||
|
}
|
||||||
|
return new SlotSelection(sectionId, slotIndex);
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Integer readInt(@Nonnull JsonObject json, @Nonnull String... keys) {
|
||||||
|
for (String key : keys) {
|
||||||
|
if (!json.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
JsonElement element = json.get(key);
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
try {
|
||||||
|
return element.getAsInt();
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
String asString = element.getAsString();
|
||||||
|
if (!asString.isBlank()) {
|
||||||
|
return Integer.parseInt(asString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String escape(@Nonnull String text) {
|
||||||
|
return text.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SlotSelection(int sectionId, int slotIndex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class UiEvent {
|
||||||
|
public static final BuilderCodec<UiEvent> CODEC =
|
||||||
|
BuilderCodec.builder(UiEvent.class, UiEvent::new).build();
|
||||||
|
|
||||||
|
private UiEvent() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.disklexar.mmorpg.ui;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.InventoryComponent;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads live player inventory sections for the MMORPG inventory page.
|
||||||
|
*/
|
||||||
|
public final class PlayerInventoryReader {
|
||||||
|
|
||||||
|
public record Snapshot(
|
||||||
|
int storageUsed,
|
||||||
|
int storageCapacity,
|
||||||
|
int hotbarUsed,
|
||||||
|
int hotbarCapacity,
|
||||||
|
int armorUsed,
|
||||||
|
int armorCapacity,
|
||||||
|
int utilityUsed,
|
||||||
|
int utilityCapacity,
|
||||||
|
int totalItemCount) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ItemView(
|
||||||
|
@Nonnull String name,
|
||||||
|
@Nonnull String rarity,
|
||||||
|
@Nonnull String description,
|
||||||
|
@Nonnull String itemId,
|
||||||
|
int quantity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerInventoryReader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static Snapshot snapshot(@Nonnull Store<EntityStore> store, @Nonnull Ref<EntityStore> ref) {
|
||||||
|
SectionCounts storage = countSection(store, ref, InventoryComponent.STORAGE_SECTION_ID);
|
||||||
|
SectionCounts hotbar = countSection(store, ref, InventoryComponent.HOTBAR_SECTION_ID);
|
||||||
|
SectionCounts armor = countSection(store, ref, InventoryComponent.ARMOR_SECTION_ID);
|
||||||
|
SectionCounts utility = countSection(store, ref, InventoryComponent.UTILITY_SECTION_ID);
|
||||||
|
|
||||||
|
return new Snapshot(
|
||||||
|
storage.used(),
|
||||||
|
storage.capacity(),
|
||||||
|
hotbar.used(),
|
||||||
|
hotbar.capacity(),
|
||||||
|
armor.used(),
|
||||||
|
armor.capacity(),
|
||||||
|
utility.used(),
|
||||||
|
utility.capacity(),
|
||||||
|
storage.used() + hotbar.used() + armor.used() + utility.used());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static ItemView itemAt(
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
int sectionId,
|
||||||
|
int slotIndex) {
|
||||||
|
ItemContainer container = container(store, ref, sectionId);
|
||||||
|
if (container == null || slotIndex < 0 || slotIndex >= container.getCapacity()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ItemStack stack = container.getItemStack((short) slotIndex);
|
||||||
|
if (ItemStack.isEmpty(stack)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toView(stack, sectionId, slotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ItemContainer container(
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
int sectionId) {
|
||||||
|
var type = InventoryComponent.getComponentTypeById(sectionId);
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
InventoryComponent component = store.getComponent(ref, type);
|
||||||
|
return component == null ? null : component.getInventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static SectionCounts countSection(
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
int sectionId) {
|
||||||
|
ItemContainer container = container(store, ref, sectionId);
|
||||||
|
if (container == null) {
|
||||||
|
return new SectionCounts(0, 0);
|
||||||
|
}
|
||||||
|
int used = 0;
|
||||||
|
for (short slot = 0; slot < container.getCapacity(); slot++) {
|
||||||
|
if (!ItemStack.isEmpty(container.getItemStack(slot))) {
|
||||||
|
used++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SectionCounts(used, container.getCapacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static ItemView toView(
|
||||||
|
@Nonnull ItemStack stack,
|
||||||
|
int sectionId,
|
||||||
|
int slotIndex) {
|
||||||
|
String itemId = stack.getItemId() == null ? "inconnu" : stack.getItemId();
|
||||||
|
String name = formatItemId(itemId);
|
||||||
|
int quantity = stack.getQuantity();
|
||||||
|
|
||||||
|
StringBuilder description = new StringBuilder();
|
||||||
|
description.append("ID : ").append(itemId);
|
||||||
|
description.append("\nSection : ").append(sectionLabel(sectionId));
|
||||||
|
description.append("\nEmplacement : ").append(slotIndex + 1);
|
||||||
|
description.append("\nQuantité : ").append(quantity);
|
||||||
|
|
||||||
|
if (stack.getMaxDurability() > 0) {
|
||||||
|
description.append("\nDurabilité : ")
|
||||||
|
.append((int) stack.getDurability())
|
||||||
|
.append(" / ")
|
||||||
|
.append((int) stack.getMaxDurability());
|
||||||
|
}
|
||||||
|
|
||||||
|
String rarity = stack.isBroken() ? "Endommagé" : "Objet vanilla";
|
||||||
|
|
||||||
|
return new ItemView(name, rarity, description.toString(), itemId, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String sectionLabel(int sectionId) {
|
||||||
|
if (sectionId == InventoryComponent.STORAGE_SECTION_ID) {
|
||||||
|
return "Sac";
|
||||||
|
}
|
||||||
|
if (sectionId == InventoryComponent.HOTBAR_SECTION_ID) {
|
||||||
|
return "Barre rapide";
|
||||||
|
}
|
||||||
|
if (sectionId == InventoryComponent.ARMOR_SECTION_ID) {
|
||||||
|
return "Armure";
|
||||||
|
}
|
||||||
|
if (sectionId == InventoryComponent.UTILITY_SECTION_ID) {
|
||||||
|
return "Utilitaire";
|
||||||
|
}
|
||||||
|
return "Section " + sectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String formatItemId(@Nonnull String itemId) {
|
||||||
|
String trimmed = itemId;
|
||||||
|
if (trimmed.startsWith("Weapon_")) {
|
||||||
|
trimmed = trimmed.substring("Weapon_".length());
|
||||||
|
} else if (trimmed.startsWith("Tool_")) {
|
||||||
|
trimmed = trimmed.substring("Tool_".length());
|
||||||
|
} else if (trimmed.startsWith("Armor_")) {
|
||||||
|
trimmed = trimmed.substring("Armor_".length());
|
||||||
|
}
|
||||||
|
return trimmed.replace('_', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SectionCounts(int used, int capacity) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
package com.disklexar.mmorpg.ui;
|
||||||
|
|
||||||
|
import com.disklexar.mmorpg.combat.AbilityService;
|
||||||
|
import com.disklexar.mmorpg.player.model.PlayerProfile;
|
||||||
|
import com.disklexar.mmorpg.progression.ProgressionService;
|
||||||
|
import com.disklexar.mmorpg.rpg.clazz.AbilityDefinition;
|
||||||
|
import com.disklexar.mmorpg.rpg.clazz.ClassCatalog;
|
||||||
|
import com.disklexar.mmorpg.rpg.clazz.PlayerClass;
|
||||||
|
import com.disklexar.mmorpg.rpg.job.JobCatalog;
|
||||||
|
import com.disklexar.mmorpg.rpg.job.JobDefinition;
|
||||||
|
import com.disklexar.mmorpg.rpg.power.PowerCatalog;
|
||||||
|
import com.disklexar.mmorpg.rpg.power.PowerDefinition;
|
||||||
|
import com.disklexar.mmorpg.rpg.race.RaceCatalog;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.EventData;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MMORPG player menu with tabs: Personnage, Inventaire, Compétences.
|
||||||
|
* <p>
|
||||||
|
* Inventaire uses {@code MmorpgInventoryPage.ui} as the root document so {@code ItemGrid.InventorySectionId}
|
||||||
|
* binds correctly. Personnage / Compétences use {@code MmorpgPlayerShell.ui}.
|
||||||
|
*/
|
||||||
|
public final class PlayerMenuPage extends InteractiveCustomUIPage<PlayerMenuPage.UiEvent> {
|
||||||
|
|
||||||
|
private static final String SHELL = "Pages/MmorpgPlayerShell.ui";
|
||||||
|
private static final String CHARACTER_TAB = "Pages/MmorpgCharacterTab.ui";
|
||||||
|
private static final String SKILLS_TAB = "Pages/MmorpgSkillsTab.ui";
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FORMAT =
|
||||||
|
DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm").withZone(ZoneId.systemDefault());
|
||||||
|
|
||||||
|
public enum Tab {
|
||||||
|
CHARACTER,
|
||||||
|
INVENTORY,
|
||||||
|
SKILLS
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PlayerRef playerRef;
|
||||||
|
private final PlayerProfile profile;
|
||||||
|
private final ProgressionService progression;
|
||||||
|
private final AbilityService abilities;
|
||||||
|
private Tab activeTab;
|
||||||
|
|
||||||
|
public PlayerMenuPage(
|
||||||
|
@Nonnull PlayerRef playerRef,
|
||||||
|
@Nonnull PlayerProfile profile,
|
||||||
|
@Nullable ProgressionService progression,
|
||||||
|
@Nullable AbilityService abilities,
|
||||||
|
@Nonnull Tab initialTab) {
|
||||||
|
super(playerRef, CustomPageLifetime.CanDismiss, UiEvent.CODEC);
|
||||||
|
this.playerRef = playerRef;
|
||||||
|
this.profile = profile;
|
||||||
|
this.progression = progression;
|
||||||
|
this.abilities = abilities;
|
||||||
|
this.activeTab = initialTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull UIEventBuilder events,
|
||||||
|
@Nonnull Store<EntityStore> store) {
|
||||||
|
buildShellRoot(ref, ui, events, store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDataEvent(
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull String raw) {
|
||||||
|
Tab tab = parseTab(raw);
|
||||||
|
if (tab != null && tab != activeTab) {
|
||||||
|
if (tab == Tab.INVENTORY) {
|
||||||
|
openInventoryPage(ref, store);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeTab = tab;
|
||||||
|
UICommandBuilder ui = new UICommandBuilder();
|
||||||
|
UIEventBuilder events = new UIEventBuilder();
|
||||||
|
bindTabButtons(events);
|
||||||
|
loadShellTab(ui, events, store, ref, activeTab);
|
||||||
|
sendUpdate(ui, events, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openInventoryPage(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
|
||||||
|
Player player = store.getComponent(ref, Player.getComponentType());
|
||||||
|
if (player != null) {
|
||||||
|
player.getPageManager().openCustomPage(ref, store, new PlayerInventoryPage(
|
||||||
|
playerRef,
|
||||||
|
profile,
|
||||||
|
progression,
|
||||||
|
abilities));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildShellRoot(
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull UIEventBuilder events,
|
||||||
|
@Nonnull Store<EntityStore> store) {
|
||||||
|
ui.append(SHELL);
|
||||||
|
applyHeader(ui);
|
||||||
|
bindTabButtons(events);
|
||||||
|
loadShellTab(ui, events, store, ref, activeTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadShellTab(
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull UIEventBuilder events,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull Ref<EntityStore> ref,
|
||||||
|
@Nonnull Tab tab) {
|
||||||
|
applyTabHighlight(ui, tab);
|
||||||
|
ui.clear("#DynamicTabHost");
|
||||||
|
switch (tab) {
|
||||||
|
case CHARACTER -> {
|
||||||
|
ui.append("#DynamicTabHost", CHARACTER_TAB);
|
||||||
|
applyCharacterTab(ui, store, ref);
|
||||||
|
ui.set("#FooterHint.Text", escape("Échap pour fermer — onglet Personnage"));
|
||||||
|
}
|
||||||
|
case SKILLS -> {
|
||||||
|
ui.append("#DynamicTabHost", SKILLS_TAB);
|
||||||
|
applySkillsTab(ui);
|
||||||
|
ui.set("#FooterHint.Text", escape(
|
||||||
|
"Échap pour fermer — touches Ability 1/2/3 en jeu"));
|
||||||
|
}
|
||||||
|
case INVENTORY -> { }
|
||||||
|
}
|
||||||
|
ui.set("#HeaderSubtitle.Text", escape(tabSubtitle(tab)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTabHighlight(@Nonnull UICommandBuilder ui, @Nonnull Tab tab) {
|
||||||
|
ui.set("#TabCharacterIndicator.Visible", tab == Tab.CHARACTER);
|
||||||
|
ui.set("#TabInventoryIndicator.Visible", tab == Tab.INVENTORY);
|
||||||
|
ui.set("#TabSkillsIndicator.Visible", tab == Tab.SKILLS);
|
||||||
|
ui.set("#TabCharacterWrap.Background", tab == Tab.CHARACTER ? "#3a3f4a" : "#00000000");
|
||||||
|
ui.set("#TabInventoryWrap.Background", tab == Tab.INVENTORY ? "#3a3f4a" : "#00000000");
|
||||||
|
ui.set("#TabSkillsWrap.Background", tab == Tab.SKILLS ? "#3a3f4a" : "#00000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyHeader(@Nonnull UICommandBuilder ui) {
|
||||||
|
ui.set("#PlayerName.Text", escape(profile.getDisplayName()));
|
||||||
|
ui.set("#LevelLabel.Text", escape("Nv. " + profile.getLevel()));
|
||||||
|
ui.set("#MoneyLabel.Text", escape(profile.getMoney() + " pièces"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCharacterTab(
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull Ref<EntityStore> ref) {
|
||||||
|
PlayerClass playerClass = ClassCatalog.byId(profile.getClassId());
|
||||||
|
String className = playerClass == null ? "Aucune" : playerClass.displayName();
|
||||||
|
|
||||||
|
ui.set("#IdentityName.Text", escape(profile.getDisplayName()));
|
||||||
|
ui.set("#RaceLabel.Text", escape("Race : " + RaceCatalog.byId(profile.getRaceId()).displayName()));
|
||||||
|
ui.set("#ClassLabel.Text", escape("Classe : " + className));
|
||||||
|
|
||||||
|
long xpMax = progression == null ? 100L : Math.max(1L, progression.xpForLevel(profile.getLevel()));
|
||||||
|
long xpCurrent = profile.getExperience();
|
||||||
|
float xpRatio = Math.min(1f, (float) xpCurrent / xpMax);
|
||||||
|
ui.set("#XpValue.Text", escape(xpCurrent + " / " + xpMax + " XP"));
|
||||||
|
ui.set("#XpBar.Value", xpRatio);
|
||||||
|
|
||||||
|
PlayerVitalsReader.Vitals vitals = PlayerVitalsReader.read(store, ref);
|
||||||
|
if (vitals != null) {
|
||||||
|
applyVitalBar(ui, "#HealthBar", "#HealthValue", "Vie", vitals.current(), vitals.max());
|
||||||
|
applyVitalBar(ui, "#StaminaBar", "#StaminaValue", "Endurance",
|
||||||
|
vitals.staminaCurrent(), vitals.staminaMax());
|
||||||
|
} else {
|
||||||
|
ui.set("#HealthValue.Text", escape("Vie : —"));
|
||||||
|
ui.set("#StaminaValue.Text", escape("Endurance : —"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int level = profile.getLevel();
|
||||||
|
ui.set("#StatStr.Text", escape("FOR " + (10 + level)));
|
||||||
|
ui.set("#StatAgi.Text", escape("AGI " + (8 + level)));
|
||||||
|
ui.set("#StatInt.Text", escape("INT " + (6 + level)));
|
||||||
|
|
||||||
|
ui.set("#PowersList.Text", escape(formatPowers()));
|
||||||
|
ui.set("#JobsList.Text", escape(formatJobs()));
|
||||||
|
ui.set("#GroupLabel.Text", escape(
|
||||||
|
"Groupe : " + (profile.getGroupId() != null ? "Oui" : "Aucun")));
|
||||||
|
ui.set("#GuildLabel.Text", escape(
|
||||||
|
"Guilde : " + (profile.getGuildId() != null ? profile.getGuildId() : "Aucune")));
|
||||||
|
ui.set("#PlayTimeLabel.Text", escape("Temps de jeu : " + formatDuration(profile.getTotalTimePlay())));
|
||||||
|
ui.set("#LastLoginLabel.Text", escape(
|
||||||
|
"Dernière connexion : " + formatDate(profile.getLastDateConnected())));
|
||||||
|
ui.set("#CreatedLabel.Text", escape("Création : " + formatDate(profile.getDateCreation())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySkillsTab(@Nonnull UICommandBuilder ui) {
|
||||||
|
PlayerClass playerClass = ClassCatalog.byId(profile.getClassId());
|
||||||
|
if (playerClass == null) {
|
||||||
|
ui.set("#ClassTitle.Text", escape("Aucune classe"));
|
||||||
|
ui.set("#WeaponsLabel.Text", escape("Choisissez une classe : /mmorpg class choisir"));
|
||||||
|
ui.set("#PointsLabel.Text", escape("Points de compétence : —"));
|
||||||
|
for (int slot = 1; slot <= 3; slot++) {
|
||||||
|
ui.set("#Ability" + slot + "Key.Text", escape(AbilitySlotKeys.keyLabel(slot)));
|
||||||
|
ui.set("#Ability" + slot + "Name.Text", escape("—"));
|
||||||
|
ui.set("#Ability" + slot + "Cooldown.Text", escape("—"));
|
||||||
|
ui.set("#Ability" + slot + "Desc.Text", escape("Aucune capacité sans classe."));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.set("#ClassTitle.Text", escape(playerClass.displayName()));
|
||||||
|
ui.set("#WeaponsLabel.Text", escape("Armes : " + String.join(", ", playerClass.weapons())));
|
||||||
|
ui.set("#PointsLabel.Text", escape("Points de compétence : 0 (arbre M1)"));
|
||||||
|
|
||||||
|
UUID playerId = profile.getUuid();
|
||||||
|
for (int slot = 1; slot <= 3; slot++) {
|
||||||
|
ui.set("#Ability" + slot + "Key.Text", escape(AbilitySlotKeys.keyLabel(slot)));
|
||||||
|
}
|
||||||
|
for (AbilityDefinition ability : playerClass.abilities()) {
|
||||||
|
int slot = ability.slot();
|
||||||
|
ui.set("#Ability" + slot + "Name.Text", escape(ability.displayName()));
|
||||||
|
ui.set("#Ability" + slot + "Cooldown.Text", escape(cooldownLabel(playerId, ability)));
|
||||||
|
ui.set("#Ability" + slot + "Desc.Text", escape(ability.description()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindTabButtons(@Nonnull UIEventBuilder events) {
|
||||||
|
events.addEventBinding(
|
||||||
|
CustomUIEventBindingType.Activating,
|
||||||
|
"#TabCharacter",
|
||||||
|
EventData.of("Tab", "character"),
|
||||||
|
false);
|
||||||
|
events.addEventBinding(
|
||||||
|
CustomUIEventBindingType.Activating,
|
||||||
|
"#TabInventory",
|
||||||
|
EventData.of("Tab", "inventory"),
|
||||||
|
false);
|
||||||
|
events.addEventBinding(
|
||||||
|
CustomUIEventBindingType.Activating,
|
||||||
|
"#TabSkills",
|
||||||
|
EventData.of("Tab", "skills"),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyVitalBar(
|
||||||
|
@Nonnull UICommandBuilder ui,
|
||||||
|
@Nonnull String barSelector,
|
||||||
|
@Nonnull String valueSelector,
|
||||||
|
@Nonnull String label,
|
||||||
|
long current,
|
||||||
|
long max) {
|
||||||
|
float ratio = max <= 0 ? 0f : Math.min(1f, (float) current / max);
|
||||||
|
ui.set(barSelector + ".Value", ratio);
|
||||||
|
ui.set(valueSelector + ".Text", escape(label + " : " + current + " / " + max));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private String cooldownLabel(@Nonnull UUID playerId, @Nonnull AbilityDefinition ability) {
|
||||||
|
if (abilities == null) {
|
||||||
|
return "Prêt";
|
||||||
|
}
|
||||||
|
long remaining = abilities.cooldownRemaining(playerId, ability.id());
|
||||||
|
if (remaining <= 0) {
|
||||||
|
return "Prêt";
|
||||||
|
}
|
||||||
|
return (remaining / 1000 + 1) + "s de recharge";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private String formatPowers() {
|
||||||
|
if (profile.getPowers().isEmpty()) {
|
||||||
|
return "Aucun";
|
||||||
|
}
|
||||||
|
StringJoiner joiner = new StringJoiner("\n• ", "• ", "");
|
||||||
|
for (String id : profile.getPowers()) {
|
||||||
|
PowerDefinition power = PowerCatalog.byId(id);
|
||||||
|
joiner.add(power != null ? power.displayName() : id);
|
||||||
|
}
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private String formatJobs() {
|
||||||
|
if (profile.getJobs().isEmpty()) {
|
||||||
|
return "Aucun";
|
||||||
|
}
|
||||||
|
StringJoiner joiner = new StringJoiner("\n• ", "• ", "");
|
||||||
|
for (String id : profile.getJobs()) {
|
||||||
|
JobDefinition job = JobCatalog.byId(id);
|
||||||
|
joiner.add(job != null ? job.displayName() : id);
|
||||||
|
}
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String tabSubtitle(@Nonnull Tab tab) {
|
||||||
|
return switch (tab) {
|
||||||
|
case CHARACTER -> "Personnage";
|
||||||
|
case INVENTORY -> "Inventaire";
|
||||||
|
case SKILLS -> "Compétences";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Tab parseTab(@Nonnull String raw) {
|
||||||
|
try {
|
||||||
|
JsonObject json = JsonParser.parseString(raw).getAsJsonObject();
|
||||||
|
if (!json.has("Tab")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String value = json.get("Tab").getAsString().toLowerCase();
|
||||||
|
return switch (value) {
|
||||||
|
case "character", "personnage" -> Tab.CHARACTER;
|
||||||
|
case "inventory", "inventaire" -> Tab.INVENTORY;
|
||||||
|
case "skills", "competences", "compétences" -> Tab.SKILLS;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String formatDuration(long millis) {
|
||||||
|
if (millis <= 0) {
|
||||||
|
return "0 min";
|
||||||
|
}
|
||||||
|
Duration duration = Duration.ofMillis(millis);
|
||||||
|
long hours = duration.toHours();
|
||||||
|
long minutes = duration.toMinutesPart();
|
||||||
|
return hours > 0 ? hours + "h " + minutes + "min" : minutes + "min";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String formatDate(long epochMillis) {
|
||||||
|
if (epochMillis <= 0) {
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
return DATE_FORMAT.format(Instant.ofEpochMilli(epochMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String escape(@Nonnull String text) {
|
||||||
|
return text.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class UiEvent {
|
||||||
|
public static final BuilderCodec<UiEvent> CODEC =
|
||||||
|
BuilderCodec.builder(UiEvent.class, UiEvent::new).build();
|
||||||
|
|
||||||
|
private UiEvent() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -149,7 +149,7 @@ Group {
|
|||||||
|
|
||||||
Label #Slot1Key {
|
Label #Slot1Key {
|
||||||
Style: (FontSize: 12, RenderBold: true, TextColor: #f5c842);
|
Style: (FontSize: 12, RenderBold: true, TextColor: #f5c842);
|
||||||
Text: "[1]";
|
Text: "[Q]";
|
||||||
Anchor: (Bottom: 2);
|
Anchor: (Bottom: 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ Group {
|
|||||||
|
|
||||||
Label #Slot2Key {
|
Label #Slot2Key {
|
||||||
Style: (FontSize: 12, RenderBold: true, TextColor: #f5c842);
|
Style: (FontSize: 12, RenderBold: true, TextColor: #f5c842);
|
||||||
Text: "[2]";
|
Text: "[E]";
|
||||||
Anchor: (Bottom: 2);
|
Anchor: (Bottom: 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ Group {
|
|||||||
|
|
||||||
Label #Slot3Key {
|
Label #Slot3Key {
|
||||||
Style: (FontSize: 12, RenderBold: true, TextColor: #f5c842);
|
Style: (FontSize: 12, RenderBold: true, TextColor: #f5c842);
|
||||||
Text: "[3]";
|
Text: "[R]";
|
||||||
Anchor: (Bottom: 2);
|
Anchor: (Bottom: 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
@MmorpgAccent = #f5c842;
|
||||||
|
@MmorpgText = #e8e8ec;
|
||||||
|
@MmorpgMuted = #878e9c;
|
||||||
|
@MmorpgBorder = #3a3f4a;
|
||||||
|
@HealthFill = PatchStyle(Color: #e74c3c);
|
||||||
|
@HealthBg = PatchStyle(Color: #2a1515);
|
||||||
|
@StaminaFill = PatchStyle(Color: #2ecc71);
|
||||||
|
@StaminaBg = PatchStyle(Color: #152a1c);
|
||||||
|
@XpFill = PatchStyle(Color: #4a9eff);
|
||||||
|
@XpBg = PatchStyle(Color: #152030);
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Left;
|
||||||
|
|
||||||
|
Group #LeftColumn {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: 400, Right: 16);
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "IDENTITÉ";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #IdentityName {
|
||||||
|
Style: (FontSize: 18, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Joueur";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #RaceLabel {
|
||||||
|
Style: (FontSize: 13, TextColor: @MmorpgText);
|
||||||
|
Text: "Race : Humain";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ClassLabel {
|
||||||
|
Style: (FontSize: 13, TextColor: @MmorpgText);
|
||||||
|
Text: "Classe : —";
|
||||||
|
Anchor: (Bottom: 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "PROGRESSION";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #XpValue {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgText);
|
||||||
|
Text: "0 / 100 XP";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar #XpBar {
|
||||||
|
Anchor: (Width: 360, Height: 14, Bottom: 10);
|
||||||
|
Bar: @XpFill;
|
||||||
|
Background: @XpBg;
|
||||||
|
Value: 0.0;
|
||||||
|
Direction: End;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "VITALITÉ";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #HealthValue {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgText);
|
||||||
|
Text: "Vie : —";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar #HealthBar {
|
||||||
|
Anchor: (Width: 360, Height: 12, Bottom: 6);
|
||||||
|
Bar: @HealthFill;
|
||||||
|
Background: @HealthBg;
|
||||||
|
Value: 1.0;
|
||||||
|
Direction: End;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #StaminaValue {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgText);
|
||||||
|
Text: "Endurance : —";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar #StaminaBar {
|
||||||
|
Anchor: (Width: 360, Height: 12, Bottom: 10);
|
||||||
|
Bar: @StaminaFill;
|
||||||
|
Background: @StaminaBg;
|
||||||
|
Value: 1.0;
|
||||||
|
Direction: End;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "ATTRIBUTS";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
|
||||||
|
Label #StatStr {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "FOR 10";
|
||||||
|
Padding: (Horizontal: 10, Vertical: 4);
|
||||||
|
Background: #2a3140;
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #StatAgi {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "AGI 10";
|
||||||
|
Padding: (Horizontal: 10, Vertical: 4);
|
||||||
|
Background: #2a3140;
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #StatInt {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "INT 10";
|
||||||
|
Padding: (Horizontal: 10, Vertical: 4);
|
||||||
|
Background: #2a3140;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #StatsHint {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Attributs dérivés du niveau (système M1 à venir)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #RightColumn {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "POUVOIRS PASSIFS";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #PowersList {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText, Wrap: true);
|
||||||
|
Text: "Aucun";
|
||||||
|
Anchor: (Bottom: 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "MÉTIERS";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #JobsList {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText, Wrap: true);
|
||||||
|
Text: "Aucun";
|
||||||
|
Anchor: (Bottom: 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "SOCIAL";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #GroupLabel {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "Groupe : Aucun";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #GuildLabel {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "Guilde : Aucune";
|
||||||
|
Anchor: (Bottom: 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "STATISTIQUES";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #PlayTimeLabel {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "Temps de jeu : 0 min";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #LastLoginLabel {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "Dernière connexion : —";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #CreatedLabel {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "Création : —";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
@MmorpgBg = #0d0f14(0.96);
|
||||||
|
@MmorpgPanel = #1a1c20;
|
||||||
|
@MmorpgAccent = #f5c842;
|
||||||
|
@MmorpgText = #e8e8ec;
|
||||||
|
@MmorpgMuted = #878e9c;
|
||||||
|
@MmorpgBorder = #3a3f4a;
|
||||||
|
|
||||||
|
@ShellWidth = 920;
|
||||||
|
@ShellHeight = 660;
|
||||||
|
|
||||||
|
@GridStyle = ItemGridStyle(
|
||||||
|
SlotSpacing: 4,
|
||||||
|
SlotSize: 52,
|
||||||
|
SlotIconSize: 44
|
||||||
|
);
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Center;
|
||||||
|
|
||||||
|
Group #MmorpgShell {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: @ShellWidth, Height: @ShellHeight);
|
||||||
|
Background: @MmorpgBg;
|
||||||
|
Padding: (Full: 16);
|
||||||
|
|
||||||
|
Group #Header {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Anchor: (Bottom: 12);
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
|
||||||
|
Label #PlayerName {
|
||||||
|
Style: (FontSize: 20, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Joueur";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ClassLabel {
|
||||||
|
Style: (FontSize: 13, TextColor: @MmorpgText);
|
||||||
|
Text: "Classe";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Right: 24);
|
||||||
|
|
||||||
|
Label #LevelLabel {
|
||||||
|
Style: (FontSize: 14, RenderBold: true, TextColor: @MmorpgText);
|
||||||
|
Text: "Nv. 1";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #MoneyLabel {
|
||||||
|
Style: (FontSize: 13, TextColor: @MmorpgAccent);
|
||||||
|
Text: "0 pièces";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabBar {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Padding: (Bottom: 10);
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
|
||||||
|
Group #TabCharacterWrap {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Horizontal: 12, Vertical: 4);
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
Background: PatchStyle(Color: #00000000);
|
||||||
|
|
||||||
|
TextButton #TabCharacter {
|
||||||
|
Text: "Personnage";
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabCharacterIndicator {
|
||||||
|
Anchor: (Height: 2, Top: 4);
|
||||||
|
Background: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Visible: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabInventoryWrap {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Horizontal: 12, Vertical: 4);
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
Background: PatchStyle(Color: #00000000);
|
||||||
|
|
||||||
|
TextButton #TabInventory {
|
||||||
|
Text: "Inventaire";
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabInventoryIndicator {
|
||||||
|
Anchor: (Height: 2, Top: 4);
|
||||||
|
Background: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Visible: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabSkillsWrap {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Horizontal: 12, Vertical: 4);
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
Background: PatchStyle(Color: #00000000);
|
||||||
|
|
||||||
|
TextButton #TabSkills {
|
||||||
|
Text: "Compétences";
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabSkillsIndicator {
|
||||||
|
Anchor: (Height: 2, Top: 4);
|
||||||
|
Background: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Visible: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabContent {
|
||||||
|
LayoutMode: Left;
|
||||||
|
FlexWeight: 1;
|
||||||
|
|
||||||
|
Group #EquipmentPanel {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: 88, Right: 12);
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "ARMURE";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ArmorSummary {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Armure : 0 / 4";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #ArmorGrid {
|
||||||
|
Anchor: (Width: 56, Height: 232, Bottom: 10);
|
||||||
|
SlotsPerRow: 1;
|
||||||
|
InventorySectionId: -3;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "UTILITAIRE";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #UtilitySummary {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Utilitaire : 0 / 4";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #UtilityGrid {
|
||||||
|
Anchor: (Width: 56, Height: 232);
|
||||||
|
SlotsPerRow: 1;
|
||||||
|
InventorySectionId: -5;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #InventoryCenter {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
|
||||||
|
Label #StorageSummary {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Sac : 0 / 36";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #StorageGrid {
|
||||||
|
Anchor: (Width: 500, Height: 232, Bottom: 10);
|
||||||
|
SlotsPerRow: 9;
|
||||||
|
InventorySectionId: -2;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #HotbarSummary {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Barre rapide : 0 / 9";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #HotbarGrid {
|
||||||
|
Anchor: (Width: 500, Height: 60);
|
||||||
|
SlotsPerRow: 9;
|
||||||
|
InventorySectionId: -1;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #ItemDetails {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: 260, Left: 12);
|
||||||
|
Padding: (Full: 10);
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "DÉTAILS";
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ItemName {
|
||||||
|
Style: (FontSize: 15, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Aucun objet";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ItemRarity {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Sélectionnez un emplacement";
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ItemDescription {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText, Wrap: true);
|
||||||
|
Text: "Cliquez un objet pour afficher ses informations.";
|
||||||
|
Anchor: (Bottom: 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #WeightTrack {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Remplissage du sac";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar #WeightBar {
|
||||||
|
Anchor: (Width: 220, Height: 10);
|
||||||
|
Bar: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Background: PatchStyle(Color: @MmorpgBorder);
|
||||||
|
Value: 0.0;
|
||||||
|
Direction: End;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #WeightValue {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "0 piles";
|
||||||
|
Anchor: (Top: 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #FooterHint {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Échap pour fermer — glisser-déposer actif (inventaire vanilla synchronisé)";
|
||||||
|
Anchor: (Top: 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
@MmorpgPanel = #1a1c20;
|
||||||
|
@MmorpgAccent = #f5c842;
|
||||||
|
@MmorpgText = #e8e8ec;
|
||||||
|
@MmorpgMuted = #878e9c;
|
||||||
|
@MmorpgBorder = #3a3f4a;
|
||||||
|
|
||||||
|
@GridStyle = ItemGridStyle(
|
||||||
|
SlotSpacing: 4,
|
||||||
|
SlotSize: 52,
|
||||||
|
SlotIconSize: 44
|
||||||
|
);
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Left;
|
||||||
|
|
||||||
|
Group #EquipmentPanel {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: 88, Right: 12);
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "ARMURE";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ArmorSummary {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Armure : 0 / 4";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #ArmorGrid {
|
||||||
|
Anchor: (Width: 56, Height: 232, Bottom: 10);
|
||||||
|
SlotsPerRow: 1;
|
||||||
|
InventorySectionId: -3;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "UTILITAIRE";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #UtilitySummary {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Utilitaire : 0 / 4";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #UtilityGrid {
|
||||||
|
Anchor: (Width: 56, Height: 232);
|
||||||
|
SlotsPerRow: 1;
|
||||||
|
InventorySectionId: -5;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #InventoryCenter {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
|
||||||
|
Label #StorageSummary {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Sac : 0 / 36";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #StorageGrid {
|
||||||
|
Anchor: (Width: 500, Height: 232, Bottom: 10);
|
||||||
|
SlotsPerRow: 9;
|
||||||
|
InventorySectionId: -2;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #HotbarSummary {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Barre rapide : 0 / 9";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid #HotbarGrid {
|
||||||
|
Anchor: (Width: 500, Height: 60);
|
||||||
|
SlotsPerRow: 9;
|
||||||
|
InventorySectionId: -1;
|
||||||
|
AreItemsDraggable: true;
|
||||||
|
DisplayItemQuantity: true;
|
||||||
|
RenderItemQualityBackground: true;
|
||||||
|
Style: @GridStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #ItemDetails {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: 260, Left: 12);
|
||||||
|
Padding: (Full: 10);
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "DÉTAILS";
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ItemName {
|
||||||
|
Style: (FontSize: 15, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Aucun objet";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ItemRarity {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Sélectionnez un emplacement";
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #ItemDescription {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText, Wrap: true);
|
||||||
|
Text: "Cliquez un objet pour afficher ses informations.";
|
||||||
|
Anchor: (Bottom: 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #WeightTrack {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Remplissage du sac";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar #WeightBar {
|
||||||
|
Anchor: (Width: 220, Height: 10);
|
||||||
|
Bar: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Background: PatchStyle(Color: @MmorpgBorder);
|
||||||
|
Value: 0.0;
|
||||||
|
Direction: End;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #WeightValue {
|
||||||
|
Style: (FontSize: 9, TextColor: @MmorpgMuted);
|
||||||
|
Text: "0 piles";
|
||||||
|
Anchor: (Top: 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
@MmorpgBg = #0d0f14(0.96);
|
||||||
|
@MmorpgPanel = #1a1c20;
|
||||||
|
@MmorpgAccent = #f5c842;
|
||||||
|
@MmorpgText = #e8e8ec;
|
||||||
|
@MmorpgMuted = #878e9c;
|
||||||
|
|
||||||
|
@ShellWidth = 920;
|
||||||
|
@ShellHeight = 660;
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Center;
|
||||||
|
|
||||||
|
Group #MmorpgShell {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Width: @ShellWidth, Height: @ShellHeight);
|
||||||
|
Background: @MmorpgBg;
|
||||||
|
Padding: (Full: 16);
|
||||||
|
|
||||||
|
Group #Header {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Anchor: (Bottom: 12);
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
|
||||||
|
Label #PlayerName {
|
||||||
|
Style: (FontSize: 20, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Joueur";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #HeaderSubtitle {
|
||||||
|
Style: (FontSize: 13, TextColor: @MmorpgText);
|
||||||
|
Text: "Profil MMORPG";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Anchor: (Right: 24);
|
||||||
|
|
||||||
|
Label #LevelLabel {
|
||||||
|
Style: (FontSize: 14, RenderBold: true, TextColor: @MmorpgText);
|
||||||
|
Text: "Nv. 1";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #MoneyLabel {
|
||||||
|
Style: (FontSize: 13, TextColor: @MmorpgAccent);
|
||||||
|
Text: "0 pièces";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabBar {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Padding: (Bottom: 10);
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
|
||||||
|
Group #TabCharacterWrap {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Horizontal: 12, Vertical: 4);
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
Background: PatchStyle(Color: #00000000);
|
||||||
|
|
||||||
|
TextButton #TabCharacter {
|
||||||
|
Text: "Personnage";
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabCharacterIndicator {
|
||||||
|
Anchor: (Height: 2, Top: 4);
|
||||||
|
Background: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Visible: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabInventoryWrap {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Horizontal: 12, Vertical: 4);
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
Background: PatchStyle(Color: #00000000);
|
||||||
|
|
||||||
|
TextButton #TabInventory {
|
||||||
|
Text: "Inventaire";
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabInventoryIndicator {
|
||||||
|
Anchor: (Height: 2, Top: 4);
|
||||||
|
Background: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Visible: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabSkillsWrap {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Horizontal: 12, Vertical: 4);
|
||||||
|
Anchor: (Right: 6);
|
||||||
|
Background: PatchStyle(Color: #00000000);
|
||||||
|
|
||||||
|
TextButton #TabSkills {
|
||||||
|
Text: "Compétences";
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabSkillsIndicator {
|
||||||
|
Anchor: (Height: 2, Top: 4);
|
||||||
|
Background: PatchStyle(Color: @MmorpgAccent);
|
||||||
|
Visible: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #TabContent {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
|
||||||
|
Group #DynamicTabHost {
|
||||||
|
LayoutMode: Top;
|
||||||
|
FlexWeight: 1;
|
||||||
|
Background: @MmorpgPanel;
|
||||||
|
Padding: (Full: 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #FooterHint {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Échap pour fermer";
|
||||||
|
Anchor: (Top: 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
@MmorpgAccent = #f5c842;
|
||||||
|
@MmorpgText = #e8e8ec;
|
||||||
|
@MmorpgMuted = #878e9c;
|
||||||
|
@MmorpgSlot = #2a3140;
|
||||||
|
@MmorpgLocked = #1e2228;
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Top;
|
||||||
|
|
||||||
|
Label #ClassTitle {
|
||||||
|
Style: (FontSize: 16, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "Classe";
|
||||||
|
Anchor: (Bottom: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #WeaponsLabel {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Armes : —";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #PointsLabel {
|
||||||
|
Style: (FontSize: 12, TextColor: @MmorpgText);
|
||||||
|
Text: "Points de compétence : 0";
|
||||||
|
Anchor: (Bottom: 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "CAPACITÉS DE CLASSE";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Anchor: (Bottom: 14);
|
||||||
|
|
||||||
|
Group #AbilityPanel1 {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgSlot;
|
||||||
|
Anchor: (Width: 250, Right: 10);
|
||||||
|
|
||||||
|
Label #Ability1Key {
|
||||||
|
Style: (FontSize: 12, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "[Q]";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability1Name {
|
||||||
|
Style: (FontSize: 14, RenderBold: true, TextColor: @MmorpgText);
|
||||||
|
Text: "Capacité 1";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability1Cooldown {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Prêt";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability1Desc {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted, Wrap: true);
|
||||||
|
Text: "—";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #AbilityPanel2 {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgSlot;
|
||||||
|
Anchor: (Width: 250, Right: 10);
|
||||||
|
|
||||||
|
Label #Ability2Key {
|
||||||
|
Style: (FontSize: 12, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "[E]";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability2Name {
|
||||||
|
Style: (FontSize: 14, RenderBold: true, TextColor: @MmorpgText);
|
||||||
|
Text: "Capacité 2";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability2Cooldown {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Prêt";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability2Desc {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted, Wrap: true);
|
||||||
|
Text: "—";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #AbilityPanel3 {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgSlot;
|
||||||
|
Anchor: (Width: 250);
|
||||||
|
|
||||||
|
Label #Ability3Key {
|
||||||
|
Style: (FontSize: 12, RenderBold: true, TextColor: @MmorpgAccent);
|
||||||
|
Text: "[R]";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability3Name {
|
||||||
|
Style: (FontSize: 14, RenderBold: true, TextColor: @MmorpgText);
|
||||||
|
Text: "Capacité 3";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability3Cooldown {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Prêt";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Ability3Desc {
|
||||||
|
Style: (FontSize: 11, TextColor: @MmorpgMuted, Wrap: true);
|
||||||
|
Text: "—";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Style: (FontSize: 11, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "BRANCHES DE COMPÉTENCES";
|
||||||
|
Anchor: (Bottom: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
|
LayoutMode: Left;
|
||||||
|
Anchor: (Bottom: 8);
|
||||||
|
|
||||||
|
Group #Branch1 {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgLocked;
|
||||||
|
Anchor: (Width: 180, Right: 10);
|
||||||
|
|
||||||
|
Label #Branch1Title {
|
||||||
|
Style: (FontSize: 12, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Offensive";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Branch1Status {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Verrouillé — M1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #Branch2 {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgLocked;
|
||||||
|
Anchor: (Width: 180, Right: 10);
|
||||||
|
|
||||||
|
Label #Branch2Title {
|
||||||
|
Style: (FontSize: 12, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Défensive";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Branch2Status {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Verrouillé — M1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group #Branch3 {
|
||||||
|
LayoutMode: Top;
|
||||||
|
Padding: (Full: 8);
|
||||||
|
Background: @MmorpgLocked;
|
||||||
|
Anchor: (Width: 180);
|
||||||
|
|
||||||
|
Label #Branch3Title {
|
||||||
|
Style: (FontSize: 12, RenderBold: true, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Utilitaire";
|
||||||
|
Anchor: (Bottom: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #Branch3Status {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted);
|
||||||
|
Text: "Verrouillé — M1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label #SkillsHint {
|
||||||
|
Style: (FontSize: 10, TextColor: @MmorpgMuted, Wrap: true);
|
||||||
|
Text: "Utilisez [Q] [E] [R] (Use Ability 1/2/3) en jeu. L'arbre de talents complet arrive en M1.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,166 @@
|
|||||||
{
|
{
|
||||||
"Id": "Weapon_Battleaxe_Mithril",
|
"Parent": "Template_Weapon_Battleaxe",
|
||||||
|
"TranslationProperties": {
|
||||||
|
"Name": "server.items.Weapon_Battleaxe_Mithril.name"
|
||||||
|
},
|
||||||
|
"Model": "Items/Weapons/Battleaxe/Mithril.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Battleaxe/Mithril_Texture.png",
|
||||||
|
"Icon": "Icons/ItemsGenerated/Weapon_Battleaxe_Mithril.png",
|
||||||
|
"Recipe": {
|
||||||
|
"TimeSeconds": 5,
|
||||||
|
"KnowledgeRequired": false,
|
||||||
|
"Input": [
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Bar_Mithril",
|
||||||
|
"Quantity": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Leather_Storm",
|
||||||
|
"Quantity": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Voidheart",
|
||||||
|
"Quantity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"BenchRequirement": [
|
||||||
|
{
|
||||||
|
"Type": "Crafting",
|
||||||
|
"Categories": [
|
||||||
|
"Weapon_Battleaxe"
|
||||||
|
],
|
||||||
|
"Id": "Weapon_Bench",
|
||||||
|
"RequiredTierLevel": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Quality": "Epic",
|
||||||
|
"ItemLevel": 50,
|
||||||
|
"InteractionVars": {
|
||||||
|
"Swing_Down_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Battleaxe_Primary_Swing_Down_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 73
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Battleaxe_T2_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Battleaxe_T2_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Swing_Down_Left_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Battleaxe_Primary_Swing_Down_Left_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 36
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Battleaxe_T2_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Battleaxe_T2_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Swing_Down_Right_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Battleaxe_Primary_Swing_Down_Right_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 44
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Battleaxe_T2_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Battleaxe_T2_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Downstrike_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Battleaxe_Primary_Downstrike_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 57
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [
|
||||||
|
{
|
||||||
|
"EntityStatId": "SignatureEnergy",
|
||||||
|
"Amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Battleaxe_T2_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Battleaxe_T2_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Signature_Whirlwind_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Battleaxe_Signature_Whirlwind_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 29
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Battleaxe_T2_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Battleaxe_T2_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Guard_Wield": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Battleaxe_Secondary_Guard_Wield",
|
||||||
|
"StaminaCost": {
|
||||||
|
"Value": 20,
|
||||||
|
"CostType": "Damage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MaxDurability": 220,
|
||||||
|
"DurabilityLossOnHit": 0.45,
|
||||||
"Interactions": {
|
"Interactions": {
|
||||||
"Ability1": {
|
"Ability1": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 1 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability2": {
|
"Ability2": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 2 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability3": {
|
"Ability3": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 3 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 3
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,87 @@
|
|||||||
{
|
{
|
||||||
"Id": "Weapon_Crossbow_Ancient_Steel",
|
"Parent": "Template_Weapon_Crossbow",
|
||||||
|
"TranslationProperties": {
|
||||||
|
"Name": "server.items.Weapon_Crossbow_Ancient_Steel.name"
|
||||||
|
},
|
||||||
|
"Model": "Items/Weapons/Crossbow/Iron.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Crossbow/Iron_Rusty_Texture.png",
|
||||||
|
"Icon": "Icons/ItemsGenerated/Weapon_Crossbow_Iron_Rusty.png",
|
||||||
|
"Quality": "Rare",
|
||||||
|
"ItemLevel": 30,
|
||||||
|
"InteractionVars": {
|
||||||
|
"Standard_Projectile_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Crossbow_Damage_Standard_Projectile",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Combo_Projectile_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Crossbow_Damage_Combo_Projectile",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Signature_BigArrow_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Crossbow_Damage_Signature_Projectile",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Guard_Wield": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Crossbow_Secondary_Guard_Wield",
|
||||||
|
"StaminaCost": {
|
||||||
|
"Value": 15.56,
|
||||||
|
"CostType": "Damage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MaxDurability": 180,
|
||||||
|
"DurabilityLossOnHit": 0.28,
|
||||||
"Interactions": {
|
"Interactions": {
|
||||||
"Ability1": {
|
"Ability1": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 1 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability2": {
|
"Ability2": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 2 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability3": {
|
"Ability3": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 3 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 3
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,462 @@
|
|||||||
{
|
{
|
||||||
"Id": "Weapon_Daggers_Mithril",
|
"Parent": "Template_Weapon_Daggers",
|
||||||
|
"TranslationProperties": {
|
||||||
|
"Name": "server.items.Weapon_Daggers_Mithril.name"
|
||||||
|
},
|
||||||
|
"Model": "Items/Weapons/Dagger/Mithril.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Dagger/Mithril_Texture.png",
|
||||||
|
"Icon": "Icons/ItemsGenerated/Weapon_Daggers_Mithril.png",
|
||||||
|
"Recipe": {
|
||||||
|
"TimeSeconds": 5,
|
||||||
|
"KnowledgeRequired": false,
|
||||||
|
"Input": [
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Bar_Mithril",
|
||||||
|
"Quantity": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Leather_Storm",
|
||||||
|
"Quantity": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Voidheart",
|
||||||
|
"Quantity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"BenchRequirement": [
|
||||||
|
{
|
||||||
|
"Type": "Crafting",
|
||||||
|
"Categories": [
|
||||||
|
"Weapon_Daggers"
|
||||||
|
],
|
||||||
|
"Id": "Weapon_Bench",
|
||||||
|
"RequiredTierLevel": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Quality": "Epic",
|
||||||
|
"ItemLevel": 50,
|
||||||
|
"InteractionVars": {
|
||||||
|
"Swing_Left_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Primary_Swing_Left_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Swing_Right_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Primary_Swing_Right_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Stab_Left_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Primary_Stab_Left_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Stab_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Stab_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Stab_Right_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Primary_Stab_Right_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Stab_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Stab_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Pounce_Sweep_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Primary_Pounce_Sweep_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 61
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [
|
||||||
|
{
|
||||||
|
"EntityStatId": "SignatureEnergy",
|
||||||
|
"Amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": [
|
||||||
|
{
|
||||||
|
"Angle": 180,
|
||||||
|
"AngleDistance": 80,
|
||||||
|
"DamageEffects": {
|
||||||
|
"Knockback": {
|
||||||
|
"Direction": {
|
||||||
|
"X": 0,
|
||||||
|
"Y": 2,
|
||||||
|
"Z": -3
|
||||||
|
},
|
||||||
|
"Type": "Force",
|
||||||
|
"Force": 6,
|
||||||
|
"VelocityType": "Set",
|
||||||
|
"VelocityConfig": {
|
||||||
|
"AirResistance": 0.99,
|
||||||
|
"AirResistanceMax": 0.98,
|
||||||
|
"GroundResistance": 0.94,
|
||||||
|
"GroundResistanceMax": 0.3,
|
||||||
|
"Threshold": 3.0,
|
||||||
|
"Style": "Linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorldParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Impact_Critical",
|
||||||
|
"Scale": 1,
|
||||||
|
"RotationOffset": {
|
||||||
|
"Yaw": 180,
|
||||||
|
"Pitch": 0,
|
||||||
|
"Roll": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 92
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Pounce_Stab_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Primary_Pounce_Stab_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 76
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [
|
||||||
|
{
|
||||||
|
"EntityStatId": "SignatureEnergy",
|
||||||
|
"Amount": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Stab_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Stab_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": [
|
||||||
|
{
|
||||||
|
"Angle": 180,
|
||||||
|
"AngleDistance": 80,
|
||||||
|
"DamageEffects": {
|
||||||
|
"Knockback": {
|
||||||
|
"Direction": {
|
||||||
|
"X": 0,
|
||||||
|
"Y": 1,
|
||||||
|
"Z": -2
|
||||||
|
},
|
||||||
|
"Type": "Force",
|
||||||
|
"Force": 5,
|
||||||
|
"VelocityType": "Set",
|
||||||
|
"VelocityConfig": {
|
||||||
|
"AirResistance": 0.99,
|
||||||
|
"AirResistanceMax": 0.98,
|
||||||
|
"GroundResistance": 0.94,
|
||||||
|
"GroundResistanceMax": 0.3,
|
||||||
|
"Threshold": 3.0,
|
||||||
|
"Style": "Linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorldParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Impact_Critical",
|
||||||
|
"Scale": 1,
|
||||||
|
"RotationOffset": {
|
||||||
|
"Yaw": 180,
|
||||||
|
"Pitch": 0,
|
||||||
|
"Roll": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Stab_Impact",
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Stab_Impact"
|
||||||
|
},
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 114
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Razorstrike_Slash_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Signature_Razorstrike_Slash_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 49
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": [
|
||||||
|
{
|
||||||
|
"Angle": 180,
|
||||||
|
"AngleDistance": 80,
|
||||||
|
"DamageEffects": {
|
||||||
|
"Knockback": {
|
||||||
|
"Direction": {
|
||||||
|
"X": 0,
|
||||||
|
"Y": 1,
|
||||||
|
"Z": -2
|
||||||
|
},
|
||||||
|
"Type": "Force",
|
||||||
|
"Force": 7,
|
||||||
|
"VelocityType": "Add",
|
||||||
|
"VelocityConfig": {
|
||||||
|
"AirResistance": 0.99,
|
||||||
|
"AirResistanceMax": 0.98,
|
||||||
|
"GroundResistance": 0.94,
|
||||||
|
"GroundResistanceMax": 0.3,
|
||||||
|
"Threshold": 3.0,
|
||||||
|
"Style": "Linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorldParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Impact_Critical",
|
||||||
|
"Scale": 1,
|
||||||
|
"RotationOffset": {
|
||||||
|
"Yaw": 180,
|
||||||
|
"Pitch": 0,
|
||||||
|
"Roll": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 74
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Razorstrike_Sweep_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Signature_Razorstrike_Sweep_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 61
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": [
|
||||||
|
{
|
||||||
|
"Angle": 180,
|
||||||
|
"AngleDistance": 80,
|
||||||
|
"DamageEffects": {
|
||||||
|
"Knockback": {
|
||||||
|
"Direction": {
|
||||||
|
"X": 0,
|
||||||
|
"Y": 1,
|
||||||
|
"Z": -1
|
||||||
|
},
|
||||||
|
"Type": "Force",
|
||||||
|
"Force": 7,
|
||||||
|
"VelocityType": "Add",
|
||||||
|
"VelocityConfig": {
|
||||||
|
"AirResistance": 0.99,
|
||||||
|
"AirResistanceMax": 0.98,
|
||||||
|
"GroundResistance": 0.94,
|
||||||
|
"GroundResistanceMax": 0.3,
|
||||||
|
"Threshold": 3.0,
|
||||||
|
"Style": "Linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorldParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Impact_Critical",
|
||||||
|
"Scale": 1,
|
||||||
|
"RotationOffset": {
|
||||||
|
"Yaw": 180,
|
||||||
|
"Pitch": 0,
|
||||||
|
"Roll": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 92
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Razorstrike_Lunge_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Signature_Razorstrike_Lunge_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 73
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EntityStatsOnHit": [],
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"AngledDamage": [
|
||||||
|
{
|
||||||
|
"Angle": 180,
|
||||||
|
"AngleDistance": 80,
|
||||||
|
"DamageEffects": {
|
||||||
|
"Knockback": {
|
||||||
|
"Direction": {
|
||||||
|
"X": 0,
|
||||||
|
"Y": 2,
|
||||||
|
"Z": -3
|
||||||
|
},
|
||||||
|
"Type": "Force",
|
||||||
|
"Force": 12,
|
||||||
|
"VelocityType": "Set",
|
||||||
|
"VelocityConfig": {
|
||||||
|
"AirResistance": 0.99,
|
||||||
|
"AirResistanceMax": 0.98,
|
||||||
|
"GroundResistance": 0.94,
|
||||||
|
"GroundResistanceMax": 0.3,
|
||||||
|
"Threshold": 3.0,
|
||||||
|
"Style": "Linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorldParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Impact_Critical",
|
||||||
|
"Scale": 1,
|
||||||
|
"RotationOffset": {
|
||||||
|
"Yaw": 180,
|
||||||
|
"Pitch": 0,
|
||||||
|
"Roll": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"LocalSoundEventId": "SFX_Daggers_T2_Slash_Impact",
|
||||||
|
"WorldSoundEventId": "SFX_Daggers_T2_Slash_Impact"
|
||||||
|
},
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 110
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Guard_Wield": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Daggers_Secondary_Guard_Wield",
|
||||||
|
"StaminaCost": {
|
||||||
|
"Value": 20,
|
||||||
|
"CostType": "Damage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MaxDurability": 220,
|
||||||
|
"DurabilityLossOnHit": 0.1,
|
||||||
"Interactions": {
|
"Interactions": {
|
||||||
"Ability1": {
|
"Ability1": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 1 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability2": {
|
"Ability2": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 2 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability3": {
|
"Ability3": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 3 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 3
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,167 @@
|
|||||||
{
|
{
|
||||||
"Id": "Weapon_Longsword_Mithril",
|
"TranslationProperties": {
|
||||||
|
"Name": "server.items.Weapon_Longsword_Mithril.name"
|
||||||
|
},
|
||||||
|
"Categories": [
|
||||||
|
"Items.Weapons"
|
||||||
|
],
|
||||||
|
"Quality": "Epic",
|
||||||
|
"ItemLevel": 50,
|
||||||
|
"Model": "Items/Weapons/Longsword/Mithril.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Longsword/Mithril_Texture.png",
|
||||||
|
"PlayerAnimationsId": "Longsword",
|
||||||
"Interactions": {
|
"Interactions": {
|
||||||
|
"Primary": "Longsword_Attack",
|
||||||
"Ability1": {
|
"Ability1": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 1 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability2": {
|
"Ability2": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 2 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability3": {
|
"Ability3": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 3 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 3
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"InteractionVars": {
|
||||||
|
"Longsword_Swing_Left_Effect": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Swing_Left_Effect",
|
||||||
|
"Effects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Swing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Longsword_Swing_Left_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Swing_Left_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"Type": "Absolute",
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 70
|
||||||
|
},
|
||||||
|
"RandomPercentageModifier": 0.15
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Longsword_Swing_Right_Effect": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Swing_Left_Effect",
|
||||||
|
"Effects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Swing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Longsword_Swing_Right_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Swing_Right_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"Type": "Absolute",
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 70
|
||||||
|
},
|
||||||
|
"RandomPercentageModifier": 0.15
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Longsword_Swing_Up_Left_Effect": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Swing_Left_Effect",
|
||||||
|
"Effects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Swing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Longsword_Swing_Up_Left_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Swing_Up_Left_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"Type": "Absolute",
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 70
|
||||||
|
},
|
||||||
|
"RandomPercentageModifier": 0.15
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Longsword_Stab_Charged_Effect": "Longsword_Stab_Charged_Effect",
|
||||||
|
"Longsword_Stab_Charged_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Longsword_Stab_Charged_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Physical": 210
|
||||||
|
},
|
||||||
|
"Type": "Absolute",
|
||||||
|
"RandomPercentageModifier": 0.15
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"WorldSoundEventId": "SFX_Longsword_Special_Impact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DroppedItemAnimation": "Items/Animations/Dropped/Dropped_Diagonal_Left.blockyanim",
|
||||||
|
"IconProperties": {
|
||||||
|
"Scale": 0.31,
|
||||||
|
"Translation": [
|
||||||
|
-47,
|
||||||
|
-47
|
||||||
|
],
|
||||||
|
"Rotation": [
|
||||||
|
45,
|
||||||
|
90,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Icon": "Icons/ItemsGenerated/Weapon_Longsword_Mithril.png",
|
||||||
|
"Tags": {
|
||||||
|
"Type": [
|
||||||
|
"Weapon"
|
||||||
|
],
|
||||||
|
"Family": [
|
||||||
|
"Longsword"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MaxDurability": 160,
|
||||||
|
"DurabilityLossOnHit": 0.52,
|
||||||
|
"Weapon": {},
|
||||||
|
"ItemSoundSetId": "ISS_Weapons_Blade_Large"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,313 @@
|
|||||||
{
|
{
|
||||||
"Id": "Weapon_Shortbow_Cobalt",
|
"TranslationProperties": {
|
||||||
|
"Name": "server.items.Weapon_Shortbow_Cobalt.name"
|
||||||
|
},
|
||||||
|
"Parent": "Template_Weapon_Shortbow",
|
||||||
|
"Quality": "Rare",
|
||||||
|
"Model": "Items/Weapons/Bow/Cobalt.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Bow/Cobalt_Texture.png",
|
||||||
|
"Icon": "Icons/ItemsGenerated/Weapon_Bow_Cobalt.png",
|
||||||
|
"ItemLevel": 35,
|
||||||
|
"Recipe": {
|
||||||
|
"TimeSeconds": 4,
|
||||||
|
"KnowledgeRequired": false,
|
||||||
|
"Input": [
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Bar_Cobalt",
|
||||||
|
"Quantity": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Leather_Heavy",
|
||||||
|
"Quantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemId": "Ingredient_Fabric_Scrap_Shadoweave",
|
||||||
|
"Quantity": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"BenchRequirement": [
|
||||||
|
{
|
||||||
|
"Type": "Crafting",
|
||||||
|
"Categories": [
|
||||||
|
"Weapon_Bow"
|
||||||
|
],
|
||||||
|
"Id": "Weapon_Bench",
|
||||||
|
"RequiredTierLevel": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "DiagramCrafting",
|
||||||
|
"Categories": [
|
||||||
|
"Weapons.Bow"
|
||||||
|
],
|
||||||
|
"Id": "Armory"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"InteractionVars": {
|
||||||
|
"Primary_Shoot_Damage_Strength_0": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Primary_Shoot_Damage_Strength_0",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Primary_Shoot_Damage_Strength_1": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Primary_Shoot_Damage_Strength_1",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Primary_Shoot_Damage_Strength_2": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Primary_Shoot_Damage_Strength_2",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Primary_Shoot_Damage_Strength_3": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Primary_Shoot_Damage_Strength_3",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Primary_Shoot_Damage_Strength_4": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Primary_Shoot_Damage_Strength_4",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 23
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TargetedDamage": {
|
||||||
|
"Head": {
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DamageEffects": {
|
||||||
|
"Knockback": {
|
||||||
|
"Type": "Force",
|
||||||
|
"VelocityConfig": {
|
||||||
|
"AirResistance": 0.99,
|
||||||
|
"AirResistanceMax": 0.98,
|
||||||
|
"GroundResistance": 0.94,
|
||||||
|
"GroundResistanceMax": 0.3,
|
||||||
|
"Threshold": 3.0,
|
||||||
|
"Style": "Linear"
|
||||||
|
},
|
||||||
|
"Direction": {
|
||||||
|
"X": 0.0,
|
||||||
|
"Y": 0.5,
|
||||||
|
"Z": -1
|
||||||
|
},
|
||||||
|
"Force": 8,
|
||||||
|
"VelocityType": "Set"
|
||||||
|
},
|
||||||
|
"WorldParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Impact_Critical",
|
||||||
|
"Scale": 1,
|
||||||
|
"RotationOffset": {
|
||||||
|
"Yaw": 180,
|
||||||
|
"Pitch": 0,
|
||||||
|
"Roll": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Signature_Volley_Damage": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Signature_Volley_Damage",
|
||||||
|
"DamageCalculator": {
|
||||||
|
"BaseDamage": {
|
||||||
|
"Projectile": 17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Guard_Wield": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Weapon_Shortbow_Secondary_Guard_Wield",
|
||||||
|
"StaminaCost": {
|
||||||
|
"Value": 12.22,
|
||||||
|
"CostType": "Damage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ItemAppearanceConditions": {
|
||||||
|
"SignatureCharges": [
|
||||||
|
{
|
||||||
|
"Condition": [
|
||||||
|
1,
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"ConditionValueType": "Percent",
|
||||||
|
"Model": "Items/Weapons/Bow/Cobalt_Triple.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Bow/Cobalt_Texture.png",
|
||||||
|
"LocalSoundEventId": "SFX_Bow_T2_Signature_Loop_Local",
|
||||||
|
"WorldSoundEventId": "SFX_Bow_T2_Signature_Loop",
|
||||||
|
"Particles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Bow_Signature_Charge",
|
||||||
|
"TargetNodeName": "ARROW-PLACEHOLDER",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.5,
|
||||||
|
"Y": 0.25,
|
||||||
|
"Z": 0
|
||||||
|
},
|
||||||
|
"Scale": 0.5,
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Bow_Signature_Charge",
|
||||||
|
"TargetNodeName": "ARROW-PLACEHOLDER",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.5,
|
||||||
|
"Y": 0,
|
||||||
|
"Z": 0
|
||||||
|
},
|
||||||
|
"Scale": 0.5,
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Bow_Signature_Charge",
|
||||||
|
"TargetNodeName": "ARROW-PLACEHOLDER",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.5,
|
||||||
|
"Y": -0.25,
|
||||||
|
"Z": 0
|
||||||
|
},
|
||||||
|
"Scale": 0.5,
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ModelVFXId": "Bow_Signature_Status"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SignatureEnergy": [
|
||||||
|
{
|
||||||
|
"Condition": [
|
||||||
|
100,
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"ConditionValueType": "Percent",
|
||||||
|
"Particles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Sword_Signature_Ready",
|
||||||
|
"TargetNodeName": "Handle",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.8
|
||||||
|
},
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Sword_Signature_Status_Spawn",
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Bow_Signature_Status",
|
||||||
|
"TargetNodeName": "Bow-Top",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.55
|
||||||
|
},
|
||||||
|
"TargetEntityPart": "PrimaryItem",
|
||||||
|
"Scale": 1.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Bow_Signature_Status",
|
||||||
|
"TargetNodeName": "Bow-Bot",
|
||||||
|
"TargetEntityPart": "PrimaryItem",
|
||||||
|
"Scale": 1.3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"FirstPersonParticles": [
|
||||||
|
{
|
||||||
|
"SystemId": "Sword_Signature_Ready",
|
||||||
|
"TargetNodeName": "Handle",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.8
|
||||||
|
},
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Sword_Signature_Status_Spawn",
|
||||||
|
"TargetNodeName": "Handle",
|
||||||
|
"TargetEntityPart": "PrimaryItem",
|
||||||
|
"PositionOffset": {
|
||||||
|
"Y": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SystemId": "Bow_Signature_Status_FP",
|
||||||
|
"TargetNodeName": "Bow-Top",
|
||||||
|
"PositionOffset": {
|
||||||
|
"X": 0.5
|
||||||
|
},
|
||||||
|
"TargetEntityPart": "PrimaryItem"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MaxDurability": 150,
|
||||||
|
"DurabilityLossOnHit": 0.58,
|
||||||
"Interactions": {
|
"Interactions": {
|
||||||
"Ability1": {
|
"Ability1": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 1 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability2": {
|
"Ability2": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 2 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability3": {
|
"Ability3": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 3 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 3
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,94 @@
|
|||||||
{
|
{
|
||||||
"Id": "Weapon_Staff_Mithril",
|
"TranslationProperties": {
|
||||||
|
"Name": "server.items.Weapon_Staff_Mithril.name"
|
||||||
|
},
|
||||||
|
"Categories": [
|
||||||
|
"Items.Weapons"
|
||||||
|
],
|
||||||
|
"Quality": "Epic",
|
||||||
|
"ItemLevel": 50,
|
||||||
|
"Model": "Items/Weapons/Staff/Mithril.blockymodel",
|
||||||
|
"Texture": "Items/Weapons/Staff/Mithril_Texture.png",
|
||||||
|
"PlayerAnimationsId": "Staff",
|
||||||
"Interactions": {
|
"Interactions": {
|
||||||
|
"Primary": "Staff_Primary",
|
||||||
|
"Secondary": "Staff_Primary",
|
||||||
"Ability1": {
|
"Ability1": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 1 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability2": {
|
"Ability2": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 2 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 2
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Ability3": {
|
"Ability3": {
|
||||||
"Interactions": [
|
"Interactions": [
|
||||||
{ "Type": "mmorpg_cast_ability", "Slot": 3 }
|
{
|
||||||
|
"Type": "mmorpg_cast_ability",
|
||||||
|
"Slot": 3
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"InteractionVars": {
|
||||||
|
"Staff_Cast_Summon_Charged": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Staff_Cast_Summon_Charged",
|
||||||
|
"Costs": {
|
||||||
|
"Mana": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Staff_Cast_Summon_Cost": {
|
||||||
|
"Interactions": [
|
||||||
|
{
|
||||||
|
"Parent": "Staff_Cast_Cost",
|
||||||
|
"StatModifiers": {
|
||||||
|
"Mana": -50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Staff_Cast_Summon_Launch": "Staff_Cast_Launch",
|
||||||
|
"Staff_Cast_Summon_Effect": "Staff_Cast_Effect",
|
||||||
|
"Staff_Cast_Summon_Fail": "Staff_Cast_Fail",
|
||||||
|
"Spear_Swing_Left_Damage": "Spear_Swing_Left_Damage",
|
||||||
|
"Spear_Swing_Right_Damage": "Spear_Swing_Right_Damage",
|
||||||
|
"Spear_Swing_Left_Effect": "Spear_Swing_Left_Effect",
|
||||||
|
"Spear_Swing_Right_Effect": "Spear_Swing_Right_Effect"
|
||||||
|
},
|
||||||
|
"IconProperties": {
|
||||||
|
"Scale": 0.28,
|
||||||
|
"Translation": [
|
||||||
|
-52,
|
||||||
|
-52
|
||||||
|
],
|
||||||
|
"Rotation": [
|
||||||
|
22.5,
|
||||||
|
45,
|
||||||
|
315
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DroppedItemAnimation": "Items/Animations/Dropped/Dropped_Diagonal_Left.blockyanim",
|
||||||
|
"Icon": "Icons/ItemsGenerated/Weapon_Staff_Mithril.png",
|
||||||
|
"Tags": {
|
||||||
|
"Type": [
|
||||||
|
"Weapon"
|
||||||
|
],
|
||||||
|
"Family": [
|
||||||
|
"Staff"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Weapon": {},
|
||||||
|
"ItemSoundSetId": "ISS_Weapons_Wood"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Group": "com.disklexar",
|
"Group": "com.disklexar",
|
||||||
"Name": "MMORPG",
|
"Name": "MMORPG",
|
||||||
"Version": "0.2.7",
|
"Version": "0.3.5",
|
||||||
"Description": "Serveur MMORPG Hytale — progression, persistance et systèmes sociaux",
|
"Description": "Serveur MMORPG Hytale — progression, persistance et systèmes sociaux",
|
||||||
"Authors": [
|
"Authors": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user