Anything I can do?

Rising ourselves by our own hair.
User avatar
broxen
Posts: 33
Joined: Tue Jun 07, 2016 8:26 am
Location: USA
Contact:

Re: Anything I can do?

Post by broxen » Mon Oct 09, 2017 8:19 am

OK, bear with me... While I used to write scripts for UOX, develop in PHP, and study C++ in school, I haven't actually coded anything in over 10yrs. To say I'm rusty is an understatement--I'm a total rookie at this point. That said, I'd like to help, so maybe with some guidance...

Here's what I've got so far for Officer Flint, in LUA as that seems popular as a scripting language for games and has significant resources on the web for reference. There are two things to note when reviewing these files: (1) there's still a non-trivial amount of psuedo-code, as I haven't fleshed out the structure for dialog system and player class. (2) my motivation is to script as much as possible to provide maximum flexibility to future server-owners.

Dialog strings were culled from misc.pigg *.MS and *.NPC files where possible. Also, I realize that I'm calling a lot of variables without their table prefix.

contacts.lua -- defines our contact class and helper functions

Code: Select all

--[[
  Contacts.lua
  Contacts Module defining generic contacts and related functions
  
  TODO: Extend from broader NPC Class, define more defaults
--]]

-- Language Localisation Tables
local en_dialog = {
  K1493134086 =	"HelloString",
  K234187279	= "You should go see {LastContactName} now, {HeroName}. There's nothing more you can do for me.",
  K2433540917	= "Click on <A HREF=CONTACTLINK_MISSIONS>ask about available missions</A> to proceed.",
  K348547894  = "IDontKnowYou",
}


-- Contact Class and Prototype
local p = PlayerClass(player)
local client = Client(clientclass)

local Contact = {}

  -- NOTE: it's not advisable to define nil-value variable keys in LUA Tables
local Contact.prototype {
  model = "MaleNPC",
  name = "Generic_NPC",
  displayname = "Generic NPC",
  costume, -- future use: reference .costume file for npc
  location = {map = "map.outbreak",x = 0,y = 0,z = 0}, -- y is the elevation
  piggcategory, -- = nil is implicit
  piggnpc,
  group,
  morality = "Heroes",
  idle = "PL_StandStill", -- idle animation
  about = self:i18n("K2475752812"), -- pass the key, not the variable
  -- Variables pulled from npcs_client.bin xml schema
  supergroupcontact,
  ai,
  registersupergroup,
  storecount,
  store,
  cantailor,
  canrespec,
  canauction,
  noheadshot,
  shoutdialog,
  shoutfrequency,
  shoutvariation,
  autorewards,
  talkstostrangers,
  visionphases,
  exclusivevisionphase,
  wrongalliancestring,
  -- Dialog Table with Localisation
  dialog = {
    en = en_dialog, -- English Localisation
    fr = fr_dialog, -- French Localisation
    de = de_dialog, -- German Localisation
    es = es_dialog, -- Spanish Localisation
  },
}

local function private()
  print("private function example")
end

function Contact:i18n(s)
  -- Append two letter localisation code to variable name based upon user config
  s = self.dialog .. "." .. client:LoadSettings("lang") .. "." .. s
  return s
end

-- Function to define new Contact
function Contact:new(o)
  o = o or {}   -- create object if user does not provide one
  setmetatable(o, self)
  self.__index = self
  return o
end

return Contact
Contact_OfficerFlint.lua -- Officer Flint himself

Code: Select all

--[[
  Contact_OfficerFlint.lua
  Officer Flint from the Outbreak Tutorial Mission for Heroes

  misc.pigg\text\English\Contacts\Tutorial\Officer_Flint.npc.ms
  K194545998 = "Officer Flint" -- matches P-string from misc.pigg

  Mined from npcs_client.bin:
  SCRIPTS.LOC/CONTACTS/TUTORIAL/OFFICER_FLINT.NPC
  Model_OfficerFlint
  Officer_Flint
  P194545998 -- matches K-string from misc.pigg
  PL_StandStill
  Contacts/Tutorial/Officer_Flint.contact
--]]

-- Player and Client objects
local p = PlayerClass(player)
local client = Client(clientclass)

-- Include Contact class
local Contact = require "contacts"

