Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

uMMORPG Official Thread

Discussion in 'Assets and Asset Store' started by mischa2k, Dec 29, 2015.

  1. leomoyses

    leomoyses

    Joined:
    Feb 2, 2017
    Posts:
    14
    <3
     
  2. luis29vm

    luis29vm

    Joined:
    Oct 25, 2016
    Posts:
    164
    I'm running the game in a Vps server headless mode, I don't run the server in my computer, my problem is that I don't know the code to maintains the server open. If you know any please reply or pm, thank you my friend.
     
  3. Tiny-Tree

    Tiny-Tree

    Joined:
    Dec 26, 2012
    Posts:
    1,314
    if you want to keep server running when you quit ssh session use: nohup ./build.x86_64
     
    luis29vm and mischa2k like this.
  4. camta005

    camta005

    Joined:
    Dec 16, 2016
    Posts:
    320
    You can also use the following:

    ./game.x86_64
    Ctrl + Z
    bg
    disown
     
    Last edited: Feb 10, 2017
    luis29vm and mischa2k like this.
  5. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203
    In case you want to use screen, then here is small fast video for that
     
    luis29vm and mischa2k like this.
  6. GOLDY00

    GOLDY00

    Joined:
    Mar 16, 2013
    Posts:
    139
    What's underscreen
     
  7. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203
    To avoid misunderstanding, i renamed video title.
     
  8. honig

    honig

    Joined:
    Jan 26, 2017
    Posts:
    3
    For sure.
    Yes, packed into a password-protected file so that the bad boys do not do anything with it.
    And it also does not change the company name and the product name unasked, just as your project does. ;-)
    That's always good.
     
    mischa2k likes this.
  9. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    Good morning everyone!

    Progress: working on the next update. A lot of variables in the UI scripts will be renamed to something simpler and more literal. For example, 'buttonCancel' will become 'cancelButton', 'idx' will become 'index', 'LogEntry.text' will become 'LogEntry.message' and so on. This more literal naming convention makes understanding easier and allows for fewer comments, which makes everything shorter and more elegant.
     
    Ronith and leomoyses like this.
  10. Ronith

    Ronith

    Joined:
    Feb 20, 2014
    Posts:
    69


    A small Update...
    • Footstep Sounds
    • Animated Lootindicators
    • Improved Playeravatar
    • Automaticdoors stay open while someone is standing in the doorway
    Nothing extraordinary, but I have fun :)

    Question: Sometimes the indicator is not displayed after targeting. ist there a known/same issue in other projects?
     
    Last edited: Feb 10, 2017
  11. leomoyses

    leomoyses

    Joined:
    Feb 2, 2017
    Posts:
    14
    Still no way to load different scenes right? Like entering a cave, a house, or a dungeon?

    Would it be too heavy to have it all in one big huge map? (4096x4096 Gaia)

    EDIT: And what would be a good asset to optimize performance? (Would World Streamer work for example?)

    Thanks guys!
     
  12. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    When it happens, try pressing Pause and then see where the indicator is in the Hierarchy. It's probably on the bottom of the monster but slightly below the ground, so you can't see it.

    You can't load different scenes yet. You can put multiple locations in one scene though, like a dungeon that is at the far right of the map.

    The server will only really care about the NavMesh complexity. A big map should be fine for the server, it will take you a while before you even reach 8GB of RAM or more. For the client you should probably try Unity's occlusion culling so it doesn't have to draw the whole world all the time.
     
    leomoyses and Ronith like this.
  13. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    Update:
    V1.61 [released 2017-02-10]
    • Database.ExecuteNonQuery/Scalar/Reader uses 'new SqliteCommand(sql, conn)' now for simplicity and compatibility with MYSQL
    • Database.CharactersForAccount query uses 'FROM' instead of 'from' for consistency
    • Database Constructor: table creation uses tables and field names without '' now for compatibility with MYSQL
     
  14. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203

    Just small screen how re-designed deposit/withdraw gold option will look like (warehouse addon). At the moment everything seems to work. Just need to fix small tiny issues and im up to release updated warehouse addon.
     
  15. Queen_Harley

    Queen_Harley

    Joined:
    Feb 10, 2017
    Posts:
    12
    Party system is it available now?
     
  16. Ronith

    Ronith

    Joined:
    Feb 20, 2014
    Posts:
    69
    not yet
     
  17. Michealunity

    Michealunity

    Joined:
    Mar 15, 2015
    Posts:
    65
    Hi , there is a way to play for each skill a specific animation? How can i do?
    Example :
    - Click on "SomeSkill" skill
    - Play specific animation called "SomeAnimationName"
     
  18. Natalynn

    Natalynn

    Joined:
    Apr 21, 2013
    Posts:
    197
    This is nice :). Maybe @vis2k can add something like this into uMMORPG by default, a warehouse would be super useful.
     
  19. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    Yes, skillCur is passed to the Animator already. So if you want to have a special animation for your third skill, use '= skillCur 3' as a Transition in the Animator.
     
  20. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    Yes maybe some day. If cioa posts his solution then that's worth a lot for now too.
     
  21. MHolmstrom

    MHolmstrom

    Joined:
    Aug 16, 2012
    Posts:
    115
    So for windows hosting with No-IP here is a rather basic tutorial that works just fine for me!

    Start off by creating an account on https://www.noip.com/sign-up and confirm your email.

    Now it's time to sign in, you will be redirected to your dashboard where you will be greeted with the following page.

    Here you can great your free Host domain if you use a free domain, of course, you can buy a better-looking one without sub-domain. Just press Add Hostname.

    Now that we created our free domain press Active


    Now a new page opens up try to write down the Targeted IP somewhere you can just look it up at any time.

    Head over to the Dashboard and press Dynamic Update Client for Windows "Download" or press
    https://www.noip.com/download Then Download now!

    • Start the Installer DUCSetup.
    • Set Installation Path.
    • Check both boxes.

    Finish. Now the Client opens up and you will be asked to log in.
    In the Manage Host tab, check your host domain you will be using.


    Now the status should be green and ready to go!

    - Go green am I right?
    Now you have a constantly Updating Dynamic DNS, what should we do with this DDNS then you might ask?
    Press you Windows Start Icon type in Wordpad.
    Right-click it, and select "Run as Administrator".

    You have to be admin to change the Hosts file.
    Now Ctrl + O (Open file).
    In the in file path type in.
    C:\Windows\System32\Drivers\etc
    "File type all documents"
    And open Hosts.fil it will look like this (default).
    # Copyright (c) 1993-2009 Microsoft Corp.
    #
    # This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
    #
    # This file contains the mappings of IP addresses to host names. Each
    # entry should be kept on an individual line. The IP address should
    # be placed in the first column followed by the corresponding host name.
    # The IP address and the host name should be separated by at least one
    # space.
    #
    # Additionally, comments (such as these) may be inserted on individual
    # lines or following the machine name denoted by a '#' symbol.
    #
    # For example:
    #
    # 102.54.94.97 rhino.acme.com # source server
    # 38.25.63.10 x.acme.com # x client host
    #
    # localhost name resolution is handled within DNS itself.
    # 127.0.0.1 localhost
    # ::1 localhost
    Now what we want to do is edit localhost and add some of your PCs IP.
    On your keyboard hit "Windows button + R" is opens "Run".
    Write CMD the console opens up and write ipconfig.
    Ipv4 Adress Default Gateway is the ips you want to write down from here.
    Now go to http://www.whatsmyip.net and copy you IP address displayed is a big font size.
    head over to Wordpad and do the following.

    Because the host we created my Dynamic DNS is Solumfable.ddns.com

    You have to change the values from default gateway, ipv4 address, and whatsmyip.net to something like
    10.11.123.123 etc.
    Now Save the file, you will need admin rights to save.
    I do not want to say something wrong but the text explains it self. Localhost name resolution is handled within DNS itself, it kinda converts you ip adresses to work within your dynamic dns.
    Make sure No-Ip client is running.
    Head over to Unity and run your Ummorpg project, open up your game scene.
    in the Hierarchy find "Network Manager"
    Open Network Info and find Network adress, now you see you whatismyip.net ip now listens to your dns so go ahead and type that in there.
    Then head over to Server List > Name and IP. Type desired Server name such as "World one" but the ip will be in my case solumfable.ddns.com.
    Now Build and Run, start your game in the Editor. dedicated server or just Host and Play.
    Open the Fresh build and use Connect. This should just work fine now, Ill add more images later but I'm at work and I'm working at sea!
     
    JBR-games and GOLDY00 like this.
  22. Pronax

    Pronax

    Joined:
    Feb 6, 2017
    Posts:
    13
    @vis2k
    We are quite unsure how safe your server backend is, because we are a group of graphic designer and one coder. But nobody really knows much about server backend, first of all we thought it would be great that u got one included already. But i don't really understand how it works, there is no Database???

    Our plan was to buy a Smartfox server backend. Do u maybe know if it is possible to use this instead of yours, or is yours fine enough for a large MMO?
     
  23. Demonith88

    Demonith88

    Joined:
    Jun 30, 2014
    Posts:
    216
    Is there a way to add new classes ? Like Mages etc....
     
  24. Pronax

    Pronax

    Joined:
    Feb 6, 2017
    Posts:
    13
    And i got another question, i added a new Terrain next to the floor. Teleporting to there works fine, like everything else. But the Mini Map is broken as soon as i use the teleport...
     
  25. ianmhart

    ianmhart

    Joined:
    Mar 16, 2014
    Posts:
    28
    mischa2k likes this.
  26. Pronax

    Pronax

    Joined:
    Feb 6, 2017
    Posts:
    13
    maybe im blind, but where do i see this in the documentation @ianmhart ?
    Before asking here i already looked it up :D
     
  27. Demonith88

    Demonith88

    Joined:
    Jun 30, 2014
    Posts:
    216
    Well well this is the amazing just one more things what about character creator or we can use intergrating new asset or system for that
     
  28. Ronith

    Ronith

    Joined:
    Feb 20, 2014
    Posts:
    69

    Finally: The "Aliens" motiontracker

    do you like it? :)
     
    Neviah, jagatai33 and mischa2k like this.
  29. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    Progress: still simplyfing the UI scripts. The UI code will be so much more simple soon. For example, all those entry.GetChild(1).GetChild(0).GetComponentInChildren calls will all be gone.

    Thanks for the tutorial. I might add it to the documentation later. Not sure yet, it might be risky for inexperienced users who could get hacked after exposing their computer to the internet.

    uMMORPG uses UNET for networking, did you find any security issues there or what do you mean?
    There is a database, it uses SQLite. You can use MYSQL too if you want.
    You don't need smartfox or anything else. Just build a headless server, upload it to your dedicated Linux server and run it. Here is a tutorial: https://noobtuts.com/unity/unet-server-hosting

    So when it comes to safety, there are several parts:
    • The Linux server. If you rent someone from Amazon, Google, or a hoster like Namecheap, you will be fine. It's very unlikely that you will encounter security holes in Google's infrastructure.
    • UNET's LLAPI for raw packet sending: Alex Abramychev seems to know what he's doing, I am not worried about the safety of that one.
    • UNET's HLAPI for high level networking: you can see all the souce code on Bitbucket: https://bitbucket.org/Unity-Technologies/networking/ so you can evaluate this yourself if needed.
    • uMMORPG: I carefully evaluate every client message, so it should be safe unless I missed something. You can check it out yourself though, open the Player script and look through all the Commands. They all take simple input like ints or floats and all the inputs are validated right now.
    Please go more into detail, 'is broken' is not very descriptive. Does it look like a broken screen? Does it show errors? What is 'is broken'?
    Please read: https://forum.unity3d.com/threads/ummorpg-official-thread.376636/page-35#post-2886028

    What do you mean with character creator? You can already create characters. Please be more detailed.

    Great progress!
     
  30. MHolmstrom

    MHolmstrom

    Joined:
    Aug 16, 2012
    Posts:
    115
    No worries, I know its risky, you could just use a vpn or a tunnel or why not both!?
    The character customization he is talking about change face textures, skin color etc then save the prefab, uma is good but I think implementing things is just not worth it bc sooner or later you add it :)
    The minimap camera may not be following the player once you teleport? I think if you make a empty gameobject then attach it as a child to your player prefab it should be just fine.

    Now for my question, is it easy to add more states? Like sitting, and easting etc?
    I want to have many options to fil the rpg element with animation bools and so on but everything breaks because Moving, Idle etc always gets trigged. Like moving around the target dont go back to casting state once you stopped and it makes the combat pale and blend. Was thinking about using my hoover over script to add things such as gathering if mouse over X tag is true move intp state Y.
     
    CrandellWS likes this.
  31. CrandellWS

    CrandellWS

    Joined:
    Oct 31, 2015
    Posts:
    178
    @vis2k I think @Demonith88 meant something like
    https://www.assetstore.unity3d.com/en/#!/content/49847

    Though there are many ways you can customize the character the players are limited in choice.

    I would think this would be costly for things like networking. I do agree with @MHolmstrom it may not be worth it unless it can be done in an ultra simple fashion...

    Perhaps it would be enough if players could select from a dynamic list of built-in models...

    Like being able to choose an archer with green or yellow clothing...idk just thinking in the forum...
     
    Last edited: Feb 11, 2017
  32. Demonith88

    Demonith88

    Joined:
    Jun 30, 2014
    Posts:
    216
  33. Demonith88

    Demonith88

    Joined:
    Jun 30, 2014
    Posts:
    216
    Still i have problems find the sql file that i can import to database and setup only i can find for Linux setup
     
  34. MHolmstrom

    MHolmstrom

    Joined:
    Aug 16, 2012
    Posts:
    115
    @CrandellWS I agree about a dynamic list of premade models, sure sliders and height are fun but that's kinda in-depth. But something that would be very attractive for uMMORPG showcase would be a neat lil character customization script.

    I'm not that much of a programmer rather an artist and I feel so isolated with our player state machine etc so all I can do is improve the art aspect, just a simple thing as adding a bool checking if "Looting" Parameter is true to run an animation can be a headache.
    If I want to run the animation another state will interrupt it no matter what, I wish we had a better/easier use of the animation parameters because as I said simply add a sit animation will break sooner or later since return IDLE or return X reset navigation path or something screw you over. The more I go through the code the less user-friendly it is, especially for artists with a semi/low-skill level programmers, then again using UNET could not be easy until this framework came. But I'm getting kinda disappointed about the animation system. To modify one thing you have to change a whole lot of code. Might just be a bad day, frustration or something else but I feel SO LIMITED..
     
  35. Pronax

    Pronax

    Joined:
    Feb 6, 2017
    Posts:
    13
    @vis2k
    Thanks, already solved the problem.
    But now i encounter a new problem.
    I created a new class, everything works fine. In the same way i created a new mob now, but I can't click the mob in game. I can see it, it attacks me and so on. but i can't click on it and can't attack it.

    I did it in the same way as the tutorial and as it worked with a new class. only thing i skipped was the NetworkManager, because as i see the skeleton isn't included there aswell

    What could be the problem?
     
  36. MHolmstrom

    MHolmstrom

    Joined:
    Aug 16, 2012
    Posts:
    115
    Check tag so its set to monster or
    Place a capsule collider on the root bone :)
     
  37. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    You would have to implement it yourself. It can be done though, someone here posted a video about UMA integration a while ago.

    There is a Database.sqlite file that appears after you played the game once.

    Don't get discouraged. You have to keep in mind that this is an MMORPG - the kind of software that was only available to million dollar companies 10 years ago.

    Modifying the loot system in a single player RPG is not that easy already. Modifying it in an MMORPG is hard because you have to think about the client, the server, and the client<->server communication.

    uMMORPG makes it as easy as possible, but it's still effort. Adding a looting animation requires a new "LOOTING" state. This is 'easy', but only if you are comfortable with state machines. It also requires passing the state to the animator, playing the correct animation and returning to the IDLE state afterwards. This is also 'easy', but only because if you are comfortable with Mecanim.

    Think about this for a moment: if you are able to modify just one feature for your own MMO, that's a big deal already. What used to take a team of programmers a few weeks of work, takes a week for one Artist now. That's crazy.

    This reminds me of something that I wanted to post for everyone:
    If you are a hobbyist developer, feel free to go crazy and hack around in uMMORPG all day!
    If you are serious about launching your own MMORPG, then all you should focus on 24/7 is getting past the point where 99% of Indie MMORPGs fail, which is launching. Don't worry about adding all your dream MMO features, you will have the next 10 years to do that after launching. uMMORPG has more features than most Indie MMORPGs ever had. What you should do is modify all the visuals to fit your genre, add maybe one or two custom features if they are really important to the core mechanics of your game, and then focus on Kickstarter, Community, Payments, Team building, etc.

    Think about it this way: when World of Warcraft launched, no one cared about loot systems, mounts, resources or guilds. What people cared about was living in a fantasy world with an epic war between the alliance and the horde. They cared about that unexplored virtual world with mysterious dungeons and boss monsters that they could slay with their friends.

    Compare your new mob prefab to the skeleton prefab step by step. Does it have the same tag? Does it have a collider on the pelivs? Is the collider also a trigger? etc.
     
    Last edited: Feb 11, 2017
  38. Demonith88

    Demonith88

    Joined:
    Jun 30, 2014
    Posts:
    216
    @vis2k Tnx for replay i find the file but its dll not sql file that i can use with Heidi Sql :/
     
  39. jagatai33

    jagatai33

    Joined:
    Feb 2, 2016
    Posts:
    165
    @MHolmstrom

    Perhaps we can help each other out, im not an artist (at all) im more of a code monkey (lol). Im in need of logos and pictures for my website and a few other things with regards to terrain generation ( i have the tools, i dont have the artistic eye to make something nice looking ), i would be more than glad to help you in return with programming needs for your project (as long as its within reason).

    -J
     
  40. MHolmstrom

    MHolmstrom

    Joined:
    Aug 16, 2012
    Posts:
    115
    Spunds
    Sounds like you have yourself a deal sir. What terrain generatior do you use? I pref GeNa and GAIA, I have a freelancing webdesign company as a side work from my regular work so I belive I could get you what you desire. Only problem is my work schedule, I work 7 days at sea then Im home for 7 days so sending files with 20kb/s can be a pain but we can work something out for sure and show what we can do with uMMORPG :)
    Web design, logos, Environments, UI elements, 3d art you name it!
    Within reason as you said, if you will use the code on your project im fine with that too.
     
    Last edited: Feb 11, 2017
  41. gghitman69

    gghitman69

    Joined:
    Mar 4, 2016
    Posts:
    93
    Hi everybody

    I do not understand the use you made of UMMORPG I already said

    1 - personally I connect to my database to give with MySQL and php for the login register activation account ...

    2 - I can choose the character to create or create another (personalization system)

    3 - only the I connect the character with unet and choosing a server i instancie a character

    All its to tell you not to be satisfied with what is done is a base to adapt to your idea
     
  42. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203
    HeidiSQL tool can handle MySQL, MSSQL and PostgreSQL, but uMMORPG is using sqlite.

    For sqlite use this - http://sqlitebrowser.org or if you are using linux as server side (without graphical interface) then you got also various other options including command line tool.

    Not sure where did you tried to find that sqlite database file but my file strucutre is like this (ignore Database folder and .hg or builds_out folders, just look at picture, you should have that Database.sqlite file on your root folder).

    I might assume you tried to use that dll (sqlite3.dll). If so then that is just a library dll file.

     
    Last edited: Feb 11, 2017
    mischa2k likes this.
  43. Demonith88

    Demonith88

    Joined:
    Jun 30, 2014
    Posts:
    216
    Tnx u @cioa00 i try to export with that software to sql file and import in Heidi but i have a error
    ''accounts'' at line 1
     
    Last edited: Feb 12, 2017
  44. leomoyses

    leomoyses

    Joined:
    Feb 2, 2017
    Posts:
    14
    Great text!
     
    mischa2k and Ronith like this.
  45. jessejarvis

    jessejarvis

    Joined:
    Aug 9, 2013
    Posts:
    303
    Hello vis2k,

    I think you should make a forums or website or something for this product :D Would be nice to have our own section with Tutorials, Support, General, etc.

    *Updated - Added skill effect target* - February 11

    [Adding Skill Effects]

    In SkillTemplates.cs above:

    Code (CSharp):
    1. [CreateAssetMenu(fileName="New Skill", menuName="uMMORPG Skill", order=999)]
    Add

    Code (CSharp):
    1. public enum SkillEffectTarget { None, Caster, Target };
    In the same file below:

    Code (CSharp):
    1. public bool learnDefault; // normal attack etc.
    Add:

    Code (CSharp):
    1. public GameObject effectPrefab;
    2. public SkillEffectTarget effectTarget;
    In Entity.cs below:

    Code (CSharp):
    1. else if (skill.category == "Buff") {
    2.         // set the buff end time (the rest is done in .damage etc.)
    3.         mp -= skill.manaCosts;
    4.         skill.buffTimeEnd = Time.time + skill.buffTime;
    5. }
    Add:

    Code (CSharp):
    1. if (skill.template.effectPrefab)
    2. {
    3.     var go = Instantiate<GameObject>(skill.template.effectPrefab);
    4.     NetworkServer.Spawn(go);
    5.  
    6.      if (skill.template.effectTarget == SkillEffectTarget.Caster)
    7.         go.transform.position = transform.position;
    8.     else if (skill.template.effectTarget == SkillEffectTarget.Target)
    9.         go.transform.position = target.transform.position;
    10. }
    Done. Now on your skills which are located in 'uMMORPG/Prefabs/Skills, Buffs, Status Effects [keep them all in one folder, use no subfolders]' you have an extra option 'Effect Prefab'. Just drag your effectPrefab (particle for me) there, and set 'Effect Target' to what you want your effect to play on. You should probably attach the uMMORPG 'DestroyAfter.cs' script to it as well so that it gets destroyed.

    This is how I did my effectPrefab. I'm not fully not, eventually when I expand on it I will add more customization. Currently this creates the effectPrefab on the target.
     
    Last edited: Feb 12, 2017
    shamsfk, leomoyses, luis29vm and 5 others like this.
  46. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,347
    Good morning everyone!
    To keep you up to date, I am still working on the UI code update today. It's almost done and it will make the code and future UI modifications so much easier. Upgrading your custom projects will take a few minutes because some public variable names have changed and will have to be reassigned in the Inspector, but that's a price worth paying for future simplicity.

    I am not sure if I understand you correctly, do you have a question or is this a feature request?

    Thanks for sharing, I added a link to the documentation: https://noobtuts.com/unity/MMORPG#community-addon-skill-effects

    Yes, a forum is on my ToDo list. Others have requested it before and it will come eventually. I am currently trying to decide on the website type / name / hoster / features first.

    Thanks. I am actually considering to fill a PDF with MMO insights, because it starts to be too much and too important for a quick forum post. There is that monetization that I talked about before. And the whole hacks / bots / duping topic and how to protect against those attacks. And UDP vs. TCP for MMOs. And why not having to worry about race conditions is really good for us. And about that lone wolf MMORPG developer part of it all. And the whole launching/Kickstarter issue.
     
    Last edited: Feb 12, 2017
  47. MHolmstrom

    MHolmstrom

    Joined:
    Aug 16, 2012
    Posts:
    115
    For people with no version controll what so ever check out winmerg (pc only I think).
    It takes some time to open every document one and onr but hey its free and compares.
    So make a new project with no changes and update that project first then check each new script changed and modify you own moded project. Then import World to your own project for canvas updates etc. You should just duplicate the original world file!
     
    mischa2k and cioa00 like this.
  48. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203
    Alright, before i will release video about how to make canvas UI for warehouse addon, here is the code part. P.S - this code is based on uMMORPG v1.60. I haven't checked yet if its ready to go for v1.61.

    Code (CSharp):
    1. // make new file under Scripts\_UI folder called UIWarehouse.cs
    2.  
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class UIWarehouse : MonoBehaviour {
    7.     [SerializeField] GameObject panel;
    8.     [SerializeField] GameObject slotPrefab;
    9.     [SerializeField] Transform content;
    10.     [SerializeField] GameObject goldInOutPanel;
    11.     [SerializeField] Button buttonDeposit;
    12.     [SerializeField] Button buttonWithdrawal;
    13.     [SerializeField] InputField goldInputPanel;
    14.     [SerializeField] Text goldTextPlaceholder;
    15.     [SerializeField] Button buttonAction;
    16.     [SerializeField] Text goldText;
    17.     [SerializeField] Text goldInventoryText;
    18.  
    19.     private ColorBlock btnDColor;
    20.     private ColorBlock btnWColor;
    21.  
    22.     private int bdc = 0;
    23.     private int bwc = 0;
    24.  
    25.     void Update() {
    26.         var player = Utils.ClientLocalPlayer();
    27.         if (!player) return;
    28.  
    29.         // only update the panel if it's active
    30.         if (player.target != null && player.target is Npc &&
    31.             Utils.ClosestDistance(player.collider, player.target.collider) <= player.talkRange) {
    32.             // instantiate/destroy enough slots
    33.             UIUtils.BalancePrefabs(slotPrefab, player.warehouse.Count, content);
    34.  
    35.             goldText.text = player.warehousegold.ToString();
    36.          
    37.             // refresh all
    38.             for (int i = 0; i < player.warehouse.Count; ++i) {
    39.                 var entry = content.GetChild(i).GetChild(0); // slot entry
    40.                 entry.name = i.ToString(); // for drag and drop
    41.                 var item = player.warehouse[i];
    42.  
    43.                 if (item.valid) {
    44.                     // set state
    45.                     entry.GetComponent<UIShowToolTip>().enabled = true;
    46.                     entry.GetComponent<UIDragAndDropable>().dragable = true;
    47.                     // note: entries should be dropable at all times
    48.  
    49.                     // image
    50.                     entry.GetComponent<Image>().color = Color.white;
    51.                     entry.GetComponent<Image>().sprite = item.image;
    52.                     entry.GetComponent<UIShowToolTip>().text = item.Tooltip();
    53.  
    54.                     // amount overlay
    55.                     entry.GetChild(0).gameObject.SetActive(item.amount > 1);
    56.                     if (item.amount > 1) entry.GetComponentInChildren<Text>().text = item.amount.ToString();
    57.                 } else {
    58.                     // remove listeners
    59.                     entry.GetComponent<Button>().onClick.RemoveAllListeners();
    60.  
    61.                     // set state
    62.                     entry.GetComponent<UIShowToolTip>().enabled = false;
    63.                     entry.GetComponent<UIDragAndDropable>().dragable = false;
    64.  
    65.                     // image
    66.                     entry.GetComponent<Image>().color = Color.clear;
    67.                     entry.GetComponent<Image>().sprite = null;
    68.  
    69.                     // amount overlay
    70.                     entry.GetChild(0).gameObject.SetActive(false);
    71.                 }
    72.             }
    73.  
    74.  
    75.             buttonDeposit.interactable = player.hp > 0 && player.gold > 0;
    76.             buttonWithdrawal.interactable = player.hp > 0 && player.warehousegold > 0;
    77.             goldInputPanel.interactable = player.hp > 0 && (player.gold > 0 || player.warehousegold > 0);
    78.  
    79.             buttonDeposit.onClick.SetListener(() => {          
    80.                 if (goldInOutPanel.activeInHierarchy) {
    81.                     goldInOutPanel.SetActive(false);
    82.                 }
    83.  
    84.                 bdc = (bdc > 0 ? 0 : 1);
    85.                 bwc = 0;
    86.  
    87.                 if (bdc == 1) {
    88.                     goldTextPlaceholder.text = "Deposit gold:";
    89.                     goldInOutPanel.SetActive(true);
    90.                     goldInputPanel.ActivateInputField();
    91.                 }
    92.             });
    93.  
    94.             buttonWithdrawal.onClick.SetListener(() => {
    95.                 if (goldInOutPanel.activeInHierarchy) {
    96.                     goldInOutPanel.SetActive(false);
    97.                 }
    98.  
    99.                 bwc = (bwc > 0 ? 0 : 1);
    100.                 bdc = 0;
    101.  
    102.                 if (bwc == 1) {
    103.                     goldTextPlaceholder.text = "Withdraw gold:";
    104.                     goldInOutPanel.SetActive(true);
    105.                     goldInputPanel.ActivateInputField();
    106.                 }
    107.             });
    108.  
    109.             buttonAction.onClick.SetListener(() => {
    110.                 var amountValue = goldInputPanel.text;
    111.  
    112.                 if (!Utils.IsNullOrWhiteSpace(amountValue)) {
    113.                     int amount = int.Parse(amountValue);
    114.  
    115.                     if((bdc != 1 || bwc != 1) && amount < 1) return;
    116.  
    117.                     if (bdc == 1) {
    118.                         player.CmdDepositGold(amount);                          
    119.                     }
    120.  
    121.                     if (bwc == 1) {
    122.                         player.CmdWithdrawGold(amount);
    123.                     }
    124.  
    125.                     goldText.text = player.warehousegold.ToString();
    126.                     goldInventoryText.text = player.gold.ToString();
    127.  
    128.                     bdc = 0;
    129.                     bwc = 0;
    130.                 }
    131.  
    132.                 goldInputPanel.text = "";
    133.  
    134.                 goldInOutPanel.SetActive(false);
    135.             });
    136.         } else {
    137.             panel.SetActive(false);
    138.         }
    139.     }
    140. }
    141.  
    Code (CSharp):
    1. // open file Scripts\Database.cs
    2.  
    3. // find line ExecuteNonQuery(@"CREATE TABLE IF NOT EXISTS 'accounts' (
    4. // after that code block add
    5.  
    6.         ExecuteNonQuery(@"CREATE TABLE IF NOT EXISTS 'warehouse' (
    7.                            'account' TEXT NOT NULL,
    8.                            'slot' INTEGER NOT NULL,
    9.                            'name' TEXT NOT NULL,
    10.                            'valid' INTEGER NOT NULL,
    11.                            'amount' INTEGER NOT NULL)");
    12.  
    13. // find again that line ExecuteNonQuery(@"CREATE TABLE IF NOT EXISTS 'accounts' (
    14. // replace it with
    15.         ExecuteNonQuery(@"CREATE TABLE IF NOT EXISTS 'accounts' (
    16.                            'name' TEXT NOT NULL PRIMARY KEY,
    17.                            'password' TEXT NOT NULL,
    18.                            'banned' INTEGER NOT NULL,
    19.                            'warehouse_gold' INTEGER NOT NULL)");
    20.  
    21.  
    22. // replace line ExecuteNonQuery("INSERT INTO accounts VALUES (@name, @password, 0)", new SqliteParameter("@name", account), new SqliteParameter("@password", password));
    23. // with
    24. ExecuteNonQuery("INSERT INTO accounts VALUES (@name, @password, 0, 0)", new SqliteParameter("@name", account), new SqliteParameter("@password", password));
    25.  
    26. // find method CharacterLoad
    27. // after line player.coins              = (long)mainrow[14];
    28. // add
    29.                 var whGoldData = ExecuteReader("SELECT warehouse_gold FROM accounts WHERE name=@account", new SqliteParameter("@account", player.account));
    30.                 if (whGoldData.Count == 1) {
    31.                     player.warehousegold = (long)whGoldData[0][0];
    32.                 }
    33.  
    34.                 // load warehouse based on warehouseSize
    35.                 for (int i = 0; i < player.warehouseSize; ++i) {
    36.                     // any saved data for that slot?
    37.                     table = ExecuteReader("SELECT name, valid, amount FROM warehouse WHERE account=@account AND slot=@slot;", new SqliteParameter("@account", player.account), new SqliteParameter("@slot", i));
    38.                     if (table.Count == 1) {
    39.                         var row = table[0];
    40.                         var item = new Item();
    41.                         item.name = (string)row[0];
    42.                         item.valid = ((long)row[1]) != 0; // sqlite has no bool
    43.                         item.amount = Convert.ToInt32((long)row[2]);
    44.  
    45.                         // add item if template still exists, otherwise empty
    46.                         player.warehouse.Add(item.valid && item.TemplateExists() ? item : new Item());
    47.                     } else {
    48.                         // add empty slot or default item if any
    49.                         player.warehouse.Add(i < player.defaultWarehouseItems.Length ? new Item(player.defaultWarehouseItems[i]) : new Item());
    50.                     }
    51.                 }
    52.  
    53.  
    54. // find line public static void CharacterSave(string name, string account, string className, Vector3 position, int level, int hp, int mp, int strength, int intelligence, long exp, long skillExp, long gold, long coins, List<Item> inventory, List<Item> equipment, List<Skill> skills, List<Quest> quests, bool useTransaction = true) {
    55.  
    56. // replace it with
    57. public static void CharacterSave(string name, string account, string className, Vector3 position, int level, int hp, int mp, int strength, int intelligence, long exp, long skillExp, long gold, long coins, List<Item> warehouse, List<Item> inventory, List<Item> equipment, List<Skill> skills, List<Quest> quests, long warehousegold, bool useTransaction = true, bool isNewChar = false) {
    58.  
    59. // inside method CharacterSave find comment
    60. // // inventory: remove old entries first, then add all new ones
    61. // add before
    62.         ExecuteNonQuery("UPDATE accounts SET warehouse_gold=@warehouse_gold WHERE name=@account", new SqliteParameter("@warehouse_gold", warehousegold), new SqliteParameter("@account", account));
    63.  
    64.         // warehouse: remove old entries first, then add all new ones
    65.         // (we could use UPDATE where slot=... but deleting everything makes
    66.         //  sure that there are never any ghosts)
    67.         if (!isNewChar) {
    68.             ExecuteNonQuery("DELETE FROM warehouse WHERE account=@account", new SqliteParameter("@account", account));
    69.             for (int i = 0; i < warehouse.Count; ++i) {
    70.                 var item = warehouse[i];
    71.                 ExecuteNonQuery("INSERT INTO warehouse VALUES (@account, @slot, @name, @valid, @amount)",
    72.                                 new SqliteParameter("@account", account),
    73.                                 new SqliteParameter("@slot", i),
    74.                                 new SqliteParameter("@name", item.valid ? item.name : ""),
    75.                                 new SqliteParameter("@valid", Convert.ToInt32(item.valid)),
    76.                                 new SqliteParameter("@amount", item.valid ? item.amount : 0));
    77.             }          
    78.         }
    79.  
    80. // find second method CharacterSave and replace it with
    81.     public static void CharacterSave(Player player, bool useTransaction = true, bool isNewChar = false) {
    82.         CharacterSave(player.name, player.account, player.className, player.transform.position, player.level, player.hp, player.mp, player.strength, player.intelligence, player.exp, player.skillExp, player.gold, player.coins, player.warehouse.ToList(), player.inventory.ToList(), player.equipment.ToList(), player.skills.ToList(), player.quests.ToList(), player.warehousegold, useTransaction, isNewChar);
    83. }
    84.  
    85.  
    86.  
    87.  
     
    Last edited: Feb 12, 2017
  49. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203
    Code (CSharp):
    1. // open file Scripts\NetworkManagerMMO.cs
    2. // find Database.CharacterSave(
    3. // replace method call with
    4.                                 Database.CharacterSave(
    5.                                     msg.name,
    6.                                     account,
    7.                                     prefab.name,
    8.                                     GetStartPosition().position,
    9.                                     prefab.level,
    10.                                     prefab.hpMax,
    11.                                     prefab.mpMax,
    12.                                     prefab.strength,
    13.                                     prefab.intelligence,
    14.                                     prefab.exp,
    15.                                     prefab.skillExp,
    16.                                     prefab.gold,
    17.                                     prefab.coins,
    18.                                     prefab.warehouse.ToList(),
    19.                                     prefab.inventory.ToList(),
    20.                                     prefab.equipment.ToList(),
    21.                                     prefab.skills.ToList(),
    22.                                     prefab.quests.ToList(),
    23.                                     prefab.warehousegold,
    24.                                     false,
    25.                                     true
    26.                                 );
    27.  
    Code (CSharp):
    1. // open file Scripts\Player.cs
    2. // after public ItemTemplate[] defaultItems;
    3. // add
    4.     [Header("Warehouse")]
    5.     public int warehouseSize = 90;
    6.     public SyncListItem warehouse = new SyncListItem();
    7.     public ItemTemplate[] defaultWarehouseItems;
    8.     [SerializeField, SyncVar] long _warehousegold = 0;
    9.     public long warehousegold { get { return _warehousegold; } set { _warehousegold = Math.Max(value, 0); } }
    10.  
    11.  
    12. // find comment  // equipment ///////////////////////////////////////////////////////////////
    13. // add before
    14.     // warehouse ///////////////////////////////////////////////////////////////
    15.     // helper function to find an item in the warehouse
    16.     public int GetWarehouseIndexByName(string itemName) {
    17.         return warehouse.FindIndex(item => item.valid && item.name == itemName);
    18.     }
    19.  
    20.     // helper function to calculate the free slots
    21.     public int WarehouseSlotsFree() {
    22.         return warehouse.Where(item => !item.valid).Count();
    23.     }
    24.  
    25.     // helper function to calculate the total amount of an item type in warehouse
    26.     public int WarehouseCountAmount(string itemName) {
    27.         return (from item in warehouse
    28.                 where item.valid && item.name == itemName
    29.                 select item.amount).Sum();
    30.     }
    31.  
    32.     // helper function to remove 'n' items from the warehouse
    33.     public bool WarehouseRemoveAmount(string itemName, int amount) {
    34.         for (int i = 0; i < warehouse.Count; ++i) {
    35.             if (warehouse[i].valid && warehouse[i].name == itemName) {
    36.                 var item = warehouse[i];
    37.  
    38.                 // take as many as possible
    39.                 int take = Mathf.Min(amount, item.amount);
    40.                 item.amount -= take;
    41.                 amount -= take;
    42.  
    43.                 // make slot invalid if amount is 0 now
    44.                 if (item.amount == 0) item.valid = false;
    45.  
    46.                 // save all changes
    47.                 warehouse[i] = item;
    48.  
    49.                 // are we done?
    50.                 if (amount == 0) return true;
    51.             }
    52.         }
    53.  
    54.         // if we got here, then we didn't remove enough items
    55.         return false;
    56.     }
    57.  
    58.     // helper function to check if the warehouse has space for 'n' items of type
    59.     // -> the easiest solution would be to check for enough free item slots
    60.     // -> it's better to try to add it onto existing stacks of the same type
    61.     //    first though
    62.     // -> it could easily take more than one slot too
    63.     // note: this checks for one item type once. we can't use this function to
    64.     //       check if we can add 10 potions and then 10 potions again (e.g. when
    65.     //       doing player to player trading), because it will be the same result
    66.     public bool WarehouseCanAddAmount(ItemTemplate item, int amount) {
    67.         // go through each slot
    68.         for (int i = 0; i < warehouse.Count; ++i) {
    69.             // empty? then subtract maxstack
    70.             if (!warehouse[i].valid)
    71.                 amount -= item.maxStack;
    72.             // not empty and same type? then subtract free amount (max-amount)
    73.             else if (warehouse[i].valid && warehouse[i].name == item.name)
    74.                 amount -= (warehouse[i].maxStack - warehouse[i].amount);
    75.  
    76.             // were we able to fit the whole amount already?
    77.             if (amount <= 0) return true;
    78.         }
    79.  
    80.         // if we got here than amount was never <= 0
    81.         return false;
    82.     }
    83.  
    84.     // helper function to put 'n' items of a type into the warehouse, while
    85.     // trying to put them onto existing item stacks first
    86.     // -> this is better than always adding items to the first free slot
    87.     // -> function will only add them if there is enough space for all of them
    88.     public bool WarehouseAddAmount(ItemTemplate item, int amount) {
    89.         // we only want to add them if there is enough space for all of them, so
    90.         // let's double check
    91.         if (WarehouseCanAddAmount(item, amount)) {
    92.             // go through each slot
    93.             for (int i = 0; i < warehouse.Count; ++i) {
    94.                 // empty? then fill slot with as many as possible
    95.                 if (!warehouse[i].valid) {
    96.                     int add = Mathf.Min(amount, item.maxStack);
    97.                     warehouse[i] = new Item(item, add);
    98.                     amount -= add;
    99.                 }
    100.                 // not empty and same type? then add free amount (max-amount)
    101.                 else if (warehouse[i].valid && warehouse[i].name == item.name) {
    102.                     int space = warehouse[i].maxStack - warehouse[i].amount;
    103.                     int add = Mathf.Min(amount, space);
    104.                     var temp = warehouse[i];
    105.                     temp.amount += add;
    106.                     warehouse[i] = temp;
    107.                     amount -= add;
    108.                 }
    109.  
    110.                 // were we able to fit the whole amount already?
    111.                 if (amount <= 0) return true;
    112.             }
    113.             // we should have been able to add all of them
    114.             if (amount != 0) Debug.LogError("warehouse add failed: " + item.name + " " + amount);
    115.         }
    116.         return false;
    117.     }
    118.  
    119.     [Command(channel=Channels.DefaultUnreliable)] // unimportant => unreliable
    120.     public void CmdSwapInventoryWarehouse(int fromIndex, int toIndex) {
    121.         // note: should never send a command with complex types!
    122.         // validate: make sure that the slots actually exist in the inventory
    123.         // and that they are not equal
    124.         if ((state == "IDLE" || state == "MOVING" || state == "CASTING") &&
    125.             0 <= fromIndex && fromIndex < inventory.Count &&
    126.             0 <= toIndex && toIndex < warehouse.Count &&
    127.             fromIndex != toIndex) {
    128.             // swap them
    129.             var temp = inventory[fromIndex];
    130.             inventory[fromIndex] = warehouse[toIndex];
    131.             warehouse[toIndex] = temp;
    132.         }
    133.     }
    134.  
    135.     [Command(channel=Channels.DefaultUnreliable)] // unimportant => unreliable
    136.     public void CmdSwapWarehouseInventory(int fromIndex, int toIndex) {
    137.         // note: should never send a command with complex types!
    138.         // validate: make sure that the slots actually exist in the warehouse
    139.         // and that they are not equal
    140.         if ((state == "IDLE" || state == "MOVING" || state == "CASTING") &&
    141.             0 <= fromIndex && fromIndex < warehouse.Count &&
    142.             0 <= toIndex && toIndex < inventory.Count &&
    143.             fromIndex != toIndex) {
    144.             // swap them
    145.             var temp = warehouse[fromIndex];
    146.             warehouse[fromIndex] = inventory[toIndex];
    147.             inventory[toIndex] = temp;
    148.         }
    149.     }
    150.  
    151.     [Command(channel=Channels.DefaultUnreliable)] // unimportant => unreliable
    152.     public void CmdSwapWarehouseWarehouse(int fromIndex, int toIndex) {
    153.         // note: should never send a command with complex types!
    154.         // validate: make sure that the slots actually exist in the warehouse
    155.         // and that they are not equal
    156.         if ((state == "IDLE" || state == "MOVING" || state == "CASTING") &&
    157.             0 <= fromIndex && fromIndex < warehouse.Count &&
    158.             0 <= toIndex && toIndex < warehouse.Count &&
    159.             fromIndex != toIndex) {
    160.             // swap them
    161.             var temp = warehouse[fromIndex];
    162.             warehouse[fromIndex] = warehouse[toIndex];
    163.             warehouse[toIndex] = temp;
    164.         }
    165.     }  
    166.  
    167.     [Command(channel=Channels.DefaultUnreliable)] // unimportant => unreliable
    168.     public void CmdWarehouseSplit(int fromIndex, int toIndex) {
    169.         // note: should never send a command with complex types!
    170.         // validate: make sure that the slots actually exist in the warehouse
    171.         // and that they are not equal
    172.         if ((state == "IDLE" || state == "MOVING" || state == "CASTING") &&
    173.             0 <= fromIndex && fromIndex < warehouse.Count &&
    174.             0 <= toIndex && toIndex < warehouse.Count &&
    175.             fromIndex != toIndex) {
    176.             // slotFrom has to have an entry, slotTo has to be empty
    177.             if (warehouse[fromIndex].valid && !warehouse[toIndex].valid) {
    178.                 // from entry needs at least amount of 2
    179.                 if (warehouse[fromIndex].amount >= 2) {
    180.                     // split them serversided (has to work for even and odd)
    181.                     var itemFrom = warehouse[fromIndex];
    182.                     var itemTo = warehouse[fromIndex]; // copy the value
    183.                     //inventory[toIndex] = inventory[fromIndex]; // copy value type
    184.                     itemTo.amount = itemFrom.amount / 2;
    185.                     itemFrom.amount -= itemTo.amount; // works for odd too
    186.  
    187.                     // put back into the list
    188.                     warehouse[fromIndex] = itemFrom;
    189.                     warehouse[toIndex] = itemTo;
    190.                 }
    191.             }
    192.         }
    193.     }
    194.  
    195.     [Command(channel=Channels.DefaultUnreliable)] // unimportant => unreliable
    196.     public void CmdWarehouseMerge(int fromIndex, int toIndex) {
    197.         if ((state == "IDLE" || state == "MOVING" || state == "CASTING") &&
    198.             0 <= fromIndex && fromIndex < warehouse.Count &&
    199.             0 <= toIndex && toIndex < warehouse.Count &&
    200.             fromIndex != toIndex) {
    201.             // both items have to be valid
    202.             if (warehouse[fromIndex].valid && warehouse[toIndex].valid) {
    203.                 // make sure that items are the same type
    204.                 if (warehouse[fromIndex].name == warehouse[toIndex].name) {
    205.                     // merge from -> to
    206.                     var itemFrom = warehouse[fromIndex];
    207.                     var itemTo = warehouse[toIndex];
    208.                     int stack = Mathf.Min(itemFrom.amount + itemTo.amount, itemTo.maxStack);
    209.                     int put = stack - itemFrom.amount;
    210.                     itemFrom.amount = itemTo.amount - put;
    211.                     itemTo.amount = stack;
    212.                     // 'from' empty now? then clear it
    213.                     if (itemFrom.amount == 0) itemFrom.valid = false;
    214.                     // put back into the list
    215.                     warehouse[fromIndex] = itemFrom;
    216.                     warehouse[toIndex] = itemTo;
    217.                 }
    218.             }
    219.         }
    220.     }
    221.  
    222.     [Command]
    223.     public void CmdDepositGold(int amount) {
    224.         if (HasEnoughtGoldOnInventory(amount)) {
    225.             gold -= amount;
    226.             warehousegold += amount;
    227.         }
    228.     }
    229.  
    230.     [Command]
    231.     public void CmdWithdrawGold(int amount) {
    232.         if (HasEnoughGoldOnWarehouse(amount)) {
    233.             warehousegold -= amount;
    234.             gold += amount;
    235.         }
    236.     }
    237.  
    238.     [Server]
    239.     public bool HasEnoughtGoldOnInventory(int amount) {
    240.         return amount > 0 && gold >= amount;
    241.     }
    242.  
    243.     [Server]
    244.     public bool HasEnoughGoldOnWarehouse(int amount) {
    245.         return amount > 0 && warehousegold >= amount;
    246.     }
    247.  
     
  50. cioa00

    cioa00

    Joined:
    May 31, 2016
    Posts:
    203
    Code (CSharp):
    1. // open file Scripts\PlayerDndHandling.cs
    2. // before class ends, add
    3.     // warehouse //////////////////////////////////////////////
    4.     void OnDnd_WarehouseSlot_WarehouseSlot(int[] slotIndices) {
    5.         // slotIndices[0] = slotFrom; slotIndices[1] = slotTo
    6.  
    7.         // merge? (just check the name, rest is done server sided)
    8.         if (player.warehouse[slotIndices[0]].valid && player.warehouse[slotIndices[1]].valid &&
    9.             player.warehouse[slotIndices[0]].name == player.warehouse[slotIndices[1]].name) {
    10.             player.CmdWarehouseMerge(slotIndices[0], slotIndices[1]);
    11.         // split?
    12.         } else if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) {
    13.             player.CmdWarehouseSplit(slotIndices[0], slotIndices[1]);
    14.         // swap?
    15.         } else {
    16.             player.CmdSwapWarehouseWarehouse(slotIndices[0], slotIndices[1]);
    17.         }
    18.     }
    19.  
    20.     void OnDnd_WarehouseSlot_InventorySlot(int[] slotIndices) {
    21.         if(player.warehouse[slotIndices[0]].valid && player.warehouse[slotIndices[0]].name != "") {
    22.             player.CmdSwapWarehouseInventory(slotIndices[0], slotIndices[1]);
    23.         }
    24.     }
    25.  
    26.     void OnDnd_InventorySlot_WarehouseSlot(int[] slotIndices) {
    27.         if(player.inventory[slotIndices[0]].valid && player.inventory[slotIndices[0]].name != "") {
    28.             player.CmdSwapInventoryWarehouse(slotIndices[0], slotIndices[1]);
    29.         }
    30.     }
    31.  
    Code (CSharp):
    1. // open file Scripts\_UI\UINpcDialogue.cs
    2. // after [SerializeField] GameObject inventoryPanel;
    3. // add
    4. [SerializeField] Button warehouseButton;
    5. [SerializeField] GameObject warehousePanel;
    6.  
    7. // before         } else panel.SetActive(false); // hide
    8. // add
    9.             // warehouse button
    10.             warehouseButton.gameObject.SetActive(true);
    11.             warehouseButton.onClick.SetListener(() => {
    12.                 warehousePanel.SetActive(true);
    13.                 inventoryPanel.SetActive(true);
    14.                 panel.SetActive(false);
    15.             });
    16.  
     
    MHolmstrom and Neviah like this.