Skip to content

Commit

Permalink
adding clickable monsters in the hex crawling table text
Browse files Browse the repository at this point in the history
  • Loading branch information
ulrikL committed Jan 21, 2022
1 parent 78c06fd commit e8614a5
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 95 deletions.
2 changes: 1 addition & 1 deletion app/src/main/assets/monsters/goblins.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"monster":[
{ "id":1,
"name":"Goblin",
"tags": [ "Goblin", "goblin", "Goblins", "goblins" ],
"tags": [ "goblin", "goblins" ],
"stats": {
"traits": { "phy": "12", "min": "10", "int": "10", "cha": "10" },
"skills": { "bur": 69, "kno": 17, "mag": 15, "mel": 49, "soc": 38, "sur": 58 },
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/assets/monsters/orcs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"monster":[
{ "id":1,
"name":"Orc",
"tags": [ "Orc", "orc", "Orcs", "orcs" ],
"tags": [ "orc", "orcs" ],
"stats": {
"traits": { "phy": "30", "min": "15", "int": "15", "cha": "5" },
"skills": { "bur": 39, "kno": 27, "mag": 25, "mel": 60, "soc": 28, "sur": 52 },
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/assets/monsters/trolls.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"monster":[
{ "id":1,
"name":"Troll",
"tags": [ "Troll", "troll", "Trolls", "trolls" ],
"tags": [ "troll", "trolls" ],
"stats": {
"traits": { "phy": "30", "min": "15", "int": "10", "cha": "5" },
"skills": { "bur": 48, "kno": 29, "mag": 27, "mel": 63, "soc": 18, "sur": 43 },
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) 2021 Ulrik Laurén
// Part of RuinMastersTables
// MIT License, see LICENSE file

package my.tablelogic.ruinmasters

import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.os.Bundle
import android.text.Spanned
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat
import androidx.core.text.toSpannable
import java.util.regex.Pattern

private const val ARG_HEADER = "ARG_HEADER"
private const val ARG_TERRAIN = "ARG_TERRAIN"
private const val ARG_ENCOUNTER = "ARG_ENCOUNTER"
private const val ARG_TREASURE = "ARG_TREASURE"

class DisplayTableResultFragment : DialogFragment() {
private var headerText: String? = null
private var terrainText: String? = null
private var encounterText: String? = null
private var treasureText: String? = null
private lateinit var myView: View
private lateinit var myContext: Context
private lateinit var monsterTagIdMap : Map<String, Int>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
headerText = it.getString(ARG_HEADER)
terrainText = it.getString(ARG_TERRAIN)
encounterText = it.getString(ARG_ENCOUNTER)
treasureText = it.getString(ARG_TREASURE)
}
monsterTagIdMap = (activity as MainActivity).getMonsterTagMap()
}

override fun onAttach(context: Context) {
super.onAttach(context)
myContext = context
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
myView = inflater.inflate(R.layout.fragment_display_result, container, false)
val header = myView.findViewById<TextView>(R.id.tvHeaderText)
val terrain = myView.findViewById<TextView>(R.id.tvTerrainText)
val encounter = myView.findViewById<TextView>(R.id.tvEncounterText)
val treasure = myView.findViewById<TextView>(R.id.tvTreasureText)
val dismissButton = myView.findViewById<AppCompatButton>(R.id.btnDismiss)

header.text = arguments?.getString(ARG_HEADER)
terrain.text = arguments?.getString(ARG_TERRAIN)
encounter.text = arguments?.getString(ARG_ENCOUNTER)
treasure.text = arguments?.getString(ARG_TREASURE)

setClickableTags(terrain)
setClickableTags(encounter)

dismissButton.setOnClickListener { dismiss() }

return myView
}

override fun onResume() {
super.onResume()
setWidthPercent(95)
}

private fun countMatches(string: String, pattern: String): Int {
return string.split(pattern, ignoreCase = true)
.dropLastWhile { it.isEmpty() }
.toTypedArray().size - 1
}

private fun setClickableTags(clickableTextView: TextView) {
val str = clickableTextView.text.toSpannable()

debug("Parsing '${str}'")

// Go through the tags in length order to resolve the 'giant wolf' vs 'wolf' problem
val foundTags = (monsterTagIdMap.keys.filter { str.contains(it, ignoreCase = true) }).sortedByDescending { it.length }
if (foundTags.isNotEmpty()) {
foundTags.forEach { tag ->
val monsterId = monsterTagIdMap.getOrDefault(tag, -1)
debug("Found tag=$tag with id=$monsterId ${countMatches(str.toString(), tag)} times.")

val matcher = Pattern.compile(tag, Pattern.CASE_INSENSITIVE).matcher(str)

while (matcher.find()) {
val matchStart = matcher.start(0)
val matchEnd = matcher.end()
val clickableSpan: ClickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
Log.d("RuinMastersTables::DisplayTableResultFragment", "Clicked on $tag.")
if (monsterId > 0) {
val monster = (activity as MainActivity).getMonster(monsterId)
if (monster != null) {
debug("Show monster with id=${monster.id} and name='${monster.name}'")
val displayMonsterFragment: DisplayMonsterFragment = DisplayMonsterFragment.newInstance(monster)
displayMonsterFragment.show(requireActivity().supportFragmentManager, "fragment_display_monster")
} else {
Log.d("RuinMastersTables::DisplayTableResultFragment","Failed to get monster.")
}
} else {
warning("Invalid monster Id=monsterId")
}
}

override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.color = ContextCompat.getColor(myContext, R.color.rm_text_dark)//Color.parseColor("#689899")
ds.isFakeBoldText = true
ds.isUnderlineText = false // set to false to remove underline
}
}
debug("Set span for tag=$tag starting at $matchStart and ending at $matchEnd.")
str.setSpan(clickableSpan, matchStart, matchEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
} else {
debug("No tags found in text.")
}

// Make the text view text clickable
clickableTextView.movementMethod = LinkMovementMethod()
clickableTextView.text = str
}

private fun setWidthPercent(percentage: Int) {
val percent = percentage.toFloat() / 100
val dm = Resources.getSystem().displayMetrics
val rect = dm.run { Rect(0, 0, widthPixels, heightPixels) }
val percentWidth = rect.width() * percent
dialog?.window?.setLayout(percentWidth.toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
}

private fun warning(message: String) {
if (BuildConfig.DEBUG) Log.w("RuinMastersTables::DisplayTableResultFragment", message)
}

private fun debug(message: String) {
if (BuildConfig.DEBUG) Log.d("RuinMastersTables::DisplayTableResultFragment", message)
}

companion object {
@JvmStatic
fun newInstance(headerText: String, terrainText: String, encounterText: String, treasureText: String) =
DisplayTableResultFragment().apply {
arguments = Bundle().apply {
putString(ARG_HEADER, headerText)
putString(ARG_TERRAIN, terrainText)
putString(ARG_ENCOUNTER, encounterText)
putString(ARG_TREASURE, treasureText)
}
}
}
}
44 changes: 41 additions & 3 deletions app/src/main/java/my/tablelogic/ruinmasters/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ class MainActivity : AppCompatActivity() {
return Pair(loadedMonsterData, loadedMonsterFiles)
}

private fun isInteger(input: String) = input.all { it in '0'..'9' }

private fun isMonsterDataValid(monsterData : MonsterData, monsterDataFiles: ArrayList<String>) {
val allIds = ArrayList<Int>()
val allTags = ArrayList<String>()
Expand Down Expand Up @@ -268,6 +270,15 @@ class MainActivity : AppCompatActivity() {
if (monster.abilities.size > 10) {
error("To many abilities defined (${monster.combat.attacks.size}) used by id=${getActualMonsterId(monster.id)} in ${getMonsterFileName(monster.id, monsterDataFiles)}")
}

if (monster.tags.isNotEmpty()) {
if (!isInteger(monster.stats.traits.phy) ||
!isInteger(monster.stats.traits.min) ||
!isInteger(monster.stats.traits.int) ||
!isInteger(monster.stats.traits.cha)) {
error("Tags are only supported for monsters not using random values, issue with id=${getActualMonsterId(monster.id)} in ${getMonsterFileName(monster.id, monsterDataFiles)}!")
}
}
}
if (allIds.size != allIds.distinct().count()) {
val duplicatedIds = allIds.groupingBy { it }.eachCount().filter { it.value > 1 }
Expand All @@ -288,15 +299,25 @@ class MainActivity : AppCompatActivity() {
}

private fun createMonsterTagMap(monsterData : MonsterData) : Map<String, Int>{
val idTagMap = emptyMap<String, Int>().toMutableMap()
val tagIdMap = emptyMap<String, Int>().toMutableMap()
monsterData.monster.forEach { monster ->
if (monster.tags.isNotEmpty()) {
monster.tags.forEach { tag ->
idTagMap += mapOf(Pair(tag, monster.id))
if (!tagIdMap.containsKey(tag)) {
tagIdMap += mapOf(Pair(tag, monster.id))
} else {
error("Already found tag=$tag in the tagIdMap! Duplicates not allowed")
val existingId = tagIdMap.getOrDefault(tag,-1)
if (existingId > 0) {
error("Collision between id=${getActualMonsterId(existingId)} in ${getMonsterFileName(existingId,monsters.second)}" +
" and id=${getActualMonsterId(monster.id)} in ${getMonsterFileName(monster.id,monsters.second)}")
}
}
}
tagIdMap.toSortedMap()
}
}
return idTagMap.toSortedMap()
return tagIdMap
}

private fun getActualMonsterId(monsterId : Int) : Int {
Expand Down Expand Up @@ -342,6 +363,23 @@ class MainActivity : AppCompatActivity() {
}
}

fun getMonster(id: Int) : Monster? {
debug("Try to get monster with id=$id.")
monsters.first.monster.forEach { monster ->
if (monster.id == id) {
return monster.deepCopy()
}
}
error("Did not find id='$id'in monsterData.")
return null
}

fun getMonsterTagMap() : Map<String, Int> {
debug("Get monsterTagMap, it has ${monsterTagMap.size} number of entries.")

return monsterTagMap
}

private fun error(message: String) {
Log.e("RuinMastersTables::MainActivity", message)
}
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/java/my/tablelogic/ruinmasters/TablesFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class TablesFragment(tables: TableData, files: ArrayList<String>) : Fragment(),
treasureText = treasureText.trimStart()
if (treasureText.isNotBlank()) treasureText = replaceDieRolls(treasureText)

showEditDialog((v as Button).text.toString(), terrainText, encounterText, treasureText)
showTableResultDialog((v as Button).text.toString(), terrainText, encounterText, treasureText)
}
}

Expand Down Expand Up @@ -205,9 +205,9 @@ class TablesFragment(tables: TableData, files: ArrayList<String>) : Fragment(),
return localText
}

private fun showEditDialog(headerText: String, terrainText: String, encounterText: String, treasureText: String) {
val displayResultFragment: DisplayResultFragment = DisplayResultFragment.newInstance(headerText, terrainText, encounterText, treasureText)
displayResultFragment.show(requireActivity().supportFragmentManager, "fragment_display_result")
private fun showTableResultDialog(headerText: String, terrainText: String, encounterText: String, treasureText: String) {
val displayResultResultFragment: DisplayTableResultFragment = DisplayTableResultFragment.newInstance(headerText, terrainText, encounterText, treasureText)
displayResultResultFragment.show(requireActivity().supportFragmentManager, "fragment_display_result")
}

private fun error(message: String) {
Expand Down

0 comments on commit e8614a5

Please sign in to comment.