-- Define English (EN) dialog
-- From misc.pigg\text\English\Contacts\Tutorial\Officer_Flint.contact.ms
local en_dialog = {
  K1493134086 =	"HelloString",
  K1898059956	= "Outbreak",
  K194545998	= "Officer Flint",
  K1967244823	= "Traffic Cop",
  K2060623986	= "<A HREF=CONTACTLINK_INTRODUCE>Officer Parks</A> radioed. He needs help fighting off some rioting thugs down the street.",
  K234187279	= "You should go see {LastContactName} now, {HeroName}. There's nothing more you can do for me.",
  K2433540917	= "Click on <A HREF=CONTACTLINK_MISSIONS>ask about available missions</A> to proceed.",
  K2475752812	= "Officer Flint has simple duties in in the Paragon City Police Department, but, when a crisis arises, he's always first to volunteer for extra duty. His friendly nature and good heart make him an ideal contact for new heroes learning their way around Paragon City.",
  K348547894  = "IDontKnowYou",
  K3871256239	= "We have a crisis going on here, and we need help desperately. Some thugs took an experimental drug thinking it was something else, and now they're trashing the area. We have to regain control to ensure the saftey of the citizens.",
}

-- Add this NPC to the contacts table
c = Contact:new{
  model = "Model_OfficerFlint",
  name = "Officer_Flint",
  displayname = "Officer Flint",
  location = {map = "map.outbreak",x = -61,y = -0,z = 161}, -- y is the elevation
  piggcategory = "P1344310001", -- culled from mission architect definition
  piggnpc = "P194545998", -- culled from misc.pigg
  group = "Tutorial",
  morality = "Heroes",
  idle = "PL_StandStill", -- idle animation
  about = self.i18n("K2475752812"), -- pass the key, not the variable
  dialog = {
    en = en_dialog, -- Allow script localisation
--    fr = fr_dialog,
--    de = de_dialog,
--    es = es_dialog,
  },
  storyarcs = {
    K1898059956	= "Outbreak",
  },
  tasks = {
    K3928824237	= "Deliver the serum to Dr. Miller",
  }
}

-- The Antidote
-- From misc.pigg\text\English\Contacts\Tutorial\tasks\Contact1_Task.taskset.ms
-- TODO: Move to it's own mission lua file, with logic for dialog tree
taskset.contact1_task = {
  K1617305617	= "We need an antidote for these berserk thugs. I've collected this blood sample. <A HREF={ACCEPT}>Please take it to Dr. Miller</A>; he's marked on your map and compass.",
  K2241903306	= "Blood sample",
  K312082289	= "Excellent job. What did you call yourself again? Was it {HeroName}? That's pretty original. You seem to navigate well, so let me tell you about the Map. Click the Map Button to bring up an overhead view of the zone.<br><br><img src=Tut_Menu_Map.tga><br><br> Click on any item on the map to target it. The target appears on your compass.",
  K2673347275	= "Excellent job. What did you call yourself again? Was it {HeroName}? That's pretty original. You seem to navigate well, so let me tell you about the Map. Click the Map Button <img src=Tut_Menu_Map.tga> to bring up an overhead view of the zone. Click on any item on the map to target it. The target appears on your compass.",
  K1114271724	= "You should read the plaque in front of Preston Hospital, right next to Dr. Miller.",
  K300190428	= "Thank you! This sample will help our research immensely.<br><br>Since you are new to Paragon City, you should familiarize yourself with the way hospitals work here. There is a plaque in front of Preston Hospital next to me you need to read. You can then return to Officer Flint for more instructions.",
  K3316883996	= "The blood sample has been placed in your Clue Bag",
  K3637128862	= "A blood sample that must be delivered to Dr. Miller.",
  K3647315663	= "Return to Officer Flint for more duties",
  K3928824237	= "Deliver the serum to Dr. Miller",
  K511052023	= "Dr. Miller is just down the street. Give him the sample, and see if he has anything further for you.",
  K644472974	= "Read the plaque",
  K657338103	= "Click here to accept this task",
  K934716567	= "Use the compass at the top of your screen to find the hospital. Deliver the serum, then see if he has anything more for you.",
}

modal.tutorial = {
  tut_nav   = "The Navigation Window is at the top of your screen. <img src=Tut_Menu_Nav.tga>", 
  tut_clue  = "You have been issued a clue. You can learn more about the Clue by clicking the Clues botton on your Nav window. Whenever you are issued new Clues, you can read more about them there as well.",
}

-- TODO: Move to global Dialog Class, which will be referenced by Contact:dialog()
links = {
  CONTACTLINK_MISSIONS = "Ask about available missions",
  CONTACTLINK_ABOUT = "Ask about this contact",
  CONTACTLINK_ACCEPT = "Click here to accept this task",
  CONTACTLINK_WHATELSE = "Talk about what else is going on",
  CONTACTLINK_LEAVE = "Leave",
  MODAL_OK = "OK",
}

