version: 1
title: Planetside 2
contributor: https://github.com/emanb29
summary: Models real-time player kill data from Planetside 2 and supplements the killfeed graph with detailed information about the player characters and the weapons used.
description: |-
  Ingests the websockets killfeed from Daybreak Games' MMOFPS "PlanetSide 2", invoking the getJsonLines procedure to lazily fill out unknown static data. Replace all instances of `s:example` with a service-id acquired from http://census.daybreakgames.com/#service-id
ingestStreams:
  - type: WebsocketSimpleStartupIngest
    url: wss://push.planetside2.com/streaming?environment=ps2&service-id=s:example
    initMessages:
    # A couple notes: character names are not reused across servers, so we can subscribe to all servers ("worlds") and not worry about namespacing character names
    # Characters can be *renamed*, but this is rare because it costs the player $25
    - |- 
      {
        "service":"event",
        "action":"subscribe",
        "worlds": ["all"],
        "characters":["all"],
        "eventNames":["Death"]
      }
    format:
      type: CypherJson
      query: |-
        WITH * WHERE $that.type = 'serviceMessage'
        CREATE (m:murder) // these are never replayed, so no reason to idFrom
        SET m = COALESCE($that.payload, {})
        WITH id(m) as mId
        MATCH (murder) WHERE id(murder) = mId
        MATCH (victim) WHERE id(victim) = idFrom('character', murder.character_id)
        MATCH (attacker) WHERE id(attacker) = idFrom('character', murder.attacker_character_id)
        MATCH (weapon) WHERE id(weapon) = idFrom('weapon', murder.attacker_weapon_id)
        SET weapon.uninitialized = weapon.weapon_id IS NULL // flag the weapon for initialization if applicable
        SET victim:character, attacker:character, weapon:weapon,
          victim.character_id = murder.character_id, attacker.character_id = murder.attacker_character_id,
          weapon.weapon_id = murder.attacker_weapon_id
        CREATE (victim)<-[:victim]-(murder)-[:attacker]->(attacker), (murder)-[:weapon]->(weapon)
        // characters contain mutable data, eg certs. We'll add the timestamp to give us something to hook for refreshing data
        WITH murder, victim, attacker
        UNWIND [victim, attacker] AS character
        SET character.last_update = murder.timestamp
standingQueries:
  # Populate character data
  - pattern:
      type: Cypher
      # match each new character-label node
      query: MATCH (newCharacter:character) WHERE newCharacter.character_id IS NOT NULL RETURN DISTINCT id(newCharacter) AS id
    outputs:
      populate-fresh-character:
        type: CypherQuery
        query: |-
          MATCH (c)
          WHERE id(c) = $that.data.id
          CALL loadJsonLines("https://census.daybreakgames.com/s:example/get/ps2:v2/character/?character_id="+c.character_id) YIELD value
          SET c += COALESCE(value.character_list[0], {}) // there should always be a "character_list" with exactly 1 value: the character we queried
  # Populate weapon data
  - pattern:
      type: Cypher
      query: MATCH (weapon:weapon) WHERE weapon.uninitialized = true AND weapon.weapon_id IS NOT NULL RETURN DISTINCT id(weapon) AS id
    outputs:
      populate-weapon:
        type: CypherQuery
        query: |-
          MATCH (weapon) WHERE id(weapon) = $that.data.id
          CALL loadJsonLines("https://census.daybreakgames.com/s:example/get/ps2:v2/item?item_id="+weapon.weapon_id+"&c:join=weapon_datasheet") YIELD value
          SET weapon += COALESCE(value.item_list[0], {}) // there should always be a "item_list" with exactly 1 value: the weapon we queried
          REMOVE weapon.uninitialized
  # Future Standing Query idea: monitor for "trades" (ie, when two players kill each other simultaneously)
nodeAppearances:
  - predicate:
      propertyKeys: []
      dbLabel: character
      knownValues: {}
    icon: ion-android-person
  - predicate:
      propertyKeys: []
      dbLabel: murder
      knownValues: {}
    icon: "\u2694\uFE0F"
  - predicate:
      propertyKeys: []
      dbLabel: weapon
      knownValues: {}
    icon: "\uD83D\uDD2B"
  - predicate:
      propertyKeys: []
      dbLabels: []
      knownValues: {}
quickQueries: []
sampleQueries: []
