/// Request to walk /// 0x00A7 9: public static void WalkToXY(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // TODO: this is the worst system i've ever seen.. // we receive the FINAL position instead of step by step packets // and have to search a path to the final position // if a path has been found, we walk it step by step // this is done using a Timer.. Character c = state.Account.ActiveChar; int unkInt = reader.ReadInt32(); // Since 2008-08-27aRagexeRE, X/Y starts at pos 6 byte b1 = reader.ReadByte(); byte b2 = reader.ReadByte(); byte b3 = reader.ReadByte(); int x = b1 * 4 + (b2 >> 6); int y = ((b2 & 63) << 4) + (b3 >> 4); ServerConsole.DebugLine("WalkToXY: from " + c.Location.X + "/" + c.Location.Y + " to " + x + "/" + y); WalkingHelper.WalkToXY(c, new Point2D(x, y)); }
/// <summary> /// <para>ID 0x007D</para> /// <para>Length 0</para> /// <para>Notification that the client ressource loading has completed</para> /// <para> /// No data given, but its the right place to load and send /// everything else. /// </para> /// </summary> public static void LoadEndAck(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // TODO: // - check if variable loading and every other hist has been finished // - check for rewarp (find out) // - changelook calls // - send inventory list // - check items // - if cart on, send cart-info and check items // - send weights // - if guild, send short guild info // - check for invicible timer // - clif_spawn() (.. what? Oo) // - if party, send infos // - check for pvp, gvg, duel, ... // - getareacharinfo for nearby objects // - pet, homunculus, mercanery // - if first connection, send complete status // - send friendlist info (friend logged in) // - login npc events // - if not first connect, refresh some infos // - many many more.. }
/// Request for attack stop /// 0118 2: public static void StopAttack(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } state.ActiveChar.StopAttack(); }
/// Request for game exiting /// 018A 4: <dont_know>.W public static void QuitGame(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // TODO: logout check state.Send(new Response.WorldConfirmGameExit(true)); }
/// Notification of the state of client command /effect (CZ_LESSEFFECT) /// 021D 6: <state>.L public static void LessEffect(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // 0 = Full effects // 1 = Reduced effects state.Account.ActiveChar.State.Lesseffect = reader.ReadBoolean(); }
/// Notification of the state of client command /effect (CZ_LESSEFFECT) /// 021D 6: <state>.L public static void LessEffect(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // 0 = Full effects // 1 = Reduced effects int lessEffect = reader.ReadInt32(); }
/// Request servert ticks /// 0089 11: <clientTicks>.L public static void TickSend(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // TODO: has a length of 11.. // state.Account.ClientTick = reader.ReadInt32(); // TODO: send server ticks }
/// Requesting unit's name /// 0094 6: <object id>.L public static void CharNameRequest(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } int id = reader.ReadInt32(); // Disguises using -ID.. if (id < 0 && -id == state.Account.ID) { id = (int)state.Account.ID; } SerialObject obj = World.GetObject(id); if (obj == null) { // Lagged clients could request names of already gone mobs/players.. // so no disconnect on invalid requests.. //state.Disconnect(); return; } if (obj.Location.Map != state.Account.ActiveChar.Location.Map) { // || !check_distance_bl(&sd->bl, bl, AREA_SIZE) return; // Block namerequests past view range } // 'see people in GM hide' cheat detection /* disabled due to false positives (network lag + request name of char that's about to hide = race condition) * sc = status_get_sc(bl); * if (sc && sc->option&OPTION_INVISIBLE && !disguised(bl) && * bl->type != BL_NPC && //Skip hidden NPCs which can be seen using Maya Purple * pc_isGM(sd) < battle_config.hack_info_GM_level * ) { * char gm_msg[256]; * sprintf(gm_msg, "Hack on NameRequest: character '%s' (account: %d) requested the name of an invisible target (id: %d).\n", sd->status.name, sd->status.account_id, id); * ShowWarning(gm_msg); * // information is sent to all online GMs * intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, gm_msg); * return; * } */ // TODO: implement me :o //clif_charnameack(fd, bl); }
/// Request for attack (single or continue), sit or stand /// 0437 4: <type>.B <objectID>.L public static void ActionRequest(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } int id = reader.ReadInt32(); byte action = reader.ReadByte(); Callback_ActionRequest(state.ActiveChar, action, id); }
/// Request servert ticks /// 0089 11: <clientTicks>.L public static void TickSend(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // TODO: has a length of 11.. // state.Account.ClientTick = reader.ReadInt32(); // TODO: send server ticks // but the client didnt care about it // but we could implement a latency info if we diff the ticks.. o.o }
/// Sends information about the guild of the target /// 014f 6: <state>.L public static void GuildRequestInfo(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } Character c = state.Account.ActiveChar; // Nothing to send? if (!(c.Status.GuildID is WorldID)) { // TODO: or battleground ID return; } int requestType = reader.ReadInt32(); switch (requestType) { case 0: //clif_guild_basicinfo(sd); //clif_guild_allianceinfo(sd); break; case 1: //clif_guild_positionnamelist(sd); //clif_guild_memberlist(sd); break; case 2: //clif_guild_positionnamelist(sd); //clif_guild_positioninfolist(sd); break; case 3: //clif_guild_skillinfo(sd); break; case 4: //clif_guild_expulsionlist(sd); break; default: ServerConsole.ErrorLine("GuildRequestInfo: Unknown request type {0}", requestType); break; } }
/// Request for game exiting /// 018A 4: <dont_know>.W public static void QuitGame(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } // TODO: logout check // Save data state.ActiveChar.Save(); state.Send(new WorldResponseGameExit(true)); }
/// Validates and processes global messages /// 008C/00F3 -1: <packet len>.W <text>.?B (<name> : <message>) 00 public static void GlobalMessage(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } short textLen = (short)(reader.ReadInt16() - 4); string text = reader.ReadString(textLen); // TODO: // - check name/message // - check atcommand // - process message to all in range (9 cells) // - trigger listening npc's // - log chat }
/// Request to walk /// 0x00A7 9: public static void WalkToXY(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } Character c = state.Account.ActiveChar; int unkInt = reader.ReadInt32(); // Since 2008-08-27aRagexeRE, X/Y starts at pos 6 byte b1 = reader.ReadByte(); byte b2 = reader.ReadByte(); byte b3 = reader.ReadByte(); int x = b1 * 4 + (b2 >> 6); int y = ((b2 & 0x3f) << 4) + (b3 >> 4); Point2D targetLoc = new Point2D(x, y); c.TargetLocation = c.Location.Point + (c.Location.Point - targetLoc); }
/// Validates and processes global messages /// 008C/00F3 -1: <packet len>.W <text>.?B (<name> : <message>) 00 public static void GlobalMessage(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } short baseLength = (short)(reader.ReadInt16() - 4); string baseMessage = reader.ReadString(baseLength); string username, message; if (ChatHelper.ValidateMessage(state, baseMessage, out username, out message) == false) { return; } // Check is implemented, but no commands yet.. xD if (PlayerCommandHelper.IsCommand(message) == true) { PlayerCommandHelper.Execute(state, message); return; } // Send packet to all players in the chatroom or in hearable area // Note: yes, the Client needs the full String "Name : Message" // and dosnt take the name from the source.. // so we just use the original received string ESendTarget target = (state.ActiveChar.Status.ChatID is WorldID ? ESendTarget.ChatWithoutSelf : ESendTarget.HearableAreaWithoutChatrooms); World.Send(new WorldChatMessage(state.ActiveChar, baseMessage), state.ActiveChar, target); // Send packet to himself // Note: the base string "Name : Message" is used again.. World.Send(new WorldMessageOverHead(baseMessage), state.ActiveChar, ESendTarget.Self); // TODO: // - trigger listening npc's // - log chat }
/// Notification that the client ressource loading has completed /// 007D 2: No data given public static void LoadEndAck(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } state.Send(new WorldMessageOverHead("Welcome to Rovolution v0.1 Alpha!")); state.Send(new WorldMessageOverHead("© GodLesZ 2011")); // TODO: eAthena requests the registery and then trigger clif_parse_LoadEndAckk // so move the code to a callback (if implemented an async registry loading) // pc_reg_received // TODO: party_member_joined, guild_member_joined // TODO request pet and homunculus data.. state.ActiveChar.BaseStatus.CalculateCharacter(true); // TODO: request mail box, quest log // TODO: pc_inventory_rentals() // clif_parse_LoadEndAck() // Note: This is called as a packet from the client // but if the registry loading hasent been finished, its stoped and called manually // from pc_reg_received // TODO: ChangeLook // Update clothes color, or some sprite bugs & display errors will happen if (state.ActiveChar.ViewData.ClothesColor > 0) { state.Send(new WorldRefreshLook((int)state.Account.ID, ELookType.Clothes_color, (short)state.ActiveChar.ViewData.ClothesColor)); } // Send inventory // Note: This is a special packet.. // In OnBeforeSend() is checked if items are found; only if found, the packet will be send // In OnSend() is checked for arrow and stackable items and send, if found // So this call may send 3 packet a time, to each player World.Send(new WorldInventoryList(state.ActiveChar), state.ActiveChar, ESendTarget.Self); // Client got the item infos, now validate inventory // TODO: :D state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Weight)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Maxweight)); // TODO:shit // - check if variable loading and every other shit has been finished // - check for rewarp (find out) // - changelook calls // - send inventory list // - check items // - if cart on, send cart-info and check items // - send weights // - if guild, send short guild info // - check for invicible timer // - clif_spawn() (.. what? Oo) // - if party, send infos // - check for pvp, gvg, duel, ... // - getareacharinfo for nearby objects // - pet, homunculus, mercanery // - if first connection, send complete status // - send friendlist info (friend logged in) // - login npc events // - if not first connect, refresh some infos // - many many more.. // Inform nearby objects about me WorldObjectSpawn.Send(state.ActiveChar, true); // Info about nearby objects //map_foreachinarea(clif_getareachar, sd->bl.m, sd->bl.x - AREA_SIZE, sd->bl.y - AREA_SIZE, sd->bl.x + AREA_SIZE, sd->bl.y + AREA_SIZE, BL_ALL, sd); state.ActiveChar.Map.ForeachInRange(new Rectangle2D(state.ActiveChar.Location.Point - Global.AREA_SIZE_POINT, state.ActiveChar.Location.Point - Global.AREA_SIZE_POINT), EDatabaseType.All, new ForeachInRangeVoidDelegate(Callback_GetAreaChar), new object[] { state.ActiveChar }); // TODO: spawn pets, homu, ect if (state.ActiveChar.State.ConnectNew == true) { state.ActiveChar.State.ConnectNew = false; // send skills, hotkeys, status, .. state.Send(new WorldSendSkillList(state.ActiveChar)); state.Send(new WorldSendHotkeys(state.ActiveChar)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Baseexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Nextbaseexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Jobexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Nextjobexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Skillpoint)); state.Send(new WorldSendInitialStatus(state.ActiveChar)); if ((state.ActiveChar.StatusChange.Option & EStatusOption.Falcon) > 0) { //clif_status_load(&sd->bl, SI_FALCON, 1); } if ((state.ActiveChar.StatusChange.Option & EStatusOption.Riding) > 0) { //clif_status_load(&sd->bl, SI_RIDING, 1); } if (state.ActiveChar.Status.Manner < 0) { //sc_start(&sd->bl,SC_NOCHAT,100,0,0); } /* //Auron reported that This skill only triggers when you logon on the map o.O if ((lv = pc_checkskill(sd,SG_KNOWLEDGE)) > 0) { if(sd->bl.m == sd->feel_map[0].m || sd->bl.m == sd->feel_map[1].m || sd->bl.m == sd->feel_map[2].m) sc_start(&sd->bl, SC_KNOWLEDGE, 100, lv, skill_get_time(SG_KNOWLEDGE, lv)); } */ //if(sd->pd && sd->pd->pet.intimate > 900) // clif_pet_emotion(sd->pd,(sd->pd->pet.class_ - 100)*100 + 50 + pet_hungry_val(sd->pd)); //if(merc_is_hom_active(sd->hd)) // merc_hom_init_timers(sd->hd); //if (night_flag && map[sd->bl.m].flag.nightenabled) { // sd->state.night = 1; // clif_status_load(&sd->bl, SI_NIGHT, 1); //} // Notify everyone that this friend logged in //map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 1); // TODO: npc login event } else { // refresh infos ect } }
/// Requesting unit's name /// 0094 14: <dont_know>.? Position 10 <object id>.L public static void CharNameRequest(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } reader.Seek(10, System.IO.SeekOrigin.Begin); int id = reader.ReadInt32(); // Disguises using -ID.. if (id < 0 && -id == state.Account.ID) { id = (int)state.Account.ID; } // First option - a character WorldObject obj = World.Objects[EDatabaseType.Char, id]; if (obj == null) { // 2nd - a monster obj = World.Objects[EDatabaseType.Mob, id]; if (obj == null) { // 3rd - a item (?) obj = World.Objects[EDatabaseType.Item, id]; if (obj == null) { // Lagged clients could request names of already gone mobs/players.. // so no disconnect on invalid requests.. //state.Disconnect(); return; } } } // Fix: we send the accountID as unique ID for chars (its working because no account could connect twice) // this leads to this situation, we will get the account object from World.Objects // instead of the character if (obj is Account) { obj = (obj as Account).ActiveChar; } // Moveable unit? if ( (obj is WorldObjectUnit) && ( (obj as WorldObjectUnit).Location.Map != state.Account.ActiveChar.Location.Map || (obj as WorldObjectUnit).InRange(state.ActiveChar, Global.AREA_SIZE) == false ) ) { return; // Block namerequests past view range } // 'see people in GM hide' cheat detection /* disabled due to false positives (network lag + request name of char that's about to hide = race condition) * sc = status_get_sc(bl); * if (sc && sc->option&OPTION_INVISIBLE && !disguised(bl) && * bl->type != BL_NPC && //Skip hidden NPCs which can be seen using Maya Purple * pc_isGM(sd) < battle_config.hack_info_GM_level * ) { * char gm_msg[256]; * sprintf(gm_msg, "Hack on NameRequest: character '%s' (account: %d) requested the name of an invisible target (id: %d).\n", sd->status.name, sd->status.account_id, id); * ShowWarning(gm_msg); * // information is sent to all online GMs * intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, gm_msg); * return; * } */ state.Send(new WorldResponseCharNameAck(obj)); }
/// Notification that the client ressource loading has completed /// 007D 2: No data given public static void LoadEndAck(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } state.Send(new WorldMessageOverHead("Welcome to Rovolution v0.1 Alpha!")); state.Send(new WorldMessageOverHead("© GodLesZ 2011")); // TODO: eAthena requests the registery and then trigger clif_parse_LoadEndAckk // so move the code to a callback (if implemented an async registry loading) // pc_reg_received // TODO: party_member_joined, guild_member_joined // TODO request pet and homunculus data.. state.ActiveChar.BaseStatus.CalculateCharacter(true); // TODO: request mail box, quest log // TODO: pc_inventory_rentals() // clif_parse_LoadEndAck() // Note: This is called as a packet from the client // but if the registry loading hasent been finished, its stoped and called manually // from pc_reg_received // TODO: ChangeLook // Update clothes color, or some sprite bugs & display errors will happen if (state.ActiveChar.ViewData.ClothesColor > 0) { state.Send(new WorldRefreshLook((int)state.Account.ID, ELookType.Clothes_color, (short)state.ActiveChar.ViewData.ClothesColor)); } // Send inventory // Note: This is a special packet.. // In OnBeforeSend() is checked if items are found; only if found, the packet will be send // In OnSend() is checked for arrow and stackable items and send, if found // So this call may send 3 packet a time, to each player World.Send(new WorldInventoryList(state.ActiveChar), state.ActiveChar, ESendTarget.Self); // Client got the item infos, now validate inventory // TODO: :D state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Weight)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Maxweight)); // TODO:shit // - check if variable loading and every other shit has been finished // - check for rewarp (find out) // - changelook calls // - send inventory list // - check items // - if cart on, send cart-info and check items // - send weights // - if guild, send short guild info // - check for invicible timer // - clif_spawn() (.. what? Oo) // - if party, send infos // - check for pvp, gvg, duel, ... // - getareacharinfo for nearby objects // - pet, homunculus, mercanery // - if first connection, send complete status // - send friendlist info (friend logged in) // - login npc events // - if not first connect, refresh some infos // - many many more.. // Inform nearby objects about me WorldObjectSpawn.Send(state.ActiveChar, true); // Info about nearby objects //map_foreachinarea(clif_getareachar, sd->bl.m, sd->bl.x - AREA_SIZE, sd->bl.y - AREA_SIZE, sd->bl.x + AREA_SIZE, sd->bl.y + AREA_SIZE, BL_ALL, sd); state.ActiveChar.Map.ForeachInRange(new Rectangle2D(state.ActiveChar.Location.Point - Global.AREA_SIZE_POINT, state.ActiveChar.Location.Point - Global.AREA_SIZE_POINT), EDatabaseType.All, new ForeachInRangeVoidDelegate(Callback_GetAreaChar), new object[] { state.ActiveChar }); // TODO: spawn pets, homu, ect if (state.ActiveChar.State.ConnectNew == true) { state.ActiveChar.State.ConnectNew = false; // send skills, hotkeys, status, .. state.Send(new WorldSendSkillList(state.ActiveChar)); state.Send(new WorldSendHotkeys(state.ActiveChar)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Baseexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Nextbaseexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Jobexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Nextjobexp)); state.Send(new WorldUpdateStatus(state.ActiveChar, EStatusParam.Skillpoint)); state.Send(new WorldSendInitialStatus(state.ActiveChar)); if ((state.ActiveChar.StatusChange.Option & EStatusOption.Falcon) > 0) { //clif_status_load(&sd->bl, SI_FALCON, 1); } if ((state.ActiveChar.StatusChange.Option & EStatusOption.Riding) > 0) { //clif_status_load(&sd->bl, SI_RIDING, 1); } if (state.ActiveChar.Status.Manner < 0) { //sc_start(&sd->bl,SC_NOCHAT,100,0,0); } /* * //Auron reported that This skill only triggers when you logon on the map o.O * if ((lv = pc_checkskill(sd,SG_KNOWLEDGE)) > 0) { * if(sd->bl.m == sd->feel_map[0].m || sd->bl.m == sd->feel_map[1].m || sd->bl.m == sd->feel_map[2].m) || sc_start(&sd->bl, SC_KNOWLEDGE, 100, lv, skill_get_time(SG_KNOWLEDGE, lv)); ||} */ //if(sd->pd && sd->pd->pet.intimate > 900) // clif_pet_emotion(sd->pd,(sd->pd->pet.class_ - 100)*100 + 50 + pet_hungry_val(sd->pd)); //if(merc_is_hom_active(sd->hd)) // merc_hom_init_timers(sd->hd); //if (night_flag && map[sd->bl.m].flag.nightenabled) { // sd->state.night = 1; // clif_status_load(&sd->bl, SI_NIGHT, 1); //} // Notify everyone that this friend logged in //map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 1); // TODO: npc login event } else { // refresh infos ect } }
/// Requesting unit's name /// 0094 14: <dont_know>.? Position 10 <object id>.L public static void CharNameRequest(NetState state, PacketReader reader) { if (state.IsValid(EAccountState.World) == false) { state.Disconnect(); return; } reader.Seek(10, System.IO.SeekOrigin.Begin); int id = reader.ReadInt32(); // Disguises using -ID.. if (id < 0 && -id == state.Account.ID) { id = (int)state.Account.ID; } // First option - a character WorldObject obj = World.Objects[EDatabaseType.Char, id]; if (obj == null) { // 2nd - a monster obj = World.Objects[EDatabaseType.Mob, id]; if (obj == null) { // 3rd - a item (?) obj = World.Objects[EDatabaseType.Item, id]; if (obj == null) { // Lagged clients could request names of already gone mobs/players.. // so no disconnect on invalid requests.. //state.Disconnect(); return; } } } // Fix: we send the accountID as unique ID for chars (its working because no account could connect twice) // this leads to this situation, we will get the account object from World.Objects // instead of the character if (obj is Account) { obj = (obj as Account).ActiveChar; } // Moveable unit? if ( (obj is WorldObjectUnit) && ( (obj as WorldObjectUnit).Location.Map != state.Account.ActiveChar.Location.Map || (obj as WorldObjectUnit).InRange(state.ActiveChar, Global.AREA_SIZE) == false ) ) { return; // Block namerequests past view range } // 'see people in GM hide' cheat detection /* disabled due to false positives (network lag + request name of char that's about to hide = race condition) sc = status_get_sc(bl); if (sc && sc->option&OPTION_INVISIBLE && !disguised(bl) && bl->type != BL_NPC && //Skip hidden NPCs which can be seen using Maya Purple pc_isGM(sd) < battle_config.hack_info_GM_level ) { char gm_msg[256]; sprintf(gm_msg, "Hack on NameRequest: character '%s' (account: %d) requested the name of an invisible target (id: %d).\n", sd->status.name, sd->status.account_id, id); ShowWarning(gm_msg); // information is sent to all online GMs intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, gm_msg); return; } */ state.Send(new WorldResponseCharNameAck(obj)); }