-- Move MODAL_WELCOME to some kind of new player start lua file.
MODAL_WELCOME = "Welcome to City of Heroes!\nThis short tutorial will go over the basics of gameplay in order to get you started on your path to becoming one of the greatest heroes in Paragon City.\n\nTo get started, walk up to Officer Flint and talk to him. Talking is as simple as left clicking on the person you would like to talk to.\n\nTo move, simply use the keyboard as illustrated here.\n\nUI Wasd.jpg\n\nHolding the right mouse button down and moving the mouse left and right allows you to turn. When you have familiarized yourself with the controls, click OK in the bottom of this dialog box to close it.",


-- Begin with code new player start. This logic should go in another file
-- specifically for New Player Spawns
do
  p:Spawn()

  -- Call Modal() to show popup.
  -- Welcome & WASD Configuration Message
  client:Modal(MODAL_WELCOME,buttons{MODAL_OK})

  -- Assign starting storyarc and mission
  p:AssignStoryArc(K1898059956) -- K1898059956 = "Outbreak"

  -- set active contact and current waypoint
  p:SetActiveContact(c.name) -- Officer Flint
  p:SetWaypoint(c.location) -- Officer Flint's location
end

-- c:Dialog should inherit from Contact class, which should inherit from a Dialog class
function c:Dialog(d)
  repeat
    -- TODO: Rewrite as lookup table?
    if p:HasStoryArc(K1898059956) then
      if d:state == "intro" then
        msg = { K3871256239, d:style(K2433540917, "blue") }
        ln = { CONTACTLINK_MISSIONS, CONTACTLINK_ABOUT, CONTACTLINK_LEAVE }
        -- dialog class has logic for handling links and advancing dialog state
      elseif d:state == "briefing" then
        msg = { K1617305617, d:style(tut_nav, "blue"), d:style(tut_clue, "blue")}
        ln = { CONTACTLINK_ACCEPT, CONTACTLINK_WHATELSE, CONTACTLINK_LEAVE }
        -- dialog class has logic for handling links and advancing dialog state
      elseif d:state == "acceptance" then
        p:AwardClue(K2241903306,K3637128862,K3316883996) -- AwardClue(cluename,description,message)
        msg = { K511052023 }
        ln = { CONTACTLINK_ABOUT, CONTACTLINK_LEAVE }
        -- dialog class has logic for handling links and advancing dialog state
      elseif d:state == "solicitation" then
        msg = { K934716567 }
        ln = { CONTACTLINK_ABOUT, CONTACTLINK_LEAVE }
        -- dialog class has logic for handling links and advancing dialog state
      else
        -- nothing else to say, so direct user back to next contact
        msg = { K234187279 }
        ln = { CONTACTLINK_ABOUT, CONTACTLINK_LEAVE }
      end
      -- Feed variables and show modal box
      -- modal has logic for handling links and advancing dialog state
      client:Modal(msg,ln,state)
    else
      p:AssignStoryArc(K1898059956) -- K1898059956 = "Outbreak"
      d:state = "intro"
    end
  until d:state == "leave"
end
Let me know your thoughts here.
If I'm completely off-base here, please let me know. I don't want to derail any existing progress.

And feel free to provide direction, or examples of what you'd rather see.

Some necessary functions, as I currently see it:

Code: Select all

-- p = player
p:Spawn(player) -- spawn a new player
p:AssignStoryArc(arc,is_active) -- I can't remember if you are allowed multiple storyarcs at one time
p:HasStoryArc(arc)  -- also returns active state
p:AssignTask(task,is_active) -- I can't remember if you are allowed multiple tasks at one time
p:HasTask(task)  -- also returns active state
p:SetWaypoint(wp)
p:GetWaypoint(wp)
p:AwardClue(clue,description,message)
p:HasClue(clue)
p:AssignContact(contact,is_active)
p:HasContact(contact) -- also returns active state
p:AwardXP(xp)
p:AwardDebt(debt)
p:AwardSouvenir(souvenir)
-- c = contact
c = Contact:new{} -- use LUA sugar to call function with single table as only argument
c:Dialog(event) -- or maybe c:OnDialog(d), maybe pulls from Dialog class
c:OnAssigned(event) -- when assigned to player
c:i18n(s) -- localisation shiv
c:Prerequisites(player) -- checks contacts prereqs against player
Some variables

Code: Select all

p.Level -- Current Level
p.XP -- Current XP
p.Debt -- Current Debt
p.Costume -- Costume Value, could be key reference or dictionary table (array)
-- you get the point.
-broxen

User avatar
broxen
Posts: 33
Joined: Tue Jun 07, 2016 8:26 am
Location: USA
Contact:

Re: Anything I can do?

Post by broxen » Mon Oct 09, 2017 4:47 pm

