diff --git a/Assets/PerfectWorld/Scripts/Common/EC_Configs.cs b/Assets/PerfectWorld/Scripts/Common/EC_Configs.cs index e39c5b680a..e740bb2b4b 100644 --- a/Assets/PerfectWorld/Scripts/Common/EC_Configs.cs +++ b/Assets/PerfectWorld/Scripts/Common/EC_Configs.cs @@ -787,6 +787,11 @@ namespace BrewMonster public void DefaultUserSettings(ref EC_SYSTEM_SETTING pss, ref EC_VIDEO_SETTING pvs, ref EC_GAME_SETTING pgs, ref EC_BLACKLIST_SETTING pbs, ref EC_COMPUTER_AIDED_SETTING pcas) { + pss = new EC_SYSTEM_SETTING(); + pvs = new EC_VIDEO_SETTING(); + pgs = new EC_GAME_SETTING(); + pbs = new EC_BLACKLIST_SETTING(); + pcas = new EC_COMPUTER_AIDED_SETTING(); pss.Reset(); pvs.Reset(); pgs.Reset(); diff --git a/Assets/PerfectWorld/Scripts/Skills/FLOW_CREATE_SKILL_COMBO_DRAG_DROP.md b/Assets/PerfectWorld/Scripts/Skills/FLOW_CREATE_SKILL_COMBO_DRAG_DROP.md new file mode 100644 index 0000000000..8088d4dfa3 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Skills/FLOW_CREATE_SKILL_COMBO_DRAG_DROP.md @@ -0,0 +1,625 @@ +# Flow: Create Skill Combo by Drag-Drop - C++ Implementation + +## Overview +This document describes the complete flow for creating a skill combo by drag-dropping individual skills into the combo skill list in the skill edit dialog. This is the process of building a combo skill sequence that can later be placed in the quick bar. + +--- + +## Entry Points + +### 1. **Open Skill Edit Dialog** + +**Location:** `DlgSkillSubOther.cpp:49-57` + +The user can open the skill edit dialog in two ways: + +#### **A. Create New Combo Skill** (Button: "Btn_ConNew") +```cpp +// DlgSkillSubOther.cpp:54-57 +void CDlgSkillSubOther::OnCommandNew(const char * szCommand) { + GetGameUIMan()->m_pDlgSkillEdit->SetData(0); // 0 = new combo + GetGameUIMan()->m_pDlgSkillEdit->Show(true); +} +``` + +#### **B. Edit Existing Combo Skill** (Button: "Btn_ConEdi") +```cpp +// DlgSkillSubOther.cpp:49-52 +void CDlgSkillSubOther::OnCommandEdit(const char * szCommand) { + GetGameUIMan()->m_pDlgSkillEdit->SetData(m_nComboSelect); // 1-based combo index + GetGameUIMan()->m_pDlgSkillEdit->Show(true); +} +``` + +**Flow:** +1. User clicks "New" or "Edit" button +2. Set combo skill index (0 for new, 1-based for existing) +3. Show skill edit dialog (`Win_SkillEdit`) + +--- + +## Detailed Flow: Drag-Drop Skill to Combo List + +### **Step 1: Skill Edit Dialog Initialization** + +**Location:** `DlgSkillEdit.cpp:143-231` - `OnShowDialog()` + +When the skill edit dialog is shown, it initializes the combo skill list: + +```cpp +// DlgSkillEdit.cpp:143-207 +void CDlgSkillEdit::OnShowDialog() { + EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings(); + + // If creating new combo (GetData() == 0), find first empty slot + if( GetData() == 0 ) { + for (i = 0; i < EC_COMBOSKILL_NUM; i++) + if( setting.comboSkill[i].nIcon == 0 ) { + SetData(i + 1); // Set to 1-based index + break; + } + } + + // Load existing combo skill data if editing + if( setting.comboSkill[GetData() - 1].nIcon != 0 ) { + // Load existing skills from config + for (i = 0; i < EC_COMBOSKILL_LEN; i++) { + int idSkill = setting.comboSkill[GetData() - 1].idSkill[i]; + if( idSkill > 0 ) { + CECSkill *pSkill = GetHostPlayer()->GetNormalSkill(idSkill); + if (!pSkill) pSkill = GetHostPlayer()->GetEquipSkillByID(idSkill); + GetGameUIMan()->m_pDlgSkillSubOther->SetImage(pImage, pSkill); + } + else { + // Special icons (Attack, Loop Start) + int iType = -idSkill; + SetSpecialIcon(pImage, iType); + } + } + } +} +``` + +**Key Points:** +- Dialog displays empty slots (`Item_1`, `Item_2`, ..., `Item_N`) for combo skill list +- Each slot can hold a skill or special icon (Attack, Loop Start) +- Existing combo skills are loaded from `EC_VIDEO_SETTING.comboSkill[]` + +--- + +### **Step 2: User Drags Skill from Skill List** + +**Location:** `DlgSkillSubOther.cpp:235-253` - `OnEventLButtonDownFixed()` / `OnEventLButtonDownItem()` + +Skills can be dragged from multiple sources: + +#### **A. Fixed Skills** (`Img_InSkill*`) +```cpp +// DlgSkillSubOther.cpp:235-243 +void CDlgSkillSubOther::OnEventLButtonDownFixed(WPARAM wParam, LPARAM lParam, AUIObject * pObj) { + if (pObj->GetDataPtr("ptr_CECSkill") == 0) { + return; // No skill data + } + + A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam(); + GetGameUIMan()->m_ptLButtonDown.x = GET_X_LPARAM(lParam) - p->X; + GetGameUIMan()->m_ptLButtonDown.y = GET_Y_LPARAM(lParam) - p->Y; + GetGameUIMan()->InvokeDragDrop(this, pObj, GetGameUIMan()->m_ptLButtonDown); +} +``` + +#### **B. Item Skills** (`Img_ItemSkill*`) +```cpp +// DlgSkillSubOther.cpp:245-253 +void CDlgSkillSubOther::OnEventLButtonDownItem(WPARAM wParam, LPARAM lParam, AUIObject * pObj) { + if (pObj->GetDataPtr("ptr_CECSkill") == 0) { + return; // No skill data + } + + A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam(); + GetGameUIMan()->m_ptLButtonDown.x = GET_X_LPARAM(lParam) - p->X; + GetGameUIMan()->m_ptLButtonDown.y = GET_Y_LPARAM(lParam) - p->Y; + GetGameUIMan()->InvokeDragDrop(this, pObj, GetGameUIMan()->m_ptLButtonDown); +} +``` + +**Key Points:** +- Skill data is stored via `SetDataPtr(pSkill, "ptr_CECSkill")` when skill icon is created +- Mouse position is captured for drag operation +- `InvokeDragDrop()` initiates the drag-drop system + +--- + +### **Step 3: Drag-Drop System Routes to Handler** + +**Location:** `DlgDragDrop.cpp:79-124` - `OnEventLButtonUp()` + +When user releases mouse button, the drag-drop system processes the drop: + +```cpp +// DlgDragDrop.cpp:79-124 +void CDlgDragDrop::OnEventLButtonUp(WPARAM wParam, LPARAM lParam, AUIObject *pObj) { + PAUIOBJECT pObjSrc = (PAUIOBJECT)pItem->GetDataPtr("ptr_AUIObject"); + if( !pObjSrc ) return; + + // Get destination dialog and object via hit test + PAUIDIALOG pDlgOver; + PAUIOBJECT pObjOver; + GetGameUIMan()->HitTest(x, y, &pDlgOver, &pObjOver, this); + + PAUIDIALOG pDlgSrc = pObjSrc->GetParent(); + + // Route to appropriate handler + OnGeneralScene(pDlgSrc, pObjSrc, pDlgOver, pObjOver, pIvtrSrc); +} +``` + +**Flow:** +1. Get source object from drag item +2. Hit test to find destination dialog and object +3. Route to `OnGeneralScene()` for processing + +--- + +### **Step 4: Skill Drag-Drop Handler** + +**Location:** `DlgDragDrop.cpp:589-627` - `OnSkillDragDrop()` + +The handler checks if drop is on skill edit dialog: + +```cpp +// DlgDragDrop.cpp:589-627 +void CDlgDragDrop::OnSkillDragDrop(PAUIDIALOG pDlgSrc, PAUIOBJECT pObjSrc, + PAUIDIALOG pDlgOver, PAUIOBJECT pObjOver, + CECIvtrItem* pIvtrSrc) { + // Check if dropped on skill edit dialog + if( pDlgOver && stricmp(pDlgOver->GetName(), "Win_SkillEdit") == 0 + && pObjOver && strstr(pObjOver->GetName(), "Item_") + && !strstr(pObjSrc->GetName(), "Img_ConSkill")) // Don't allow combo icons + { + GetGameUIMan()->m_pDlgSkillEdit->DragDropItem(pDlgSrc, pObjSrc, pObjOver); + } + // ... other handlers (quick bar, auto policy, etc.) +} +``` + +**Key Points:** +- Only processes drops on `Win_SkillEdit` dialog +- Destination must be an `Item_*` slot (combo skill list slot) +- Prevents dragging combo skill icons into combo list (prevents recursion) + +--- + +### **Step 5: Add Skill to Combo List** + +**Location:** `DlgSkillEdit.cpp:249-293` - `DragDropItem()` + +This is the core function that adds the skill to the combo list: + +```cpp +// DlgSkillEdit.cpp:249-293 +void CDlgSkillEdit::DragDropItem(PAUIDIALOG pDlgSrc, PAUIOBJECT pObjSrc, PAUIOBJECT pObjOver) { + if( pObjSrc == pObjOver ) + return; // Same object, do nothing + + if( !pObjOver ) { + // Dropped outside - remove skill from source + if( !(pDlgSrc == this && strstr(pObjSrc->GetName(), "Special")) ) + GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjSrc, NULL); + } + else { + // Extract target slot number (1-based) + int iSlot = atoi(pObjOver->GetName() + strlen("Item_")); + + // Check if source is special icon (Attack, Loop Start) + if( strstr(pObjSrc->GetName(), "Special_") ) { + int iType = atoi(pObjSrc->GetName() + strlen("Special_")); + SetSpecialIcon((PAUIIMAGEPICTURE)pObjOver, iType); + } + else { + // Regular skill drag-drop + CECSkill *pSkill = (CECSkill *)pObjSrc->GetDataPtr("ptr_CECSkill"); + int iType = pObjSrc->GetData(); + + if( pDlgSrc != this ) { + // Dragging from external dialog (skill list) + if( iType != 0) + SetSpecialIcon((PAUIIMAGEPICTURE)pObjOver, iType); + else + GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjOver, pSkill); + } + else { + // Dragging within skill edit dialog (reordering) + CECSkill *pSkill1 = (CECSkill *)pObjOver->GetDataPtr("ptr_CECSkill"); + int iType1 = pObjOver->GetData(); + + // Swap skills + if( iType != 0) + SetSpecialIcon((PAUIIMAGEPICTURE)pObjOver, iType); + else + GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjOver, pSkill); + + if( iType1 != 0) + SetSpecialIcon((PAUIIMAGEPICTURE)pObjSrc, iType1); + else + GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjSrc, pSkill1); + } + } + } +} +``` + +**Key Points:** +- **External Drag (from skill list):** Adds skill to target slot, clears source if needed +- **Internal Drag (within dialog):** Swaps skills between slots (reordering) +- **Special Icons:** Can add Attack (`Special_1`) or Loop Start (`Special_2`) icons +- Uses `SetImage()` to display skill icon in target slot +- Skill data is stored via `SetDataPtr(pSkill, "ptr_CECSkill")` + +--- + +### **Step 6: Save Combo Skill Configuration (Local)** + +**Location:** `DlgSkillEdit.cpp:56-85` - `OnCommandConfirm()` + +When user clicks "Confirm" button, the combo skill is saved locally: + +```cpp +// DlgSkillEdit.cpp:56-85 +void CDlgSkillEdit::OnCommandConfirm(const char *szCommand) { + EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings(); + + // Save icon index + setting.comboSkill[GetData() - 1].nIcon = m_nIcon; + + // Save skill sequence + int i; + int j = 0; + for (i = 0; ; i++) { + AString strName; + strName.Format("Item_%d", i + 1); + PAUIIMAGEPICTURE pImage = static_cast(GetDlgItem(strName)); + if (!pImage) break; + + CECSkill *pSkill = (CECSkill *)pImage->GetDataPtr("ptr_CECSkill"); + int iType = pImage->GetData(); + + if( iType == 0 && pSkill ) { + // Regular skill + setting.comboSkill[GetData() - 1].idSkill[j] = pSkill->GetSkillID(); + j++; + } + else { + // Special icon (Attack = -1, Loop Start = -2) + setting.comboSkill[GetData() - 1].idSkill[j] = -iType; + j++; + } + } + + // Save to local config (does NOT send to server immediately) + GetGame()->GetConfigs()->SetVideoSettings(setting); + Show(false); + + // Update combo skill display + GetGameUIMan()->m_pDlgSkillSubOther->UpdateComboSkill(); +} +``` + +**Key Points:** +- Saves icon index (`nIcon`) for combo skill +- Saves skill sequence to `idSkill[]` array: + - **Positive values:** Skill IDs + - **Negative values:** Special icons (-1 = Attack, -2 = Loop Start) +- Updates combo skill icons in main skill dialog +- **Note:** This only saves to local memory (`m_vs`), does NOT send to server yet + +--- + +### **Step 7: Send Combo Skill to Server** + +**Location:** `EC_GameRun.cpp:1942-2061` - `SaveConfigsToServer()` + +Combo skill data is sent to server as part of the user configuration when: +- Player logs out +- Periodically during gameplay +- When explicitly triggered (e.g., via UI command) + +```cpp +// EC_GameRun.cpp:1942-2061 +DWORD CECGameRun::SaveConfigsToServer() { + // ... validation checks ... + + // Calculate total size needed + int iTotalSize = 0; + iTotalSize += sizeof(DWORD); // Version + + // Host player config (shortcuts, etc.) + int iHostSize = 0; + pHost->SaveConfigData(NULL, &iHostSize); + iTotalSize += sizeof(int) + iHostSize; + + // UI layout config + DWORD dwUISize = 0; + pGameUI->GetUserLayout(NULL, dwUISize); + iTotalSize += sizeof(int) + (int)dwUISize; + + // User settings config (INCLUDES COMBO SKILLS!) + int iSettingSize = 0; + g_pGame->GetConfigs()->SaveUserConfigData(NULL, &iSettingSize); + iTotalSize += sizeof(int) + iSettingSize; + + // Allocate buffer and pack data + void* pDataBuf = a_malloctemp(iTotalSize); + // ... pack all data ... + + // Compress and send to server + g_pGame->GetGameSession()->SaveConfigData(pCompBuf, dwCompLen+iVerLen); +} +``` + +**Key Function:** `EC_Configs::SaveUserConfigData()` + +**Location:** `EC_Configs.cpp:569-615` + +```cpp +// EC_Configs.cpp:569-615 +bool CECConfigs::SaveUserConfigData(void* pDataBuf, int* piSize) { + int iTotalSize = 0; + BYTE* pData = (BYTE*)pDataBuf; + + // Version + iTotalSize += sizeof(DWORD); + if (pDataBuf) { + *((DWORD*)pData) = EC_CONFIG_VERSION; + pData += sizeof(DWORD); + } + + // ⭐ SAVE VIDEO SETTINGS (INCLUDES COMBO SKILLS!) + iTotalSize += sizeof(EC_VIDEO_SETTING); + if (pDataBuf) { + *((EC_VIDEO_SETTING*)pData) = m_vs; // Contains comboSkill[] array! + pData += sizeof(EC_VIDEO_SETTING); + } + + // ... other settings (game, blacklist, computer aided) ... +} +``` + +**Key Points:** +- **Entire `EC_VIDEO_SETTING` structure is sent**, which includes: + - `comboSkill[EC_COMBOSKILL_NUM]` array + - Each combo skill contains `nIcon` and `idSkill[]` array +- **Data is compressed** before sending to reduce network traffic +- **Sent asynchronously** - not immediately when combo is saved +- **Server stores** this data and sends it back when player logs in + +--- + +### **Step 8: Server Receives and Stores Combo Skill Data** + +**Location:** Server-side (not in client codebase) + +When server receives the config data: +1. Server decompresses the data +2. Server parses `EC_VIDEO_SETTING` structure +3. Server extracts `comboSkill[]` array +4. Server stores combo skill data in player's account/profile +5. Server sends combo skill data back to client on next login + +**Note:** The server does NOT validate or execute combo skills - they are purely client-side UI configurations. The server only stores them for persistence across sessions. + +--- + +## Complete Flow Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ STEP 1: Open Skill Edit Dialog │ +│ Location: DlgSkillSubOther.cpp:49-57 │ +│ - User clicks "New" or "Edit" button │ +│ - SetData(0) for new, SetData(n) for existing │ +│ - Show Win_SkillEdit dialog │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 2: Dialog Initialization │ +│ Location: DlgSkillEdit.cpp:143-231 │ +│ - Load existing combo skill if editing │ +│ - Display empty slots (Item_1, Item_2, ...) │ +│ - Show special icons (Attack, Loop Start) │ +└──────────────────────┬──────────────────────────────────────┘ + │ + │ (User drags skill from skill list) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 3: Drag Initiation │ +│ Location: DlgSkillSubOther.cpp:235-253 │ +│ - OnEventLButtonDownFixed() or OnEventLButtonDownItem() │ +│ - Capture mouse position │ +│ - InvokeDragDrop(this, pObj, pt) │ +└──────────────────────┬──────────────────────────────────────┘ + │ + │ (User drags to combo list slot) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 4: Drop Processing │ +│ Location: DlgDragDrop.cpp:79-124 │ +│ - OnEventLButtonUp() detects drop │ +│ - Hit test to find destination │ +│ - Route to OnSkillDragDrop() │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 5: Skill Drag Handler │ +│ Location: DlgDragDrop.cpp:589-627 │ +│ - Check: pDlgOver == "Win_SkillEdit" │ +│ - Check: pObjOver name contains "Item_" │ +│ - Call DragDropItem() │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 6: Add Skill to Combo List │ +│ Location: DlgSkillEdit.cpp:249-293 │ +│ - Extract target slot: atoi("Item_N") │ +│ - Get skill: pObjSrc->GetDataPtr("ptr_CECSkill") │ +│ - SetImage(pObjOver, pSkill) │ +│ - Store skill data in slot │ +└──────────────────────┬──────────────────────────────────────┘ + │ + │ (User can drag more skills) + │ (User can reorder by dragging within dialog) + │ + │ (User clicks "Confirm" button) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 6: Save Configuration (Local) │ +│ Location: DlgSkillEdit.cpp:56-85 │ +│ - Loop through all Item_* slots │ +│ - Extract skill IDs or special icon types │ +│ - Save to setting.comboSkill[n].idSkill[] │ +│ - Save icon: setting.comboSkill[n].nIcon │ +│ - SetVideoSettings(setting) → saves to m_vs (local only) │ +│ - UpdateComboSkill() to refresh display │ +└──────────────────────┬──────────────────────────────────────┘ + │ + │ (Later: on logout, periodically, or explicit save) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 7: Send to Server │ +│ Location: EC_GameRun.cpp:1942-2061 │ +│ - SaveConfigsToServer() called │ +│ - SaveUserConfigData() includes entire EC_VIDEO_SETTING │ +│ - Combo skills included in m_vs.comboSkill[] │ +│ - Data compressed and sent via SaveConfigData() │ +└──────────────────────┬──────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ STEP 8: Server Storage │ +│ Location: Server-side (not in client code) │ +│ - Server receives compressed config data │ +│ - Server decompresses and parses EC_VIDEO_SETTING │ +│ - Server stores comboSkill[] in player account │ +│ - Server sends back on next login │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Data Structures + +### **EC_VIDEO_SETTING.comboSkill[]** +```cpp +struct COMBO_SKILL { + short nIcon; // Icon index (1-based, 0 = empty) + short idSkill[EC_COMBOSKILL_LEN]; // Skill sequence array + // idSkill values: + // > 0: Skill ID + // -1: Attack (SID_ATTACK) + // -2: Loop Start (SID_LOOPSTART) + // 0: Empty slot +}; +``` + +### **Skill Storage in UI Objects** +- **Skill Pointer:** `pImage->SetDataPtr(pSkill, "ptr_CECSkill")` +- **Special Icon Type:** `pImage->SetData(iType)` where `iType = 1` (Attack) or `2` (Loop Start) +- **Regular Skill:** `pImage->SetData(0)` + +--- + +## Key Constants + +- **EC_COMBOSKILL_NUM:** Maximum number of combo skill groups (typically 8-12) +- **EC_COMBOSKILL_LEN:** Maximum number of skills per combo (typically 8-16) +- **SID_ATTACK:** Special skill ID for normal attack (typically -1) +- **SID_LOOPSTART:** Special skill ID for loop start marker (typically -2) + +--- + +## Important Notes + +1. **Index Conversion:** + - Combo group index is **1-based** in UI (`GetData()` returns 1, 2, 3, ...) + - Array index is **0-based** in config (`comboSkill[GetData() - 1]`) + +2. **Skill Sources:** + - Skills can be dragged from: + - `Win_SkillSubOther` (Fixed skills, Item skills) + - `Win_SkillSubPool` (Skill pool) + - `Win_SkillSubListSkillItem` (Skill list items) + +3. **Special Icons:** + - Attack icon (`Special_1`) = Normal attack in combo + - Loop Start icon (`Special_2`) = Marks where combo should loop back + +4. **Reordering:** + - Users can drag skills within the skill edit dialog to reorder + - `DragDropItem()` handles swapping when `pDlgSrc == this` + +5. **Validation:** + - Combo skill icons cannot be dragged into combo list (prevents recursion) + - Empty slots are allowed (stored as 0 in `idSkill[]`) + +--- + +## Related Files + +- **DlgSkillEdit.cpp/h:** Skill edit dialog implementation +- **DlgSkillSubOther.cpp/h:** Skill sub-other panel (source of skills) +- **DlgDragDrop.cpp/h:** Drag-drop system handler +- **EC_Configs.cpp/h:** Configuration management (stores combo skills locally) +- **EC_GameRun.cpp/h:** Game run logic (saves configs to server) +- **EC_GameSession.cpp/h:** Network session (sends config data to server) +- **EC_ComboSkill.cpp/h:** Combo skill execution logic + +--- + +## Network Protocol Details + +### **Protocol Command** +- **Command:** `SaveConfigData` (via `EC_GameSession::SaveConfigData()`) +- **Data Format:** Compressed binary blob containing: + 1. Version (DWORD) + 2. Host player config (shortcuts, etc.) + 3. UI layout config + 4. **User settings config (includes combo skills)** + +### **Data Structure Sent** +```cpp +struct USER_CONFIG_DATA { + DWORD version; // USERCFG_VERSION + int hostConfigSize; + BYTE hostConfig[hostConfigSize]; // Host player shortcuts, etc. + int uiLayoutSize; + BYTE uiLayout[uiLayoutSize]; // UI window positions, etc. + int settingsSize; + BYTE settings[settingsSize]; // EC_VIDEO_SETTING + other settings +}; + +// Inside settings: +struct EC_VIDEO_SETTING { + // ... other video settings ... + EC_COMBOSKILL comboSkill[EC_COMBOSKILL_NUM]; // ⭐ COMBO SKILLS HERE! +}; + +struct EC_COMBOSKILL { + short nIcon; // Icon index (0 = empty) + short idSkill[EC_COMBOSKILL_LEN]; // Skill sequence +}; +``` + +### **When Data is Sent** +1. **On Logout:** `DlgExit.cpp` triggers `SaveConfigsToServer()` +2. **Periodically:** Game may auto-save configs during gameplay +3. **Explicit Save:** UI commands can trigger save (e.g., `EC_GameUICommand.cpp:143`) +4. **Pending Actions:** Various pending actions append save requests + +### **Important Notes** +- **Combo skills are NOT sent immediately** when saved - they're queued for next config save +- **Server does NOT validate combo skills** - they're client-side UI configurations +- **Combo skills are NOT executed on server** - execution is purely client-side +- **Server only stores for persistence** - so player's combo skills persist across sessions diff --git a/Assets/PerfectWorld/Scripts/Skills/FLOW_CREATE_SKILL_COMBO_DRAG_DROP.md.meta b/Assets/PerfectWorld/Scripts/Skills/FLOW_CREATE_SKILL_COMBO_DRAG_DROP.md.meta new file mode 100644 index 0000000000..4e70ea6673 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Skills/FLOW_CREATE_SKILL_COMBO_DRAG_DROP.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4abca7ea223861b47b89d130f6902111 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgDragDrop.cs b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgDragDrop.cs new file mode 100644 index 0000000000..53dec1542c --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgDragDrop.cs @@ -0,0 +1,28 @@ +using BrewMonster.Network; +using BrewMonster.UI; +using UnityEngine; + +namespace BrewMonster +{ + public class CDlgDragDrop : MonoBehaviour + { +#if UNITY_EDITOR + private void Update() + { + if (Input.GetKeyDown(KeyCode.S)) + OnSkillDragDrop(); + } +#endif + public void OnSkillDragDrop() + { + var iSlot = 2; + var nCombo = 2; + CECShortcutSet pSCS = CECUIManager.Instance.GetInGameUIMan().GetSCSByDlg(1); + if (pSCS.GetShortcut(iSlot - 1) == null || !EC_Game.GetConfigs().GetGameSettings().bLockQuickBar) + pSCS.CreateSkillGroupShortcut(iSlot - 1, nCombo - 1); + } + } +} + +/*AString s = WC2AS(*pstr); // convert wide -> ansi/utf8 tùy macro bạn định nghĩa +a_LogOutput(0, "HoangDev: 1 n=%d, pstr=%s", n, static_cast (s));*/ \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgDragDrop.cs.meta b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgDragDrop.cs.meta new file mode 100644 index 0000000000..40e16805bb --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgDragDrop.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ad855801e0cddfe46a48b448016ee952 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubOther.cs b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubOther.cs index 17ebfb9e2b..eb60b4192b 100644 --- a/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubOther.cs +++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/CDlgSkillSubOther.cs @@ -158,7 +158,7 @@ namespace BrewMonster.UI // Update combo skill icons - called from DlgSkill public void UpdateComboSkill() { - CECConfigs configs = EC_Game.GetConfigs(); + /*CECConfigs configs = EC_Game.GetConfigs(); if (configs == null) return; @@ -195,7 +195,7 @@ namespace BrewMonster.UI // pImage->SetHint(""); } } - } + }*/ } // Update fixed skill display diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs index 5a717386ea..b76ae95487 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs @@ -32,9 +32,7 @@ namespace BrewMonster public bool UpdateShortcuts() { CECShortcut pSC; - Image skillImage; CECSCSkill pSCSkill; - int iIconFile, nMax; AUIImagePicture pCell; CECSkill pSkill = new CECSkill(-1, -1); AUIClockIcon pClock; @@ -68,7 +66,6 @@ namespace BrewMonster { if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILL) { - iIconFile = (int)EC_GAMEUI_ICONS.ICONS_SKILL; pSCSkill = (CECSCSkill)pSC; pSkill = pSCSkill.GetSkill(); if (false/*m_bDelGoblinSkillSC && GNET::ElementSkill::IsGoblinSkill(pSkill->GetSkillID())*/) @@ -504,11 +501,11 @@ namespace BrewMonster pszPanel.Add(dlgName); } } - private int GetCurPanel1() + public int GetCurPanel1() { return m_nCurPanel1; } - private int GetCurPanel2() + public int GetCurPanel2() { return m_nCurPanel2; } diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs index d21d1c050c..f8579c4dcd 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs @@ -10,7 +10,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Unity.VisualScripting; using UnityEngine; +using static UnityEngine.Rendering.DebugUI; namespace BrewMonster.UI { @@ -60,7 +62,24 @@ namespace BrewMonster.UI } m_pDlgNPC.PopupNPCDialog(pTalk); } - + public CECShortcutSet GetSCSByDlg(int indexPanel) + { + CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer(); + CDlgQuickBar cDlgQuickBar = CECUIManager.Instance.GetCDlgQuickBar(); + CECShortcutSet pSCS = null; + int index = (0); + if (indexPanel == 1) + { + int panel = (index < 0 ? cDlgQuickBar.GetCurPanel1() : index) - 1; + pSCS = pHost.GetShortcutSet1(0); + } + else + { + int panel = (index < 0 ? cDlgQuickBar.GetCurPanel2() : index) - 1; + pSCS = pHost.GetShortcutSet2(panel); + } + return pSCS; + } // 弹出任务完成对话框(到达/离开地点等触发) // Popup task-finish dialog (reach/leave site, etc.) // C++: pTask->PopupTaskFinishDialog(taskId, &awardTalk); then OnUIDialogEnd() notifies server. public bool PopupTaskFinishDialog(uint taskId, talk_proc pTalk) diff --git a/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillEdit.cs b/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillEdit.cs new file mode 100644 index 0000000000..9a76761aa2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillEdit.cs @@ -0,0 +1,80 @@ +using Animancer; +using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay; +using BrewMonster.Network; +using BrewMonster.UI; +using System; +using UnityEngine; + +namespace BrewMonster +{ + public class CDlgSkillEdit : AUIDialog + { + int m_nIcon; + + // Start is called once before the first execution of Update after the MonoBehaviour is created + public override void Start() + { + m_nIcon = 1; + } +#if UNITY_EDITOR + public override void Update() + { + if (Input.GetKeyDown(KeyCode.T)) + { + OnShowDialog(); + OnCommandConfirm(); + } + } +#endif + public void OnShowDialog() + { + EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings(); + if (GetData() == 0) + { + for (uint i = 0; i < EC_ConfigConstants.EC_COMBOSKILL_NUM; i++) + { + if (setting.comboSkill[i].nIcon == 0) + { + SetData(i + 1); + break; + } + } + if (GetData() == 0) + { + //Show(false); + return; + } + } + } + public void OnCommandConfirm() + { + EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings(); + setting.comboSkill[GetData() - 1].nIcon = (byte)m_nIcon; + int i; + int j = 0; + for (i = 0; i < 2; i++) + { + /* AString strName; + strName.Format("Item_%d", i + 1); + PAUIIMAGEPICTURE pImage = static_cast(GetDlgItem(strName)); + if (!pImage) break;*/ + + //CECSkill pSkill = (CECSkill)pImage.GetDataPtr("ptr_CECSkill"); + //int iType = pImage->GetData(); + if (true/*iType == 0 && pSkill != null*/) + { + setting.comboSkill[GetData() - 1].idSkill[j] = (short)(i +1) /*pSkill.GetSkillID()*/; + j++; + } + else + { + //setting.comboSkill[GetData() - 1].idSkill[j] = -iType; + j++; + } + } + EC_Game.GetConfigs().SetVideoSettings(setting); + //Show(false); + CECUIManager.Instance.m_pDlgSkillSubOther.UpdateComboSkill(); + } + } +} diff --git a/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillEdit.cs.meta b/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillEdit.cs.meta new file mode 100644 index 0000000000..427f5ad1e3 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/SkillUI/CDlgSkillEdit.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b46a84d2ff078524f974fb6d60019d8c \ No newline at end of file diff --git a/Assets/Scenes/a61.unity b/Assets/Scenes/a61.unity index c4f8966cc0..53c3655cc3 100644 --- a/Assets/Scenes/a61.unity +++ b/Assets/Scenes/a61.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:def3c6a4f58ef9b5c9558210328e5b6f3ed11bfee5b577271ee43fa110616b82 -size 200521689 +oid sha256:80d98663fdebe84a0752357c66954e1834c6d78f1e61ca61df0122a35b5116c7 +size 200524021 diff --git a/Assets/Scripts/CECGameRun.cs b/Assets/Scripts/CECGameRun.cs index dd4c09a8e6..f0ebb28181 100644 --- a/Assets/Scripts/CECGameRun.cs +++ b/Assets/Scripts/CECGameRun.cs @@ -528,7 +528,10 @@ public partial class CECGameRun return szRet; } public int GetGameState() { return m_iGameState; } - + public void SaveConfigsToServer() + { + + } } public enum GameState diff --git a/Assets/Scripts/CECUIManager.cs b/Assets/Scripts/CECUIManager.cs index aa099bab68..3812ccae20 100644 --- a/Assets/Scripts/CECUIManager.cs +++ b/Assets/Scripts/CECUIManager.cs @@ -25,6 +25,7 @@ public class CECUIManager : MonoSingleton [SerializeField] private UnityEngine.UI.Button btnSecondClick; [SerializeField] CDlgQuickBar m_pDlgQuickBar1; + public CDlgSkillSubOther m_pDlgSkillSubOther; CDlgMessageBox m_pDlgMessageBox; public CDlgSkillAction m_pDlgSkillAction;