Jump to content

Kidro

Active Member
  • Posts

    58
  • Joined

  • Last visited

  • Days Won

    1
  • Feedback

    0%

Kidro last won the day on July 16 2020

Kidro had the most liked content!

1 Follower

About Kidro

  • Birthday 02/25/2000

Recent Profile Visitors

2524 profile views

Kidro's Achievements

Community Regular

Community Regular (8/16)

  • Very Popular Rare
  • Reacting Well
  • Dedicated
  • First Post
  • Collaborator Rare

Recent Badges

188

Reputation

  1. replace if (pkInstTarget->GetInstanceType() != TYPE_ENEMY) continue; with if (pkInstTarget->GetInstanceType() != CActorInstance::TYPE_ENEMY) continue; if you get error 'TYPE_ENEMY': undeclared identifier
  2. A while ago I had to code this in a short amount of time, and since I didn’t have much time, I made it using the simplest method. char_battle.cpp inside: bool CHARACTER::Damage before: if (pAttacker) SendDamagePacket(pAttacker, dam, damageFlag); add: std::map<int, int> damageLimits = { {8009, 25000}, {8010, 30000}, {8011, 35000}, {8012, 40000}, {8013, 45000}, {8014, 50000}, {8024, 55000}, {8025, 60000}, {8026, 70000}, {8027, 100000}, {8127, 100000}, {8158, 100000} }; auto it = damageLimits.find(GetRaceNum()); if (it != damageLimits.end() && dam > it->second) { dam = it->second; } client part: uitarget.py search: GRADE_NAME = { nonplayer.PAWN : localeInfo.TARGET_LEVEL_PAWN, nonplayer.S_PAWN : localeInfo.TARGET_LEVEL_S_PAWN, nonplayer.KNIGHT : localeInfo.TARGET_LEVEL_KNIGHT, nonplayer.S_KNIGHT : localeInfo.TARGET_LEVEL_S_KNIGHT, nonplayer.BOSS : localeInfo.TARGET_LEVEL_BOSS, nonplayer.KING : localeInfo.TARGET_LEVEL_KING, } add: LIMITE_NIVEL = { 8009: "25000", 8010: "30000", 8011: "35000", 8012: "40000", 8013: "45000", 8014: "50000", 8024: "55000", 8025: "60000", 8026: "70000", 8027: "100000", 8127: "100000", 8158: "100000", } inside: def __init__(self): search: closeButton = ui.Button() add before: damageLimitText = ui.TextLine() damageLimitText.SetParent(self) damageLimitText.SetOutline() damageLimitText.SetHorizontalAlignRight() damageLimitText.Hide() self.damageLimitText = damageLimitText after whole function: def __ShowMainCharacterMenu(self): add: def ShowDamageLimitText(self, vid): vnum = nonplayer.GetRaceNumByVID(vid) if vnum in self.LIMITE_NIVEL: limita_damage = self.LIMITE_NIVEL[vnum] self.damageLimitText.SetText("Limita damage este: " + str(limita_damage)) else: self.damageLimitText.SetText("Nu exista limita de damage pentru acest metin.") self.damageLimitText.Show() replace: def UpdatePosition(self): with: def UpdatePosition(self): self.SetPosition(wndMgr.GetScreenWidth() / 2 - self.GetWidth() / 2, 10) if chr.GetInstanceType(self.vid) == chr.INSTANCE_TYPE_STONE: self.SetSize(self.GetWidth(), self.GetHeight() + 20) self.ShowDamageLimitText(self.vid) self.damageLimitText.SetPosition(self.GetWidth() / 2, 30) self.damageLimitText.Show() else: self.damageLimitText.Hide()
      • 3
      • Love
      • Good
      • Metin2 Dev
  3. The multi-split system is publicly available on the internet, but here you have it adapted for the special inventory and with a fix for the crash-core issue. Previously, using command /split_items 0 0 0 0 could crash any server running this system, but this vulnerability has now been resolved in the fixed version provided here. uipickitem.py import wndMgr import ui import ime import localeInfo class PickItemDialog(ui.ScriptWindow): def __init__(self): ui.ScriptWindow.__init__(self) self.unitValue = 1 self.maxValue = 0 self.eventAccept = 0 self.doAll = False def __del__(self): ui.ScriptWindow.__del__(self) def LoadDialog(self): try: pyScrLoader = ui.PythonScriptLoader() pyScrLoader.LoadScriptFile(self, "UIScript/PickItemDialog.py") except: import exception exception.Abort("MoneyDialog.LoadDialog.LoadScript") try: self.board = self.GetChild("board") self.maxValueTextLine = self.GetChild("max_value") self.pickValueEditLine = self.GetChild("money_value") self.acceptButton = self.GetChild("accept_button") self.cancelButton = self.GetChild("cancel_button") except: import exception exception.Abort("MoneyDialog.LoadDialog.BindObject") self.pickValueEditLine.SetReturnEvent(ui.__mem_func__(self.OnAccept)) self.pickValueEditLine.SetEscapeEvent(ui.__mem_func__(self.Close)) self.acceptButton.SetEvent(ui.__mem_func__(self.OnAccept)) self.cancelButton.SetEvent(ui.__mem_func__(self.Close)) self.board.SetCloseEvent(ui.__mem_func__(self.Close)) self.checkBox = ui.CheckBoxClassic() self.checkBox.SetParent(self) self.checkBox.SetPosition(25, 50) self.checkBox.SetWindowVerticalAlignBottom() self.checkBox.SetEvent(ui.__mem_func__(self.SetSplitFunction), "ON_CHECK", True) self.checkBox.SetEvent(ui.__mem_func__(self.SetSplitFunction), "ON_UNCKECK", False) self.checkBox.SetCheckStatus(self.doAll) self.checkBox.SetTextInfo("Multiple Split") self.checkBox.Show() def SplitClear(self): self.doAll = False self.checkBox.SetCheckStatus(self.doAll) def SetSplitFunction(self, checkType, autoFlag): self.doAll = autoFlag def IsSplitAll(self): return self.doAll def Destroy(self): self.ClearDictionary() self.eventAccept = 0 self.maxValue = 0 self.pickValueEditLine = 0 self.acceptButton = 0 self.cancelButton = 0 self.board = None self.doAll = False def SetTitleName(self, text): self.board.SetTitleName(text) def SetAcceptEvent(self, event): self.eventAccept = event def SetMax(self, max): self.pickValueEditLine.SetMax(max) def Open(self, maxValue, unitValue=1): if localeInfo.IsYMIR() or localeInfo.IsCHEONMA() or localeInfo.IsHONGKONG(): unitValue = "" width = self.GetWidth() (mouseX, mouseY) = wndMgr.GetMousePosition() if mouseX + width/2 > wndMgr.GetScreenWidth(): xPos = wndMgr.GetScreenWidth() - width elif mouseX - width/2 < 0: xPos = 0 else: xPos = mouseX - width/2 self.SetPosition(xPos, mouseY - self.GetHeight() - 20) if localeInfo.IsARABIC(): self.maxValueTextLine.SetText("/" + str(maxValue)) else: self.maxValueTextLine.SetText(" / " + str(maxValue)) self.pickValueEditLine.SetText(str(unitValue)) self.pickValueEditLine.SetFocus() ime.SetCursorPosition(1) self.unitValue = unitValue self.maxValue = maxValue self.Show() self.SetTop() def Close(self): self.pickValueEditLine.KillFocus() self.Hide() def OnAccept(self): text = self.pickValueEditLine.GetText() if len(text) > 0 and text.isdigit(): money = int(text) money = min(money, self.maxValue) if money > 0: if self.eventAccept: self.eventAccept(money) self.Close() in uiinventory.py replace import uiPickMoney with import uiPickItem replace dlgPickMoney = uiPickMoney.PickMoneyDialog() with dlgPickMoney = uiPickItem.PickItemDialog() search: def SelectEmptySlot(self, selectedSlotPos): in this function search for: if player.SLOT_TYPE_INVENTORY == attachedSlotType: and replace: self.__SendMoveItemPacket(attachedSlotPos, selectedSlotPos, attachedCount) with: if self.dlgPickMoney and self.dlgPickMoney.IsSplitAll(): net.SendChatPacket("/split_items %d %d %d 0" % (attachedSlotPos, attachedCount, selectedSlotPos)) self.dlgPickMoney.SplitClear() else: self.__SendMoveItemPacket(attachedSlotPos, selectedSlotPos, attachedCount) uispecialstorage.py: inside def __init__(self): add self.dlgSplitItems = None before self.SetInventoryPage(0) self.SetCategoryPage(0) self.RefreshItemSlot() self.RefreshBagSlotWindow() add self.dlgSplitItems = uiPickItem.PickItemDialog() self.dlgSplitItems.LoadDialog() self.dlgSplitItems.Hide() inside def Destroy(self): add self.dlgSplitItems.Destroy() self.dlgSplitItems = None inside def Close(self): add if self.dlgSplitItems: self.dlgSplitItems.Close() replace def OnPickItem(self, count): with this: def OnPickItem(self, count): itemSlotIndex = self.dlgSplitItems.itemGlobalSlotIndex selectedItemVNum = player.GetItemIndex(self.SLOT_WINDOW_TYPE[self.categoryPageIndex]["window"], itemSlotIndex) mouseModule.mouseController.AttachObject(self, self.SLOT_WINDOW_TYPE[self.categoryPageIndex]["slot"], itemSlotIndex, selectedItemVNum, count) search def SelectItemSlot(self, itemSlotIndex): before else: mouseModule.mouseController.AttachObject(self, self.SLOT_WINDOW_TYPE[self.categoryPageIndex]["slot"], itemSlotIndex, selectedItemVNum, itemCount) self.wndItem.SetUseMode(False) snd.PlaySound("sound/ui/pick.wav") add elif app.IsPressed(app.DIK_LSHIFT): if itemCount > 1: self.dlgSplitItems.SetTitleName(localeInfo.PICK_ITEM_TITLE) self.dlgSplitItems.SetAcceptEvent(ui.__mem_func__(self.OnPickItem)) self.dlgSplitItems.Open(itemCount) self.dlgSplitItems.itemGlobalSlotIndex = itemSlotIndex inside def SelectEmptySlot(self, selectedSlotPos): replace elif player.RESERVED_WINDOW != attachedInvenType: with elif player.RESERVED_WINDOW != attachedInvenType: itemCount = player.GetItemCount(attachedInvenType, attachedSlotPos) attachedCount = mouseModule.mouseController.GetAttachedItemCount() if self.dlgSplitItems and self.dlgSplitItems.IsSplitAll(): net.SendChatPacket("/split_items %d %d %d %d" % (attachedSlotPos, attachedCount, selectedSlotPos, self.categoryPageIndex+1)) self.dlgSplitItems.SplitClear() else: self.__SendMoveItemPacket(attachedInvenType, attachedSlotPos, self.SLOT_WINDOW_TYPE[self.categoryPageIndex]["window"], selectedSlotPos, attachedCount) now server source part: in cmd.cpp search and after ACMD(do_item); add ACMD(do_split_items); search and after { "item", do_item, 0, POS_DEAD, GM_IMPLEMENTOR }, add { "split_items", do_split_items, 0, POS_DEAD, GM_PLAYER }, in cmd_general.cpp add ACMD(do_split_items) { if (!ch) return; char arg1[256], arg2[256], arg3[256], arg4[256]; four_arguments(argument, arg1, sizeof(arg1), arg2, sizeof(arg2), arg3, sizeof(arg3), arg4, sizeof(arg4)); if (!*arg1 || !*arg2 || !*arg3 || !*arg4) { ch->ChatPacket(CHAT_TYPE_INFO, "Usage: /split_items <cell> <count> <destCell> <window>"); return; } if (!ch->CanWarp()) { ch->ChatPacket(CHAT_TYPE_INFO, "Close all windows and wait a few seconds before using this."); return; } int cell = 0, destCell = 0, window = 0; int count = 0; str_to_number(cell, arg1); str_to_number(count, arg2); str_to_number(destCell, arg3); str_to_number(window, arg4); if (cell < 0 || cell >= INVENTORY_MAX_NUM || destCell < 0 || destCell >= INVENTORY_MAX_NUM) { ch->ChatPacket(CHAT_TYPE_INFO, "Invalid cell index."); return; } if (count <= 0 || count > 200) { ch->ChatPacket(CHAT_TYPE_INFO, "Invalid item count."); return; } if (window < 0 || window > 4) { ch->ChatPacket(CHAT_TYPE_INFO, "Invalid inventory window."); return; } // ch->SetIgnoreMoveItemCooldown(true); auto TrySplit = [&](LPITEM item, int invType, int (*getEmpty)(CHARACTER*, LPITEM)) -> void { if (!item) { ch->ChatPacket(CHAT_TYPE_INFO, "No item found in that slot."); return; } WORD itemCount = item->GetCount(); if (itemCount <= 1) { ch->ChatPacket(CHAT_TYPE_INFO, "Item cannot be split."); return; } const BYTE itemSize = item->GetSize(); int loops = 0; while (item && itemCount > 1) { if (++loops > 500) break; if (count > itemCount) count = itemCount; int emptyPos = -1; if (getEmpty) emptyPos = getEmpty(ch, item); else emptyPos = ch->GetEmptyInventoryFromIndex(destCell, itemSize); if (emptyPos < 0) { ch->ChatPacket(CHAT_TYPE_INFO, "No empty slot found."); break; } if (!ch->MoveItem(TItemPos(invType, cell), TItemPos(invType, emptyPos), count)) { ch->ChatPacket(CHAT_TYPE_INFO, "Failed to move item."); break; } item = nullptr; switch (invType) { case INVENTORY: item = ch->GetInventoryItem(cell); break; case UPGRADE_INVENTORY: item = ch->GetUpgradeInventoryItem(cell); break; case BOOK_INVENTORY: item = ch->GetBookInventoryItem(cell); break; case STONE_INVENTORY: item = ch->GetStoneInventoryItem(cell); break; case CHEST_INVENTORY: item = ch->GetChestInventoryItem(cell); break; } if (!item) break; itemCount = item->GetCount(); } }; switch (window) { case 0: TrySplit(ch->GetInventoryItem(cell), INVENTORY, nullptr); break; case 1: TrySplit(ch->GetUpgradeInventoryItem(cell), UPGRADE_INVENTORY, [](CHARACTER* c, LPITEM i) { return c->GetEmptyUpgradeInventory(i); }); break; case 2: TrySplit(ch->GetBookInventoryItem(cell), BOOK_INVENTORY, [](CHARACTER* c, LPITEM i) { return c->GetEmptyBookInventory(i); }); break; case 3: TrySplit(ch->GetStoneInventoryItem(cell), STONE_INVENTORY, [](CHARACTER* c, LPITEM i) { return c->GetEmptyStoneInventory(i); }); break; case 4: TrySplit(ch->GetChestInventoryItem(cell), CHEST_INVENTORY, [](CHARACTER* c, LPITEM i) { return c->GetEmptyChestInventory(i); }); break; default: ch->ChatPacket(CHAT_TYPE_INFO, "Invalid window index."); break; } }
      • 1
      • Love
  4. char.h bool IsExchanging() const { return m_pkExchange != nullptr; } char.cpp in ::WarpSet if (IsExchanging()) { return false; } Hi, You’re right that the issue comes from DB-cache synchronization — especially when a player changes channel or warps during an exchange. However, my modification isn’t just a redundant loop. It forces an immediate synchronous save, which removes the actual cause of the duplication: the item still being owned by the previous player in DB memory. And yes — if the server crashes exactly during the save operation, that’s a DB issue, not an Exchange logic problem.
  5. Hey everyone While working on a server (3k+ online) that uses my serverfiles, some players found a strange issue in the exchange system — a “ghost dupe” situation that made it look like items were duplicated after a trade. In some cases, an item given from Player A to Player B would remain visible in both inventories. Player A actually kept the item, but Player B could also see it in his inventory. In the database (item table), the item existed only once — owned by Player A. After teleport or relog, the item disappeared from B’s inventory, so it wasn’t a real duplication — but it definitely looked like one to players. After reviewing the code, I discovered the cause: the original CExchange::Done() handled skip_save, ownership, and database flush in the wrong order. This caused a temporary desynchronization and double visibility between memory and database states. I’m not sure if any of you have noticed or run into this issue before, but here’s my fix for it. The problem may also be triggered or influenced by other things in my server (i don't know). But, if you have this problem, after applying this fix, all trades are now synchronized correctly — no ghost dupes. bool CExchange::Done() { - int empty_pos, i; - LPITEM item; - - LPCHARACTER victim = GetCompany()->GetOwner(); - - for (i = 0; i < EXCHANGE_ITEM_MAX_NUM; ++i) + int empty_pos; + LPITEM item; + LPCHARACTER victim = GetCompany()->GetOwner(); + + std::vector<LPITEM> itemsToFlush; + + for (int i = 0; i < EXCHANGE_ITEM_MAX_NUM; ++i) { if (!(item = m_apItems[i])) continue; @@ if (item->GetWindow() == INVENTORY) { m_pOwner->SyncQuickslot(QUICKSLOT_TYPE_ITEM, item->GetCell(), 255); } item->RemoveFromCharacter(); if (item->IsDragonSoul()) item->AddToCharacter(victim, TItemPos(DRAGON_SOUL_INVENTORY, empty_pos)); #ifdef ENABLE_SPECIAL_STORAGE else if(item->IsUpgradeItem()) item->AddToCharacter(victim, TItemPos(UPGRADE_INVENTORY, empty_pos)); else if(item->IsBook()) item->AddToCharacter(victim, TItemPos(BOOK_INVENTORY, empty_pos)); else if(item->IsStone()) item->AddToCharacter(victim, TItemPos(STONE_INVENTORY, empty_pos)); else if(item->IsChest()) item->AddToCharacter(victim, TItemPos(CHEST_INVENTORY, empty_pos)); #endif else item->AddToCharacter(victim, TItemPos(INVENTORY, empty_pos)); - - ITEM_MANAGER::instance().FlushDelayedSave(item); - - item->SetExchanging(false); + + + item->SetSkipSave(false); + item->SetOwnership(victim); + item->SetExchanging(false); + + ITEM_MANAGER::instance().SaveSingleItem(item); + itemsToFlush.push_back(item); @@ { char exchange_buf[51]; @@ m_apItems[i] = NULL; } @@ if (m_lGold) { GetOwner()->PointChange(POINT_GOLD, -m_lGold, true); victim->PointChange(POINT_GOLD, m_lGold, true); @@ } - m_pGrid->Clear(); - return true; + for (LPITEM it : itemsToFlush) + ITEM_MANAGER::instance().FlushDelayedSave(it); + + m_pGrid->Clear(); + return true; }
  6. USE_TUNING and USE_DETACHMENT cases are handled together in the same switch block. If you add Tiger's condition, you gonna have the problem that you can't longer refine other items than armor and weapons. So you need to separate the cases case USE_TUNING: { LPITEM item2; if (!IsValidItemPosition(DestCell) || !(item2 = GetItem(DestCell))) return false; if (item2->IsExchanging() || item2->IsEquipped()) // @fixme114 return false; if (item2->GetVnum() >= 28330 && item2->GetVnum() <= 28343) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("+3_STONES_CANT_BE_IMPROVED_WITH_THIS")); return false; } if (item2->GetVnum() >= 28430 && item2->GetVnum() <= 28443) { if (item->GetVnum() == 71056) { RefineItem(item, item2); } else { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("STONES_CANT_BE_UPGRADED_WITH_THIS")); } } else { RefineItem(item, item2); } } break; case USE_DETACHMENT: { LPITEM item2; if (!IsValidItemPosition(DestCell) || !(item2 = GetItem(DestCell))) return false; if (item2->IsExchanging() || item2->IsEquipped()) // @fixme114 return false; if(item2->GetType() != ITEM_WEAPON && !(item2->GetType() == ITEM_ARMOR && item2->GetSubType() == ARMOR_BODY)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only use this item on weapons or armor.")); return false; } #ifdef __SASH_SYSTEM__ if (item->GetValue(0) == SASH_CLEAN_ATTR_VALUE0) { if (!CleanSashAttr(item, item2)) return false; return true; } #endif #ifdef __CHANGELOOK_SYSTEM__ if (item->GetValue(0) == CL_CLEAN_ATTR_VALUE0) { if (!CleanTransmutation(item, item2)) return false; return true; } #endif } break; (Do the cases as you have in your source)
  7. void CGuildManager::Update() { ProcessReserveWar(); time_t now = CClientManager::instance().GetCurrentTime(); if (!m_pqOnWar.empty()) { while (!m_pqOnWar.empty() && (m_pqOnWar.top().first <= now || (m_pqOnWar.top().second && m_pqOnWar.top().second->bEnd))) { TGuildWarPQElement* e = m_pqOnWar.top().second; m_pqOnWar.pop(); if (!e) continue; auto itOuter = m_WarMap.find(e->GID[0]); if (itOuter != m_WarMap.end()) { auto& innerMap = itOuter->second; auto itInner = innerMap.find(e->GID[1]); if (itInner != innerMap.end()) { innerMap.erase(itInner); if (innerMap.empty()) m_WarMap.erase(itOuter); } } if (!e->bEnd) WarEnd(e->GID[0], e->GID[1], false); delete e; } } while (!m_pqSkill.empty() && m_pqSkill.top().first <= now) { const TGuildSkillUsed& s = m_pqSkill.top().second; if (s.GID == 0) { sys_err("Invalid GuildSkillUsed GID: 0, skipping"); m_pqSkill.pop(); continue; } CClientManager::instance().SendGuildSkillUsable(s.GID, s.dwSkillVnum, true); m_pqSkill.pop(); } while (!m_pqWaitStart.empty() && m_pqWaitStart.top().first <= now) { const TGuildWaitStartInfo ws = m_pqWaitStart.top().second; m_pqWaitStart.pop(); if (ws.GID[0] == 0 || ws.GID[1] == 0) { sys_err("Invalid GuildWaitStartInfo GID: [%u, %u], skipping", ws.GID[0], ws.GID[1]); continue; } #ifdef __IMPROVED_GUILD_WAR__ StartWar(ws.bType, ws.GID[0], ws.GID[1], ws.pkReserve, ws.iMaxPlayer, ws.iMaxScore, ws.flags, ws.custom_map_index); #else StartWar(ws.bType, ws.GID[0], ws.GID[1], ws.pkReserve); #endif if (ws.lInitialScore) { UpdateScore(ws.GID[0], ws.GID[1], ws.lInitialScore, 0); UpdateScore(ws.GID[1], ws.GID[0], ws.lInitialScore, 0); } TPacketGuildWar p{}; p.bType = ws.bType; p.bWar = GUILD_WAR_ON_WAR; p.dwGuildFrom = ws.GID[0]; p.dwGuildTo = ws.GID[1]; #ifdef __IMPROVED_GUILD_WAR__ p.iMaxPlayer = ws.iMaxPlayer; p.iMaxScore = ws.iMaxScore; p.flags = ws.flags; p.custom_map_index = ws.custom_map_index; #endif CClientManager::instance().ForwardPacket(HEADER_DG_GUILD_WAR, &p, sizeof(p)); sys_log(0, "GuildWar: GUILD sending start of wait start war %d %d", ws.GID[0], ws.GID[1]); } }
  8. i fixed it like this: void CHARACTER::Reward(bool bItemDrop) under BYTE bMulPct = 10; if (IsBoss()) bMulPct = 100; and works perfectly
  9. In case you use Mali's Remote Shop System: uiinventory.py search for: def UseItemSlot(self, slotIndex): under slotIndex = self.__InventoryLocalSlotPosToGlobalSlotPos(slotIndex) add if player.GetItemIndex(self.__InventoryLocalSlotPosToGlobalSlotPos(slotIndex)) == vnumitem: self.interface.OpenRemoteShop() return replace vnumitem with your own vnum
  10. else { iMaxHP = m_pkMobData->m_table.dwMaxHP; iMaxSP = 0; iMaxStamina = 0; SetPoint(POINT_ATT_SPEED, m_pkMobData->m_table.sAttackSpeed); #ifdef ENABLE_MOB_MOVEMENT_SPEED_300 SetPoint(POINT_MOV_SPEED, m_pkMobData->m_table.sMovingSpeed+300); #else SetPoint(POINT_MOV_SPEED, m_pkMobData->m_table.sMovingSpeed); #endif SetPoint(POINT_CASTING_SPEED, m_pkMobData->m_table.sAttackSpeed); } easy, the mobs have everytime mov speed
  11. Hey, if you receive error '0122 23:42:20675 :: Invalid url start [Hidden Content]' replace the whole function with: def MakeHyperlinkTooltip(self, hyperlink): tokens = hyperlink.split(":") if tokens and len(tokens): type = tokens[0] if "item" == type: self.hyperlinkItemTooltip.SetHyperlinkItem(tokens) elif "msg" == type and str(tokens[1]) != player.GetMainCharacterName(): self.OpenWhisperDialog(str(tokens[1])) elif "web" == type and (tokens[1].startswith("httpXxX") or tokens[1].startswith("httpsXxX")): link = tokens[1].replace("XxX", "://") OpenLinkQuestionDialog = uiCommon.QuestionDialog2() OpenLinkQuestionDialog.SetText1(localeInfo.CHAT_OPEN_LINK_DANGER) OpenLinkQuestionDialog.SetText2(localeInfo.CHAT_OPEN_LINK) OpenLinkQuestionDialog.SetAcceptEvent(lambda arg=TRUE: self.AnswerOpenLink(arg)) OpenLinkQuestionDialog.SetCancelEvent(lambda arg=FALSE: self.AnswerOpenLink(arg)) constInfo.link = link OpenLinkQuestionDialog.Open() self.OpenLinkQuestionDialog = OpenLinkQuestionDialog elif "sysweb" == type: open_url_in_browser(tokens[1].replace("XxX", "://")) elif "Kidro" == type or "msg" == type and str(tokens[1]) != player.GetMainCharacterName(): self.OpenWhisperDialog(str(tokens[1])) The problem is from: constInfo.link = "start " + tokens[1].replace("XxX", "://").replace("&","^&") Here's what seems to be happening: The URL is being prefixed with "start ", which is not part of a valid URL. The ampersand (&) is being replaced with ^&, which is also not standard in URLs and likely causing the issue.
  12. Nice. It works very well. A little problem, after every teleport the window open again and again and again Fix: game.py search: class GameWindow(ui.ScriptWindow): under self.guildWarQuestionDialog = None add self.maintenance = None self.maintenance = uimaintenance.MaintenanceWindow() search in def Close(self): for self.affectShower = None and add under: self.maintenance = None replace whole function: def Maintenancegui(self,time,duration): with def Maintenancegui(self,time,duration): if self.maintenance: self.maintenance.Open(time,duration)
  13. lol, just modify in constants.cpp [Hidden Content] default is 36, 44 modify with 44, 44 [Hidden Content]
  14. Very nice person!
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.