I'd like to mention that there doesn't seem to be any correlation between the different *.MS and *.NPC files in misc.pigg, other than an inferred tie between the P string in the NPC file with the K string in the associated MS file.

I said as much in the comments of Officer Flint's Lua script.

Next to that, there doesn't even seem to be a reference tying the contact.ms file to any specific task.ms files. In fact, the task files seem to include text strings for the entire storyarcs, including parts spoken by other NPCs along the way, and clue or waypoint text.

For these reasons the K strings and key values seem completely arbitrary, and it may be better to simply construct our tables without named keys--though perhaps harder to reference later in mission scripts... If keys are kept, we should dictate some sort of consistent naming schema, for code readability.

Also, as an aside, storyarc text and dialog trees should be broken out into their own script, as it's possible or even probable that the same mission may be assigned by different NPCs, without any changes to text.
-broxen

User avatar
nemerle
Posts: 379
Joined: Thu Jan 10, 2013 3:40 pm

Re: Anything I can do?

Post by nemerle » Mon Oct 09, 2017 6:04 pm

Great work broxen, I'll look into integrating a scripting language this weekend. ( it'll be either lua or chai-script )

As for those 'K******' strings, I'm 90% sure that those numbers are actualy hash values of the mapped strings, used by the original map-server to ease the translation part. So that given a quest pseudo-script:

Code: Select all

...
if(condition)
	send_player_text(TRANSLATABLE("You cannot do this"));
else
	send_player_text(TRANSLATABLE("You can do this"));
...	
some tool will parse the quest script, extract all TRANSLATABLE strings to ease the translator's work.
"Ich was in one sumere dale,
in one suthe diyhele hale,
iherde ich holde grete tale
an hule and one niyhtingale."

User avatar
broxen
Posts: 33
Joined: Tue Jun 07, 2016 8:26 am
Location: USA
Contact:

Re: Anything I can do?

Post by broxen » Wed Oct 11, 2017 3:22 am

Right. That's what I figured too, and came up with a similar solution here:

Code: Select all

function Contact:i18n(s)
  -- Append two letter localisation code to variable name based upon user config
  s = self.dialog .. "." .. client:LoadSettings("lang") .. "." .. s
  return s
end
But that could easily be handled server side.

EDIT: Come to think of it, the proposed dialog class should probably handle this on every dialog string, so that the function doesn't need to be called at all.
-broxen

User avatar
nemerle
Posts: 379
Joined: Thu Jan 10, 2013 3:40 pm

Re: Anything I can do?

Post by nemerle » Wed Oct 11, 2017 7:49 pm

Translations will be likely handled c++-side using Qt localization support, and LUA will get access to tr('localized string') function.

The per-user configuration will be used to select proper QTranslator instance and the string will be processed by using the loaded translation table.

Also, initial LUA support is getting ready - for now a single interpreter instance is bound to a single MapInstance, and a dummy c++ Contact type is registered in LUA.
Next step will be to add a simple lua evaluation support to the chat : /lua 'lua code to run'
"Ich was in one sumere dale,
in one suthe diyhele hale,
iherde ich holde grete tale
an hule and one niyhtingale."

User avatar
broxen
Posts: 33
Joined: Tue Jun 07, 2016 8:26 am
Location: USA
Contact:

Re: Anything I can do?

Post by broxen » Wed Oct 11, 2017 10:47 pm

This is great!

I had trouble getting the server to recognize my client, but I'll try to compile the latest again this weekend.
-broxen

User avatar
nemerle
Posts: 379
Joined: Thu Jan 10, 2013 3:40 pm

Re: Anything I can do?

Post by nemerle » Thu Oct 12, 2017 7:12 am

That's strange, can you perhaps verify that your client is not attempting to connect to auth server using 127.0.0.1 ? ( the client has special processing when it encounters attempts to connect to loopback )
"Ich was in one sumere dale,
in one suthe diyhele hale,
iherde ich holde grete tale
an hule and one niyhtingale."

User avatar
broxen
Posts: 33
Joined: Tue Jun 07, 2016 8:26 am
Location: USA
Contact:

Re: Anything I can do?

Post by broxen » Thu Oct 12, 2017 7:28 am

Actually I was. Do I need to run the server on a separate computer? Or can I just connect to my own IP?
-broxen

User avatar
nemerle
Posts: 379
Joined: Thu Jan 10, 2013 3:40 pm

Re: Anything I can do?

Post by nemerle » Thu Oct 12, 2017 9:19 am

Connect to your own IP, it should be enough ( just make sure the server is configured to listen on your IP too :) )
"Ich was in one sumere dale,
in one suthe diyhele hale,
iherde ich holde grete tale
an hule and one niyhtingale."

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest