horsegen: generate scenarios since sparks use them
This commit is contained in:
@@ -12,6 +12,7 @@ This file is my notes from exploring the database.
|
|||||||
- 147 is spark names, 172 is spark descriptions
|
- 147 is spark names, 172 is spark descriptions
|
||||||
- 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names
|
- 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names
|
||||||
- 65 is player titles, 66 is title descriptions - ties with honor_data?
|
- 65 is player titles, 66 is title descriptions - ties with honor_data?
|
||||||
|
- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
|
||||||
|
|
||||||
# succession factor (sparks)
|
# succession factor (sparks)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ alias game-id = int
|
|||||||
// Specific game ID types.
|
// Specific game ID types.
|
||||||
// I've already made mistakes with ID categories and I haven't even committed this file yet.
|
// I've already made mistakes with ID categories and I haven't even committed this file yet.
|
||||||
|
|
||||||
|
pub struct scenario-id
|
||||||
|
game-id: game-id
|
||||||
|
|
||||||
// Game ID for characters.
|
// Game ID for characters.
|
||||||
// Generally numbers in the range 1000-9999.
|
// Generally numbers in the range 1000-9999.
|
||||||
pub struct character-id
|
pub struct character-id
|
||||||
|
|||||||
23
horse/global/scenario.go
Normal file
23
horse/global/scenario.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
// Automatically generated with horsegen; DO NOT EDIT
|
||||||
|
|
||||||
|
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScenarioURAFinale ScenarioID = 1 // URA Finale
|
||||||
|
ScenarioUnityCup ScenarioID = 2 // Unity Cup
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllScenarios = map[ScenarioID]Scenario{
|
||||||
|
ScenarioURAFinale: {
|
||||||
|
ID: 1,
|
||||||
|
Name: "URA Finale",
|
||||||
|
Title: "The Beginning: URA Finale",
|
||||||
|
},
|
||||||
|
ScenarioUnityCup: {
|
||||||
|
ID: 2,
|
||||||
|
Name: "Unity Cup",
|
||||||
|
Title: "Unity Cup: Shine On, Team Spirit!",
|
||||||
|
},
|
||||||
|
}
|
||||||
38
horse/global/scenario.kk
Normal file
38
horse/global/scenario.kk
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
module horse/global/scenario
|
||||||
|
|
||||||
|
// Automatically generated with horsegen; DO NOT EDIT
|
||||||
|
|
||||||
|
import horse/game-id
|
||||||
|
|
||||||
|
// Enumeration of all scenarios for type-safe programming.
|
||||||
|
pub type scenario
|
||||||
|
URA-Finale
|
||||||
|
Unity-Cup
|
||||||
|
|
||||||
|
// Get the scenario ID for a scenario.
|
||||||
|
pub fun scenario-id(s: scenario): scenario-id
|
||||||
|
match s
|
||||||
|
URA-Finale -> Scenario-id(1)
|
||||||
|
Unity-Cup -> Scenario-id(2)
|
||||||
|
|
||||||
|
// List of all scenarios in ID order for easy iterating.
|
||||||
|
pub val all = [
|
||||||
|
URA-Finale,
|
||||||
|
Unity-Cup,
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get the name for a scenario.
|
||||||
|
// If no scenario matches the ID, the result contains the numeric ID.
|
||||||
|
pub fun show(s: scenario-id): string
|
||||||
|
match s.game-id
|
||||||
|
1 -> "URA Finale"
|
||||||
|
2 -> "Unity Cup"
|
||||||
|
x -> "scenario " ++ x.show
|
||||||
|
|
||||||
|
// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
|
||||||
|
// If no scenario matches the ID, the result contains the numeric ID.
|
||||||
|
pub fun title(s: scenario-id): string
|
||||||
|
match s.game-id
|
||||||
|
1 -> "The Beginning: URA Finale"
|
||||||
|
2 -> "Unity Cup: Shine On, Team Spirit!"
|
||||||
|
x -> "scenario " ++ x.show
|
||||||
@@ -35,3 +35,12 @@ const (
|
|||||||
SaddleTypeG2
|
SaddleTypeG2
|
||||||
SaddleTypeG1
|
SaddleTypeG1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ScenarioID int8
|
||||||
|
|
||||||
|
// Scenario is metadata about a career scenario.
|
||||||
|
type Scenario struct {
|
||||||
|
ID ScenarioID
|
||||||
|
Name string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|||||||
135
horse/spark.kk
135
horse/spark.kk
@@ -1,13 +1,14 @@
|
|||||||
module horse/spark
|
module horse/spark
|
||||||
|
|
||||||
// A single spark.
|
import horse/game-id
|
||||||
// Parameterized by the spark type: stat, aptitude, unique, race, or skill.
|
|
||||||
pub struct spark<a>
|
|
||||||
kind: a
|
|
||||||
level: level
|
|
||||||
|
|
||||||
pub fun spark/show(spark: spark<a>, level-fancy: string = "*", ?kind: (a) -> string): string
|
pub type spark
|
||||||
kind(spark.kind) ++ " " ++ spark.level.show ++ level-fancy
|
Stat(s: stat, l: level)
|
||||||
|
Aptitude(a: aptitude, l: level)
|
||||||
|
Unique(s: skill-id, l: level)
|
||||||
|
Race(r: race-id, l: level)
|
||||||
|
Skill(s: skill-id, l: level)
|
||||||
|
Scenario(s: scenario-id, l: level)
|
||||||
|
|
||||||
pub type level
|
pub type level
|
||||||
One
|
One
|
||||||
@@ -64,123 +65,3 @@ pub fun aptitude/show(this : aptitude): string
|
|||||||
Pace-Chaser -> "Pace Chaser"
|
Pace-Chaser -> "Pace Chaser"
|
||||||
Late-Surger -> "Late Surger"
|
Late-Surger -> "Late Surger"
|
||||||
End-Closer -> "End Closer"
|
End-Closer -> "End Closer"
|
||||||
|
|
||||||
// Unique (green) spark.
|
|
||||||
// TODO: decide this representation; strings? umas? probably depends on skills generally
|
|
||||||
pub type unique
|
|
||||||
|
|
||||||
pub fun unique/show(this: unique): string
|
|
||||||
"TODO(zeph): unique skills"
|
|
||||||
|
|
||||||
// Race, skill, and scenario (white) sparks.
|
|
||||||
pub type generic
|
|
||||||
February-Stakes
|
|
||||||
Takamatsunomiya-Kinen
|
|
||||||
Osaka-Hai
|
|
||||||
Oka-Sho
|
|
||||||
Satsuki-Sho
|
|
||||||
Tenno-Sho-Spring
|
|
||||||
NHK-Mile-Cup
|
|
||||||
Victoria-Mile
|
|
||||||
Japanese-Oaks
|
|
||||||
Japanese-Derby
|
|
||||||
Yasuda-Kinen
|
|
||||||
Takarazuka-Kinen
|
|
||||||
Sprinters-Stakes
|
|
||||||
Shuka-Sho
|
|
||||||
Kikuka-Sho
|
|
||||||
Tenno-Sho-Autumn
|
|
||||||
Queen-Elizabeth-II-Cup
|
|
||||||
Mile-Championship
|
|
||||||
Japan-Cup
|
|
||||||
Champions-Cup
|
|
||||||
Hanshin-Juvenile-Fillies
|
|
||||||
Asahi-Hai-Futurity-Stakes
|
|
||||||
Arima-Kinen
|
|
||||||
Hopeful-Stakes
|
|
||||||
Tokyo-Daishoten
|
|
||||||
JBC-Classic
|
|
||||||
JBC-Sprint
|
|
||||||
JBC-Ladies-Classic
|
|
||||||
Japan-Dirt-Derby
|
|
||||||
Teio-Sho
|
|
||||||
Skill(skill: string)
|
|
||||||
URA-Finale
|
|
||||||
Unity-Cup
|
|
||||||
|
|
||||||
// Automatically generated.
|
|
||||||
// Equality comparison of the `generic` type.
|
|
||||||
pub fun generic/(==)(this : generic, other : generic) : e bool
|
|
||||||
match (this, other)
|
|
||||||
(February-Stakes, February-Stakes) -> True
|
|
||||||
(Takamatsunomiya-Kinen, Takamatsunomiya-Kinen) -> True
|
|
||||||
(Osaka-Hai, Osaka-Hai) -> True
|
|
||||||
(Oka-Sho, Oka-Sho) -> True
|
|
||||||
(Satsuki-Sho, Satsuki-Sho) -> True
|
|
||||||
(Tenno-Sho-Spring, Tenno-Sho-Spring) -> True
|
|
||||||
(NHK-Mile-Cup, NHK-Mile-Cup) -> True
|
|
||||||
(Victoria-Mile, Victoria-Mile) -> True
|
|
||||||
(Japanese-Oaks, Japanese-Oaks) -> True
|
|
||||||
(Japanese-Derby, Japanese-Derby) -> True
|
|
||||||
(Yasuda-Kinen, Yasuda-Kinen) -> True
|
|
||||||
(Takarazuka-Kinen, Takarazuka-Kinen) -> True
|
|
||||||
(Sprinters-Stakes, Sprinters-Stakes) -> True
|
|
||||||
(Shuka-Sho, Shuka-Sho) -> True
|
|
||||||
(Kikuka-Sho, Kikuka-Sho) -> True
|
|
||||||
(Tenno-Sho-Autumn, Tenno-Sho-Autumn) -> True
|
|
||||||
(Queen-Elizabeth-II-Cup, Queen-Elizabeth-II-Cup) -> True
|
|
||||||
(Mile-Championship, Mile-Championship) -> True
|
|
||||||
(Japan-Cup, Japan-Cup) -> True
|
|
||||||
(Champions-Cup, Champions-Cup) -> True
|
|
||||||
(Hanshin-Juvenile-Fillies, Hanshin-Juvenile-Fillies) -> True
|
|
||||||
(Asahi-Hai-Futurity-Stakes, Asahi-Hai-Futurity-Stakes) -> True
|
|
||||||
(Arima-Kinen, Arima-Kinen) -> True
|
|
||||||
(Hopeful-Stakes, Hopeful-Stakes) -> True
|
|
||||||
(Tokyo-Daishoten, Tokyo-Daishoten) -> True
|
|
||||||
(JBC-Classic, JBC-Classic) -> True
|
|
||||||
(JBC-Sprint, JBC-Sprint) -> True
|
|
||||||
(JBC-Ladies-Classic, JBC-Ladies-Classic) -> True
|
|
||||||
(Japan-Dirt-Derby, Japan-Dirt-Derby) -> True
|
|
||||||
(Teio-Sho, Teio-Sho) -> True
|
|
||||||
(Skill(skill), Skill(skill')) -> skill == skill'
|
|
||||||
(URA-Finale, URA-Finale) -> True
|
|
||||||
(Unity-Cup, Unity-Cup) -> True
|
|
||||||
(_, _) -> False
|
|
||||||
|
|
||||||
// Automatically generated.
|
|
||||||
// Shows a string representation of the `generic` type.
|
|
||||||
pub fun generic/show(this : generic) : e string
|
|
||||||
match this
|
|
||||||
February-Stakes -> "February Stakes"
|
|
||||||
Takamatsunomiya-Kinen -> "Takamatsunomiya Kinen"
|
|
||||||
Osaka-Hai -> "Osaka Hai"
|
|
||||||
Oka-Sho -> "Oka Sho"
|
|
||||||
Satsuki-Sho -> "Satsuki Sho"
|
|
||||||
Tenno-Sho-Spring -> "Tenno Sho Spring"
|
|
||||||
NHK-Mile-Cup -> "NHK Mile Cup"
|
|
||||||
Victoria-Mile -> "Victoria Mile"
|
|
||||||
Japanese-Oaks -> "Japanese Oaks"
|
|
||||||
Japanese-Derby -> "Japanese Derby"
|
|
||||||
Yasuda-Kinen -> "Yasuda Kinen"
|
|
||||||
Takarazuka-Kinen -> "Takarazuka Kinen"
|
|
||||||
Sprinters-Stakes -> "Sprinters Stakes"
|
|
||||||
Shuka-Sho -> "Shuka Sho"
|
|
||||||
Kikuka-Sho -> "Kikuka Sho"
|
|
||||||
Tenno-Sho-Autumn -> "Tenno Sho Autumn"
|
|
||||||
Queen-Elizabeth-II-Cup -> "Queen Elizabeth II Cup"
|
|
||||||
Mile-Championship -> "Mile Championship"
|
|
||||||
Japan-Cup -> "Japan Cup"
|
|
||||||
Champions-Cup -> "Champions Cup"
|
|
||||||
Hanshin-Juvenile-Fillies -> "Hanshin Juvenile Fillies"
|
|
||||||
Asahi-Hai-Futurity-Stakes -> "Asahi Hai Futurity Stakes"
|
|
||||||
Arima-Kinen -> "Arima Kinen"
|
|
||||||
Hopeful-Stakes -> "Hopeful Stakes"
|
|
||||||
Tokyo-Daishoten -> "Tokyo Daishoten"
|
|
||||||
JBC-Classic -> "JBC Classic"
|
|
||||||
JBC-Sprint -> "JBC Sprint"
|
|
||||||
JBC-Ladies-Classic -> "JBC Ladies Classic"
|
|
||||||
Japan-Dirt-Derby -> "Japan Dirt Derby"
|
|
||||||
Teio-Sho -> "Teio Sho"
|
|
||||||
Skill(skill) -> skill.show
|
|
||||||
URA-Finale -> "URA Finale"
|
|
||||||
Unity-Cup -> "Unity Cup"
|
|
||||||
|
|||||||
@@ -123,6 +123,21 @@ func ExecSaddle(t *template.Template, region string, kk, g io.Writer, saddles []
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExecScenario(t *template.Template, region string, kk, g io.Writer, scen []Scenario) error {
|
||||||
|
data := struct {
|
||||||
|
Region string
|
||||||
|
Scenarios []Scenario
|
||||||
|
}{region, scen}
|
||||||
|
var err error
|
||||||
|
if kk != nil {
|
||||||
|
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-scenario", &data))
|
||||||
|
}
|
||||||
|
if g != nil {
|
||||||
|
err = errors.Join(err, t.ExecuteTemplate(g, "go-scenario", &data))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
|
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ var raceSQL string
|
|||||||
//go:embed saddle.sql
|
//go:embed saddle.sql
|
||||||
var saddleSQL string
|
var saddleSQL string
|
||||||
|
|
||||||
|
//go:embed scenario.sql
|
||||||
|
var scenarioSQL string
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Character struct{}
|
Character struct{}
|
||||||
SkillGroup struct{}
|
SkillGroup struct{}
|
||||||
@@ -407,3 +410,40 @@ func Saddles(ctx context.Context, db *sqlitex.Pool) ([]Saddle, error) {
|
|||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Scenario struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Scenarios(ctx context.Context, db *sqlitex.Pool) ([]Scenario, error) {
|
||||||
|
conn, err := db.Take(ctx)
|
||||||
|
defer db.Put(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't get connection for scenario: %w", err)
|
||||||
|
}
|
||||||
|
stmt, _, err := conn.PrepareTransient(scenarioSQL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't prepare statement for scenario: %w", err)
|
||||||
|
}
|
||||||
|
defer stmt.Finalize()
|
||||||
|
|
||||||
|
var r []Scenario
|
||||||
|
for {
|
||||||
|
ok, err := stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error stepping scenarios: %w", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s := Scenario{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Name: stmt.ColumnText(1),
|
||||||
|
Title: stmt.ColumnText(2),
|
||||||
|
}
|
||||||
|
r = append(r, s)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ func main() {
|
|||||||
skills []Skill
|
skills []Skill
|
||||||
races []Race
|
races []Race
|
||||||
saddles []Saddle
|
saddles []Saddle
|
||||||
|
scens []Scenario
|
||||||
)
|
)
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
slog.Info("get characters")
|
slog.Info("get characters")
|
||||||
@@ -91,8 +92,14 @@ func main() {
|
|||||||
})
|
})
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
slog.Info("get saddles")
|
slog.Info("get saddles")
|
||||||
s, err := Saddles(ctx, db)
|
r, err := Saddles(ctx, db)
|
||||||
saddles = s
|
saddles = r
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
eg.Go(func() error {
|
||||||
|
slog.Info("get scenarios")
|
||||||
|
r, err := Scenarios(ctx, db)
|
||||||
|
scens = r
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
@@ -154,6 +161,18 @@ func main() {
|
|||||||
slog.Info("write saddles")
|
slog.Info("write saddles")
|
||||||
return ExecSaddle(t, region, kf, gf, saddles)
|
return ExecSaddle(t, region, kf, gf, saddles)
|
||||||
})
|
})
|
||||||
|
eg.Go(func() error {
|
||||||
|
kf, err := os.Create(filepath.Join(out, region, "scenario.kk"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gf, err := os.Create(filepath.Join(out, region, "scenario.go"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slog.Info("write scenarios")
|
||||||
|
return ExecScenario(t, region, kf, gf, scens)
|
||||||
|
})
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
slog.Error("generate", slog.Any("err", err))
|
slog.Error("generate", slog.Any("err", err))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
23
horsegen/scenario.go.template
Normal file
23
horsegen/scenario.go.template
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{{- define "go-scenario" -}}
|
||||||
|
package {{ $.Region }}
|
||||||
|
|
||||||
|
// Automatically generated with horsegen; DO NOT EDIT
|
||||||
|
|
||||||
|
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
|
|
||||||
|
const (
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
Scenario{{ goenum $s.Name }} ScenarioID = {{ $s.ID }} // {{ $s.Name }}
|
||||||
|
{{- end }}
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllScenarios = map[ScenarioID]Scenario{
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
Scenario{{ goenum $s.Name }}: {
|
||||||
|
ID: {{ $s.ID }},
|
||||||
|
Name: {{ printf "%q" $s.Name }},
|
||||||
|
Title: {{ printf "%q" $s.Title }},
|
||||||
|
},
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
45
horsegen/scenario.kk.template
Normal file
45
horsegen/scenario.kk.template
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{{- define "koka-scenario" -}}
|
||||||
|
module horse/{{ $.Region }}/scenario
|
||||||
|
|
||||||
|
// Automatically generated with horsegen; DO NOT EDIT
|
||||||
|
|
||||||
|
import horse/game-id
|
||||||
|
|
||||||
|
// Enumeration of all scenarios for type-safe programming.
|
||||||
|
pub type scenario
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
{{ kkenum $s.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
// Get the scenario ID for a scenario.
|
||||||
|
pub fun scenario-id(s: scenario): scenario-id
|
||||||
|
match s
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
{{ kkenum $s.Name }} -> Scenario-id({{ $s.ID }})
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
// List of all scenarios in ID order for easy iterating.
|
||||||
|
pub val all = [
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
{{ kkenum $s.Name }},
|
||||||
|
{{- end }}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get the name for a scenario.
|
||||||
|
// If no scenario matches the ID, the result contains the numeric ID.
|
||||||
|
pub fun show(s: scenario-id): string
|
||||||
|
match s.game-id
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
{{ $s.ID }} -> {{ printf "%q" $s.Name }}
|
||||||
|
{{- end }}
|
||||||
|
x -> "scenario " ++ x.show
|
||||||
|
|
||||||
|
// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
|
||||||
|
// If no scenario matches the ID, the result contains the numeric ID.
|
||||||
|
pub fun title(s: scenario-id): string
|
||||||
|
match s.game-id
|
||||||
|
{{- range $s := $.Scenarios }}
|
||||||
|
{{ $s.ID }} -> {{ printf "%q" $s.Title }}
|
||||||
|
{{- end }}
|
||||||
|
x -> "scenario " ++ x.show
|
||||||
|
{{ end }}
|
||||||
17
horsegen/scenario.sql
Normal file
17
horsegen/scenario.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
WITH scenario_name AS (
|
||||||
|
SELECT "index" AS id, "text" AS name
|
||||||
|
FROM text_data
|
||||||
|
WHERE category = 237
|
||||||
|
), scenario_title AS (
|
||||||
|
SELECT "index" AS id, "text" AS title
|
||||||
|
FROM text_data
|
||||||
|
WHERE category = 119
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
sc.id,
|
||||||
|
n.name,
|
||||||
|
t.title
|
||||||
|
FROM single_mode_scenario sc
|
||||||
|
JOIN scenario_name n ON sc.id = n.id
|
||||||
|
JOIN scenario_title t ON sc.id = t.id
|
||||||
|
ORDER BY sc.id
|
||||||
Reference in New Issue
Block a user