Jump to content

Tokei

Members
  • Posts

    662
  • Joined

  • Last visited

  • Days Won

    90

Everything posted by Tokei

  1. Heya, The Reins of Mount has special exceptions in the source. By default, while you are on a mount, you cannot use any item, and this includes the Reins of Mount itself. That is why there is an exception made for the item, and why you need to add an exception for your custom item as well. In pc.cpp, look for "ITEMID_REINS_OF_MOUNT" in the pc_useitem function. Change if( nameid != ITEMID_REINS_OF_MOUNT && sd->sc.data[SC_ALL_RIDING] ) to if( nameid != ITEMID_REINS_OF_MOUNT && nameid != 33014 && sd->sc.data[SC_ALL_RIDING] ) (Or in any other way you'd prefer, this is the most straightforward one I suppose.) You may also want to look at other exceptions from ITEMID_REINS_OF_MOUNT and apply them too for yours. Goodluck!
  2. Well, that's because of how you described your event. You want the rates to change on the hour at 00, but you want the event to start when the goal is reached. Both of these are impossible as the event has to end on the hour as well. So you have to choose, if the event starts at say... 3:44, does it end the next day at 4:00 or 3:00? That's how I understood it. If... you want the event to run for 24 hours and announce at non-00 intervals from when the goal is first reached, that's a different story entirely. As for the first time the script is loaded, that's normal. I was too lazy to resolve that point and since it would only happen once, I didn't care much for that.
  3. Heya, Well normally you'd just do a initnpctimer, but since your event needs to run for 24 hours, you can't really expect it to work correctly as a lot can happen in those 24 hours. You need a different approach in that case to survive a server restart/crash or a script reload. //===== rAthena Script ======================================= //= Floating Server Rates //===== By: ================================================== //= Lupus //===== Current Version: ===================================== //= 1.0 //===== Compatible With: ===================================== //= rAthena Project //===== Description: ========================================= //= It's a simply example of setbattleflag //= This script will change your server rates from 1x to 1.5x every 6 hours //= Note: It doesn't affect Card granted drops, MVP & Treasure Chests drops ^_- //= It also doesn't affect CARD drops, because they are just 0.01% //===== Additional Comments: ================================= //= You can make incredible scripts with 'setbattleflag'! //============================================================ prontera,123,209,6 script Broker#FloatingRates 84,{ if ($floating_rates_hours_left > 0) { .@seconds_left = 3600 - (gettime(2) * 60 + gettime(1)); .@hours_left = ($floating_rates_hours_left - 1) * 3600; .@time_left = .@seconds_left + .@hours_left; .@dun_d = .@time_left / 86400; .@dun_h = (.@time_left / 3600) % 24; .@dun_m = (.@time_left / 60) % 60; .@dun_s = .@time_left % 60; if (.@dun_d > 0) { .@remaining$ = .@dun_d + " day" + (.@dun_d > 1 ? "s" : "") + " and "; } .@remaining$ = .@remaining$ + (.@dun_h < 10 ? "0" : "")+.@dun_h+":"+(.@dun_m < 10 ? "0" : "")+.@dun_m+":"+(.@dun_s < 10 ? "0" : "")+.@dun_s; mes "[Broker]"; mes "The event will end in"; mes .@remaining$; mes "Current rates are: "+($@brate/100)+"."+($@brate-$@brateminus)+"x "+($@jrate/100)+"."+($@jrate-$@jrateminus)+"x"; close; } mes "[Broker]"; mes "Our server's current fund is:"; mes "" + callfunc("F_InsertComma",$fr_zeny) + " Zeny"; next; mes "[Broker]"; mes "Would you like to make a donation?"; next; switch(select("Yes:No")) { case 1: Change_Amount: mes "[Broker]"; mes "Please input your donation amount."; next; input .@fr_zeny; if (.@fr_zeny < 1) { mes "[Broker]"; mes "Input number greater than 0."; end; } mes "[Broker]"; mes "Please confirm Zeny transfer.."; next; switch(select("Cancel:Change Amount:Confirm")) { case 1: end; case 2: .@fr_zeny = 0; next; goto Change_Amount; end; case 3: if (Zeny < .@fr_zeny) { mes "[Broker]"; mes "Sorry, but you don't have enough"; mes "zeny to proceed on payment."; end; } mes "[Broker]"; mes "Zeny has succesfully transfered."; Zeny -= .@fr_zeny; $fr_zeny += .@fr_zeny; if ($fr_zeny >= .fr_targetdonation) { $fr_zeny = 0; // Up to you whether you want to add an extra hour or not, as otherwise the event will be below 24 hours. $floating_rates_hours_left = 25; donpcevent strnpcinfo(0) + "::OnMinute00"; } end; } break; case 2: break; } end; OnInit: .fr_targetdonation = 5000; OnMinute00: if (gettime(2) == 0) { $floating_rates_hours_left--; } if ($floating_rates_hours_left < 0) { end; } else if ($floating_rates_hours_left == 0) { $floating_rates_hours_left = -1; .@default_brate = 100; .@default_jrate = 100; if (getbattleflag("base_exp_rate") != .@default_brate) { setbattleflag("base_exp_rate", .@default_brate); } if (getbattleflag("job_exp_rate") != .@default_jrate) { setbattleflag("job_exp_rate", .@default_jrate); } announce "Event is over, rates were changed back to: "+(.@default_brate/100)+"x "+(.@default_jrate/100)+"x ",bc_all,0xFF6060; end; } $@brate = rand(500,800); $@jrate = rand(500,599); //$@drate = rand(100,150); //Base exp setbattleflag("base_exp_rate",$@brate); //Job exp setbattleflag("job_exp_rate",$@jrate); $@brateminus = ($@brate/100) * 100; $@jrateminus = ($@jrate/100) * 100; announce "Current rates are: "+($@brate/100)+"."+($@brate-$@brateminus)+"x "+($@jrate/100)+"."+($@jrate-$@jrateminus)+"x ",bc_all,0xFF6060; if ($floating_rates_hours_left == 1) { announce "There is one hour left!",bc_all,0xFF6060; } end; }
  4. Heya, Well, considering the error that is given to you, I'd assume this is the issue: OnEventAnnounce: .status = 1; for(.@i = .countdown; i > 0; i--) { The variable i should be .@i
  5. Heya, The issue is from your char-server. "intif" is for "interserver" and "interface", intif_parse_StorageReceived (which is for all storages, including inventory, etc) is a packet the char-server sends to the map-server, type 3 is TABLE_STORAGE. Long story short, it means it failed to load the storage data for your character. The char-server logs should give you more information about the issue, though. The... fact that you aren't seeing more information is strange. It's not an invalid storage index as you'd get an error such as "Invalid storage with id #". So this only leaves an invalid SQL query. Your storage table doesn't seem to match what the char-server is expecting. Make sure your storage table matches with what rAthena is using. You should have received an SQL error, though.
  6. Newer clients reference cps.dll twice; so keep the name to cps.dll instead of changing it to cpp.dll.
  7. Just make sure you're using 1.2.1.0 from the mediafire link.
  8. Hm, well the map-server is indeed the one struggling there. The easy solution is to upgrade your CPU to something better, but... you'd have to figure out what's draining so much power from the emulator. It feels rather high for such a population. The usual culprits are SQL usage (which doesn't appear to be case here? mysqld would show a spike there too) or bad scripts/instances. It'll be almost impossible to pinpoint your issue from the forums though.
  9. Well, htop would be more helpful here and you keeping an eye on it to see which process uses all your CPU.
  10. Heya, This setting has no impact on the map-server. The character saving process is handled on the char-server, not the map-server. Anything SQL related is handled on the char-server to prevent "lagging" the map-server (and should be kept this way). I wouldn't recommend increasing this value either. Anything past 5 minutes (300 seconds) can make you more vulnerable to "rollbacks" if the map-server crashes. If your character data hasn't been saved for more than X minutes, that can be annoying to deal with.
  11. You should open a new thread/question, as this is from 2017. Anyway, it simply means the entry is corrupted or encrypted, most likely encrypted.
  12. Heya, The ground unit IDs for skills are within the client itself, they are not found in lub files and you cannot add new ones. These IDs are mostly meant to display a visual effect on the client though. Therefore... if you want a custom ground skill to show an effect, you'll have to use a pre-existing ID from those currently defined in rAthena and work your way around that. Otherwise, if you want to display a custom effect, you'll have to use "dirty tricks". Either way, what you're looking for simply doesn't exist. But I'll say, what you've described so far is unclear. Most custom skills do not require an unit ID to begin with.
  13. Heya, This post is meant to explain the file format of RSM2 for those who are interested and want to play with them. I haven't seen many projects exploring the topic and I've finished digging through the file for GRF Editor. I shared some of the structure pubicly in BrowEdit's Discord almost a year ago, but the fields were still unknown at that point. Also before anyone asks, no I am not making a public converter for RSM2 > RSM1. That's not fully possible anyway. General The structure of a RSM file is quite simple. It's a list of mesh data with transformations applied to them. Each mesh has a transformation matrix, a position, a parent, etc. Then you have the transformation components on the mesh: Offset/Translation RotationAngle RotationAxis Scale And at last, you have the animation components on the mesh: RotationKeyFrame ScaleKeyFrame All the code presented below comes from GRF Editor. Also the structure varies quite a bit even among the 2.2 version and the 2.3 version. I was unable to find any model using versions 2.0 or 2.1. I'd guess they were only used internally...? Who knows. Animation duration changes In previous versions, below 2.2, the AnimationLength field and the frame animation field represented time in milliseconds. So a model such as ps_h_01.rsm has 48000 as a value for AnimationLength, which means the animation lasts for a whole 48 seconds before it resets. The key frames for the transformations work in the same manner. In version 2.2 and above, the AnimationLength field reprensents the total amount of frames in the model. So a model such as reserch_j_01.rsm2 has a value of 300. The keyframes would therefore range between 0 and 300. The duration is given by the new FramesPerSecond field, which is 30 for almost all 2.0 models currently existing. The delay between frames would then be 1000 / FramesPerSecond = 33.33 ms. The duration would be 1000 / FramesPerSecond * AnimationLength = 1000 / 30 * 300 = 10000 ms in our example. Shading Nothing new there, but I thought I'd go over the topic quickly. The ShadeType property is used to calculate the normals. There are three types that have been found in models to this day: 0: none; the normals are all set to (-1, -1, -1). 1: flat; normals are calculated per triangle, with a typical cross product of the 3 vertices. 2: smooth; each face of a mesh belongs to a smooth group, the normal is then calculated by adding the face normal of each connected vertices. In the real world, most models end up using the smooth shading type. The smooth group is a bit confusing at first if you've never heard of it, but some reading on the topic will help you. These are common techniques. Textures In previous versions, below 2.3, the textures were defined at the start of the file. Each mesh then defines a list of indices. So for example, a mesh could define these indices: "2, 5, 0" which means the mesh has 3 textures. Each face of the mesh then has a TextureId property from 0 to 2 in our example. If the face TextureId is 1, it would refer to the second indice previously defined, which is 5. This means that the texture used for this face would be the 5th texture defined at the start of the model. In version 2.3 and above, the textures are defined per mesh instead. There are no longer using texture indices. The TextureId defined for each face refers directly to the texture defined of that particular mesh. So say the TextureId for a face is 1, then the first texture defined on the mesh is the corresponding one. Transformation order In version 2.2 and above, the Scale/Offset/RotationAngle/RotationAxis properties were removed. Instead, it relies on animation frames or the TransformationMatrix. The order looks as such: /// <summary> /// Calculates the MeshMatrix and MeshMatrixSelf for the specified animation frame. /// </summary> /// <param name="animationFrame">The animation frame.</param> public void Calc(int animationFrame) { MeshMatrixSelf = Matrix4.Identity; MeshMatrix = Matrix4.Identity; // Calculate Matrix applied on the mesh itself if (ScaleKeyFrames.Count > 0) { MeshMatrix = Matrix4.Scale(MeshMatrix, GetScale(animationFrame)); } if (RotationKeyFrames.Count > 0) { MeshMatrix = Matrix4.Rotate(MeshMatrix, GetRotationQuaternion(animationFrame)); } else { MeshMatrix = Matrix4.Multiply2(MeshMatrix, new Matrix4(TransformationMatrix)); if (Parent != null) { MeshMatrix = Matrix4.Multiply2(MeshMatrix, new Matrix4(Parent.TransformationMatrix).Invert()); } } MeshMatrixSelf = new Matrix4(MeshMatrix); Vertex position; // Calculate the position of the mesh from its parent if (PosKeyFrames.Count > 0) { position = GetPosition(animationFrame); } else { if (Parent != null) { position = Position - Parent.Position; position = Matrix4.Multiply2(new Matrix4(Parent.TransformationMatrix).Invert(), position); } else { position = Position; } } MeshMatrixSelf.Offset = position; // Apply parent transformations Mesh mesh = this; while (mesh.Parent != null) { mesh = mesh.Parent; MeshMatrixSelf = Matrix4.Multiply2(MeshMatrixSelf, mesh.MeshMatrix); } // Set the final position relative to the parent's position if (Parent != null) { MeshMatrixSelf.Offset += Parent.MeshMatrixSelf.Offset; } // Calculate children foreach (var child in Children) { child.Calc(animationFrame); } } The original vertices are then multiplied by MeshMatrixSelf for their final positions. MeshMatrix is the resulting transformation matrix of a particular mesh only, without taking into account its parents matrixes or the mesh position. The MeshMatrixSelf is the final transformation matrix that will be applied to the vertices. Contrary to previous versions, the TransformationMatrix is applied all the way to the children. The matrix invert function may not be available in all common librairies, so here is the implementation used: public Matrix4 Invert() { if (this.IsDistinguishedIdentity) return this; if (this.IsAffine) return this.NormalizedAffineInvert(); float num1 = this[2] * this[7] - this[6] * this[3]; float num2 = this[2] * this[11] - this[10] * this[3]; float num3 = this[2] * this[15] - this[14] * this[3]; float num4 = this[6] * this[11] - this[10] * this[7]; float num5 = this[6] * this[15] - this[14] * this[7]; float num6 = this[10] * this[15] - this[14] * this[11]; float num7 = this[5] * num2 - this[9] * num1 - this[1] * num4; float num8 = this[1] * num5 - this[5] * num3 + this[13] * num1; float num9 = this[9] * num3 - this[13] * num2 - this[1] * num6; float num10 = this[5] * num6 - this[9] * num5 + this[13] * num4; float num11 = this[12] * num7 + this[8] * num8 + this[4] * num9 + this[0] * num10; if (IsZero(num11)) return false; float num12 = this[0] * num4 - this[4] * num2 + this[8] * num1; float num13 = this[4] * num3 - this[12] * num1 - this[0] * num5; float num14 = this[0] * num6 - this[8] * num3 + this[12] * num2; float num15 = this[8] * num5 - this[12] * num4 - this[4] * num6; float num16 = this[0] * this[5] - this[4] * this[1]; float num17 = this[0] * this[9] - this[8] * this[1]; float num18 = this[0] * this[13] - this[12] * this[1]; float num19 = this[4] * this[9] - this[8] * this[5]; float num20 = this[4] * this[13] - this[12] * this[5]; float num21 = this[8] * this[13] - this[12] * this[9]; float num22 = this[2] * num19 - this[6] * num17 + this[10] * num16; float num23 = this[6] * num18 - this[14] * num16 - this[2] * num20; float num24 = this[2] * num21 - this[10] * num18 + this[14] * num17; float num25 = this[10] * num20 - this[14] * num19 - this[6] * num21; float num26 = this[7] * num17 - this[11] * num16 - this[3] * num19; float num27 = this[3] * num20 - this[7] * num18 + this[15] * num16; float num28 = this[11] * num18 - this[15] * num17 - this[3] * num21; float num29 = this[7] * num21 - this[11] * num20 + this[15] * num19; float num30 = 1.0f / num11; this[0] = num10 * num30; this[1] = num9 * num30; this[2] = num8 * num30; this[3] = num7 * num30; this[4] = num15 * num30; this[5] = num14 * num30; this[6] = num13 * num30; this[7] = num12 * num30; this[8] = num29 * num30; this[9] = num28 * num30; this[10] = num27 * num30; this[11] = num26 * num30; this[12] = num25 * num30; this[13] = num24 * num30; this[14] = num23 * num30; this[15] = num22 * num30; return this; } New transformation animations TranslationKeyFrames In version 2.2 and above, PosKeyFrames are added. If you've seen the previous formats, you may be confused by this. I've seen PosKeyFrames in many implementations, but version 1.6 adds ScaleKeyFrames, not TranslationKeyFrames. The name is self-explanatory: it translates the mesh. TextureKeyFrames In version 2.3 and above, TextureKeyFrames are added. Similar to other transformations, they are defined as: struct TextureKeyFrame { public int Frame; public float Offset; } The TextureKeyFrames target a specific texture ID from the mesh and have different animation types. The Offset affects the UV offsets of the textures. The animation types are: 0: Texture translation on the X axis. The texture is tiled. 1: Texture translation on the Y axis. The texture is tiled. 2: Texture multiplication on the X axis. The texture is tiled. 3: Texture multiplication on the Y axis. The texture is tiled. 4: Texture rotation around (0, 0). The texture is not tiled. Main mesh In previous versions, below 2.2, there could only be one root mesh. This is no longer the case with newer versions. Code And those were all the changes! Here is a full description of the structure (which is again based on GRF Editor). # # RSM structure # private Rsm(IBinaryReader reader) { int count; // The magic of RMS files is always GRSM Magic = reader.StringANSI(4); MajorVersion = reader.Byte(); MinorVersion = reader.Byte(); // Simply converting the version to a more readable format Version = FormatConverters.DoubleConverter(MajorVersion + "." + MinorVersion); // See "Animation duration changes" above for more information. AnimationLength = reader.Int32(); ShadeType = reader.Int32(); Alpha = 0xFF; // Apparently this is the alpha value of the mesh... but it has no impact in-game, so... if (Version >= 1.4) { Alpha = reader.Byte(); } if (Version >= 2.3) { FrameRatePerSecond = reader.Float(); count = reader.Int32(); // In the new format, strings are now written with their length as an integer, then the string. In previous versions, strings used to be 40 in length with a null-terminator. // The syntax below may be a bit confusing at first. // reader.Int32() reads the length of the string. // reader.String(int) reads a string with the specific length. for (int i = 0; i < count; i++) { MainMeshNames.Add(reader.String(reader.Int32())); } count = reader.Int32(); } else if (Version >= 2.2) { FrameRatePerSecond = reader.Float(); int numberOfTextures = reader.Int32(); for (int i = 0; i < numberOfTextures; i++) { _textures.Add(reader.String(reader.Int32())); } count = reader.Int32(); for (int i = 0; i < count; i++) { MainMeshNames.Add(reader.String(reader.Int32())); } count = reader.Int32(); } else { // Still unknown, always appears to be 0 though. Reserved = reader.Bytes(16); count = reader.Int32(); for (int i = 0; i < count; i++) { _textures.Add(reader.String(40, '\0')); } MainMeshNames.Add(reader.String(40, '\0')); count = reader.Int32(); } // The Mesh structure is defined below for (int i = 0; i < count; i++) { _meshes.Add(new Mesh(reader, Version)); } // The rest of the structure is a bit sketchy. While this is apparently what it should be (some models do indeed have those), they have absolutely no impact in-game and can be safely ignored when rendering the model. if (Version < 1.6) { count = reader.Int32(); for (int i = 0; i < count; i++) { _scaleKeyFrames.Add(new ScaleKeyFrame { Frame = reader.Int32(), Sx = reader.Float(), Sy = reader.Float(), Sz = reader.Float(), Data = reader.Float() }); } } count = reader.Int32(); for (int i = 0; i < count; i++) { VolumeBoxes.Add(new VolumeBox() { Size = new Vertex(reader.Float(), reader.Float(), reader.Float()), Position = new Vertex(reader.Float(), reader.Float(), reader.Float()), Rotation = new Vertex(reader.Float(), reader.Float(), reader.Float()), Flag = version >= 1.3 ? reader.Int32() : 0, }); } } # # Mesh structure # public Mesh(IBinaryReader reader, double version) { int count; if (version >= 2.2) { Name = reader.String(reader.Int32()); ParentName = reader.String(reader.Int32()); } else { Name = reader.String(40, '\0'); ParentName = reader.String(40, '\0'); } if (version >= 2.3) { count = reader.Int32(); for (int i = 0; i < count; i++) { Textures.Add(reader.String(reader.Int32())); } // This is more so for backward compatibility than anything. The texture indices now refer to the texture list of the mesh directly. for (int i = 0; i < count; i++) { _textureIndexes.Add(i); } } else { count = reader.Int32(); for (int i = 0; i < count; i++) { _textureIndexes.Add(reader.Int32()); } } // The TransformationMatrix is 3x3 instead of 4x4 like everything else in the universe. TransformationMatrix = new Matrix3( reader.Float(), reader.Float(), reader.Float(), reader.Float(), reader.Float(), reader.Float(), reader.Float(), reader.Float(), reader.Float()); if (version >= 2.2) { // In 2.2, the transformations are already applied to the mesh, or calculated from the animation key frames. None of these properties are used anymore. Offset = new Vertex(0, 0, 0); Position = new Vertex(reader); RotationAngle = 0; RotationAxis = new Vertex(0, 0, 0); Scale = new Vertex(1, 1, 1); } else { // The Offset is the translation vector for the mesh. translated > scaled > rotated >TransformationMatrix. Offset = new Vertex(reader.Float(), reader.Float(), reader.Float()); // Position is the distance between the mesh and its parent. Position = new Vertex(reader.Float(), reader.Float(), reader.Float()); RotationAngle = reader.Float(); RotationAxis = new Vertex(reader.Float(), reader.Float(), reader.Float()); Scale = new Vertex(reader.Float(), reader.Float(), reader.Float()); } count = reader.Int32(); for (int i = 0; i < count; i++) { _vertices.Add(new Vertex(reader.Float(), reader.Float(), reader.Float())); } count = reader.Int32(); for (int i = 0; i < count; i++) { _tvertices.Add(new TextureVertex { Color = version >= 1.2 ? reader.UInt32() : 0xFFFFFFFF, U = reader.Float(), V = reader.Float() }); } count = reader.Int32(); // A face has changed a little in the new version. The SmoothGroup isn't only bound to the face itself, but can be bound to the vertex itself instead. for (int i = 0; i < count; i++) { Face face = new Face(); int len = -1; if (version >= 2.2) { len = reader.Int32(); } face.VertexIds = reader.ArrayUInt16(3); face.TextureVertexIds = reader.ArrayUInt16(3); face.TextureId = reader.UInt16(); face.Padding = reader.UInt16(); face.TwoSide = reader.Int32(); if (version >= 1.2) { face.SmoothGroup[0] = face.SmoothGroup[1] = face.SmoothGroup[2] = reader.Int32(); if (len > 24) { // It is unsure if this smooth group is applied to [2] or not if the length is 28. Hard to confirm. face.SmoothGroup[1] = reader.Int32(); } if (len > 28) { face.SmoothGroup[2] = reader.Int32(); } } _faces.Add(face); } // This was weirdly predicted to be in model version 1.6... which never existed? Either way, it is safe to set it as >= 1.6 if (version >= 1.6) { count = reader.Int32(); for (int i = 0; i < count; i++) { _scaleKeyFrames.Add(new ScaleKeyFrame { Frame = reader.Int32(), Sx = reader.Float(), Sy = reader.Float(), Sz = reader.Float(), Data = reader.Float() // Useless, has in impact in-game }); } } count = reader.Int32(); for (int i = 0; i < count; i++) { _rotFrames.Add(new RotKeyFrame { Frame = reader.Int32(), // Qx, Qy, Qz, Qw Quaternion = new TkQuaternion(reader.Float(), reader.Float(), reader.Float(), reader.Float()) }); } if (version >= 2.2) { count = reader.Int32(); for (int i = 0; i < count; i++) { _posKeyFrames.Add(new PosKeyFrame { Frame = reader.Int32(), X = reader.Float(), Y = reader.Float(), Z = reader.Float(), Data = reader.Int32() // Useless, has in impact in-game }); } } // Texture animations, look at "Textures" above for more information if (version >= 2.3) { count = reader.Int32(); for (int i = 0; i < count; i++) { int textureId = reader.Int32(); int amountTextureAnimations = reader.Int32(); for (int j = 0; j < amountTextureAnimations; j++) { int type = reader.Int32(); int amountFrames = reader.Int32(); for (int k = 0; k < amountFrames; k++) { _textureKeyFrameGroup.AddTextureKeyFrame(textureId, type, new TextureKeyFrame { Frame = reader.Int32(), Offset = reader.Float() }); } } } } } I'm also sharing the program I used to test the RSM2 files. It's a bit messy, but it does the job and might help someone. This testing program no longer has any purpose to me as it's been merged into GRF Editor already. https://github.com/Tokeiburu/RSM2/tree/master/Rsm2 The provided model is the following (it contains all the new features of RSM2): The chain on the right as well as the lights use these new texture animations. The red ball uses the translation key frames. This test project can read any RSM or RSM2 file as well as save them (you can edit RSM/RSM2 models via source). Changing the header version to change the output file will cause issues depending on which version you go from and to. With that said, have fun...! One day I'll make GRF Editor sources public again, one day.
  14. Heya, The issue is that act files must be loaded with their sprite counterpart to function properly. The act file stores the width and height of the image for each layer and this data is loss otherwise (causing your position issues). The script works fine otherwise. foreach (var file in Directory.GetFiles(@"C:\Users\medakas\data\sprite\¸ó½ºÅÍ\", "*.act")) { var act1 = new Act(file, file.ReplaceExtension(".spr")); act1.AnimationExecute(4, action => { action.Frames = new List<Frame> { new Frame() }; }); act1.Save(file); } Edit: Actually, I looked more into it and that explanation turns out to be somewhat incorrect. At some point, the behavior was changed so that a Sprite object is automatically created if an Act object is created without specifying the Sprite path. The problem with that is that the saving function rewrites the Width/Height values if a Sprite object exists. Act Editor abstracts the Width/Height properties of the layers as that'd just be too annoying for the endusers to setup in the first place. But this is a bug; if no image is associated with the layer with a Sprite present, it should not default to 0 Width/Height.
  15. Heya, fixed just now, in 1.8.4.1.
  16. Heya, There are multiple approaches for this issue. The first one is "lazy" but it is usually good enough for most people and it is much simpler. You run a script on all players to remove their achievement and then you run a SQL command to remove all achievements. So something along these lines: OnClock0000: // At midnight, everyday donpcevent strnpcinfo(0) + "::OnResetAchievement"; end; OnResetAchievement: donpcevent strnpcinfo(0) + "::OnResetAchievementSub"; query_sql("DELETE FROM `achievement` WHERE `id` = 100"); end; OnResetAchievementSub: addrid 0; achievementremove 100; end; The issue with the above is that the query is ran on the map-server and therefore will lag you depending on the size of your achievement table (the same goes for any query ran on the map-server). This solution is also not "atomic" and can fail in some situations where a player is logging on while the script is being ran, and the char-server has already sent the achievement data and hasn't been received by the player yet. The chances of the last scenario happening are very low though. An alternative would be to keep the achievements in the database, but only delete them when the player logs on. You would still have to delete them on the online players though. So something like this: OnClock0000: donpcevent strnpcinfo(0) + "::OnResetAchievement"; $ach_100_start = gettimetick(2); end; OnPCLoginEvent: .@aid = 100; .@res = achievementinfo(.@aid, ACHIEVEINFO_COMPLETEDATE); if (.@res > 0 && .@res < $ach_100_start) { achievementremove 100; } end; OnResetAchievement: addrid 0; achievementremove 100; end; The above works relatively well. It doesn't have concurrency issues, it won't lag your server either. The downside there would be that the achievements would still exist in your sql table. Also, both of the above need to attach a script to the player, which will cause issues if a player is already talking to a NPC (it will terminate the previously ran script). The proper solution would be to run the SQL query on the char-server instead using an inter-server packet (or using a SQL thread, if that PR is merged). Then you would run a custom script command that iterates through online players and removes their achievement with achievement_remove without ever attaching a script to them. The last solution is the best and will work in all scenarios with no in-game lag. Though it is somewhat annoying to code I suppose. (Those scripts were not tested, so you may have to fix the errors yourself.)
  17. Updated to 1.8.4.0: Added proper support for previewing RSM2 file format up to version 0x203. Enabled animations for RSM2 models. Support translation animations. Support texture animations.
  18. Heya, You need to install the softwares you want to use yourself. The default guide from rAthena's wiki should be a good place to start with: https://github.com/rathena/rathena/wiki/Install-on-Debian I wouldn't recommend using phpMyAdmin, but there are plenty of guides around. This one includes steps for phpMyAdmin and fits what you plan on doing:
  19. The costume property in the iteminfo is for the placement of the items in the storage (there is a special costume tab dedicated for this).
  20. You can also generate thor files directly with GRF Editor, which you might find easier to use. I know you already found a solution, but I thought I'd mention it...!
  21. Hello, This can happen if you have mixed DLLs where the executable is. GRF Editor uses embedded DLLs, so you can delete all other DLLs in the same folder as where you installed GRF Editor. If that doesn't solve the issue... I'd suggest to make sure you're at the latest version (currently at 1.8.3.7) and have both .NET 3.5 and 4.0 installed.
  22. Updated to 1.8.3.7: Fixed an issue where directory/uncompressed data files would not be visible when loading a GPF file.
  23. Heya, Hmmm, first of all, you can't convert a RSM2 file to RSM just by changing its extension. GRF Editor shows the model properly because it reads the file header and sees it as a RSM2 file regardless of the extension. You need to either actually downgrade the models or simply update your client to a more recent version. As for ba_lost working using this method, that is just because the client works the same; it reads the header of the file, not the extension.I don't know if 2019-06-26's client is able to read RSM2 files or not. If we assume it does, then your issue is with the RSW map format, you'd have to downgrade it (which is fairly straightforward to do). Updating the client is the easier option either way though, and a long term one at that. You'll have to do it sooner or later and then it will just work out by itself: (I need to update GRF Editor to read the newest RSW format in nif_dun02 map apparently... not that anything has really changed.) Best of luck!
  24. Well in that case it'd probably be best to limit the amount of announces to 1 per account. You'd avoid a whole bunch of issues with that. The following resolves your server restart issues, though I'm... too lazy to make a GM handling of those announces. prontera,187,210,3 script Broadcaster#1::BC 894,{ .@npcname$ = "^FF9300 Broadcaster ^000000"; .@header$ = "[^0000ff" + .@npcname$ + "^000000]"; mes .@header$; mes "Hi, I'm the Broadcaster."; mes "I can Broadcast a message for you."; mes " "; mes " "; mes " "; mes "It costs ^ff0000" + .broadcastfee + "^000000 zeny."; next; mes .@header$; mes "Would you like to Broadcast?"; next; switch (select("Yes:Nevermind:Auto-broadcast:")) { case 1: if (Broadcast > gettimetick(2)) { mes .@header$; mes "Sorry you have to wait for 1 min."; close; } if (Zeny < .broadcastfee) { goto L_NotEnoughZeny; } mes .@header$; mes "Please input your message."; next; input .@broadcast$; Zeny -= .broadcastfee; announce "Shout from " + strcharinfo(0) + ": " + .@broadcast$ + "", 0, 0x5AFF00; // Edit 5AFF00 for color code HTML Color Code Broadcast = gettimetick(2) + 60; //Timer 60 = 1 minute/s dispbottom "Broadcaster: Please wait for 1min until next broadcast to avoid flooding."; end; case 2: mes .@header$; mes "Suit yourself."; close; case 3: mes .@header$; mes "Hi, I can automatically broadcast messages for you!"; mes "It will cost you ^ff0000" + .auto_broadcastfee + "^000000 zeny per broadcast."; next; .@aid = getcharid(3); switch(select("Proceed:Check status:Exit")) { case 1: if ($bc_announces_delay[.@aid]) { mes .@header$; mes "You already have an auto announce for this account."; close; } mes .@header$; mes "Please input your message."; next; input .@broadcast$; mes .@header$; mes "How many times do you want to broadcast?"; mes "Min: 1"; mes "Max: 100"; next; input .@repeat, 1, 100; if (.@repeat < 1 || .@repeat > 100) { mes .@header$; mes "Suit yourself."; close; } .@delay = 3; //mes .@header$; //mes "Delay between announces?"; //mes "Min: 3"; //mes "Max: 20"; //next; //input .@delay, 3, 20; // //if (.@delay < 3 || .@delay > 20) { // mes .@header$; // mes "Suit yourself."; // close; //} .@cost = .auto_broadcastfee * .@repeat; mes .@header$; mes "You want to broadcast:"; mes "^ff0000" + .@broadcast$ + "^000000"; mes "Every 3 minutes for ^00ff00" + .@repeat + "^000000 time(s)?"; mes "It will cost you a total of ^ff0000" + .@cost + "^000000 zeny."; next; switch(select("Proceed:Cancel")) { case 2: mes .@header$; mes "Suit yourself."; close; } if (Zeny < .@cost) { goto L_NotEnoughZeny; } Zeny -= .@cost; $bc_announces_timer[.@aid] = 0; $bc_announces_repeat[.@aid] = .@repeat; $bc_announces_delay[.@aid] = .@delay * 60; $bc_announces_mes$[.@aid] = "Shout from " + strcharinfo(0) + ": " + .@broadcast$; $bc_announces_aid2idx[.@aid] = $bc_announces_count; $bc_announces_idx2aid[$bc_announces_count] = .@aid; $bc_announces_count++; close; case 2: if ($bc_announces_delay[.@aid] == 0) { mes .@header$; mes "You currently have no auto announces."; close; } mes .@header$; mes "Your current announce is as follow:"; mes "^ff0000" + $bc_announces_mes$[.@aid] + "^000000"; mes "It will be announced again in " + $bc_announces_timer[.@aid] + " second(s)."; mes "It will repeat " + $bc_announces_repeat[.@aid] + " more time(s)."; next; switch(select("Okay:Cancel announce:")) { case 1: mes .@header$; mes "..."; close; case 2: mes .@header$; mes "Your zeny will not be refunded, are you sure you want to cancel?"; next; switch(select("No:Yes")) { case 1: mes .@header$; mes "Suit yourself."; close; } if ($bc_announces_delay[.@aid] == 0) { // It already ended? end; } callsub L_RemoveAutoAnnounce, .@aid; mes .@header$; mes "All done."; close; } end; case 3: mes .@header$; mes "Suit yourself."; close; case 4: close; } close; } end; L_NotEnoughZeny: mes .@header$; mes "You don't have enough zeny."; close; OnTimer1000: freeloop(1); for (.@i = 0; .@i < $bc_announces_count; .@i++) { .@aid = $bc_announces_idx2aid[.@i]; $bc_announces_timer[.@aid]--; if ($bc_announces_timer[.@aid] <= 0 && .@didannounce == false) { announce $bc_announces_mes$[.@aid], 0, 0x5AFF00; $bc_announces_repeat[.@aid]--; $bc_announces_timer[.@aid] = $bc_announces_delay[.@aid]; if ($bc_announces_repeat[.@aid] <= 0) { callsub L_RemoveAutoAnnounce, .@aid; .@i--; } .@didannounce = true; // Prevents overlapping of announces } } freeloop(0); initnpctimer; end; L_RemoveAutoAnnounce: .@aid = getarg(0); .@idx = $bc_announces_aid2idx[.@aid]; $bc_announces_timer[.@aid] = 0; $bc_announces_repeat[.@aid] = 0; $bc_announces_delay[.@aid] = 0; $bc_announces_mes$[.@aid] = ""; .@last_aid = $bc_announces_idx2aid[$bc_announces_count - 1]; $bc_announces_idx2aid[.@idx] = .@last_aid; $bc_announces_idx2aid[$bc_announces_count - 1] = 0; $bc_announces_aid2idx[.@last_aid] = .@idx; $bc_announces_count--; return; OnInit: .broadcastfee = 3000000; .auto_broadcastfee = 500000; initnpctimer; end; }
  25. Your initial script has some oddities (regarding variable scope). Hmm, I see a few things you'd probably to worry about with the proposed solution: A player can do multiple auto announces. There is no way of tracking the active announcements. Once the auto announce is bought, it is fully detached from the player and cannot be linked back to him. The announces will get lost on script reload/server reboots. You'd probably want to use global arrays and have the announces displayed with a OnTimer1000 checks instead. With that being said, the following is probably what you're looking for. I personally prefer to have the loop on a separate event, but the end result is very similar in this case. prontera,187,210,3 script Broadcaster#1::BC 894,{ .@npcname$ = "^FF9300 Broadcaster ^000000"; .@header$ = "[^0000ff" + .@npcname$ + "^000000]"; mes .@header$; mes "Hi, I'm the Broadcaster."; mes "I can Broadcast a message for you."; mes " "; mes " "; mes " "; mes "It costs ^ff0000" + .broadcastfee + "^000000 zeny."; next; mes .@header$; mes "Would you like to Broadcast?"; next; switch (select("Yes:Nevermind:Auto-broadcast")) { case 1: if (Broadcast > gettimetick(2)) { mes .@header$; mes "Sorry you have to wait for 1 min."; close; } if (Zeny < .broadcastfee) { goto L_NotEnoughZeny; } mes .@header$; mes "Please input your message."; next; input .@broadcast$; Zeny -= .broadcastfee; announce "Shout from " + strcharinfo(0) + ": " + .@broadcast$ + "", 0, 0x5AFF00; // Edit 5AFF00 for color code HTML Color Code Broadcast = gettimetick(2) + 60; //Timer 60 = 1 minute/s dispbottom "Broadcaster: Please wait for 1min until next broadcast to avoid flooding."; end; case 2: mes .@header$; mes "Suit yourself."; close; case 3: mes .@header$; mes "Hi, I can automatically broadcast messages for you!"; mes "It will cost you ^ff0000" + .auto_broadcastfee + "^000000 zeny per broadcast."; next; switch(select("Proceed:Check status:Exit")) { case 1: mes .@header$; mes "Please input your message."; next; input .@broadcast$; mes .@header$; mes "How many times do you want to broadcast?"; mes "Min: 1"; mes "Max: 100"; next; input .@repeat, 1, 100; if (.@repeat < 1 || .@repeat > 100) { mes .@header$; mes "Suit yourself."; close; } .@cost = .auto_broadcastfee * .@repeat; mes .@header$; mes "You want to broadcast:"; mes "^ff0000" + .@broadcast$ + "^000000"; mes "Every 3 minutes for ^00ff00" + .@repeat + "^000000 time(s)?"; mes "It will cost you a total of ^ff0000" + .@cost + "^000000 zeny."; next; switch(select("Proceed:Cancel")) { case 2: mes .@header$; mes "Suit yourself."; close; } if (Zeny < .@cost) { goto L_NotEnoughZeny; } Zeny -= .@cost; $@bc_announce$ = "Shout from " + strcharinfo(0) + ": " + .@broadcast$; $@repeat = .@repeat; donpcevent strnpcinfo(3) + "::OnAutoAnnounce"; close; case 2: // ?? end; case 3: mes .@header$; mes "Suit yourself."; close; } close; } end; L_NotEnoughZeny: mes .@header$; mes "You don't have enough zeny."; close; OnAutoAnnounce: .@repeat = $@repeat; .@broadcast$ = $@bc_announce$; while (.@repeat > 0) { announce .@broadcast$, 0, 0x5AFF00; // Edit 5AFF00 for color code HTML Color Code sleep 180000; .@repeat--; } end; OnInit: .broadcastfee = 3000000; .auto_broadcastfee = 500000; end; }
×
×
  • Create New...